Merge "Skip testNetworkRequest_forInterfaceWhileTogglingCarrier on lower kernel version."
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 19d0d5f..28edc8a 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -134,7 +134,7 @@
"-Wthread-safety",
],
- ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
+ ldflags: ["-Wl,--exclude-libs=ALL,--error-limit=0"],
}
// Common defaults for compiling the actual APK.
@@ -231,4 +231,4 @@
cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" +
" --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"],
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index ff6e45d..e41034a 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -76,8 +76,10 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
+import android.net.ConnectivityResources;
import android.net.DataUsageRequest;
import android.net.INetd;
import android.net.INetworkStatsService;
@@ -140,6 +142,7 @@
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FileRotator;
@@ -255,7 +258,8 @@
"netstats_import_legacy_target_attempts";
static final int DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS = 1;
static final String NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME = "import.attempts";
- static final String NETSTATS_IMPORT_SUCCESS_COUNTER_NAME = "import.successes";
+ static final String NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME = "import.successes";
+ static final String NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME = "import.fallbacks";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -275,10 +279,11 @@
private final AlertObserver mAlertObserver = new AlertObserver();
// Persistent counters that backed by AtomicFile which stored in the data directory as a file,
- // to track attempts/successes count across reboot. Note that these counter values will be
- // rollback as the module rollbacks.
+ // to track attempts/successes/fallbacks count across reboot. Note that these counter values
+ // will be rollback as the module rollbacks.
private PersistentInt mImportLegacyAttemptsCounter = null;
private PersistentInt mImportLegacySuccessesCounter = null;
+ private PersistentInt mImportLegacyFallbacksCounter = null;
@VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
@@ -626,21 +631,14 @@
}
/**
- * Create the persistent counter that counts total import legacy stats attempts.
+ * Create a persistent counter for given directory and name.
*/
- public PersistentInt createImportLegacyAttemptsCounter(@NonNull Path path)
+ public PersistentInt createPersistentCounter(@NonNull Path dir, @NonNull String name)
throws IOException {
// TODO: Modify PersistentInt to call setStartTime every time a write is made.
// Create and pass a real logger here.
- return new PersistentInt(path.toString(), null /* logger */);
- }
-
- /**
- * Create the persistent counter that counts total import legacy stats successes.
- */
- public PersistentInt createImportLegacySuccessesCounter(@NonNull Path path)
- throws IOException {
- return new PersistentInt(path.toString(), null /* logger */);
+ final String path = dir.resolve(name).toString();
+ return new PersistentInt(path, null /* logger */);
}
/**
@@ -770,6 +768,11 @@
return null;
}
}
+
+ /** Gets whether the build is userdebug. */
+ public boolean isDebuggable() {
+ return Build.isDebuggable();
+ }
}
/**
@@ -918,10 +921,12 @@
return;
}
try {
- mImportLegacyAttemptsCounter = mDeps.createImportLegacyAttemptsCounter(
- mStatsDir.toPath().resolve(NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME));
- mImportLegacySuccessesCounter = mDeps.createImportLegacySuccessesCounter(
- mStatsDir.toPath().resolve(NETSTATS_IMPORT_SUCCESS_COUNTER_NAME));
+ mImportLegacyAttemptsCounter = mDeps.createPersistentCounter(mStatsDir.toPath(),
+ NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME);
+ mImportLegacySuccessesCounter = mDeps.createPersistentCounter(mStatsDir.toPath(),
+ NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME);
+ mImportLegacyFallbacksCounter = mDeps.createPersistentCounter(mStatsDir.toPath(),
+ NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME);
} catch (IOException e) {
Log.wtf(TAG, "Failed to create persistent counters, skip.", e);
return;
@@ -929,15 +934,33 @@
final int targetAttempts = mDeps.getImportLegacyTargetAttempts();
final int attempts;
+ final int fallbacks;
+ final boolean runComparison;
try {
attempts = mImportLegacyAttemptsCounter.get();
+ // Fallbacks counter would be set to non-zero value to indicate the migration was
+ // not successful.
+ fallbacks = mImportLegacyFallbacksCounter.get();
+ runComparison = shouldRunComparison();
} catch (IOException e) {
- Log.wtf(TAG, "Failed to read attempts counter, skip.", e);
+ Log.wtf(TAG, "Failed to read counters, skip.", e);
return;
}
- if (attempts >= targetAttempts) return;
- Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
+ // If the target number of attempts are reached, don't import any data.
+ // However, if comparison is requested, still read the legacy data and compare
+ // it to the importer output. This allows OEMs to debug issues with the
+ // importer code and to collect signals from the field.
+ final boolean dryRunImportOnly =
+ fallbacks != 0 && runComparison && (attempts >= targetAttempts);
+ // Return if target attempts are reached and there is no need to dry run.
+ if (attempts >= targetAttempts && !dryRunImportOnly) return;
+
+ if (dryRunImportOnly) {
+ Log.i(TAG, "Starting import : only perform read");
+ } else {
+ Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
+ }
final MigrationInfo[] migrations = new MigrationInfo[]{
new MigrationInfo(mDevRecorder), new MigrationInfo(mXtRecorder),
@@ -945,68 +968,57 @@
};
// Legacy directories will be created by recorders if they do not exist
- final File legacyBaseDir = mDeps.getLegacyStatsDir();
- final NetworkStatsRecorder[] legacyRecorders = new NetworkStatsRecorder[]{
- buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
- buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
- buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
- buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
- };
+ final NetworkStatsRecorder[] legacyRecorders;
+ if (runComparison) {
+ final File legacyBaseDir = mDeps.getLegacyStatsDir();
+ legacyRecorders = new NetworkStatsRecorder[]{
+ buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
+ };
+ } else {
+ legacyRecorders = null;
+ }
long migrationEndTime = Long.MIN_VALUE;
- boolean endedWithFallback = false;
try {
// First, read all legacy collections. This is OEM code and it can throw. Don't
// commit any data to disk until all are read.
for (int i = 0; i < migrations.length; i++) {
- String errMsg = null;
- Throwable exception = null;
final MigrationInfo migration = migrations[i];
- // Read the collection from platform code, and using fallback method if throws.
+ // Read the collection from platform code, and set fallbacks counter if throws
+ // for better debugging.
try {
migration.collection = readPlatformCollectionForRecorder(migration.recorder);
} catch (Throwable e) {
- errMsg = "Failed to read stats from platform";
- exception = e;
- }
-
- // Also read the collection with legacy method
- final NetworkStatsRecorder legacyRecorder = legacyRecorders[i];
-
- final NetworkStatsCollection legacyStats;
- try {
- legacyStats = legacyRecorder.getOrLoadCompleteLocked();
- } catch (Throwable e) {
- Log.wtf(TAG, "Failed to read stats with legacy method for recorder " + i, e);
- if (exception != null) {
- throw exception;
+ if (dryRunImportOnly) {
+ Log.wtf(TAG, "Platform data read failed. ", e);
+ return;
} else {
- // Use newer stats, since that's all that is available
- continue;
+ // Data is not imported successfully, set fallbacks counter to non-zero
+ // value to trigger dry run every later boot when the runComparison is
+ // true, in order to make it easier to debug issues.
+ tryIncrementLegacyFallbacksCounter();
+ // Re-throw for error handling. This will increase attempts counter.
+ throw e;
}
}
- if (errMsg == null) {
- try {
- errMsg = compareStats(migration.collection, legacyStats);
- } catch (Throwable e) {
- errMsg = "Failed to compare migrated stats with all stats";
- exception = e;
+ if (runComparison) {
+ final boolean success =
+ compareImportedToLegacyStats(migration, legacyRecorders[i]);
+ if (!success && !dryRunImportOnly) {
+ tryIncrementLegacyFallbacksCounter();
}
}
-
- if (errMsg != null) {
- Log.wtf(TAG, "NetworkStats import for migration " + i
- + " returned invalid data: " + errMsg, exception);
- // Fall back to legacy stats for this boot. The stats for old data will be
- // re-imported again on next boot until they succeed the import. This is fine
- // since every import clears the previous stats for the imported timespan.
- migration.collection = legacyStats;
- endedWithFallback = true;
- }
}
+ // For cases where the fallbacks are not zero but target attempts counts reached,
+ // only perform reads above and return here.
+ if (dryRunImportOnly) return;
+
// Find the latest end time.
for (final MigrationInfo migration : migrations) {
final long migrationEnd = migration.collection.getEndMillis();
@@ -1029,11 +1041,7 @@
migration.recorder.importCollectionLocked(migration.collection);
}
- if (endedWithFallback) {
- Log.wtf(TAG, "Imported platform collections with legacy fallback");
- } else {
- Log.i(TAG, "Successfully imported platform collections");
- }
+ // Success normally or uses fallback method.
} catch (Throwable e) {
// The code above calls OEM code that may behave differently across devices.
// It can throw any exception including RuntimeExceptions and
@@ -1073,8 +1081,9 @@
// Success ! No need to import again next time.
try {
mImportLegacyAttemptsCounter.set(targetAttempts);
+ Log.i(TAG, "Successfully imported platform collections");
// The successes counter is only for debugging. Hence, the synchronization
- // between these two counters are not very critical.
+ // between successes counter and attempts counter are not very critical.
final int successCount = mImportLegacySuccessesCounter.get();
mImportLegacySuccessesCounter.set(successCount + 1);
} catch (IOException e) {
@@ -1082,6 +1091,68 @@
}
}
+ void tryIncrementLegacyFallbacksCounter() {
+ try {
+ final int fallbacks = mImportLegacyFallbacksCounter.get();
+ mImportLegacyFallbacksCounter.set(fallbacks + 1);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to update fallback counter.", e);
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldRunComparison() {
+ final ConnectivityResources resources = new ConnectivityResources(mContext);
+ // 0 if id not found.
+ Boolean overlayValue = null;
+ try {
+ switch (resources.get().getInteger(R.integer.config_netstats_validate_import)) {
+ case 1:
+ overlayValue = Boolean.TRUE;
+ break;
+ case 0:
+ overlayValue = Boolean.FALSE;
+ break;
+ }
+ } catch (Resources.NotFoundException e) {
+ // Overlay value is not defined.
+ }
+ return overlayValue != null ? overlayValue : mDeps.isDebuggable();
+ }
+
+ /**
+ * Compare imported data with the data returned by legacy recorders.
+ *
+ * @return true if the data matches, false if the data does not match or throw with exceptions.
+ */
+ private boolean compareImportedToLegacyStats(@NonNull MigrationInfo migration,
+ @NonNull NetworkStatsRecorder legacyRecorder) {
+ final NetworkStatsCollection legacyStats;
+ try {
+ legacyStats = legacyRecorder.getOrLoadCompleteLocked();
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Failed to read stats with legacy method for recorder "
+ + legacyRecorder.getCookie(), e);
+ // Cannot read data from legacy method, skip comparison.
+ return false;
+ }
+
+ // The result of comparison is only for logging.
+ try {
+ final String error = compareStats(migration.collection, legacyStats);
+ if (error != null) {
+ Log.wtf(TAG, "Unexpected comparison result for recorder "
+ + legacyRecorder.getCookie() + ": " + error);
+ return false;
+ }
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Failed to compare migrated stats with legacy stats for recorder "
+ + legacyRecorder.getCookie(), e);
+ return false;
+ }
+ return true;
+ }
+
private static String str(NetworkStatsCollection.Key key) {
StringBuilder sb = new StringBuilder()
.append(key.ident.toString())
@@ -2506,6 +2577,9 @@
pw.print("platform legacy stats import successes count",
mImportLegacySuccessesCounter.get());
pw.println();
+ pw.print("platform legacy stats import fallbacks count",
+ mImportLegacyFallbacksCounter.get());
+ pw.println();
} catch (IOException e) {
pw.println("(failed to dump platform legacy stats import counters)");
}
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 81782f9..bff6953 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -179,4 +179,13 @@
Only supported up to S. On T+, the Wi-Fi code should use unregisterAfterReplacement in order
to ensure that apps see the network disconnect and reconnect. -->
<integer translatable="false" name="config_validationFailureAfterRoamIgnoreTimeMillis">-1</integer>
+
+ <!-- Whether the network stats service should run compare on the result of
+ {@link NetworkStatsDataMigrationUtils#readPlatformCollection} and the result
+ of reading from legacy recorders. Possible values are:
+ 0 = never compare,
+ 1 = always compare,
+ 2 = compare on debuggable builds (default value)
+ -->
+ <integer translatable="false" name="config_netstats_validate_import">2</integer>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index b92dd08..3389d63 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -41,6 +41,7 @@
<item type="array" name="config_ethernet_interfaces"/>
<item type="string" name="config_ethernet_iface_regex"/>
<item type="integer" name="config_validationFailureAfterRoamIgnoreTimeMillis" />
+ <item type="integer" name="config_netstats_validate_import" />
</policy>
</overlayable>
</resources>
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index dedeb38..34c6d2d 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -791,7 +791,7 @@
mAllApps.add(appId);
// Log package added.
- mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ mPermissionUpdateLogs.log("Package add: uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
+ ", tPerm=" + permissionToString(appIdTrafficPerm));
@@ -844,7 +844,7 @@
final int permission = highestUidNetworkPermission(uid);
// Log package removed.
- mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ mPermissionUpdateLogs.log("Package remove: uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
+ ", tPerm=" + permissionToString(appIdTrafficPerm));
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index da2e594..766d62f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -3377,6 +3377,7 @@
}
@Test @IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testFirewallBlocking() {
// Following tests affect the actual state of networking on the device after the test.
// This might cause unexpected behaviour of the device. So, we skip them for now.
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 4fbbc75..1385d8b 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -67,6 +67,9 @@
import static com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME;
+import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME;
+import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
@@ -92,13 +95,16 @@
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.ConnectivityResources;
import android.net.DataUsageRequest;
import android.net.INetd;
import android.net.INetworkStatsSession;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkIdentity;
import android.net.NetworkStateSnapshot;
import android.net.NetworkStats;
import android.net.NetworkStatsCollection;
@@ -125,6 +131,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.connectivity.resources.R;
import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.IBpfMap;
@@ -152,6 +159,7 @@
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Clock;
@@ -161,6 +169,7 @@
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -242,6 +251,9 @@
private int mImportLegacyTargetAttempts = 0;
private @Mock PersistentInt mImportLegacyAttemptsCounter;
private @Mock PersistentInt mImportLegacySuccessesCounter;
+ private @Mock PersistentInt mImportLegacyFallbacksCounter;
+ private @Mock Resources mResources;
+ private Boolean mIsDebuggable;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -302,6 +314,12 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
+ // Setup mock resources.
+ final Context mockResContext = mock(Context.class);
+ doReturn(mResources).when(mockResContext).getResources();
+ ConnectivityResources.setResourcesContextForTest(mockResContext);
+
final Context context = InstrumentationRegistry.getContext();
mServiceContext = new MockContext(context);
when(mLocationPermissionChecker.checkCallersLocationPermission(
@@ -382,15 +400,18 @@
}
@Override
- public PersistentInt createImportLegacyAttemptsCounter(
- @androidx.annotation.NonNull Path path) {
- return mImportLegacyAttemptsCounter;
- }
-
- @Override
- public PersistentInt createImportLegacySuccessesCounter(
- @androidx.annotation.NonNull Path path) {
- return mImportLegacySuccessesCounter;
+ public PersistentInt createPersistentCounter(@androidx.annotation.NonNull Path dir,
+ @androidx.annotation.NonNull String name) throws IOException {
+ switch (name) {
+ case NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME:
+ return mImportLegacyAttemptsCounter;
+ case NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME:
+ return mImportLegacySuccessesCounter;
+ case NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME:
+ return mImportLegacyFallbacksCounter;
+ default:
+ throw new IllegalArgumentException("Unknown counter name: " + name);
+ }
}
@Override
@@ -454,6 +475,11 @@
public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
return mAppUidStatsMap;
}
+
+ @Override
+ public boolean isDebuggable() {
+ return mIsDebuggable == Boolean.TRUE;
+ }
};
}
@@ -1890,6 +1916,114 @@
// will decrease the retry counter by 1.
}
+ @Test
+ public void testDataMigration_differentFromFallback() throws Exception {
+ assertStatsFilesExist(false);
+ expectDefaultSettings();
+
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+ forcePollAndWaitForIdle();
+ // Simulate shutdown to force persisting data
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(true);
+
+ // Move the files to the legacy directory to simulate an import from old data
+ for (File f : mStatsDir.listFiles()) {
+ Files.move(f.toPath(), mLegacyStatsDir.toPath().resolve(f.getName()));
+ }
+ assertStatsFilesExist(false);
+
+ // Prepare some unexpected data.
+ final NetworkIdentity testWifiIdent = new NetworkIdentity.Builder().setType(TYPE_WIFI)
+ .setWifiNetworkKey(TEST_WIFI_NETWORK_KEY).build();
+ final NetworkStatsCollection.Key unexpectedUidAllkey = new NetworkStatsCollection.Key(
+ Set.of(testWifiIdent), UID_ALL, SET_DEFAULT, 0);
+ final NetworkStatsCollection.Key unexpectedUidBluekey = new NetworkStatsCollection.Key(
+ Set.of(testWifiIdent), UID_BLUE, SET_DEFAULT, 0);
+ final NetworkStatsHistory unexpectedHistory = new NetworkStatsHistory
+ .Builder(965L /* bucketDuration */, 1)
+ .addEntry(new NetworkStatsHistory.Entry(TEST_START, 3L, 55L, 4L, 31L, 10L, 5L))
+ .build();
+
+ // Simulate the platform stats collection somehow is different from what is read from
+ // the fallback method. The service should read them as is. This usually happens when an
+ // OEM has changed the implementation of NetworkStatsDataMigrationUtils inside the platform.
+ final NetworkStatsCollection summaryCollection =
+ getLegacyCollection(PREFIX_XT, false /* includeTags */);
+ summaryCollection.recordHistory(unexpectedUidAllkey, unexpectedHistory);
+ final NetworkStatsCollection uidCollection =
+ getLegacyCollection(PREFIX_UID, false /* includeTags */);
+ uidCollection.recordHistory(unexpectedUidBluekey, unexpectedHistory);
+ mPlatformNetworkStatsCollection.put(PREFIX_DEV, summaryCollection);
+ mPlatformNetworkStatsCollection.put(PREFIX_XT, summaryCollection);
+ mPlatformNetworkStatsCollection.put(PREFIX_UID, uidCollection);
+ mPlatformNetworkStatsCollection.put(PREFIX_UID_TAG,
+ getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
+
+ // Mock zero usage and boot through serviceReady(), verify there is no imported data.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+ assertStatsFilesExist(false);
+
+ // Set the flag and reboot, verify the imported data is not there until next boot.
+ mStoreFilesInApexData = true;
+ mImportLegacyTargetAttempts = 3;
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(false);
+
+ // Boot through systemReady() again.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+
+ // Verify the result read from public API matches the result returned from the importer.
+ assertNetworkTotal(sTemplateWifi, 1024L + 55L, 8L + 4L, 2048L + 31L, 16L + 10L, 0 + 5);
+ assertUidTotal(sTemplateWifi, UID_BLUE,
+ 128L + 55L, 1L + 4L, 128L + 31L, 1L + 10L, 0 + 5);
+ assertStatsFilesExist(true);
+ verify(mImportLegacyAttemptsCounter).set(3);
+ verify(mImportLegacySuccessesCounter).set(1);
+ }
+
+ @Test
+ public void testShouldRunComparison() {
+ for (Boolean isDebuggable : Set.of(Boolean.TRUE, Boolean.FALSE)) {
+ mIsDebuggable = isDebuggable;
+ // Verify return false regardless of the device is debuggable.
+ doReturn(0).when(mResources)
+ .getInteger(R.integer.config_netstats_validate_import);
+ assertShouldRunComparison(false, isDebuggable);
+ // Verify return true regardless of the device is debuggable.
+ doReturn(1).when(mResources)
+ .getInteger(R.integer.config_netstats_validate_import);
+ assertShouldRunComparison(true, isDebuggable);
+ // Verify return true iff the device is debuggable.
+ for (int testValue : Set.of(-1, 2)) {
+ doReturn(testValue).when(mResources)
+ .getInteger(R.integer.config_netstats_validate_import);
+ assertShouldRunComparison(isDebuggable, isDebuggable);
+ }
+ }
+ }
+
+ private void assertShouldRunComparison(boolean expected, boolean isDebuggable) {
+ assertEquals("shouldRunComparison (debuggable=" + isDebuggable + "): ",
+ expected, mService.shouldRunComparison());
+ }
+
private NetworkStatsRecorder makeTestRecorder(File directory, String prefix, Config config,
boolean includeTags) {
final NetworkStats.NonMonotonicObserver observer =
@@ -1901,7 +2035,7 @@
}
private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
- final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, PREFIX_DEV,
+ final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, prefix,
mSettings.getDevConfig(), includeTags);
return recorder.getOrLoadCompleteLocked();
}