Merge "Remove ignoreUpTo Q statements" into main
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index ebc9e4e..d6f4572 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -19,6 +19,12 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHERING_WIGIG;
import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
@@ -45,7 +51,6 @@
import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.TetheredClient;
-import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -589,8 +594,8 @@
@NonNull final Inet4Address dnsServer, @NonNull LinkAddress serverAddr,
@Nullable Inet4Address clientAddr) {
final boolean changePrefixOnDecline =
- (mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null);
- final int subnetPrefixLength = mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
+ (mInterfaceType == TETHERING_NCM && clientAddr == null);
+ final int subnetPrefixLength = mInterfaceType == TETHERING_WIFI_P2P
? mP2pLeasesSubnetPrefixLength : 0 /* default value */;
return new DhcpServingParamsParcelExt()
@@ -690,10 +695,10 @@
final IpPrefix ipv4Prefix = asIpPrefix(mIpv4Address);
final Boolean setIfaceUp;
- if (mInterfaceType == TetheringManager.TETHERING_WIFI
- || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P
- || mInterfaceType == TetheringManager.TETHERING_ETHERNET
- || mInterfaceType == TetheringManager.TETHERING_WIGIG) {
+ if (mInterfaceType == TETHERING_WIFI
+ || mInterfaceType == TETHERING_WIFI_P2P
+ || mInterfaceType == TETHERING_ETHERNET
+ || mInterfaceType == TETHERING_WIGIG) {
// The WiFi and Ethernet stack has ownership of the interface up/down state.
// It is unclear whether the Bluetooth or USB stacks will manage their own
// state.
@@ -719,12 +724,12 @@
private boolean shouldNotConfigureBluetoothInterface() {
// Before T, bluetooth tethering configures the interface elsewhere.
- return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
+ return (mInterfaceType == TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
}
private boolean shouldUseWifiP2pDedicatedIp() {
return mIsWifiP2pDedicatedIpEnabled
- && mInterfaceType == TetheringManager.TETHERING_WIFI_P2P;
+ && mInterfaceType == TETHERING_WIFI_P2P;
}
private LinkAddress requestIpv4Address(final int scope, final boolean useLastAddress) {
@@ -844,12 +849,13 @@
}
}
- private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) {
- final int removalFailures = NetdUtils.removeRoutesFromLocalNetwork(
- mNetd, toBeRemoved);
+ private void removeRoutesFromNetworkAndLinkProperties(int netId,
+ @NonNull final List<RouteInfo> toBeRemoved) {
+ final int removalFailures = NetdUtils.removeRoutesFromNetwork(
+ mNetd, netId, toBeRemoved);
if (removalFailures > 0) {
- mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
- removalFailures));
+ mLog.e("Failed to remove " + removalFailures
+ + " IPv6 routes from network " + netId + ".");
}
for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
@@ -879,14 +885,15 @@
}
}
- private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) {
+ private void addRoutesToNetworkAndLinkProperties(int netId,
+ @NonNull final List<RouteInfo> toBeAdded) {
// It's safe to call addInterfaceToNetwork() even if
- // the interface is already in the local_network.
- addInterfaceToNetwork(INetd.LOCAL_NET_ID, mIfaceName);
+ // the interface is already in the network.
+ addInterfaceToNetwork(netId, mIfaceName);
try {
// Add routes from local network. Note that adding routes that
// already exist does not cause an error (EEXIST is silently ignored).
- NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
+ NetdUtils.addRoutesToNetwork(mNetd, netId, mIfaceName, toBeAdded);
} catch (IllegalStateException e) {
mLog.e("Failed to add IPv4/v6 routes to local table: " + e);
return;
@@ -899,7 +906,8 @@
ArraySet<IpPrefix> deprecatedPrefixes, ArraySet<IpPrefix> newPrefixes) {
// [1] Remove the routes that are deprecated.
if (!deprecatedPrefixes.isEmpty()) {
- removeRoutesFromLocalNetwork(getLocalRoutesFor(mIfaceName, deprecatedPrefixes));
+ removeRoutesFromNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ getLocalRoutesFor(mIfaceName, deprecatedPrefixes));
}
// [2] Add only the routes that have not previously been added.
@@ -910,7 +918,8 @@
}
if (!addedPrefixes.isEmpty()) {
- addRoutesToLocalNetwork(getLocalRoutesFor(mIfaceName, addedPrefixes));
+ addRoutesToNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ getLocalRoutesFor(mIfaceName, addedPrefixes));
}
}
}
@@ -1114,7 +1123,8 @@
}
try {
- NetdUtils.tetherInterface(mNetd, mIfaceName, asIpPrefix(mIpv4Address));
+ NetdUtils.tetherInterface(mNetd, INetd.LOCAL_NET_ID, mIfaceName,
+ asIpPrefix(mIpv4Address));
} catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
mLog.e("Error Tethering", e);
mLastError = TETHER_ERROR_TETHER_IFACE_ERROR;
@@ -1213,14 +1223,14 @@
return;
}
- // Remove deprecated routes from local network.
- removeRoutesFromLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
+ // Remove deprecated routes from downstream network.
+ removeRoutesFromNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ List.of(getDirectConnectedRoute(deprecatedLinkAddress)));
mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
- // Add new routes to local network.
- addRoutesToLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
+ // Add new routes to downstream network.
+ addRoutesToNetworkAndLinkProperties(INetd.LOCAL_NET_ID,
+ List.of(getDirectConnectedRoute(mIpv4Address)));
mLinkProperties.addLinkAddress(mIpv4Address);
// Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index fb16226..a942166 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -154,7 +154,9 @@
// Only launch entitlement UI for the current user if it is allowed to
// change tethering. This usually means the system user or the admin users in HSUM.
- if (SdkLevel.isAtLeastT()) {
+ // TODO (b/382624069): Figure out whether it is safe to call createContextAsUser
+ // from secondary user. And re-enable the check or remove the code accordingly.
+ if (false) {
// Create a user context for the current foreground user as UserManager#isAdmin()
// operates on the context user.
final int currentUserId = getCurrentUser();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 51c2d56..16ebbbb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -38,7 +38,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -592,16 +591,8 @@
.onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
}
- @IgnoreUpTo(SC_V2)
@Test
- public void testUiProvisioningMultiUser_aboveT() {
- doTestUiProvisioningMultiUser(true, 1);
- doTestUiProvisioningMultiUser(false, 0);
- }
-
- @IgnoreAfter(SC_V2)
- @Test
- public void testUiProvisioningMultiUser_belowT() {
+ public void testUiProvisioningMultiUser() {
doTestUiProvisioningMultiUser(true, 1);
doTestUiProvisioningMultiUser(false, 1);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 1608e1a..c329142 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -189,7 +189,6 @@
final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
- releaseDownstream(mHotspotIpServer);
// - Test previous enabled hotspot prefix(cached prefix) is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@@ -208,6 +207,7 @@
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
assertNotEquals(hotspotPrefix, etherPrefix);
+ releaseDownstream(mHotspotIpServer);
releaseDownstream(mEthernetIpServer);
}
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index 81f2cf9..868033a 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -17,6 +17,7 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkStats.UID_ALL;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -33,21 +34,25 @@
import android.content.Context;
import android.media.MediaPlayer;
import android.net.netstats.StatsResult;
+import android.net.netstats.TrafficStatsRateLimitCacheConfig;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.LruCacheWithExpiry;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
-
+import java.util.function.LongSupplier;
/**
* Class that provides network traffic statistics. These statistics include
@@ -182,13 +187,48 @@
/** @hide */
public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
+ private static final StatsResult EMPTY_STATS = new StatsResult(0L, 0L, 0L, 0L);
+
+ private static final Object sRateLimitCacheLock = new Object();
+
@GuardedBy("TrafficStats.class")
+ @Nullable
private static INetworkStatsService sStatsService;
// The variable will only be accessed in the test, which is effectively
// single-threaded.
+ @Nullable
private static INetworkStatsService sStatsServiceForTest = null;
+ // This holds the configuration for the TrafficStats rate limit caches.
+ // It will be filled with the result of a query to the service the first time
+ // the caller invokes get*Stats APIs.
+ // This variable can be accessed from any thread with the lock held.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static TrafficStatsRateLimitCacheConfig sRateLimitCacheConfig;
+
+ // Cache for getIfaceStats and getTotalStats binder interfaces.
+ // This variable can be accessed from any thread with the lock held,
+ // while the cache itself is thread-safe and can be accessed outside
+ // the lock.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static LruCacheWithExpiry<String, StatsResult> sRateLimitIfaceCache;
+
+ // Cache for getUidStats binder interface.
+ // This variable can be accessed from any thread with the lock held,
+ // while the cache itself is thread-safe and can be accessed outside
+ // the lock.
+ @GuardedBy("sRateLimitCacheLock")
+ @Nullable
+ private static LruCacheWithExpiry<Integer, StatsResult> sRateLimitUidCache;
+
+ // The variable will only be accessed in the test, which is effectively
+ // single-threaded.
+ @Nullable
+ private static LongSupplier sTimeSupplierForTest = null;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private synchronized static INetworkStatsService getStatsService() {
if (sStatsServiceForTest != null) return sStatsServiceForTest;
@@ -215,6 +255,28 @@
}
/**
+ * Set time supplier for test, or null to reset.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static void setTimeSupplierForTest(LongSupplier timeSupplier) {
+ sTimeSupplierForTest = timeSupplier;
+ }
+
+ /**
+ * Trigger query rate-limit cache config and initializing the caches.
+ *
+ * This is for test purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static void reinitRateLimitCacheForTest() {
+ maybeGetConfigAndInitRateLimitCache(true /* forceReinit */);
+ }
+
+ /**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.
*
@@ -254,6 +316,92 @@
sStatsService = statsManager.getBinder();
}
+ @Nullable
+ private static LruCacheWithExpiry<String, StatsResult> maybeGetRateLimitIfaceCache() {
+ if (!maybeGetConfigAndInitRateLimitCache(false /* forceReinit */)) return null;
+ synchronized (sRateLimitCacheLock) {
+ return sRateLimitIfaceCache;
+ }
+ }
+
+ @Nullable
+ private static LruCacheWithExpiry<Integer, StatsResult> maybeGetRateLimitUidCache() {
+ if (!maybeGetConfigAndInitRateLimitCache(false /* forceReinit */)) return null;
+ synchronized (sRateLimitCacheLock) {
+ return sRateLimitUidCache;
+ }
+ }
+
+ /**
+ * Gets the rate limit cache configuration and init caches if null.
+ *
+ * Gets the configuration from the service as the configuration
+ * is not expected to change dynamically. And use it to initialize
+ * rate-limit cache if not yet initialized.
+ *
+ * @return whether the rate-limit cache is enabled.
+ *
+ * @hide
+ */
+ private static boolean maybeGetConfigAndInitRateLimitCache(boolean forceReinit) {
+ // Access the service outside the lock to avoid potential deadlocks. This is
+ // especially important when the caller is a system component (e.g.,
+ // NetworkPolicyManagerService) that might hold other locks that the service
+ // also needs.
+ // Although this introduces a race condition where multiple threads might
+ // query the service concurrently, it's acceptable in this case because the
+ // configuration doesn't change dynamically. The configuration only needs to
+ // be fetched once before initializing the cache.
+ synchronized (sRateLimitCacheLock) {
+ if (sRateLimitCacheConfig != null && !forceReinit) {
+ return sRateLimitCacheConfig.isCacheEnabled;
+ }
+ }
+
+ final TrafficStatsRateLimitCacheConfig config;
+ try {
+ config = getStatsService().getRateLimitCacheConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ synchronized (sRateLimitCacheLock) {
+ if (sRateLimitCacheConfig == null || forceReinit) {
+ sRateLimitCacheConfig = config;
+ initRateLimitCacheLocked();
+ }
+ }
+ return config.isCacheEnabled;
+ }
+
+ @GuardedBy("sRateLimitCacheLock")
+ private static void initRateLimitCacheLocked() {
+ // Set up rate limiting caches.
+ // Use uid cache with UID_ALL to cache total stats.
+ if (sRateLimitCacheConfig.isCacheEnabled) {
+ // A time supplier which is monotonic until device reboots, and counts
+ // time spent in sleep. This is needed to ensure the get*Stats caller
+ // won't get stale value after system time adjustment or waking up from sleep.
+ final LongSupplier realtimeSupplier = (sTimeSupplierForTest != null
+ ? sTimeSupplierForTest : () -> SystemClock.elapsedRealtime());
+ sRateLimitIfaceCache = new LruCacheWithExpiry<String, StatsResult>(
+ realtimeSupplier,
+ sRateLimitCacheConfig.expiryDurationMs,
+ sRateLimitCacheConfig.maxEntries,
+ (statsResult) -> !isEmpty(statsResult)
+ );
+ sRateLimitUidCache = new LruCacheWithExpiry<Integer, StatsResult>(
+ realtimeSupplier,
+ sRateLimitCacheConfig.expiryDurationMs,
+ sRateLimitCacheConfig.maxEntries,
+ (statsResult) -> !isEmpty(statsResult)
+ );
+ } else {
+ sRateLimitIfaceCache = null;
+ sRateLimitUidCache = null;
+ }
+ }
+
/**
* Attach the socket tagger implementation to the current process, to
* get notified when a socket's {@link FileDescriptor} is assigned to
@@ -736,6 +884,14 @@
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
public static void clearRateLimitCaches() {
+ final LruCacheWithExpiry<String, StatsResult> ifaceCache = maybeGetRateLimitIfaceCache();
+ if (ifaceCache != null) {
+ ifaceCache.clear();
+ }
+ final LruCacheWithExpiry<Integer, StatsResult> uidCache = maybeGetRateLimitUidCache();
+ if (uidCache != null) {
+ uidCache.clear();
+ }
try {
getStatsService().clearTrafficStatsRateLimitCaches();
} catch (RemoteException e) {
@@ -985,35 +1141,76 @@
/** @hide */
public static long getUidStats(int uid, int type) {
- final StatsResult stats;
+ return fetchStats(maybeGetRateLimitUidCache(), uid,
+ () -> getStatsService().getUidStats(uid), type);
+ }
+
+ // Note: This method calls to the service, do not invoke this method with lock held.
+ private static <K> long fetchStats(
+ @Nullable LruCacheWithExpiry<K, StatsResult> cache, K key,
+ BinderUtils.ThrowingSupplier<StatsResult, RemoteException> statsFetcher, int type) {
try {
- stats = getStatsService().getUidStats(uid);
+ final StatsResult stats;
+ if (cache != null) {
+ stats = fetchStatsWithCache(cache, key, statsFetcher);
+ } else {
+ // Cache is not enabled, fetch directly from service.
+ stats = statsFetcher.get();
+ }
+ return getEntryValueForType(stats, type);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- return getEntryValueForType(stats, type);
+ }
+
+ // Note: This method calls to the service, do not invoke this method with lock held.
+ @Nullable
+ private static <K> StatsResult fetchStatsWithCache(LruCacheWithExpiry<K, StatsResult> cache,
+ K key, BinderUtils.ThrowingSupplier<StatsResult, RemoteException> statsFetcher)
+ throws RemoteException {
+ // Attempt to retrieve from the cache first.
+ StatsResult stats = cache.get(key);
+
+ // Although the cache instance is thread-safe, this can still introduce a
+ // race condition between threads of the same process, potentially
+ // returning non-monotonic results. This is because there is no lock
+ // between get, fetch, and put operations. This is considered acceptable
+ // because varying thread execution speeds can also cause non-monotonic
+ // results, even with locking.
+ if (stats == null) {
+ // Cache miss, fetch from the service.
+ stats = statsFetcher.get();
+
+ // Update the cache with the fetched result if valid.
+ if (stats != null && !isEmpty(stats)) {
+ final StatsResult cachedValue = cache.putIfAbsent(key, stats);
+ if (cachedValue != null) {
+ // Some other thread cached a value after this thread
+ // originally got a cache miss. Return the cached value
+ // to ensure all returned values after caching are consistent.
+ return cachedValue;
+ }
+ }
+ }
+ return stats;
+ }
+
+ private static boolean isEmpty(StatsResult stats) {
+ return stats.equals(EMPTY_STATS);
}
/** @hide */
public static long getTotalStats(int type) {
- final StatsResult stats;
- try {
- stats = getStatsService().getTotalStats();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return getEntryValueForType(stats, type);
+ // In practice, Bpf doesn't use UID_ALL for storing per-UID stats.
+ // Use uid cache with UID_ALL to cache total stats.
+ return fetchStats(maybeGetRateLimitUidCache(), UID_ALL,
+ () -> getStatsService().getTotalStats(), type);
}
/** @hide */
public static long getIfaceStats(String iface, int type) {
- final StatsResult stats;
- try {
- stats = getStatsService().getIfaceStats(iface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return getEntryValueForType(stats, type);
+ return fetchStats(maybeGetRateLimitIfaceCache(), iface,
+ () -> getStatsService().getIfaceStats(iface), type);
}
/**
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index da12a0a..deaa734 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -272,6 +272,27 @@
return mVpnRequiresValidation;
}
+ /**
+ * Whether the native network creation should be skipped.
+ *
+ * If set, the native network and routes should be maintained by the caller.
+ *
+ * @hide
+ */
+ private boolean mSkipNativeNetworkCreation = false;
+
+
+ /**
+ * @return Whether the native network creation should be skipped.
+ * @hide
+ */
+ // TODO: Expose API when ready.
+ // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
+ // @SystemApi(client = MODULE_LIBRARIES) when ready.
+ public boolean shouldSkipNativeNetworkCreation() {
+ return mSkipNativeNetworkCreation;
+ }
+
/** @hide */
public NetworkAgentConfig() {
}
@@ -293,6 +314,7 @@
mLegacyExtraInfo = nac.mLegacyExtraInfo;
excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
mVpnRequiresValidation = nac.mVpnRequiresValidation;
+ mSkipNativeNetworkCreation = nac.mSkipNativeNetworkCreation;
}
}
@@ -484,6 +506,26 @@
}
/**
+ * Sets the native network creation should be skipped.
+ *
+ * @return this builder, to facilitate chaining.
+ * @hide
+ */
+ @NonNull
+ // TODO: Expose API when ready.
+ // @FlaggedApi(Flags.FLAG_TETHERING_NETWORK_AGENT)
+ // @SystemApi(client = MODULE_LIBRARIES) when ready.
+ public Builder setSkipNativeNetworkCreation(boolean skipNativeNetworkCreation) {
+ if (!SdkLevel.isAtLeastV()) {
+ // Local agents are supported starting on U on TVs and on V on everything else.
+ // Thus, only support this flag on V+.
+ throw new UnsupportedOperationException("Method is not supported");
+ }
+ mConfig.mSkipNativeNetworkCreation = skipNativeNetworkCreation;
+ return this;
+ }
+
+ /**
* Returns the constructed {@link NetworkAgentConfig} object.
*/
@NonNull
@@ -510,7 +552,8 @@
&& Objects.equals(legacySubTypeName, that.legacySubTypeName)
&& Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
&& excludeLocalRouteVpn == that.excludeLocalRouteVpn
- && mVpnRequiresValidation == that.mVpnRequiresValidation;
+ && mVpnRequiresValidation == that.mVpnRequiresValidation
+ && mSkipNativeNetworkCreation == that.mSkipNativeNetworkCreation;
}
@Override
@@ -518,7 +561,8 @@
return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
skip464xlat, legacyType, legacySubType, legacyTypeName, legacySubTypeName,
- mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation);
+ mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation,
+ mSkipNativeNetworkCreation);
}
@Override
@@ -539,6 +583,7 @@
+ ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
+ ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
+ ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\''
+ + ", skipNativeNetworkCreation = '" + mSkipNativeNetworkCreation + '\''
+ "}";
}
@@ -563,33 +608,35 @@
out.writeString(mLegacyExtraInfo);
out.writeInt(excludeLocalRouteVpn ? 1 : 0);
out.writeInt(mVpnRequiresValidation ? 1 : 0);
+ out.writeInt(mSkipNativeNetworkCreation ? 1 : 0);
}
public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
new Creator<NetworkAgentConfig>() {
- @Override
- public NetworkAgentConfig createFromParcel(Parcel in) {
- NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
- networkAgentConfig.allowBypass = in.readInt() != 0;
- networkAgentConfig.explicitlySelected = in.readInt() != 0;
- networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
- networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
- networkAgentConfig.subscriberId = in.readString();
- networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
- networkAgentConfig.skip464xlat = in.readInt() != 0;
- networkAgentConfig.legacyType = in.readInt();
- networkAgentConfig.legacyTypeName = in.readString();
- networkAgentConfig.legacySubType = in.readInt();
- networkAgentConfig.legacySubTypeName = in.readString();
- networkAgentConfig.mLegacyExtraInfo = in.readString();
- networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
- networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
- return networkAgentConfig;
- }
+ @Override
+ public NetworkAgentConfig createFromParcel(Parcel in) {
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = in.readInt() != 0;
+ networkAgentConfig.explicitlySelected = in.readInt() != 0;
+ networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+ networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0;
+ networkAgentConfig.subscriberId = in.readString();
+ networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+ networkAgentConfig.skip464xlat = in.readInt() != 0;
+ networkAgentConfig.legacyType = in.readInt();
+ networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
+ networkAgentConfig.mLegacyExtraInfo = in.readString();
+ networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
+ networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
+ networkAgentConfig.mSkipNativeNetworkCreation = in.readInt() != 0;
+ return networkAgentConfig;
+ }
- @Override
- public NetworkAgentConfig[] newArray(int size) {
- return new NetworkAgentConfig[size];
- }
- };
+ @Override
+ public NetworkAgentConfig[] newArray(int size) {
+ return new NetworkAgentConfig[size];
+ }
+ };
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index 92b2b09..eef867c 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.server.net.ct;
import android.annotation.RequiresApi;
@@ -36,7 +37,8 @@
*/
public static boolean enabled(Context context) {
return DeviceConfig.getBoolean(
- Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_SERVICE_ENABLED, false)
+ Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_SERVICE_ENABLED,
+ /* defaultValue= */ true)
&& Flags.certificateTransparencyService();
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index fb712a1..a8e3203 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -493,7 +493,8 @@
@Nullable
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
// A feature flag to control whether the client-side rate limit cache should be enabled.
- static final String TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG =
+ @VisibleForTesting
+ public static final String TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_client_rate_limit_cache_enabled_flag";
static final String TRAFFICSTATS_SERVICE_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
diff --git a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
index 667aad1..4f99d1b 100644
--- a/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
+++ b/service-t/src/com/android/server/net/TrafficStatsRateLimitCache.java
@@ -29,7 +29,10 @@
/**
* A thread-safe cache for storing and retrieving {@link NetworkStats.Entry} objects,
* with an adjustable expiry duration to manage data freshness.
+ *
+ * @deprecated Use {@link LruCacheWithExpiry} instead.
*/
+// TODO: Remove this when service side rate limit cache solution is removed.
class TrafficStatsRateLimitCache extends
LruCacheWithExpiry<TrafficStatsRateLimitCache.TrafficStatsCacheKey, NetworkStats.Entry> {
@@ -41,7 +44,7 @@
* @param maxSize Maximum number of entries.
*/
TrafficStatsRateLimitCache(@NonNull Clock clock, long expiryDurationMs, int maxSize) {
- super(clock, expiryDurationMs, maxSize, it -> !it.isEmpty());
+ super(()-> clock.millis(), expiryDurationMs, maxSize, it -> !it.isEmpty());
}
public static class TrafficStatsCacheKey {
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
index d99eedc..553a24b 100644
--- a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -149,17 +149,17 @@
}
/** Setup interface for tethering. */
- public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
- throws RemoteException, ServiceSpecificException {
- tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ public static void tetherInterface(final INetd netd, int netId, final String iface,
+ final IpPrefix dest) throws RemoteException, ServiceSpecificException {
+ tetherInterface(netd, netId, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
}
/** Setup interface with configurable retries for tethering. */
- public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
- int maxAttempts, int pollingIntervalMs)
+ public static void tetherInterface(final INetd netd, int netId, final String iface,
+ final IpPrefix dest, int maxAttempts, int pollingIntervalMs)
throws RemoteException, ServiceSpecificException {
netd.tetherInterfaceAdd(iface);
- networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
+ networkAddInterface(netd, netId, iface, maxAttempts, pollingIntervalMs);
// Activate a route to dest and IPv6 link local.
modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
new RouteInfo(dest, null, iface, RTN_UNICAST));
@@ -174,12 +174,12 @@
* in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
* See b/158269544 for detail.
*/
- private static void networkAddInterface(final INetd netd, final String iface,
+ private static void networkAddInterface(final INetd netd, int netId, final String iface,
int maxAttempts, int pollingIntervalMs)
throws ServiceSpecificException, RemoteException {
for (int i = 1; i <= maxAttempts; i++) {
try {
- netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ netd.networkAddInterface(netId, iface);
return;
} catch (ServiceSpecificException e) {
if (e.errorCode == EBUSY && i < maxAttempts) {
@@ -203,28 +203,29 @@
}
}
- /** Add |routes| to local network. */
- public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+ /** Add |routes| to the given network. */
+ public static void addRoutesToNetwork(final INetd netd, int netId, final String iface,
final List<RouteInfo> routes) {
for (RouteInfo route : routes) {
if (!route.isDefaultRoute()) {
- modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+ modifyRoute(netd, ModifyOperation.ADD, netId, route);
}
}
// IPv6 link local should be activated always.
- modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+ modifyRoute(netd, ModifyOperation.ADD, netId,
new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
}
- /** Remove routes from local network. */
- public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+ /** Remove routes from the given network. */
+ public static int removeRoutesFromNetwork(final INetd netd, int netId,
+ final List<RouteInfo> routes) {
int failures = 0;
for (RouteInfo route : routes) {
try {
- modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+ modifyRoute(netd, ModifyOperation.REMOVE, netId, route);
} catch (IllegalStateException e) {
failures++;
}
diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
index 5069672..ab90a50 100644
--- a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
+++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
@@ -163,7 +163,7 @@
setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
try {
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX, 20, 0);
fail("Expect throw ServiceSpecificException");
} catch (ServiceSpecificException e) {
assertEquals(e.errorCode, expectedCode);
@@ -177,7 +177,7 @@
setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
try {
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX, 20, 0);
fail("Expect throw RemoteException");
} catch (RemoteException e) { }
@@ -195,7 +195,7 @@
private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
setNetworkAddInterfaceOutcome(null, expectedTries);
- NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX);
+ NetdUtils.tetherInterface(mNetd, LOCAL_NET_ID, IFACE, TEST_IPPREFIX);
verify(mNetd).tetherInterfaceAdd(IFACE);
verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any());
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index f3d8c4a..760d849 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -22,6 +22,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -413,4 +414,68 @@
}
return -1;
}
+
+ /**
+ * Concatenates multiple arrays of the same type into a single new array.
+ */
+ public static byte[] concatArrays(@NonNull byte[]... arr) {
+ int size = 0;
+ for (byte[] a : arr) {
+ size += a.length;
+ }
+ final byte[] result = new byte[size];
+ int offset = 0;
+ for (byte[] a : arr) {
+ System.arraycopy(a, 0, result, offset, a.length);
+ offset += a.length;
+ }
+ return result;
+ }
+
+ /**
+ * Concatenates multiple arrays of the same type into a single new array.
+ */
+ public static <T> T[] concatArrays(@NonNull Class<T> clazz, @NonNull T[]... arr) {
+ int size = 0;
+ for (T[] a : arr) {
+ size += a.length;
+ }
+ final T[] result = (T[]) Array.newInstance(clazz, size);
+ int offset = 0;
+ for (T[] a : arr) {
+ System.arraycopy(a, 0, result, offset, a.length);
+ offset += a.length;
+ }
+ return result;
+ }
+
+ /**
+ * Prepends the elements of a variable number of prefixes to an existing array (suffix).
+ */
+ public static byte[] prependArray(@NonNull byte[] suffix, @NonNull byte... prefixes) {
+ return concatArrays(prefixes, suffix);
+ }
+
+ /**
+ * Prepends the elements of a variable number of prefixes to an existing array (suffix).
+ */
+ public static <T> T[] prependArray(@NonNull Class<T> clazz, @NonNull T[] suffix,
+ @NonNull T... prefixes) {
+ return concatArrays(clazz, prefixes, suffix);
+ }
+
+ /**
+ * Appends the elements of a variable number of suffixes to an existing array (prefix).
+ */
+ public static byte[] appendArray(@NonNull byte[] prefix, @NonNull byte... suffixes) {
+ return concatArrays(prefix, suffixes);
+ }
+
+ /**
+ * Appends the elements of a variable number of suffixes to an existing array (prefix).
+ */
+ public static <T> T[] appendArray(@NonNull Class<T> clazz, @NonNull T[] prefix,
+ @NonNull T... suffixes) {
+ return concatArrays(clazz, prefix, suffixes);
+ }
}
diff --git a/staticlibs/framework/com/android/net/module/util/HexDump.java b/staticlibs/framework/com/android/net/module/util/HexDump.java
index a22c258..409f611 100644
--- a/staticlibs/framework/com/android/net/module/util/HexDump.java
+++ b/staticlibs/framework/com/android/net/module/util/HexDump.java
@@ -202,7 +202,7 @@
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
- throw new RuntimeException("Invalid hex char '" + c + "'");
+ throw new IllegalArgumentException("Invalid hex char '" + c + "'");
}
/**
diff --git a/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java b/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
index 80088b9..96d995a 100644
--- a/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
+++ b/staticlibs/framework/com/android/net/module/util/LruCacheWithExpiry.java
@@ -22,13 +22,13 @@
import com.android.internal.annotations.GuardedBy;
-import java.time.Clock;
import java.util.Objects;
+import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
- * An LRU cache that stores key-value pairs with an expiry time.
+ * A thread-safe LRU cache that stores key-value pairs with an expiry time.
*
* <p>This cache uses an {@link LruCache} to store entries and evicts the least
* recently used entries when the cache reaches its maximum capacity. It also
@@ -41,7 +41,7 @@
* @hide
*/
public class LruCacheWithExpiry<K, V> {
- private final Clock mClock;
+ private final LongSupplier mTimeSupplier;
private final long mExpiryDurationMs;
@GuardedBy("mMap")
private final LruCache<K, CacheValue<V>> mMap;
@@ -50,16 +50,17 @@
/**
* Constructs a new {@link LruCacheWithExpiry} with the specified parameters.
*
- * @param clock The {@link Clock} to use for determining timestamps.
+ * @param timeSupplier The {@link java.util.function.LongSupplier} to use for
+ * determining timestamps.
* @param expiryDurationMs The expiry duration for cached entries in milliseconds.
* @param maxSize The maximum number of entries to hold in the cache.
* @param shouldCacheValue A {@link Predicate} that determines whether a given value should be
* cached. This can be used to filter out certain values from being
* stored in the cache.
*/
- public LruCacheWithExpiry(@NonNull Clock clock, long expiryDurationMs, int maxSize,
- Predicate<V> shouldCacheValue) {
- mClock = clock;
+ public LruCacheWithExpiry(@NonNull LongSupplier timeSupplier, long expiryDurationMs,
+ int maxSize, Predicate<V> shouldCacheValue) {
+ mTimeSupplier = timeSupplier;
mExpiryDurationMs = expiryDurationMs;
mMap = new LruCache<>(maxSize);
mShouldCacheValue = shouldCacheValue;
@@ -119,7 +120,26 @@
public void put(@NonNull K key, @NonNull V value) {
Objects.requireNonNull(value);
synchronized (mMap) {
- mMap.put(key, new CacheValue<>(mClock.millis(), value));
+ mMap.put(key, new CacheValue<>(mTimeSupplier.getAsLong(), value));
+ }
+ }
+
+ /**
+ * Stores a value in the cache if absent, associated with the given key.
+ *
+ * @param key The key to associate with the value.
+ * @param value The value to store in the cache.
+ * @return The existing value associated with the key, if present; otherwise, null.
+ */
+ @Nullable
+ public V putIfAbsent(@NonNull K key, @NonNull V value) {
+ Objects.requireNonNull(value);
+ synchronized (mMap) {
+ final V existingValue = get(key);
+ if (existingValue == null) {
+ put(key, value);
+ }
+ return existingValue;
}
}
@@ -133,7 +153,7 @@
}
private boolean isExpired(long timestamp) {
- return mClock.millis() > timestamp + mExpiryDurationMs;
+ return mTimeSupplier.getAsLong() > timestamp + mExpiryDurationMs;
}
private static class CacheValue<V> {
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 5d588cc..4878334 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -120,6 +120,8 @@
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0 });
+ public static final Inet4Address IPV4_ADDR_ALL_HOST_MULTICAST =
+ (Inet4Address) InetAddresses.parseNumericAddress("224.0.0.1");
/**
* CLAT constants
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 7244803..1aa943e 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,7 +19,7 @@
import android.util.SparseArray
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.testutils.assertThrows
+import kotlin.test.assertContentEquals
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
@@ -196,4 +196,88 @@
assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1Copy))
assertEquals(2, CollectionUtils.getIndexForValue(sparseArray, value2))
}
+
+ @Test
+ fun testConcatEmptyByteArrays() {
+ assertContentEquals(
+ byteArrayOf(),
+ CollectionUtils.concatArrays(byteArrayOf(), byteArrayOf())
+ )
+ }
+
+ @Test
+ fun testConcatEmptyStringArrays() {
+ assertContentEquals(
+ arrayOf<String>(),
+ CollectionUtils.concatArrays(
+ String::class.java,
+ arrayOf<String>(),
+ arrayOf<String>()
+ )
+ )
+ }
+
+ @Test
+ fun testConcatByteArrays() {
+ val byteArr1 = byteArrayOf(1, 2, 3)
+ val byteArr2 = byteArrayOf(4, 5, 6)
+ val byteArr3 = byteArrayOf()
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.concatArrays(byteArr1, byteArr2, byteArr3)
+ )
+ }
+
+ @Test
+ fun testConcatStringArrays() {
+ val stringArr1 = arrayOf("1", "2", "3")
+ val stringArr2 = arrayOf("4", "5", "6")
+ val strinvArr3 = arrayOf<String>()
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.concatArrays(String::class.java, stringArr1, stringArr2, strinvArr3)
+ )
+ }
+
+ @Test
+ fun testPrependByteArrays() {
+ val byteArr2 = byteArrayOf(4, 5, 6)
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.prependArray(byteArr2, 1, 2, 3)
+ )
+ }
+
+ @Test
+ fun testPrependStringArrays() {
+ val stringArr2 = arrayOf("4", "5", "6")
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.prependArray(String::class.java, stringArr2, "1", "2", "3")
+ )
+ }
+
+ @Test
+ fun testAppendByteArrays() {
+ val byteArr1 = byteArrayOf(1, 2, 3)
+ val byteArrExpected = byteArrayOf(1, 2, 3, 4, 5, 6)
+ assertContentEquals(
+ byteArrExpected,
+ CollectionUtils.appendArray(byteArr1, 4, 5, 6)
+ )
+ }
+
+ @Test
+ fun testAppendStringArrays() {
+ val stringArr1 = arrayOf("1", "2", "3")
+ val stringArrExpected = arrayOf("1", "2", "3", "4", "5", "6")
+ assertContentEquals(
+ stringArrExpected,
+ CollectionUtils.appendArray(String::class.java, stringArr1, "4", "5", "6")
+ )
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
index 5a15585..f81978a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -50,6 +51,11 @@
}
@Test
+ public void testInvalidHexStringToByteArray() {
+ assertThrows(IllegalArgumentException.class, () -> HexDump.hexStringToByteArray("abxX"));
+ }
+
+ @Test
public void testIntegerToByteArray() {
assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x04},
HexDump.toByteArray((int) 0xff000004));
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt
new file mode 100644
index 0000000..b6af892
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LruCacheWithExpiryTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util
+
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.LongSupplier
+
+@RunWith(DevSdkIgnoreRunner::class)
+class LruCacheWithExpiryTest {
+
+ companion object {
+ private const val CACHE_SIZE = 2
+ private const val EXPIRY_DURATION_MS = 1000L
+ }
+
+ private val timeSupplier = object : LongSupplier {
+ private var currentTimeMillis = 0L
+ override fun getAsLong(): Long = currentTimeMillis
+ fun advanceTime(millis: Long) { currentTimeMillis += millis }
+ }
+
+ private val cache = LruCacheWithExpiry<Int, String>(
+ timeSupplier, EXPIRY_DURATION_MS, CACHE_SIZE) { true }
+
+ @Test
+ fun testPutIfAbsent_keyNotPresent() {
+ val value = cache.putIfAbsent(1, "value1")
+ assertNull(value)
+ assertEquals("value1", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_keyPresent() {
+ cache.put(1, "value1")
+ val value = cache.putIfAbsent(1, "value2")
+ assertEquals("value1", value)
+ assertEquals("value1", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_keyPresentButExpired() {
+ cache.put(1, "value1")
+ // Advance time to expire the entry
+ timeSupplier.advanceTime(EXPIRY_DURATION_MS + 1)
+ val value = cache.putIfAbsent(1, "value2")
+ assertNull(value)
+ assertEquals("value2", cache.get(1))
+ }
+
+ @Test
+ fun testPutIfAbsent_maxSizeReached() {
+ cache.put(1, "value1")
+ cache.put(2, "value2")
+ cache.putIfAbsent(3, "value3") // This should evict the least recently used entry (1)
+ assertNull(cache.get(1))
+ assertEquals("value2", cache.get(2))
+ assertEquals("value3", cache.get(3))
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index e5b8471..0624e5f 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -43,9 +43,14 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel.isAtLeastS
import java.io.ByteArrayOutputStream
+import java.io.CharArrayWriter
import java.io.File
import java.io.FileOutputStream
+import java.io.FileReader
+import java.io.OutputStream
+import java.io.OutputStreamWriter
import java.io.PrintWriter
+import java.io.Reader
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.CompletableFuture
@@ -80,7 +85,38 @@
var instance: ConnectivityDiagnosticsCollector? = null
}
+ /**
+ * Indicates tcpdump should be started and written to the diagnostics file on test case failure.
+ */
+ annotation class CollectTcpdumpOnFailure
+
+ private class DumpThread(
+ // Keep a reference to the ParcelFileDescriptor otherwise GC would close it
+ private val fd: ParcelFileDescriptor,
+ private val reader: Reader
+ ) : Thread() {
+ private val writer = CharArrayWriter()
+ override fun run() {
+ reader.copyTo(writer)
+ }
+
+ fun closeAndWriteTo(output: OutputStream?) {
+ join()
+ fd.close()
+ if (output != null) {
+ val outputWriter = OutputStreamWriter(output)
+ outputWriter.write("--- tcpdump stopped at ${ZonedDateTime.now()} ---\n")
+ writer.writeTo(outputWriter)
+ }
+ }
+ }
+
+ private data class TcpdumpRun(val pid: Int, val reader: DumpThread)
+
private var failureHeader: String? = null
+
+ // Accessed from the test listener methods which are synchronized by junit (see TestListener)
+ private var tcpdumpRun: TcpdumpRun? = null
private val buffer = ByteArrayOutputStream()
private val failureHeaderExtras = mutableMapOf<String, Any>()
private val collectorDir: File by lazy {
@@ -157,7 +193,57 @@
flushBufferToFileMetric(testData, baseFilename)
}
+ override fun onTestStart(testData: DataRecord, description: Description) {
+ val tcpdumpAnn = description.annotations.firstOrNull { it is CollectTcpdumpOnFailure }
+ as? CollectTcpdumpOnFailure
+ if (tcpdumpAnn != null) {
+ startTcpdumpForTestcaseIfSupported()
+ }
+ }
+
+ private fun startTcpdumpForTestcaseIfSupported() {
+ if (!DeviceInfoUtils.isDebuggable()) {
+ Log.d(TAG, "Cannot start tcpdump, build is not debuggable")
+ return
+ }
+ if (tcpdumpRun != null) {
+ Log.e(TAG, "Cannot start tcpdump: it is already running")
+ return
+ }
+ // executeShellCommand won't tokenize quoted arguments containing spaces (like pcap filters)
+ // properly, so pass in the command in stdin instead of using sh -c 'command'
+ val fds = instrumentation.uiAutomation.executeShellCommandRw("sh")
+
+ val stdout = fds[0]
+ val stdin = fds[1]
+ ParcelFileDescriptor.AutoCloseOutputStream(stdin).use { writer ->
+ // Echo the current pid, and replace it (with exec) with the tcpdump process, so the
+ // tcpdump pid is known.
+ writer.write(
+ "echo $$; exec su 0 tcpdump -n -i any -U -xx".encodeToByteArray()
+ )
+ }
+ val reader = FileReader(stdout.fileDescriptor).buffered()
+ val tcpdumpPid = Integer.parseInt(reader.readLine())
+ val dumpThread = DumpThread(stdout, reader)
+ dumpThread.start()
+ tcpdumpRun = TcpdumpRun(tcpdumpPid, dumpThread)
+ }
+
+ private fun stopTcpdumpIfRunning(output: OutputStream?) {
+ val run = tcpdumpRun ?: return
+ // Send SIGTERM for graceful shutdown of tcpdump so that it can flush its output
+ executeCommandBlocking("su 0 kill ${run.pid}")
+ run.reader.closeAndWriteTo(output)
+ tcpdumpRun = null
+ }
+
override fun onTestEnd(testData: DataRecord, description: Description) {
+ // onTestFail is called before onTestEnd, so if the test failed tcpdump would already have
+ // been stopped and output dumped. Here this stops tcpdump if the test succeeded, throwing
+ // away its output.
+ stopTcpdumpIfRunning(output = null)
+
// Tests may call methods like collectDumpsysConnectivity to collect diagnostics at any time
// during the run, for example to observe state at various points to investigate a flake
// and compare passing/failing cases.
@@ -196,6 +282,7 @@
fos.write("\n".toByteArray())
}
fos.write(buffer.toByteArray())
+ stopTcpdumpIfRunning(fos)
}
failureHeader = null
buffer.reset()
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index d640a73..fe869f8 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,6 +20,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.modules.utils.build.SdkLevel.isAtLeastT
+import com.android.modules.utils.build.SdkLevel.isAtLeastV
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.assertParcelingIsLossless
import org.junit.Assert.assertEquals
@@ -47,6 +48,9 @@
setLocalRoutesExcludedForVpn(true)
setVpnRequiresValidation(true)
}
+ if (isAtLeastV()) {
+ setSkipNativeNetworkCreation(true)
+ }
}.build()
assertParcelingIsLossless(config)
}
@@ -71,6 +75,9 @@
setLocalRoutesExcludedForVpn(true)
setVpnRequiresValidation(true)
}
+ if (isAtLeastV()) {
+ setSkipNativeNetworkCreation(true)
+ }
}.build()
assertTrue(config.isExplicitlySelected())
@@ -79,6 +86,9 @@
assertFalse(config.isPartialConnectivityAcceptable())
assertTrue(config.isUnvalidatedConnectivityAcceptable())
assertEquals("TEST_NETWORK", config.getLegacyTypeName())
+ if (isAtLeastV()) {
+ assertTrue(config.shouldSkipNativeNetworkCreation())
+ }
if (isAtLeastT()) {
assertTrue(config.areLocalRoutesExcludedForVpn())
assertTrue(config.isVpnValidationRequired())
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index e3d7240..005f6ad 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -75,6 +75,7 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.modules.utils.build.SdkLevel;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.ConnectivityDiagnosticsCollector;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -759,6 +760,7 @@
bucket.getRxBytes(), bucket.getTxBytes()));
}
+ @ConnectivityDiagnosticsCollector.CollectTcpdumpOnFailure
@Test
public void testUidTagStateDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
diff --git a/tests/unit/java/android/net/TrafficStatsTest.kt b/tests/unit/java/android/net/TrafficStatsTest.kt
new file mode 100644
index 0000000..0f85daf
--- /dev/null
+++ b/tests/unit/java/android/net/TrafficStatsTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.net.TrafficStats.UNSUPPORTED
+import android.net.netstats.StatsResult
+import android.net.netstats.TrafficStatsRateLimitCacheConfig
+import android.os.Build
+import com.android.server.net.NetworkStatsService.TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.HashMap
+import java.util.function.LongSupplier
+
+const val TEST_EXPIRY_DURATION_MS = 1000
+const val TEST_IFACE = "wlan0"
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class TrafficStatsTest {
+ private val binder = mock(INetworkStatsService::class.java)
+ private val myUid = android.os.Process.myUid()
+ private val mockMyUidStatsResult = StatsResult(5L, 6L, 7L, 8L)
+ private val mockIfaceStatsResult = StatsResult(7L, 3L, 10L, 21L)
+ private val mockTotalStatsResult = StatsResult(8L, 1L, 5L, 2L)
+ private val secondUidStatsResult = StatsResult(3L, 7L, 10L, 5L)
+ private val secondIfaceStatsResult = StatsResult(9L, 8L, 7L, 6L)
+ private val secondTotalStatsResult = StatsResult(4L, 3L, 2L, 1L)
+ private val emptyStatsResult = StatsResult(0L, 0L, 0L, 0L)
+ private val unsupportedStatsResult =
+ StatsResult(UNSUPPORTED.toLong(), UNSUPPORTED.toLong(),
+ UNSUPPORTED.toLong(), UNSUPPORTED.toLong())
+
+ private val cacheDisabledConfig = TrafficStatsRateLimitCacheConfig.Builder()
+ .setIsCacheEnabled(false)
+ .setExpiryDurationMs(0)
+ .setMaxEntries(0)
+ .build()
+ private val cacheEnabledConfig = TrafficStatsRateLimitCacheConfig.Builder()
+ .setIsCacheEnabled(true)
+ .setExpiryDurationMs(TEST_EXPIRY_DURATION_MS)
+ .setMaxEntries(100)
+ .build()
+ private val mTestTimeSupplier = TestTimeSupplier()
+
+ private val featureFlags = HashMap<String, Boolean>()
+
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @get:Rule
+ val setFeatureFlagsRule = SetFeatureFlagsRule(
+ { name, enabled -> featureFlags.put(name, enabled == true) },
+ { name -> featureFlags.getOrDefault(name, false) }
+ )
+
+ class TestTimeSupplier : LongSupplier {
+ private var currentTimeMillis = 0L
+
+ override fun getAsLong() = currentTimeMillis
+
+ fun advanceTime(millis: Int) {
+ currentTimeMillis += millis
+ }
+ }
+
+ @Before
+ fun setUp() {
+ TrafficStats.setServiceForTest(binder)
+ TrafficStats.setTimeSupplierForTest(mTestTimeSupplier)
+ mockStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ if (featureFlags.getOrDefault(TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, false)) {
+ doReturn(cacheEnabledConfig).`when`(binder).getRateLimitCacheConfig()
+ } else {
+ doReturn(cacheDisabledConfig).`when`(binder).getRateLimitCacheConfig()
+ }
+ TrafficStats.reinitRateLimitCacheForTest()
+ }
+
+ @After
+ fun tearDown() {
+ TrafficStats.setServiceForTest(null)
+ TrafficStats.setTimeSupplierForTest(null)
+ TrafficStats.reinitRateLimitCacheForTest()
+ }
+
+ private fun assertUidStats(uid: Int, stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getUidRxBytes(uid))
+ assertEquals(stats.rxPackets, TrafficStats.getUidRxPackets(uid))
+ assertEquals(stats.txBytes, TrafficStats.getUidTxBytes(uid))
+ assertEquals(stats.txPackets, TrafficStats.getUidTxPackets(uid))
+ }
+
+ private fun assertIfaceStats(iface: String, stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getRxBytes(iface))
+ assertEquals(stats.rxPackets, TrafficStats.getRxPackets(iface))
+ assertEquals(stats.txBytes, TrafficStats.getTxBytes(iface))
+ assertEquals(stats.txPackets, TrafficStats.getTxPackets(iface))
+ }
+
+ private fun assertTotalStats(stats: StatsResult) {
+ assertEquals(stats.rxBytes, TrafficStats.getTotalRxBytes())
+ assertEquals(stats.rxPackets, TrafficStats.getTotalRxPackets())
+ assertEquals(stats.txBytes, TrafficStats.getTotalTxBytes())
+ assertEquals(stats.txPackets, TrafficStats.getTotalTxPackets())
+ }
+
+ private fun mockStats(uidStats: StatsResult?, ifaceStats: StatsResult?,
+ totalStats: StatsResult?) {
+ doReturn(uidStats).`when`(binder).getUidStats(myUid)
+ doReturn(ifaceStats).`when`(binder).getIfaceStats(TEST_IFACE)
+ doReturn(totalStats).`when`(binder).getTotalStats()
+ }
+
+ private fun assertStats(uidStats: StatsResult, ifaceStats: StatsResult,
+ totalStats: StatsResult) {
+ assertUidStats(myUid, uidStats)
+ assertIfaceStats(TEST_IFACE, ifaceStats)
+ assertTotalStats(totalStats)
+ }
+
+ private fun assertStatsFetchInvocations(wantedInvocations: Int) {
+ verify(binder, times(wantedInvocations)).getUidStats(myUid)
+ verify(binder, times(wantedInvocations)).getIfaceStats(TEST_IFACE)
+ verify(binder, times(wantedInvocations)).getTotalStats()
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testRateLimitCacheExpiry_cacheEnabled() {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(1)
+
+ // Advance time within expiry, verify cached values used.
+ clearInvocations(binder)
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ mTestTimeSupplier.advanceTime(1)
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(0)
+
+ // Advance time to expire cache, verify new values fetched.
+ clearInvocations(binder)
+ mTestTimeSupplier.advanceTime(TEST_EXPIRY_DURATION_MS)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(1)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testRateLimitCacheExpiry_cacheDisabled() {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Advance time within expiry, verify new values fetched.
+ clearInvocations(binder)
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ mTestTimeSupplier.advanceTime(1)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(4)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testInvalidStatsNotCached_cacheEnabled() {
+ doTestInvalidStatsNotCached()
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testInvalidStatsNotCached_cacheDisabled() {
+ doTestInvalidStatsNotCached()
+ }
+
+ private fun doTestInvalidStatsNotCached() {
+ // Mock null stats, this usually happens when the query is not valid,
+ // e.g. query uid stats of other application.
+ mockStats(null, null, null)
+ assertStats(unsupportedStatsResult, unsupportedStatsResult, unsupportedStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Verify null stats is not cached, and mock empty stats. This usually
+ // happens when queries with non-existent interface names.
+ clearInvocations(binder)
+ mockStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStatsFetchInvocations(4)
+
+ // Verify empty result is also not cached.
+ clearInvocations(binder)
+ assertStats(emptyStatsResult, emptyStatsResult, emptyStatsResult)
+ assertStatsFetchInvocations(4)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ fun testClearRateLimitCaches_cacheEnabled() {
+ doTestClearRateLimitCaches(true)
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_CLIENT_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ fun testClearRateLimitCaches_cacheDisabled() {
+ doTestClearRateLimitCaches(false)
+ }
+
+ private fun doTestClearRateLimitCaches(cacheEnabled: Boolean) {
+ // Initial fetch, verify binder calls.
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 1 else 4)
+
+ // Verify cached values are used.
+ clearInvocations(binder)
+ assertStats(mockMyUidStatsResult, mockIfaceStatsResult, mockTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 0 else 4)
+
+ // Clear caches, verify fetching from the service.
+ clearInvocations(binder)
+ TrafficStats.clearRateLimitCaches()
+ mockStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStats(secondUidStatsResult, secondIfaceStatsResult, secondTotalStatsResult)
+ assertStatsFetchInvocations(if (cacheEnabled) 1 else 4)
+ }
+}