diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java index da5f88dc3b..350ed86ede 100644 --- a/framework-t/src/android/net/NetworkIdentity.java +++ b/framework-t/src/android/net/NetworkIdentity.java @@ -85,6 +85,12 @@ 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; + final int mType; final int mRatType; final int mSubId; @@ -346,11 +352,6 @@ public class NetworkIdentity { * Builder class for {@link NetworkIdentity}. */ public static final class Builder { - // Need to be synchronized with ConnectivityManager. - // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module. - private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST - private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; - private int mType; private int mRatType; private String mSubscriberId; @@ -413,6 +414,12 @@ public class NetworkIdentity { final WifiInfo info = (WifiInfo) transportInfo; setWifiNetworkKey(info.getNetworkKey()); } + } else if (mType == TYPE_TEST) { + final NetworkSpecifier ns = snapshot.getNetworkCapabilities().getNetworkSpecifier(); + if (ns instanceof TestNetworkSpecifier) { + // Reuse the wifi network key field to identify individual test networks. + setWifiNetworkKey(((TestNetworkSpecifier) ns).getInterfaceName()); + } } return this; } @@ -574,7 +581,7 @@ public class NetworkIdentity { } // Assert non-wifi network cannot have a wifi network key. - if (mType != TYPE_WIFI && mWifiNetworkKey != null) { + if (mType != TYPE_WIFI && mType != TYPE_TEST && mWifiNetworkKey != null) { throw new IllegalArgumentException("Invalid wifi network key for type " + mType); } } diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java index b82a126333..b6bd1a51a2 100644 --- a/framework-t/src/android/net/NetworkTemplate.java +++ b/framework-t/src/android/net/NetworkTemplate.java @@ -50,6 +50,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; +import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkIdentityUtils; import com.android.net.module.util.NetworkStatsUtils; @@ -114,6 +115,14 @@ public final class NetworkTemplate implements Parcelable { * may offer non-cellular networks like WiFi, which will be matched by this rule. */ public static final int MATCH_CARRIER = 10; + /** + * Match rule to match networks with {@link ConnectivityManager#TYPE_TEST} as the legacy + * network type. + * + * @hide + */ + @VisibleForTesting + public static final int MATCH_TEST = 11; // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL. /** @hide */ @@ -176,6 +185,7 @@ public final class NetworkTemplate implements Parcelable { case MATCH_BLUETOOTH: case MATCH_PROXY: case MATCH_CARRIER: + case MATCH_TEST: return true; default: @@ -666,6 +676,8 @@ public final class NetworkTemplate implements Parcelable { return matchesProxy(ident); case MATCH_CARRIER: return matchesCarrier(ident); + case MATCH_TEST: + return matchesTest(ident); default: // We have no idea what kind of network template we are, so we // just claim not to match anything. @@ -776,6 +788,17 @@ public final class NetworkTemplate implements Parcelable { && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId); } + /** + * Check if matches test network. If the wifiNetworkKeys in the template is specified, Then it + * will only match a network containing any of the specified the wifi network key. Otherwise, + * all test networks would be matched. + */ + private boolean matchesTest(NetworkIdentity ident) { + return ident.mType == NetworkIdentity.TYPE_TEST + && ((CollectionUtils.isEmpty(mMatchWifiNetworkKeys) + || CollectionUtils.contains(mMatchWifiNetworkKeys, ident.mWifiNetworkKey))); + } + private boolean matchesMobileWildcard(NetworkIdentity ident) { if (ident.mType == TYPE_WIMAX) { return true; @@ -829,6 +852,8 @@ public final class NetworkTemplate implements Parcelable { return "PROXY"; case MATCH_CARRIER: return "CARRIER"; + case MATCH_TEST: + return "TEST"; default: return "UNKNOWN(" + matchRule + ")"; } @@ -1079,7 +1104,9 @@ public final class NetworkTemplate implements Parcelable { } private void validateWifiNetworkKeys() { - if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) { + // Also allow querying test networks which use wifi network key as identifier. + if (mMatchRule != MATCH_WIFI && mMatchRule != MATCH_TEST + && !mMatchWifiNetworkKeys.isEmpty()) { throw new IllegalArgumentException("Trying to build non wifi match rule: " + mMatchRule + " with wifi network keys"); } diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 1fbbd25bf5..547b4babf7 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1167,6 +1167,8 @@ public class ConnectivityManager { return "PROXY"; case TYPE_VPN: return "VPN"; + case TYPE_TEST: + return "TEST"; default: return Integer.toString(type); } diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt index 3e9662dd84..6c39169d14 100644 --- a/tests/unit/java/android/net/NetworkTemplateTest.kt +++ b/tests/unit/java/android/net/NetworkTemplateTest.kt @@ -19,7 +19,10 @@ package android.net import android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA import android.content.Context import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_TEST import android.net.ConnectivityManager.TYPE_WIFI +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkIdentity.OEM_NONE import android.net.NetworkIdentity.OEM_PAID import android.net.NetworkIdentity.OEM_PRIVATE @@ -31,6 +34,7 @@ import android.net.NetworkStats.METERED_YES import android.net.NetworkStats.ROAMING_ALL import android.net.NetworkTemplate.MATCH_MOBILE import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD +import android.net.NetworkTemplate.MATCH_TEST import android.net.NetworkTemplate.MATCH_WIFI import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD import android.net.NetworkTemplate.NETWORK_TYPE_ALL @@ -97,6 +101,14 @@ class NetworkTemplateTest { (oemManaged and OEM_PAID) == OEM_PAID) setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, (oemManaged and OEM_PRIVATE) == OEM_PRIVATE) + if (type == TYPE_TEST) { + wifiKey?.let { TestNetworkSpecifier(it) }?.let { + // Must have a single non-test transport specified to use setNetworkSpecifier. + // Put an arbitrary transport type which is not used in this test. + addTransportType(TRANSPORT_TEST) + addTransportType(TRANSPORT_WIFI) + setNetworkSpecifier(it) } + } setTransportInfo(mockWifiInfo) } return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type) @@ -232,6 +244,32 @@ class NetworkTemplateTest { templateMobileNullImsiWithRatType.assertDoesNotMatch(identWifiImsi1Key1) } + @Test + fun testTestNetworkTemplateMatches() { + val templateTestKey1 = NetworkTemplate.Builder(MATCH_TEST) + .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build() + val templateTestKey2 = NetworkTemplate.Builder(MATCH_TEST) + .setWifiNetworkKeys(setOf(TEST_WIFI_KEY2)).build() + val templateTestAll = NetworkTemplate.Builder(MATCH_TEST).build() + + val stateWifiKey1 = buildNetworkState(TYPE_WIFI, null /* subscriberId */, TEST_WIFI_KEY1, + OEM_NONE, true /* metered */) + val stateTestKey1 = buildNetworkState(TYPE_TEST, null /* subscriberId */, TEST_WIFI_KEY1, + OEM_NONE, true /* metered */) + val identWifi1 = buildNetworkIdentity(mockContext, stateWifiKey1, + false /* defaultNetwork */, NetworkTemplate.NETWORK_TYPE_ALL) + val identTest1 = buildNetworkIdentity(mockContext, stateTestKey1, + false /* defaultNetwork */, NETWORK_TYPE_ALL) + + // Verify that the template matches corresponding type and the subscriberId. + templateTestKey1.assertDoesNotMatch(identWifi1) + templateTestKey1.assertMatches(identTest1) + templateTestKey2.assertDoesNotMatch(identWifi1) + templateTestKey2.assertDoesNotMatch(identTest1) + templateTestAll.assertDoesNotMatch(identWifi1) + templateTestAll.assertMatches(identTest1) + } + @Test fun testCarrierMeteredMatches() { val templateCarrierImsi1Metered = buildTemplateCarrierMetered(TEST_IMSI1) diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java index be44946e9e..ac1bb4fb46 100644 --- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java @@ -25,6 +25,7 @@ import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_TEST; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkIdentity.OEM_PAID; import static android.net.NetworkIdentity.OEM_PRIVATE; @@ -48,6 +49,7 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.MATCH_MOBILE; +import static android.net.NetworkTemplate.MATCH_TEST; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.OEM_MANAGED_NO; import static android.net.NetworkTemplate.OEM_MANAGED_YES; @@ -107,6 +109,7 @@ import android.net.NetworkStatsCollection; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; +import android.net.TestNetworkSpecifier; import android.net.TetherStatsParcel; import android.net.TetheringManager; import android.net.UnderlyingNetworkInfo; @@ -121,6 +124,7 @@ import android.os.SimpleClock; import android.provider.Settings; import android.system.ErrnoException; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Pair; @@ -209,9 +213,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private static final Network WIFI_NETWORK = new Network(100); private static final Network MOBILE_NETWORK = new Network(101); private static final Network VPN_NETWORK = new Network(102); + private static final Network TEST_NETWORK = new Network(103); private static final Network[] NETWORKS_WIFI = new Network[]{ WIFI_NETWORK }; private static final Network[] NETWORKS_MOBILE = new Network[]{ MOBILE_NETWORK }; + private static final Network[] NETWORKS_TEST = new Network[]{ TEST_NETWORK }; private static final long WAIT_TIMEOUT = 2 * 1000; // 2 secs private static final int INVALID_TYPE = -1; @@ -817,7 +823,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0); assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0); - // now pretend two UIDs are uninstalled, which should migrate stats to // special "removed" bucket. mockDefaultSettings(); @@ -940,8 +945,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // Pretend that 5g mobile network comes online final NetworkStateSnapshot[] mobileStates = - new NetworkStateSnapshot[] {buildMobileState(IMSI_1), buildMobileState(TEST_IFACE2, - IMSI_1, true /* isTemporarilyNotMetered */, false /* isRoaming */)}; + new NetworkStateSnapshot[] {buildMobileState(IMSI_1), buildStateOfTransport( + NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE, + TEST_IFACE2, IMSI_1, null /* wifiNetworkKey */, + true /* isTemporarilyNotMetered */, false /* isRoaming */)}; setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR); mService.notifyNetworkStatus(NETWORKS_MOBILE, mobileStates, getActiveIface(mobileStates), new UnderlyingNetworkInfo[0]); @@ -1172,6 +1179,41 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // already documented publicly, refer to {@link NetworkStatsManager#queryDetails}. } + @Test + public void testQueryTestNetworkUsage() throws Exception { + final NetworkTemplate templateTestAll = new NetworkTemplate.Builder(MATCH_TEST).build(); + final NetworkTemplate templateTestIface1 = new NetworkTemplate.Builder(MATCH_TEST) + .setWifiNetworkKeys(Set.of(TEST_IFACE)).build(); + final NetworkTemplate templateTestIface2 = new NetworkTemplate.Builder(MATCH_TEST) + .setWifiNetworkKeys(Set.of(TEST_IFACE2)).build(); + // Test networks might use interface as subscriberId to identify individual networks. + // Simulate both cases. + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildTestState(TEST_IFACE, TEST_IFACE), + buildTestState(TEST_IFACE2, null /* wifiNetworkKey */)}; + + // Test networks comes online. + mockNetworkStatsSummary(buildEmptyStats()); + mockNetworkStatsUidDetail(buildEmptyStats()); + mService.notifyNetworkStatus(NETWORKS_TEST, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic on both interfaces. + incrementCurrentTime(MINUTE_IN_MILLIS); + mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7L, 3L, 5L, 1L, 1L))); + forcePollAndWaitForIdle(); + + // Verify test network templates gets stats. Stats of test networks without subscriberId + // can only be matched by templates without subscriberId requirement. + assertUidTotal(templateTestAll, UID_RED, 19L, 21L, 19L, 2L, 1); + assertUidTotal(templateTestIface1, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(templateTestIface2, UID_RED, 0L, 0L, 0L, 0L, 0); + } + @Test public void testUidStatsForTransport() throws Exception { // pretend that network comes online @@ -1319,8 +1361,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // pretend that network comes online mockDefaultSettings(); NetworkStateSnapshot[] states = - new NetworkStateSnapshot[] {buildMobileState(TEST_IFACE, IMSI_1, - false /* isTemporarilyNotMetered */, true /* isRoaming */)}; + new NetworkStateSnapshot[] {buildStateOfTransport( + NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE, + TEST_IFACE, IMSI_1, null /* wifiNetworkKey */, + false /* isTemporarilyNotMetered */, true /* isRoaming */)}; mockNetworkStatsSummary(buildEmptyStats()); mockNetworkStatsUidDetail(buildEmptyStats()); @@ -2189,24 +2233,34 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } private static NetworkStateSnapshot buildMobileState(String subscriberId) { - return buildMobileState(TEST_IFACE, subscriberId, false /* isTemporarilyNotMetered */, - false /* isRoaming */); + return buildStateOfTransport(NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE, + TEST_IFACE, subscriberId, null /* wifiNetworkKey */, + false /* isTemporarilyNotMetered */, false /* isRoaming */); } - private static NetworkStateSnapshot buildMobileState(String iface, String subscriberId, + private static NetworkStateSnapshot buildTestState(@NonNull String iface, + @Nullable String wifiNetworkKey) { + return buildStateOfTransport(NetworkCapabilities.TRANSPORT_TEST, TYPE_TEST, + iface, null /* subscriberId */, wifiNetworkKey, + false /* isTemporarilyNotMetered */, false /* isRoaming */); + } + + private static NetworkStateSnapshot buildStateOfTransport(int transport, int legacyType, + String iface, String subscriberId, String wifiNetworkKey, boolean isTemporarilyNotMetered, boolean isRoaming) { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(iface); final NetworkCapabilities capabilities = new NetworkCapabilities(); - if (isTemporarilyNotMetered) { - capabilities.addCapability( - NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED); - } + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED, + isTemporarilyNotMetered); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); - capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + capabilities.addTransportType(transport); + if (legacyType == TYPE_TEST && !TextUtils.isEmpty(wifiNetworkKey)) { + capabilities.setNetworkSpecifier(new TestNetworkSpecifier(wifiNetworkKey)); + } return new NetworkStateSnapshot( - MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE); + MOBILE_NETWORK, capabilities, prop, subscriberId, legacyType); } private NetworkStats buildEmptyStats() {