Use addEntries when creating NetworkStats
In the current design, NetworkStats.addEntry is used within a
loop to create a NetworkStats instance from entries. This results
in a large memory footprint because the addEntry() method clones
the original NetworkStats instance each time it is called.
This change utilizes the newly created API to add a list of
entries at once. This approach only clones the instance once,
thereby reducing the required memory footprint.
Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.NetworkStatsUtilsTest#testPublicStatsToAndroidNetStats
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.net.module.util.NetworkStatsUtilsTest
(on S device)
Test: atest ConnectivityCoverageTests:com.android.testutils.NetworkStatsUtilsTest
(on S device)
Bug: 335680025
Change-Id: I5c635613ed3ba34d840ee71b6bef21d10105df0d
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ed0670d..94a2411 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -296,6 +296,9 @@
"framework-connectivity-t.stubs.module_lib",
"framework-location.stubs.module_lib",
],
+ static_libs: [
+ "modules-utils-build",
+ ],
jarjar_rules: "jarjar-rules-shared.txt",
visibility: [
"//cts/tests/tests/net",
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
index 41a9428..04b6174 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
@@ -19,6 +19,9 @@
import android.app.usage.NetworkStats;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.ArrayList;
/**
* Various utilities used for NetworkStats related code.
@@ -105,12 +108,21 @@
*/
public static android.net.NetworkStats fromPublicNetworkStats(
NetworkStats publiceNetworkStats) {
- android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
while (publiceNetworkStats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
publiceNetworkStats.getNextBucket(bucket);
- final android.net.NetworkStats.Entry entry = fromBucket(bucket);
- stats = stats.addEntry(entry);
+ entries.add(fromBucket(bucket));
+ }
+ android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ // The new API is only supported on devices running the mainline version of `NetworkStats`.
+ // It should always be used when available for memory efficiency.
+ if (SdkLevel.isAtLeastT()) {
+ stats = stats.addEntries(entries);
+ } else {
+ for (android.net.NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
}
return stats;
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
index baadad0..97d3c19 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
@@ -17,15 +17,27 @@
package com.android.net.module.util
import android.net.NetworkStats
+import android.net.NetworkStats.DEFAULT_NETWORK_NO
+import android.net.NetworkStats.DEFAULT_NETWORK_YES
+import android.net.NetworkStats.Entry
+import android.net.NetworkStats.METERED_NO
+import android.net.NetworkStats.ROAMING_NO
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.SET_DEFAULT
+import android.net.NetworkStats.TAG_NONE
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertEntryEquals
+import com.android.testutils.assertNetworkStatsEquals
+import com.android.testutils.makePublicStatsFromAndroidNetStats
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -93,6 +105,37 @@
assertEntryEquals(expectedEntry, entry)
}
+ @Test
+ fun testPublicStatsToAndroidNetStats() {
+ val uid1 = 10001
+ val uid2 = 10002
+ val testIface = "wlan0"
+ val testAndroidNetStats = NetworkStats(0L, 3)
+ .addEntry(Entry(testIface, uid1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(
+ testIface, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 2, 7, 2, 5, 7))
+ .addEntry(Entry(testIface, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 4, 5, 3, 1, 8))
+ val publicStats: android.app.usage.NetworkStats =
+ makePublicStatsFromAndroidNetStats(testAndroidNetStats)
+ val androidNetStats: NetworkStats =
+ NetworkStatsUtils.fromPublicNetworkStats(publicStats)
+
+ // 1. The public `NetworkStats` class does not include interface information.
+ // Interface details must be removed and items with duplicated
+ // keys need to be merged before making any comparisons.
+ // 2. The public `NetworkStats` class lacks an operations field.
+ // Thus, the information will not be preserved during the conversion.
+ val expectedStats = NetworkStats(0L, 2)
+ .addEntry(Entry(null, uid1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 0))
+ .addEntry(Entry(null, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 6, 12, 5, 6, 0))
+ assertNetworkStatsEquals(expectedStats, androidNetStats)
+ }
+
private fun makeMockBucket(
uid: Int,
tag: Int,
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
index f2b14f5..e8c7297 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
@@ -16,7 +16,12 @@
package com.android.testutils
+import android.app.usage.NetworkStatsManager
+import android.content.Context
+import android.net.INetworkStatsService
+import android.net.INetworkStatsSession
import android.net.NetworkStats
+import android.net.NetworkTemplate
import android.text.TextUtils
import com.android.modules.utils.build.SdkLevel
import kotlin.test.assertTrue
@@ -112,3 +117,32 @@
fun assertParcelingIsLossless(stats: NetworkStats) {
assertParcelingIsLossless(stats) { a, b -> orderInsensitiveEquals(a, b) }
}
+
+/**
+ * Make a {@link android.app.usage.NetworkStats} instance from
+ * a {@link android.net.NetworkStats} instance.
+ */
+// It's not possible to directly create a mocked `NetworkStats` instance
+// because of limitations with `NetworkStats#getNextBucket`.
+// As a workaround for testing, create a mock by controlling the return values
+// from the mocked service that provides the `NetworkStats` data.
+// Notes:
+// 1. The order of records in the final `NetworkStats` object might change or
+// some records might be merged if there are items with duplicate keys.
+// 2. The interface and operations fields will be empty since there is
+// no such field in the {@link android.app.usage.NetworkStats}.
+fun makePublicStatsFromAndroidNetStats(androidNetStats: NetworkStats):
+ android.app.usage.NetworkStats {
+ val mockService = Mockito.mock(INetworkStatsService::class.java)
+ val manager = NetworkStatsManager(Mockito.mock(Context::class.java), mockService)
+ val mockStatsSession = Mockito.mock(INetworkStatsSession::class.java)
+
+ Mockito.doReturn(mockStatsSession).`when`(mockService)
+ .openSessionForUsageStats(anyInt(), any())
+ Mockito.doReturn(androidNetStats).`when`(mockStatsSession).getSummaryForAllUid(
+ any(NetworkTemplate::class.java), anyLong(), anyLong(), anyBoolean())
+ return manager.querySummary(
+ Mockito.mock(NetworkTemplate::class.java),
+ Long.MIN_VALUE, Long.MAX_VALUE
+ )
+}