From bebd4090ae35e54384c691cd5e5f4dc6d0a01358 Mon Sep 17 00:00:00 2001 From: Junyu Lai Date: Wed, 21 May 2025 10:38:39 +0800 Subject: [PATCH] Add NetworkCapabilities transport types into NetworkIdentity. This change updates NetworkIdentity to have NetworkCapabilities transport types for improved network identification, and updates NetworkIdentitySet for backward compatibility with older data. Flag: EXEMPT implemented in the counter-part CL Test: atest ConnectivityCoverageTests --test-filter \ 'NetworkIdentity*' --update-device Test: atest ConnectivityCoverageTests --test-filter \ 'NetworkStats*' --update-device Bug: 419123367 Change-Id: I26cc770d90b4687d832dd029d9a11db03404db39 --- .../src/android/net/NetworkIdentity.java | 53 +++++-- .../src/android/net/NetworkIdentitySet.java | 36 ++++- .../src/android/net/NetworkTemplate.java | 4 +- framework/proto/netstats.proto | 2 + .../net/module/util/CollectionUtils.java | 14 ++ .../module/util/NetworkCapabilitiesUtils.java | 38 ++++- .../net/module/util/CollectionUtilsTest.kt | 31 +++- .../android/net/NetworkIdentitySetTest.kt | 145 ++++++++++++++++-- .../java/android/net/NetworkIdentityTest.kt | 140 +++++++++++++---- .../net/NetworkStatsCollectionTest.java | 8 +- .../server/net/NetworkStatsObserversTest.java | 5 +- 11 files changed, 407 insertions(+), 69 deletions(-) diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java index 947a0925c6..a48032b6b4 100644 --- a/framework-t/src/android/net/NetworkIdentity.java +++ b/framework-t/src/android/net/NetworkIdentity.java @@ -22,6 +22,9 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.net.module.util.CollectionUtils.intArrayToSet; +import static com.android.net.module.util.NetworkCapabilitiesUtils.TYPE_TEST; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -43,6 +46,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; +import java.util.Set; /** * Network definition that includes strong identity. Analogous to combining @@ -86,9 +90,6 @@ public class NetworkIdentity { private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE; - // Need to be synchronized with ConnectivityManager. - // TODO: Use {@code ConnectivityManager#*} when visible. - static final int TYPE_TEST = 18; private static final int MAX_NETWORK_TYPE = TYPE_TEST; private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; @@ -101,11 +102,13 @@ public class NetworkIdentity { final boolean mMetered; final boolean mDefaultNetwork; final int mOemManaged; + final long mTransportTypesBits; /** @hide */ public NetworkIdentity( int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey, - boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) { + boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId, + long transportTypesBits) { mType = type; mRatType = ratType; mSubscriberId = subscriberId; @@ -115,25 +118,26 @@ public class NetworkIdentity { mDefaultNetwork = defaultNetwork; mOemManaged = oemManaged; mSubId = subId; + mTransportTypesBits = transportTypesBits; } @Override public int hashCode() { return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, - mDefaultNetwork, mOemManaged, mSubId); + mDefaultNetwork, mOemManaged, mSubId, mTransportTypesBits); } @Override public boolean equals(@Nullable Object obj) { - if (obj instanceof NetworkIdentity) { - final NetworkIdentity ident = (NetworkIdentity) obj; + if (obj instanceof NetworkIdentity ident) { return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming && Objects.equals(mSubscriberId, ident.mSubscriberId) && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey) && mMetered == ident.mMetered && mDefaultNetwork == ident.mDefaultNetwork && mOemManaged == ident.mOemManaged - && mSubId == ident.mSubId; + && mSubId == ident.mSubId + && mTransportTypesBits == ident.mTransportTypesBits; } return false; } @@ -162,6 +166,8 @@ public class NetworkIdentity { builder.append(", defaultNetwork=").append(mDefaultNetwork); builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged)); builder.append(", subId=").append(mSubId); + // TODO: Print human friendly names instead. + builder.append(", transports=").append(getTransportTypes()); return builder.append("}").toString(); } @@ -204,7 +210,7 @@ public class NetworkIdentity { proto.write(NetworkIdentityProto.METERED, mMetered); proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork); proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged); - + proto.write(NetworkIdentityProto.TRANSPORT_TYPES, mTransportTypesBits); proto.end(start); } @@ -273,6 +279,14 @@ public class NetworkIdentity { return mSubId; } + /** + * Get transport types + * @hide + */ + public Set getTransportTypes() { + return intArrayToSet(BitUtils.unpackBits(mTransportTypesBits)); + } + /** * Assemble a {@link NetworkIdentity} from the passed arguments. * @@ -322,6 +336,9 @@ public class NetworkIdentity { public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) { Objects.requireNonNull(right); int res = Integer.compare(left.mType, right.mType); + if (res == 0) { + res = Long.compare(left.mTransportTypesBits, right.mTransportTypesBits); + } if (res == 0) { res = Integer.compare(left.mRatType, right.mRatType); } @@ -362,6 +379,7 @@ public class NetworkIdentity { private boolean mDefaultNetwork; private int mOemManaged; private int mSubId; + private long mTransportTypes; /** * Creates a new Builder. @@ -377,6 +395,7 @@ public class NetworkIdentity { mDefaultNetwork = false; mOemManaged = NetworkTemplate.OEM_MANAGED_NO; mSubId = INVALID_SUBSCRIPTION_ID; + mTransportTypes = 0L; } /** @@ -389,6 +408,7 @@ public class NetworkIdentity { * - metered * - oemManaged * - wifiNetworkKey + * - transportTypes * * @param snapshot The target {@link NetworkStateSnapshot} object. * @return The builder object. @@ -397,6 +417,7 @@ public class NetworkIdentity { @NonNull public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) { setType(snapshot.getLegacyType()); + setTransportTypes(intArrayToSet(snapshot.getNetworkCapabilities().getTransportTypes())); setSubscriberId(snapshot.getSubscriberId()); setRoaming(!snapshot.getNetworkCapabilities().hasCapability( @@ -406,6 +427,7 @@ public class NetworkIdentity { setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities())); + // TODO: Use the transports instead. if (mType == TYPE_WIFI) { final NetworkCapabilities nc = snapshot.getNetworkCapabilities(); final TransportInfo transportInfo = nc.getTransportInfo(); @@ -580,6 +602,17 @@ public class NetworkIdentity { return this; } + /** + * Set the transport types. + * + * @hide + */ + @NonNull + public Builder setTransportTypes(Set transportTypes) { + mTransportTypes = BitUtils.packBits(CollectionUtils.toIntArray(transportTypes)); + return this; + } + private void ensureValidParameters() { // Assert non-mobile network cannot have a ratType. if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) { @@ -602,7 +635,7 @@ public class NetworkIdentity { public NetworkIdentity build() { ensureValidParameters(); return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, - mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId); + mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId, mTransportTypes); } } } diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java index d88408eef0..a0b3d3dec2 100644 --- a/framework-t/src/android/net/NetworkIdentitySet.java +++ b/framework-t/src/android/net/NetworkIdentitySet.java @@ -19,10 +19,17 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.net.module.util.CollectionUtils.toIntArray; +import static com.android.net.module.util.NetworkCapabilitiesUtils.deduceTransportTypeForLegacyNetworkType; + import android.annotation.NonNull; import android.service.NetworkIdentitySetProto; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + +import com.android.net.module.util.BitUtils; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -44,6 +51,7 @@ public class NetworkIdentitySet extends HashSet { private static final int VERSION_ADD_DEFAULT_NETWORK = 5; private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6; private static final int VERSION_ADD_SUB_ID = 7; + private static final int VERSION_ADD_TRANSPORT_TYPES = 8; /** * Construct a {@link NetworkIdentitySet} object. @@ -112,8 +120,23 @@ public class NetworkIdentitySet extends HashSet { subId = INVALID_SUBSCRIPTION_ID; } + final long transportTypesBits; + if (version >= VERSION_ADD_TRANSPORT_TYPES) { + transportTypesBits = in.readLong(); + } else { + final int deducedTransport = deduceTransportTypeForLegacyNetworkType(type); + if (deducedTransport != -1) { + transportTypesBits = BitUtils.packBits(new int[]{ deducedTransport }); + } else { + // Ignore legacy or unknown types. This is fine since this means + // the legacy data cannot be queried by transport types, + // which is not even supported now. + transportTypesBits = 0; + } + } + add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemNetCapabilities, subId)); + defaultNetwork, oemNetCapabilities, subId, transportTypesBits)); } } @@ -122,7 +145,7 @@ public class NetworkIdentitySet extends HashSet { * @hide */ public void writeToStream(DataOutput out) throws IOException { - out.writeInt(VERSION_ADD_SUB_ID); + out.writeInt(VERSION_ADD_TRANSPORT_TYPES); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); @@ -134,6 +157,7 @@ public class NetworkIdentitySet extends HashSet { out.writeBoolean(ident.isDefaultNetwork()); out.writeInt(ident.getOemManaged()); out.writeInt(ident.getSubId()); + out.writeLong(BitUtils.packBits(toIntArray(ident.getTransportTypes()))); } } @@ -186,7 +210,13 @@ public class NetworkIdentitySet extends HashSet { return true; } - private static void writeOptionalString(DataOutput out, String value) throws IOException { + /** + * Writes an optional string to a {@link DataOutput} stream. + * + * @hide + */ + @VisibleForTesting + public static void writeOptionalString(DataOutput out, String value) throws IOException { if (value != null) { out.writeByte(1); out.writeUTF(value); diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java index 77b166cc3a..0e9e8d1dde 100644 --- a/framework-t/src/android/net/NetworkTemplate.java +++ b/framework-t/src/android/net/NetworkTemplate.java @@ -37,6 +37,8 @@ import static android.net.NetworkStats.ROAMING_ALL; import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.ROAMING_YES; +import static com.android.net.module.util.NetworkCapabilitiesUtils.TYPE_TEST; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -870,7 +872,7 @@ public final class NetworkTemplate implements Parcelable { * all test networks would be matched. */ private boolean matchesTest(NetworkIdentity ident) { - return ident.mType == NetworkIdentity.TYPE_TEST + return ident.mType == TYPE_TEST && ((CollectionUtils.isEmpty(mMatchWifiNetworkKeys) || CollectionUtils.contains(mMatchWifiNetworkKeys, ident.mWifiNetworkKey))); } diff --git a/framework/proto/netstats.proto b/framework/proto/netstats.proto index 3c9f73cc57..24e8714dcf 100644 --- a/framework/proto/netstats.proto +++ b/framework/proto/netstats.proto @@ -64,6 +64,8 @@ message NetworkIdentityProto { optional bool default_network = 6; optional int32 oem_managed_network = 7; + + optional int64 transport_types = 8; } // Corresponds to NetworkStatsRecorder. diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java index 760d849d42..df8d9f6030 100644 --- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java +++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java @@ -19,6 +19,7 @@ package com.android.net.module.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Pair; import android.util.SparseArray; @@ -27,6 +28,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -51,6 +53,18 @@ public final class CollectionUtils { return collection == null || collection.isEmpty(); } + /** + * Convert an int array to a Integer set. + */ + @NonNull + public static Set intArrayToSet(@NonNull int[] array) { + final Set set = new ArraySet<>(); + for (int item : array) { + set.add(item); + } + return set; + } + /** * Returns an int array from the given Integer list. */ diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java index 7066131618..710022caaa 100644 --- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java +++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java @@ -16,6 +16,12 @@ package com.android.net.module.util; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_PROXY; +import static android.net.ConnectivityManager.TYPE_VPN; +import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; @@ -40,13 +46,13 @@ import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_USB; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static com.android.net.module.util.BitUtils.packBits; -import static com.android.net.module.util.BitUtils.unpackBits; import android.annotation.NonNull; import android.net.NetworkCapabilities; @@ -58,6 +64,10 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public final class NetworkCapabilitiesUtils { + // Need to be synchronized with ConnectivityManager. + // TODO: Use {@code ConnectivityManager#*} when visible. + public static final int TYPE_TEST = 18; + // Transports considered to classify networks in UI, in order of which transport should be // surfaced when there are multiple transports. Transports not in this list do not have // an ordering preference (in practice they will have a deterministic order based on the @@ -150,6 +160,32 @@ public final class NetworkCapabilitiesUtils { return transports[0]; } + /** + * Deduces the primary NetworkCapabilities.TRANSPORT_* type from a legacy + * ConnectivityManager.TYPE_* network type. + * + * This is used to deduce transport type for legacy persistent NetworkStats files + * with NetworkIdentitySet version under VERSION_ADD_TRANSPORT_TYPES. + * + * @param legacyNetworkType One of the deprecated ConnectivityManager.TYPE_* constants + * (e.g., ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_MOBILE). + * @return The corresponding NetworkCapabilities.TRANSPORT_* constant, or + * -1 if no direct or clear mapping exists or unknown. + */ + public static int deduceTransportTypeForLegacyNetworkType(int legacyNetworkType) { + return switch (legacyNetworkType) { + case TYPE_WIFI -> TRANSPORT_WIFI; + case TYPE_MOBILE -> TRANSPORT_CELLULAR; + case TYPE_ETHERNET -> TRANSPORT_ETHERNET; + case TYPE_VPN -> TRANSPORT_VPN; + // TYPE_PROXY (deprecated) historically represented a network primarily + // for proxying traffic, often over Bluetooth (e.g., on Wear OS devices + // tethered to a phone). See ProxyNetworkAgent (deprecated) for more detail. + case TYPE_BLUETOOTH, TYPE_PROXY -> TRANSPORT_BLUETOOTH; + case TYPE_TEST -> TRANSPORT_TEST; + default -> -1; // Ignore legacy types such as TYPE_WIMAX, TYPE_MOBILE_*, etc. + }; + } /** * Infers that all the capabilities it provides are typically provided by restricted networks diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt index 1aa943e015..84548fbe5d 100644 --- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt +++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt @@ -19,15 +19,16 @@ package com.android.net.module.util import android.util.SparseArray import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 +import com.android.net.module.util.CollectionUtils.intArrayToSet import kotlin.test.assertContentEquals -import org.junit.Test -import org.junit.runner.RunWith import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertSame import kotlin.test.assertTrue +import org.junit.Test +import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SmallTest @@ -144,13 +145,19 @@ class CollectionUtilsTest { val listAE = listOf("A", "B", "C", "D", "E") val list15 = listOf(1, 2, 3, 4, 5) // Normal #zip returns kotlin.Pair, not android.util.Pair - assertEquals(list15.zip(listAE).map { android.util.Pair(it.first, it.second) }, - CollectionUtils.zip(list15, listAE)) + assertEquals( + list15.zip(listAE).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listAE) + ) val listNull = listOf("A", null, "B", "C", "D") - assertEquals(list15.zip(listNull).map { android.util.Pair(it.first, it.second) }, - CollectionUtils.zip(list15, listNull)) - assertEquals(emptyList>(), - CollectionUtils.zip(emptyList(), emptyList())) + assertEquals( + list15.zip(listNull).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listNull) + ) + assertEquals( + emptyList>(), + CollectionUtils.zip(emptyList(), emptyList()) + ) assertFailsWith { // Different size CollectionUtils.zip(listOf(1, 2), list15) @@ -280,4 +287,12 @@ class CollectionUtilsTest { CollectionUtils.appendArray(String::class.java, stringArr1, "4", "5", "6") ) } + + @Test + fun intArrayToSet_arrayWithDuplicateElements_returnsSetWithUniqueElements() { + val inputArray = intArrayOf(1, 2, 2, 3, 4, 4, 4, 5, 1) + val expectedSet = setOf(1, 2, 3, 4, 5) + val result = intArrayToSet(inputArray) + assertEquals(expectedSet, result) + } } diff --git a/tests/unit/java/android/net/NetworkIdentitySetTest.kt b/tests/unit/java/android/net/NetworkIdentitySetTest.kt index d61ebf97be..cf94e84d4c 100644 --- a/tests/unit/java/android/net/NetworkIdentitySetTest.kt +++ b/tests/unit/java/android/net/NetworkIdentitySetTest.kt @@ -17,15 +17,32 @@ package android.net import android.content.Context +import android.net.ConnectivityManager.TYPE_BLUETOOTH +import android.net.ConnectivityManager.TYPE_ETHERNET import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_VPN +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.ConnectivityManager.TYPE_WIMAX +import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkIdentitySet.writeOptionalString import android.os.Build import android.telephony.TelephonyManager +import com.android.net.module.util.NetworkCapabilitiesUtils.TYPE_TEST import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.DataInputStream +import java.io.DataOutputStream +import kotlin.test.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock -import kotlin.test.assertEquals private const val TEST_IMSI1 = "testimsi1" @@ -34,22 +51,54 @@ private const val TEST_IMSI1 = "testimsi1" class NetworkIdentitySetTest { private val mockContext = mock(Context::class.java) - private fun buildMobileNetworkStateSnapshot( - caps: NetworkCapabilities, - subscriberId: String + private fun buildMobileNetworkStateSnapshot(subscriberId: String) = + buildNetworkStateSnapshot(NetworkCapabilities(), subscriberId, TYPE_MOBILE) + + private fun buildNetworkStateSnapshot( + caps: NetworkCapabilities, + subscriberId: String, + legacyNetworkType: Int ): NetworkStateSnapshot { - return NetworkStateSnapshot(mock(Network::class.java), caps, - LinkProperties(), subscriberId, TYPE_MOBILE) + return NetworkStateSnapshot( + mock(Network::class.java), + caps, + LinkProperties(), + subscriberId, + legacyNetworkType + ) + } + + private fun buildNetworkIdentity( + legacyNetworkType: Int, + vararg transportTypes: Int + ): NetworkIdentity { + val caps = NetworkCapabilities().apply { + transportTypes.forEach { + addTransportType(it) + } + } + return NetworkIdentity.buildNetworkIdentity( + mockContext, + buildNetworkStateSnapshot(caps, TEST_IMSI1, legacyNetworkType), + false, + 0 + ) } @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) + val ident1 = NetworkIdentity.buildNetworkIdentity( + mockContext, + buildMobileNetworkStateSnapshot(TEST_IMSI1), + false /* defaultNetwork */, + TelephonyManager.NETWORK_TYPE_UMTS + ) + val ident2 = NetworkIdentity.buildNetworkIdentity( + mockContext, + buildMobileNetworkStateSnapshot(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())) @@ -63,4 +112,76 @@ class NetworkIdentitySetTest { assertEquals(0, NetworkIdentitySet.compare(identSet1, identSet1)) assertEquals(-1, NetworkIdentitySet.compare(identSet1, identSet2)) } + + @Test + fun testTransportTypesSerialization() { + val originalSet = NetworkIdentitySet() + originalSet.add(buildNetworkIdentity(TYPE_MOBILE, TRANSPORT_CELLULAR)) + originalSet.add(buildNetworkIdentity(TYPE_WIFI, TRANSPORT_VPN, TRANSPORT_WIFI)) + + val baos = ByteArrayOutputStream() + val outDataStream = DataOutputStream(baos) + originalSet.writeToStream(outDataStream) + + val bais = ByteArrayInputStream(baos.toByteArray()) + val inDataStream = DataInputStream(bais) + val deserializedSet = NetworkIdentitySet(inDataStream) + + assertEquals(originalSet, deserializedSet) + } + + /** + * Writes a single NetworkIdentity to the stream in a specific older version format. + * This simulates an old saved state. + * Version: VERSION_ADD_SUB_ID (7), which does NOT include transportTypesBits explicitly. + */ + private fun writeNetworkIdentityV7(out: DataOutputStream, ident: NetworkIdentity) { + out.writeInt(7) // Write the old version number + out.writeInt(1) // Number of identities in the set + out.writeInt(ident.type) + out.writeInt(ident.ratType) + writeOptionalString(out, ident.subscriberId) + writeOptionalString(out, ident.wifiNetworkKey) + out.writeBoolean(ident.isRoaming) + out.writeBoolean(ident.isMetered) + out.writeBoolean(ident.isDefaultNetwork) + out.writeInt(ident.oemManaged) + out.writeInt(ident.subId) + // V7 and earlier versions DO NOT write transportTypesBits + } + + @Test + fun testMigrateToSupportTransportTypes() { + doTestMigrateToSupportTransportTypes(TYPE_MOBILE, TRANSPORT_CELLULAR) + doTestMigrateToSupportTransportTypes(TYPE_WIFI, TRANSPORT_WIFI) + doTestMigrateToSupportTransportTypes(TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH) + doTestMigrateToSupportTransportTypes(TYPE_ETHERNET, TRANSPORT_ETHERNET) + doTestMigrateToSupportTransportTypes(TYPE_VPN, TRANSPORT_VPN) + doTestMigrateToSupportTransportTypes(TYPE_TEST, TRANSPORT_TEST) + doTestMigrateToSupportTransportTypes(TYPE_WIMAX) + } + + private fun doTestMigrateToSupportTransportTypes( + legacyNetworkType: Int, + vararg expectedTransportTypes: Int + ) { + val ident = buildNetworkIdentity(legacyNetworkType) + + val baos = ByteArrayOutputStream() + val outDataStream = DataOutputStream(baos) + writeNetworkIdentityV7(outDataStream, ident) + + val bais = ByteArrayInputStream(baos.toByteArray()) + val inDataStream = DataInputStream(bais) + val deserializedSet = NetworkIdentitySet(inDataStream) + + // Prepare the expected identity set, verify the transport type deduced from + // the legacy network type. + val expectedIdent = buildNetworkIdentity(legacyNetworkType, *expectedTransportTypes) + val expectedSet = NetworkIdentitySet().also { + it.add(expectedIdent) + } + + assertEquals(expectedSet, deserializedSet) + } } diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt index 9667f8fbd3..60f35c270d 100644 --- a/tests/unit/java/android/net/NetworkIdentityTest.kt +++ b/tests/unit/java/android/net/NetworkIdentityTest.kt @@ -16,6 +16,7 @@ package android.net +import android.app.usage.NetworkStatsManager import android.content.Context import android.net.ConnectivityManager.MAX_NETWORK_TYPE import android.net.ConnectivityManager.TYPE_ETHERNET @@ -23,22 +24,26 @@ import android.net.ConnectivityManager.TYPE_MOBILE import android.net.ConnectivityManager.TYPE_NONE import android.net.ConnectivityManager.TYPE_WIFI import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_SATELLITE +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkIdentity.OEM_NONE import android.net.NetworkIdentity.OEM_PAID import android.net.NetworkIdentity.OEM_PRIVATE import android.net.NetworkIdentity.getOemBitfield -import android.app.usage.NetworkStatsManager -import android.telephony.TelephonyManager import android.os.Build +import android.telephony.TelephonyManager +import com.android.net.module.util.BitUtils 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 import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock private const val TEST_WIFI_KEY = "testwifikey" private const val TEST_IMSI1 = "testimsi1" @@ -52,11 +57,16 @@ class NetworkIdentityTest { private val mockContext = mock(Context::class.java) private fun buildMobileNetworkStateSnapshot( - caps: NetworkCapabilities, - subscriberId: String + caps: NetworkCapabilities, + subscriberId: String ): NetworkStateSnapshot { - return NetworkStateSnapshot(mock(Network::class.java), caps, - LinkProperties(), subscriberId, TYPE_MOBILE) + return NetworkStateSnapshot( + mock(Network::class.java), + caps, + LinkProperties(), + subscriberId, + TYPE_MOBILE + ) } @Test @@ -87,18 +97,24 @@ class NetworkIdentityTest { @Test fun testIsMetered() { // Verify network is metered. - val netIdent1 = NetworkIdentity.buildNetworkIdentity(mockContext, + val netIdent1 = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1), - false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) + false /* defaultNetwork */, + TelephonyManager.NETWORK_TYPE_UMTS + ) assertTrue(netIdent1.isMetered()) // Verify network is not metered because it has NET_CAPABILITY_NOT_METERED capability. val capsNotMetered = NetworkCapabilities.Builder().apply { addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) }.build() - val netIdent2 = NetworkIdentity.buildNetworkIdentity(mockContext, + val netIdent2 = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(capsNotMetered, TEST_IMSI1), - false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) + false /* defaultNetwork */, + TelephonyManager.NETWORK_TYPE_UMTS + ) assertFalse(netIdent2.isMetered()) // In current design, a network that has NET_CAPABILITY_TEMPORARILY_NOT_METERED @@ -106,9 +122,12 @@ class NetworkIdentityTest { val capsTempNotMetered = NetworkCapabilities().apply { setCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED, true) } - val netIdent3 = NetworkIdentity.buildNetworkIdentity(mockContext, + val netIdent3 = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(capsTempNotMetered, TEST_IMSI1), - false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) + false /* defaultNetwork */, + TelephonyManager.NETWORK_TYPE_UMTS + ) assertTrue(netIdent3.isMetered()) } @@ -122,15 +141,20 @@ class NetworkIdentityTest { setNetworkSpecifier(specifier1) } val identFromSnapshot = NetworkIdentity.Builder().setNetworkStateSnapshot( - buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI1)) + buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI1) + ) .setDefaultNetwork(true) .setRatType(TelephonyManager.NETWORK_TYPE_UMTS) .setSubId(TEST_SUBID1) .build() - val identFromLegacyBuild = NetworkIdentity.buildNetworkIdentity(mockContext, + val identFromLegacyBuild = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI1), - true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) - val identFromConstructor = NetworkIdentity(TYPE_MOBILE, + true /* defaultNetwork */, + TelephonyManager.NETWORK_TYPE_UMTS + ) + val identFromConstructor = NetworkIdentity( + TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, TEST_IMSI1, null /* wifiNetworkKey */, @@ -138,7 +162,9 @@ class NetworkIdentityTest { false /* metered */, true /* defaultNetwork */, NetworkTemplate.OEM_MANAGED_PRIVATE, - TEST_SUBID1) + TEST_SUBID1, + BitUtils.packBits(intArrayOf(TRANSPORT_CELLULAR)) + ) assertEquals(identFromLegacyBuild, identFromSnapshot) assertEquals(identFromConstructor, identFromSnapshot) @@ -180,8 +206,12 @@ class NetworkIdentityTest { @Test fun testBuilder_ratType() { // Assert illegal ratTypes cannot make an identity. - listOf(Integer.MIN_VALUE, NetworkTemplate.NETWORK_TYPE_ALL, - NetworkStatsManager.NETWORK_TYPE_5G_NSA - 1, Integer.MAX_VALUE) + listOf( + Integer.MIN_VALUE, + NetworkTemplate.NETWORK_TYPE_ALL, + NetworkStatsManager.NETWORK_TYPE_5G_NSA - 1, + Integer.MAX_VALUE + ) .forEach { assertFailsWith { NetworkIdentity.Builder() @@ -208,8 +238,12 @@ class NetworkIdentityTest { @Test fun testBuilder_oemManaged() { // Assert illegal oemManage values cannot make an identity. - listOf(Integer.MIN_VALUE, NetworkTemplate.OEM_MANAGED_ALL, NetworkTemplate.OEM_MANAGED_YES, - Integer.MAX_VALUE) + listOf( + Integer.MIN_VALUE, + NetworkTemplate.OEM_MANAGED_ALL, + NetworkTemplate.OEM_MANAGED_YES, + Integer.MAX_VALUE + ) .forEach { oemManaged -> assertFailsWith { NetworkIdentity.Builder() @@ -220,9 +254,13 @@ class NetworkIdentityTest { } // Verify legitimate oem managed values can make an identity. - listOf(NetworkTemplate.OEM_MANAGED_NO, NetworkTemplate.OEM_MANAGED_PAID, - NetworkTemplate.OEM_MANAGED_PRIVATE, NetworkTemplate.OEM_MANAGED_PAID or - NetworkTemplate.OEM_MANAGED_PRIVATE) + listOf( + NetworkTemplate.OEM_MANAGED_NO, + NetworkTemplate.OEM_MANAGED_PAID, + NetworkTemplate.OEM_MANAGED_PRIVATE, + NetworkTemplate.OEM_MANAGED_PAID or + NetworkTemplate.OEM_MANAGED_PRIVATE + ) .forEach { oemManaged -> NetworkIdentity.Builder() .setOemManaged(oemManaged) @@ -245,14 +283,54 @@ class NetworkIdentityTest { setNetworkSpecifier(specifier2) } - val netIdent1 = NetworkIdentity.buildNetworkIdentity(mockContext, + val netIdent1 = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(capSUBID1, TEST_IMSI1), - false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) + false, + TelephonyManager.NETWORK_TYPE_UMTS + ) assertEquals(TEST_SUBID1, netIdent1.getSubId()) - val netIdent2 = NetworkIdentity.buildNetworkIdentity(mockContext, + val netIdent2 = NetworkIdentity.buildNetworkIdentity( + mockContext, buildMobileNetworkStateSnapshot(capSUBID2, TEST_IMSI2), - false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS) + false, + TelephonyManager.NETWORK_TYPE_UMTS + ) assertEquals(TEST_SUBID2, netIdent2.getSubId()) } + + @Test + fun testSetTransportTypes() { + doTestSetTransportTypes(setOf()) + doTestSetTransportTypes(setOf(TRANSPORT_WIFI)) + doTestSetTransportTypes(setOf(TRANSPORT_CELLULAR, TRANSPORT_VPN, TRANSPORT_ETHERNET)) + } + + private fun doTestSetTransportTypes(transportTypes: Set) { + val identity = NetworkIdentity.Builder() + .setType(TYPE_MOBILE) // legacy type, for builder compatibility + .setTransportTypes(transportTypes) + .build() + assertEquals(transportTypes.toSet(), identity.transportTypes) + } + + @Test + fun testTransportTypes_fromNetworkStateSnapshot() { + val caps = NetworkCapabilities().apply { + addTransportType(TRANSPORT_SATELLITE) + addTransportType(TRANSPORT_VPN) + addTransportType(TRANSPORT_WIFI) + } + val identity = NetworkIdentity.buildNetworkIdentity( + mockContext, + buildMobileNetworkStateSnapshot(caps, TEST_IMSI1), + false, + TelephonyManager.NETWORK_TYPE_UMTS + ) + assertEquals( + setOf(TRANSPORT_SATELLITE, TRANSPORT_VPN, TRANSPORT_WIFI), + identity.transportTypes + ) + } } diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java index 1af89adde9..5d6f980772 100644 --- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java +++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java @@ -17,6 +17,7 @@ package android.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.IFACE_ALL; @@ -56,6 +57,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.frameworks.tests.net.R; +import com.android.net.module.util.BitUtils; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; @@ -249,7 +251,8 @@ public class NetworkStatsCollectionTest { final NetworkStats.Entry entry = new NetworkStats.Entry(); final NetworkIdentitySet identSet = new NetworkIdentitySet(); identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, - TEST_IMSI, null, false, true, true, OEM_NONE, TEST_SUBID)); + TEST_IMSI, null, false, true, true, OEM_NONE, TEST_SUBID, + BitUtils.packBits(new int[]{TRANSPORT_CELLULAR}))); int myUid = Process.myUid(); int otherUidInSameUser = Process.myUid() + 1; @@ -511,7 +514,8 @@ public class NetworkStatsCollectionTest { final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS); final NetworkIdentitySet ident = new NetworkIdentitySet(); ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null, - false, true, true, OEM_NONE, TEST_SUBID)); + false, true, true, OEM_NONE, TEST_SUBID, + BitUtils.packBits(new int[] {TRANSPORT_CELLULAR}))); large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B, new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12_730_893_164L, 1, 0, 0, 0)); diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java index 10b1396071..617c4ec9d0 100644 --- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java +++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java @@ -17,6 +17,7 @@ package com.android.server.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.DEFAULT_NETWORK_YES; @@ -54,6 +55,7 @@ import android.util.ArrayMap; import androidx.test.filters.SmallTest; +import com.android.net.module.util.BitUtils; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; import com.android.testutils.HandlerUtils; @@ -300,7 +302,8 @@ public class NetworkStatsObserversTest { identSet.add(new NetworkIdentity( TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, IMSI_1, null /* networkId */, false /* roaming */, true /* metered */, - true /* defaultNetwork */, OEM_NONE, SUBID_1)); + true /* defaultNetwork */, OEM_NONE, SUBID_1, + BitUtils.packBits(new int[] { TRANSPORT_CELLULAR }))); return identSet; }