Merge "Deflake testStartStopVpnProfileV6 and testStartStopVpnProfileV4"
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index b4e3ba4..836761f 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -36,4 +36,5 @@
void onTetherStatesChanged(in TetherStatesParcel states);
void onTetherClientsChanged(in List<TetheredClient> clients);
void onOffloadStatusChanged(int status);
+ void onSupportedTetheringTypes(long supportedBitmap);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 253eacb..f33f846 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -26,7 +26,7 @@
* @hide
*/
parcelable TetheringCallbackStartedParcel {
- boolean tetheringSupported;
+ long supportedTypes;
Network upstreamNetwork;
TetheringConfigurationParcel config;
TetherStatesParcel states;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 6f9b33e..b3f0cf2 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -183,6 +183,12 @@
*/
public static final int TETHERING_WIGIG = 6;
+ /**
+ * The int value of last tethering type.
+ * @hide
+ */
+ public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
@@ -520,6 +526,9 @@
}
@Override
+ public void onSupportedTetheringTypes(long supportedBitmap) { }
+
+ @Override
public void onUpstreamChanged(Network network) { }
@Override
@@ -1033,15 +1042,29 @@
/**
* Called when tethering supported status changed.
*
+ * <p>This callback will be called immediately after the callback is
+ * registered, and never be called if there is changes afterward.
+ *
+ * <p>Tethering may be disabled via system properties, device configuration, or device
+ * policy restrictions.
+ *
+ * @param supported whether any tethering type is supported.
+ */
+ default void onTetheringSupported(boolean supported) {}
+
+ /**
+ * Called when tethering supported status changed.
+ *
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
*
* <p>Tethering may be disabled via system properties, device configuration, or device
* policy restrictions.
*
- * @param supported The new supported status
+ * @param supportedTypes a set of @TetheringType which is supported.
+ * @hide
*/
- default void onTetheringSupported(boolean supported) {}
+ default void onSupportedTetheringTypes(@NonNull Set<Integer> supportedTypes) {}
/**
* Called when tethering upstream changed.
@@ -1339,7 +1362,8 @@
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
executor.execute(() -> {
- callback.onTetheringSupported(parcel.tetheringSupported);
+ callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes));
+ callback.onTetheringSupported(parcel.supportedTypes != 0);
callback.onUpstreamChanged(parcel.upstreamNetwork);
sendErrorCallbacks(parcel.states);
sendRegexpsChanged(parcel.config);
@@ -1358,6 +1382,13 @@
});
}
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ executor.execute(() -> {
+ callback.onSupportedTetheringTypes(unpackBits(supportedBitmap));
+ });
+ }
+
private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
parcel.tetherableBluetoothRegexs,
@@ -1396,6 +1427,23 @@
}
/**
+ * Unpack bitmap to a set of bit position intergers.
+ * @hide
+ */
+ public static ArraySet<Integer> unpackBits(long val) {
+ final ArraySet<Integer> result = new ArraySet<>(Long.bitCount(val));
+ int bitPos = 0;
+ while (val != 0) {
+ if ((val & 1) == 1) result.add(bitPos);
+
+ val = val >>> 1;
+ bitPos++;
+ }
+
+ return result;
+ }
+
+ /**
* Remove tethering event callback previously registered with
* {@link #registerTetheringEventCallback}.
*
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 7b5ae0d..2905e28 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -12,6 +12,11 @@
native <methods>;
}
+# Ensure runtime-visible field annotations are kept when using R8 full mode.
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
+-keep interface com.android.networkstack.tethering.util.Struct$Field {
+ *;
+}
-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
*;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index bba0a40..ecb6478 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -65,6 +65,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
@@ -120,6 +121,8 @@
private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
// Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
private static final String DUMP_BASE64_DELIMITER = ",";
@@ -1074,7 +1077,8 @@
}
}
- private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+ private <K extends Struct, V extends Struct> String bpfMapEntryToBase64String(
+ final K key, final V value) {
final byte[] keyBytes = key.writeToBytes();
final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
.replace("\n", "");
@@ -1085,28 +1089,45 @@
return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
}
- private void dumpRawIpv4ForwardingRuleMap(
- BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+ private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
+ IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
- pw.println("No IPv4 support");
+ pw.println("No BPF support");
return;
}
if (map.isEmpty()) {
- pw.println("No rules");
+ pw.println("No entries");
return;
}
- map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+ map.forEach((k, v) -> pw.println(bpfMapEntryToBase64String(k, v)));
}
/**
* Dump raw BPF map in base64 encoded strings. For test only.
+ * Only allow to dump one map path once.
+ * Format:
+ * $ dumpsys tethering bpfRawMap --<map name>
*/
- public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
- try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- // TODO: dump downstream map.
- dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
- } catch (ErrnoException e) {
- pw.println("Error dumping IPv4 map: " + e);
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, @Nullable String[] args) {
+ // TODO: consider checking the arg order that <map name> is after "bpfRawMap". Probably
+ // it is okay for now because this is used by test only and test is supposed to use
+ // expected argument order.
+ // TODO: dump downstream4 map.
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
+ try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
+ dumpRawMap(statsMap, pw);
+ } catch (ErrnoException e) {
+ pw.println("Error dumping stats map: " + e);
+ }
+ return;
+ }
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
+ try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+ dumpRawMap(upstreamMap, pw);
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ return;
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0b607bd..4504829 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -135,6 +135,7 @@
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.CollectionUtils;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
@@ -278,6 +279,11 @@
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
+ // AIDL doesn't support Set<Integer>. Maintain a int bitmap here. When the bitmap is passed to
+ // TetheringManager, TetheringManager would convert it to a set of Integer types.
+ // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be
+ // read from binder thread which called TetheringService directly.
+ private volatile long mSupportedTypeBitmap;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -494,6 +500,8 @@
mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
mConfig.isDunRequired);
reportConfigurationChanged(mConfig.toStableParcelable());
+
+ updateSupportedDownstreams(mConfig);
}
private void maybeDunSettingChanged() {
@@ -1513,26 +1521,6 @@
return mConfig;
}
- boolean hasAnySupportedDownstream() {
- if ((mConfig.tetherableUsbRegexs.length != 0)
- || (mConfig.tetherableWifiRegexs.length != 0)
- || (mConfig.tetherableBluetoothRegexs.length != 0)) {
- return true;
- }
-
- // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
- // disabled (whole tethering settings would be hidden). This means tethering would also not
- // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
- // some devices in the field rely on this to disable tethering entirely.
- if (!SdkLevel.isAtLeastT()) return false;
-
- return (mConfig.tetherableWifiP2pRegexs.length != 0)
- || (mConfig.tetherableNcmRegexs.length != 0)
- || isEthernetSupported();
- }
-
- // TODO: using EtherentManager new API to check whether ethernet is supported when the API is
- // ready to use.
private boolean isEthernetSupported() {
return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
}
@@ -2322,7 +2310,7 @@
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
- parcel.tetheringSupported = isTetheringSupported();
+ parcel.supportedTypes = mSupportedTypeBitmap;
parcel.upstreamNetwork = mTetherUpstream;
parcel.config = mConfig.toStableParcelable();
parcel.states =
@@ -2361,6 +2349,22 @@
});
}
+ private void reportTetheringSupportedChange(final long supportedBitmap) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes(
+ supportedBitmap);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
private void reportUpstreamChanged(UpstreamNetworkState ns) {
final int length = mTetheringEventCallbacks.beginBroadcast();
final Network network = (ns != null) ? ns.network : null;
@@ -2445,18 +2449,56 @@
}
}
+ private void updateSupportedDownstreams(final TetheringConfiguration config) {
+ final long preSupportedBitmap = mSupportedTypeBitmap;
+
+ if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) {
+ mSupportedTypeBitmap = 0;
+ } else {
+ mSupportedTypeBitmap = makeSupportedDownstreams(config);
+ }
+
+ if (preSupportedBitmap != mSupportedTypeBitmap) {
+ reportTetheringSupportedChange(mSupportedTypeBitmap);
+ }
+ }
+
+ private long makeSupportedDownstreams(final TetheringConfiguration config) {
+ long types = 0;
+ if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB);
+
+ if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI);
+
+ if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH);
+
+ // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
+ // disabled (whole tethering settings would be hidden). This means tethering would also not
+ // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
+ // some devices in the field rely on this to disable tethering entirely.
+ if (!SdkLevel.isAtLeastT() && types == 0) return types;
+
+ if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM);
+
+ if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P);
+
+ if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET);
+
+ return types;
+ }
+
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
- boolean isTetheringSupported() {
+ private boolean isTetheringAllowed() {
final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1;
final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
- final boolean tetherEnabledInSettings = tetherSupported
+ return tetherSupported
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+ }
- return tetherEnabledInSettings && hasAnySupportedDownstream()
- && !mEntitlementMgr.isProvisioningNeededButUnavailable();
+ boolean isTetheringSupported() {
+ return mSupportedTypeBitmap > 0;
}
private void dumpBpf(IndentingPrintWriter pw) {
@@ -2472,13 +2514,12 @@
writer, " ");
// Used for testing instead of human debug.
- // TODO: add options to choose which map to dump.
- if (argsContain(args, "bpfRawMap")) {
- mBpfCoordinator.dumpRawMap(pw);
+ if (CollectionUtils.contains(args, "bpfRawMap")) {
+ mBpfCoordinator.dumpRawMap(pw, args);
return;
}
- if (argsContain(args, "bpf")) {
+ if (CollectionUtils.contains(args, "bpf")) {
dumpBpf(pw);
return;
}
@@ -2544,7 +2585,7 @@
pw.println("Log:");
pw.increaseIndent();
- if (argsContain(args, "--short")) {
+ if (CollectionUtils.contains(args, "--short")) {
pw.println("<log removed for brevity>");
} else {
mLog.dump(fd, pw, args);
@@ -2588,13 +2629,6 @@
if (e != null) throw e;
}
- private static boolean argsContain(String[] args, String target) {
- for (String arg : args) {
- if (target.equals(arg)) return true;
- }
- return false;
- }
-
private void updateConnectedClients(final List<WifiClient> wifiClients) {
if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
wifiClients)) {
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index de81a38..e73b7d5 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -74,6 +74,8 @@
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv4Header;
@@ -128,6 +130,13 @@
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
+ // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int RX_UDP_PACKET_SIZE = 30;
+ private static final int RX_UDP_PACKET_COUNT = 456;
+ // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
+ private static final int TX_UDP_PACKET_SIZE = 44;
+ private static final int TX_UDP_PACKET_COUNT = 123;
+
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
@@ -136,6 +145,8 @@
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
private static final String BASE64_DELIMITER = ",";
private static final String LINE_DELIMITER = "\\n";
@@ -168,12 +179,13 @@
mUiAutomation.adoptShellPermissionIdentity(
MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
- mRunTests = mTm.isTetheringSupported() && mEm != null;
- assumeTrue(mRunTests);
-
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+
+ mRunTests = isEthernetTetheringSupported();
+ assumeTrue(mRunTests);
+
mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
}
@@ -201,7 +213,6 @@
mHandler.post(() -> reader.stop());
mDownstreamReader = null;
}
- mHandlerThread.quitSafely();
mTetheredInterfaceRequester.release();
mEm.setIncludeTestInterfaces(false);
maybeDeleteTestInterface();
@@ -212,6 +223,7 @@
try {
if (mRunTests) cleanUp();
} finally {
+ mHandlerThread.quitSafely();
mUiAutomation.dropShellPermissionIdentity();
}
}
@@ -400,6 +412,23 @@
// client, which is not possible in this test.
}
+ private boolean isEthernetTetheringSupported() throws Exception {
+ final CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final TetheringEventCallback callback = new TetheringEventCallback() {
+ @Override
+ public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+ future.complete(supportedTypes.contains(TETHERING_ETHERNET));
+ }
+ };
+
+ try {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } finally {
+ mTm.unregisterTetheringEventCallback(callback);
+ }
+ }
+
private static final class MyTetheringEventCallback implements TetheringEventCallback {
private final TetheringManager mTm;
private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
@@ -938,27 +967,69 @@
return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
});
- final HashMap<Tether4Key, Tether4Value> upstreamMap = pollIpv4UpstreamMapFromDump();
+ // [1] Verify IPv4 upstream rule map.
+ final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+ Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
assertNotNull(upstreamMap);
assertEquals(1, upstreamMap.size());
final Map.Entry<Tether4Key, Tether4Value> rule =
upstreamMap.entrySet().iterator().next();
- final Tether4Key key = rule.getKey();
- assertEquals(IPPROTO_UDP, key.l4proto);
- assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), key.src4));
- assertEquals(LOCAL_PORT, key.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), key.dst4));
- assertEquals(REMOTE_PORT, key.dstPort);
+ final Tether4Key upstream4Key = rule.getKey();
+ assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+ assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+ assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+ assertEquals(REMOTE_PORT, upstream4Key.dstPort);
- final Tether4Value value = rule.getValue();
+ final Tether4Value upstream4Value = rule.getValue();
assertTrue(Arrays.equals(publicIp4Addr.getAddress(),
- InetAddress.getByAddress(value.src46).getAddress()));
- assertEquals(LOCAL_PORT, value.srcPort);
+ InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+ assertEquals(LOCAL_PORT, upstream4Value.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
- InetAddress.getByAddress(value.dst46).getAddress()));
- assertEquals(REMOTE_PORT, value.dstPort);
+ InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+ assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+ // [2] Verify stats map.
+ // Transmit packets on both direction for verifying stats. Because we only care the
+ // packet count in stats test, we just reuse the existing packets to increaes
+ // the packet count on both direction.
+
+ // Send packets on original direction.
+ for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ });
+ }
+
+ // Send packets on reply direction.
+ for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ });
+ }
+
+ // Dump stats map to verify.
+ final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+ TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+ assertNotNull(statsMap);
+ assertEquals(1, statsMap.size());
+
+ final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+ statsMap.entrySet().iterator().next();
+
+ // TODO: verify the upstream index in TetherStatsKey.
+
+ final TetherStatsValue statsValue = stats.getValue();
+ assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+ assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+ assertEquals(0, statsValue.rxErrors);
+ assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+ assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+ assertEquals(0, statsValue.txErrors);
}
}
@@ -1003,7 +1074,8 @@
}
@Nullable
- private Pair<Tether4Key, Tether4Value> parseTether4KeyValue(@NonNull String dumpStr) {
+ private <K extends Struct, V extends Struct> Pair<K, V> parseMapKeyValue(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String dumpStr) {
Log.w(TAG, "Parsing string: " + dumpStr);
String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
@@ -1016,36 +1088,38 @@
Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
keyByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Key tether4Key = Struct.parse(Tether4Key.class, keyByteBuffer);
- Log.w(TAG, "tether4Key: " + tether4Key);
+ final K k = Struct.parse(keyClass, keyByteBuffer);
final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
valueByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Value tether4Value = Struct.parse(Tether4Value.class, valueByteBuffer);
- Log.w(TAG, "tether4Value: " + tether4Value);
+ final V v = Struct.parse(valueClass, valueByteBuffer);
- return new Pair<>(tether4Key, tether4Value);
+ return new Pair<>(k, v);
}
@NonNull
- private HashMap<Tether4Key, Tether4Value> dumpIpv4UpstreamMap() throws Exception {
- final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE,
- DUMPSYS_TETHERING_RAWMAP_ARG);
- final HashMap<Tether4Key, Tether4Value> map = new HashMap<>();
+ private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+ final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args);
+ final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<Tether4Key, Tether4Value> rule = parseTether4KeyValue(line.trim());
+ final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
}
@Nullable
- private HashMap<Tether4Key, Tether4Value> pollIpv4UpstreamMapFromDump() throws Exception {
+ private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
- final HashMap<Tether4Key, Tether4Value> map = dumpIpv4UpstreamMap();
+ final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
if (!map.isEmpty()) return map;
Thread.sleep(DUMP_POLLING_INTERVAL_MS);
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 0388758..2fd7f48 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -142,6 +142,7 @@
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
+import android.net.TetheringManager;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -175,6 +176,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -211,6 +213,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -1696,6 +1699,7 @@
private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
+ private final ArrayList<Long> mSupportedBitmaps = new ArrayList<>();
// This function will remove the recorded callbacks, so it must be called once for
// each callback. If this is called after multiple callback, the order matters.
@@ -1748,6 +1752,10 @@
assertTrue(leases.containsAll(result));
}
+ public void expectSupportedTetheringTypes(Set<Integer> expectedTypes) {
+ assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0)));
+ }
+
@Override
public void onUpstreamChanged(Network network) {
mActualUpstreams.add(network);
@@ -1780,11 +1788,17 @@
mTetherStates.add(parcel.states);
mOffloadStatus.add(parcel.offloadStatus);
mTetheredClients.add(parcel.tetheredClients);
+ mSupportedBitmaps.add(parcel.supportedTypes);
}
@Override
public void onCallbackStopped(int errorCode) { }
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ mSupportedBitmaps.add(supportedBitmap);
+ }
+
public void assertNoUpstreamChangeCallback() {
assertTrue(mActualUpstreams.isEmpty());
}
@@ -2836,53 +2850,81 @@
runStopUSBTethering();
}
+ public static ArraySet<Integer> getAllSupportedTetheringTypes() {
+ return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI,
+ TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
+ }
+
@Test
public void testTetheringSupported() throws Exception {
+ final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
+ // Check tethering is supported after initialization.
setTetheringSupported(true /* supported */);
- updateConfigAndVerifySupported(true /* supported */);
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ updateConfigAndVerifySupported(callback, expectedTypes);
// Could disable tethering supported by settings.
Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Could disable tethering supported by user restriction.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
when(mUserManager.hasUserRestriction(
UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Tethering is supported if it has any supported downstream.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Usb tethering is not supported:
+ expectedTypes.remove(TETHERING_USB);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Wifi tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
-
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Bluetooth tethering is not supported:
+ expectedTypes.remove(TETHERING_BLUETOOTH);
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+ .thenReturn(new String[0]);
if (isAtLeastT()) {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+
+ // P2p tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI_P2P);
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ncm tethering is not supported:
+ expectedTypes.remove(TETHERING_NCM);
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ethernet tethering (last supported type) is not supported:
+ expectedTypes.remove(TETHERING_ETHERNET);
mForceEthernetServiceUnavailable = true;
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
+
} else {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(false /* supported */);
+ // If wifi, usb and bluetooth are all not supported, all the types are not supported.
+ expectedTypes.clear();
+ updateConfigAndVerifySupported(callback, expectedTypes);
}
}
- private void updateConfigAndVerifySupported(boolean supported) {
+ private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+ final ArraySet<Integer> expectedTypes) {
sendConfigurationChanged();
- assertEquals(supported, mTethering.isTetheringSupported());
+
+ assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
+ callback.expectSupportedTetheringTypes(expectedTypes);
}
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java
index ad3a958..d88408e 100644
--- a/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/framework-t/src/android/net/NetworkIdentitySet.java
@@ -206,6 +206,7 @@
public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
Objects.requireNonNull(left);
Objects.requireNonNull(right);
+ if (left.isEmpty() && right.isEmpty()) return 0;
if (left.isEmpty()) return -1;
if (right.isEmpty()) return 1;
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index e385b33..b59a890 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -776,7 +776,7 @@
if (!templateMatches(groupTemplate, key.ident)) continue;
if (key.set >= NetworkStats.SET_DEBUG_START) continue;
- final Key groupKey = new Key(null, key.uid, key.set, key.tag);
+ final Key groupKey = new Key(new NetworkIdentitySet(), key.uid, key.set, key.tag);
NetworkStatsHistory groupHistory = grouped.get(groupKey);
if (groupHistory == null) {
groupHistory = new NetworkStatsHistory(value.getBucketDuration());
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a0e75ec..4835438 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3266,11 +3266,12 @@
return;
}
- pw.print("NetworkProviders for:");
+ pw.println("NetworkProviders for:");
+ pw.increaseIndent();
for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
- pw.print(" " + npi.name);
+ pw.println(npi.providerId + ": " + npi.name);
}
- pw.println();
+ pw.decreaseIndent();
pw.println();
final NetworkAgentInfo defaultNai = getDefaultNetwork();
@@ -3319,6 +3320,14 @@
pw.decreaseIndent();
pw.println();
+ pw.println("Network Offers:");
+ pw.increaseIndent();
+ for (final NetworkOfferInfo offerInfo : mNetworkOffers) {
+ pw.println(offerInfo.offer);
+ }
+ pw.decreaseIndent();
+ pw.println();
+
mLegacyTypeTracker.dump(pw);
pw.println();
diff --git a/service/src/com/android/server/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index 1e975dd..eea382e 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -22,6 +22,7 @@
import android.net.NetworkRequest;
import android.os.RemoteException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -143,6 +144,11 @@
@Override
public String toString() {
- return "NetworkOffer [ Score " + score + " Caps " + caps + "]";
+ final ArrayList<Integer> neededRequestIds = new ArrayList<>();
+ for (final NetworkRequest request : mCurrentlyNeeded) {
+ neededRequestIds.add(request.requestId);
+ }
+ return "NetworkOffer [ Provider Id (" + providerId + ") " + score + " Caps "
+ + caps + " Needed by " + neededRequestIds + "]";
}
}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 28437c2..e7b2815 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -28,5 +28,5 @@
void sendNotification(int notificationId, String notificationType);
void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
void unregisterNetworkCallback();
- void scheduleJob(in JobInfo jobInfo);
+ int scheduleJob(in JobInfo jobInfo);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index f460180..96ce65f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside;
+import static android.app.job.JobScheduler.RESULT_SUCCESS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
@@ -151,7 +152,7 @@
protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
- private static final long BROADCAST_TIMEOUT_MS = 15_000;
+ private static final long BROADCAST_TIMEOUT_MS = 5_000;
protected Context mContext;
protected Instrumentation mInstrumentation;
@@ -855,7 +856,8 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setTransientExtras(extras)
.build();
- mServiceClient.scheduleJob(jobInfo);
+ assertEquals("Error scheduling " + jobInfo,
+ RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
final int resultCode = result.get(0).first;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 8b70f9b..0610774 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -107,7 +107,7 @@
mService.unregisterNetworkCallback();
}
- public void scheduleJob(JobInfo jobInfo) throws RemoteException {
- mService.scheduleJob(jobInfo);
+ public int scheduleJob(JobInfo jobInfo) throws RemoteException {
+ return mService.scheduleJob(jobInfo);
}
}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index f2a7b3f..3ed5391 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -165,10 +165,10 @@
}
@Override
- public void scheduleJob(JobInfo jobInfo) {
+ public int scheduleJob(JobInfo jobInfo) {
final JobScheduler jobScheduler = getApplicationContext()
.getSystemService(JobScheduler.class);
- jobScheduler.schedule(jobInfo);
+ return jobScheduler.schedule(jobInfo);
}
};
diff --git a/tests/native/connectivity_native_test.cpp b/tests/native/connectivity_native_test.cpp
index 8b089ab..3db5265 100644
--- a/tests/native/connectivity_native_test.cpp
+++ b/tests/native/connectivity_native_test.cpp
@@ -14,14 +14,15 @@
* limitations under the License.
*/
+#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
#include <android-modules-utils/sdk_level.h>
#include <cutils/misc.h> // FIRST_APPLICATION_UID
#include <gtest/gtest.h>
#include <netinet/in.h>
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
-#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
+#include "bpf/BpfUtils.h"
using aidl::android::net::connectivity::aidl::IConnectivityNative;
@@ -40,6 +41,10 @@
if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() <<
"Should be at least T device.";
+ // Skip test case if not on 5.4 kernel which is required by bpf prog.
+ if (!android::bpf::isAtLeastKernelVersion(5, 4, 0)) GTEST_SKIP() <<
+ "Kernel should be at least 5.4.";
+
ASSERT_NE(nullptr, mService.get());
// If there are already ports being blocked on device unblockAllPortsForBind() store
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 545f7b9..5b926de 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -63,6 +63,7 @@
"java/android/net/IpSecManagerTest.java",
"java/android/net/IpSecTransformTest.java",
"java/android/net/KeepalivePacketDataUtilTest.java",
+ "java/android/net/NetworkIdentitySetTest.kt",
"java/android/net/NetworkIdentityTest.kt",
"java/android/net/NetworkStats*.java",
"java/android/net/NetworkTemplateTest.kt",
diff --git a/tests/unit/java/android/net/NetworkIdentitySetTest.kt b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
new file mode 100644
index 0000000..d61ebf9
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.content.Context
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.os.Build
+import android.telephony.TelephonyManager
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+
+private const val TEST_IMSI1 = "testimsi1"
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkIdentitySetTest {
+ private val mockContext = mock(Context::class.java)
+
+ private fun buildMobileNetworkStateSnapshot(
+ caps: NetworkCapabilities,
+ subscriberId: String
+ ): NetworkStateSnapshot {
+ return NetworkStateSnapshot(mock(Network::class.java), caps,
+ LinkProperties(), subscriberId, TYPE_MOBILE)
+ }
+
+ @Test
+ fun testCompare() {
+ val ident1 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val ident2 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+
+ // Verify that the results of comparing two empty sets are equal
+ assertEquals(0, NetworkIdentitySet.compare(NetworkIdentitySet(), NetworkIdentitySet()))
+
+ val identSet1 = NetworkIdentitySet()
+ val identSet2 = NetworkIdentitySet()
+ identSet1.add(ident1)
+ identSet2.add(ident2)
+ assertEquals(-1, NetworkIdentitySet.compare(NetworkIdentitySet(), identSet1))
+ assertEquals(1, NetworkIdentitySet.compare(identSet1, NetworkIdentitySet()))
+ assertEquals(0, NetworkIdentitySet.compare(identSet1, identSet1))
+ assertEquals(-1, NetworkIdentitySet.compare(identSet1, identSet2))
+ }
+}