script-astra/Android/Sdk/sources/android-35/android/net/netstats/NetworkStatsDataMigrationUtils.java

593 lines
24 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2022 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.netstats;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.NonNull;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.net.NetworkIdentity;
import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Environment;
import android.util.AtomicFile;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArtFastDataInput;
import libcore.io.IoUtils;
import java.io.BufferedInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Helper class to read old version of persistent network statistics.
*
* The implementation is intended to be modified by OEM partners to
* accommodate their custom changes.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public class NetworkStatsDataMigrationUtils {
/**
* Prefix of the files which are used to store per network interface statistics.
*/
public static final String PREFIX_XT = "xt";
/**
* Prefix of the files which are used to store per uid statistics.
*/
public static final String PREFIX_UID = "uid";
/**
* Prefix of the files which are used to store per uid tagged traffic statistics.
*/
public static final String PREFIX_UID_TAG = "uid_tag";
/** @hide */
@StringDef(prefix = {"PREFIX_"}, value = {
PREFIX_XT,
PREFIX_UID,
PREFIX_UID_TAG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Prefix {}
private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of(
PREFIX_XT, "netstats_xt.bin",
PREFIX_UID, "netstats_uid.bin",
PREFIX_UID_TAG, "netstats_uid.bin");
// These version constants are copied from NetworkStatsCollection/History, which is okay for
// OEMs to modify to adapt their own logic.
private static class CollectionVersion {
static final int VERSION_NETWORK_INIT = 1;
static final int VERSION_UID_INIT = 1;
static final int VERSION_UID_WITH_IDENT = 2;
static final int VERSION_UID_WITH_TAG = 3;
static final int VERSION_UID_WITH_SET = 4;
static final int VERSION_UNIFIED_INIT = 16;
}
private static class HistoryVersion {
static final int VERSION_INIT = 1;
static final int VERSION_ADD_PACKETS = 2;
static final int VERSION_ADD_ACTIVE = 3;
}
private static class IdentitySetVersion {
static final int VERSION_INIT = 1;
static final int VERSION_ADD_ROAMING = 2;
static final int VERSION_ADD_NETWORK_ID = 3;
static final int VERSION_ADD_METERED = 4;
static final int VERSION_ADD_DEFAULT_NETWORK = 5;
static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
static final int VERSION_ADD_SUB_ID = 7;
}
/**
* File header magic number: "ANET". The definition is copied from NetworkStatsCollection,
* but it is fine for OEM to re-define to their own value to adapt the legacy file reading
* logic.
*/
private static final int FILE_MAGIC = 0x414E4554;
/** Default buffer size from BufferedInputStream */
private static final int BUFFER_SIZE = 8192;
// Constructing this object is not allowed.
private NetworkStatsDataMigrationUtils() {
}
// Used to read files at /data/system/netstats_*.bin.
@NonNull
private static File getPlatformSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
// Used to read files at /data/system/netstats/<tag>.<start>-<end>.
@NonNull
private static File getPlatformBaseDir() {
File baseDir = new File(getPlatformSystemDir(), "netstats");
baseDir.mkdirs();
return baseDir;
}
// Get /data/system/netstats_*.bin legacy files. Does not check for existence.
@NonNull
private static File getLegacyBinFileForPrefix(@NonNull @Prefix String prefix) {
return new File(getPlatformSystemDir(), sPrefixLegacyFileNameMap.get(prefix));
}
// List /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files.
@NonNull
private static ArrayList<File> getPlatformFileListForPrefix(@NonNull @Prefix String prefix) {
final ArrayList<File> list = new ArrayList<>();
final File platformFiles = getPlatformBaseDir();
if (platformFiles.exists()) {
final String[] files = platformFiles.list();
if (files == null) return list;
Arrays.sort(files);
for (String name : files) {
// Skip when prefix doesn't match.
if (!name.startsWith(prefix + ".")) continue;
list.add(new File(platformFiles, name));
}
}
return list;
}
/**
* Read legacy persisted network stats from disk.
*
* This function provides the implementation to read legacy network stats
* from disk. It is used for migration of legacy network stats into the
* stats provided by the Connectivity module.
* This function needs to know about the previous format(s) of the network
* stats data that might be stored on this device so it can be read and
* conserved upon upgrade to Android 13 or above.
*
* This function will be called multiple times sequentially, all on the
* same thread, and will not be called multiple times concurrently. This
* function is expected to do a substantial amount of disk access, and
* doesn't need to return particularly fast, but the first boot after
* an upgrade to Android 13+ will be held until migration is done. As
* migration is only necessary once, after the first boot following the
* upgrade, this delay is not incurred.
*
* If this function fails in any way, it should throw an exception. If this
* happens, the system can't know about the data that was stored in the
* legacy files, but it will still count data usage happening on this
* session. On the next boot, the system will try migration again, and
* merge the returned data with the data used with the previous session.
* The system will only try the migration up to three (3) times. The remaining
* count is stored in the netstats_import_legacy_file_needed device config. The
* legacy data is never deleted by the mainline module to avoid any possible
* data loss.
*
* It is possible to set the netstats_import_legacy_file_needed device config
* to any positive integer to force the module to perform the migration. This
* can be achieved by calling the following command before rebooting :
* adb shell device_config put connectivity netstats_import_legacy_file_needed 1
*
* The AOSP implementation provides code to read persisted network stats as
* they were written by AOSP prior to Android 13.
* OEMs who have used the AOSP implementation of persisting network stats
* to disk don't need to change anything.
* OEM that had modifications to this format should modify this function
* to read from their custom file format or locations if necessary.
*
* @param prefix Type of data which is being read by the service.
* @param bucketDuration Duration of the buckets of the object, in milliseconds.
* @return {@link NetworkStatsCollection} instance.
*/
@NonNull
public static NetworkStatsCollection readPlatformCollection(
@NonNull @Prefix String prefix, long bucketDuration) throws IOException {
final NetworkStatsCollection.Builder builder =
new NetworkStatsCollection.Builder(bucketDuration);
// Import /data/system/netstats_uid.bin legacy files if exists.
switch (prefix) {
case PREFIX_UID:
case PREFIX_UID_TAG:
final File uidFile = getLegacyBinFileForPrefix(prefix);
if (uidFile.exists()) {
readLegacyUid(builder, uidFile, PREFIX_UID_TAG.equals(prefix) ? true : false);
}
break;
default:
// Ignore other types.
}
// Import /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files if exists.
final ArrayList<File> platformFiles = getPlatformFileListForPrefix(prefix);
for (final File platformFile : platformFiles) {
if (platformFile.exists()) {
readPlatformCollection(builder, platformFile);
}
}
return builder.build();
}
private static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder,
@NonNull File file) throws IOException {
final FileInputStream is = new FileInputStream(file);
final ArtFastDataInput dataIn = new ArtFastDataInput(is, BUFFER_SIZE);
try {
readPlatformCollection(builder, dataIn);
} finally {
IoUtils.closeQuietly(dataIn);
}
}
/**
* Helper function to read old version of NetworkStatsCollections that resided in the platform.
*
* @hide
*/
@VisibleForTesting
public static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder,
@NonNull DataInput in) throws IOException {
// verify file magic header intact
final int magic = in.readInt();
if (magic != FILE_MAGIC) {
throw new ProtocolException("unexpected magic: " + magic);
}
final int version = in.readInt();
switch (version) {
case CollectionVersion.VERSION_UNIFIED_INIT: {
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
final int identSize = in.readInt();
for (int i = 0; i < identSize; i++) {
final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in);
final int size = in.readInt();
for (int j = 0; j < size; j++) {
final int uid = in.readInt();
final int set = in.readInt();
final int tag = in.readInt();
final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key(
ident, uid, set, tag);
final NetworkStatsHistory history = readPlatformHistory(in);
builder.addEntry(key, history);
}
}
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
}
// Copied from NetworkStatsHistory#DataStreamUtils.
private static long[] readFullLongArray(DataInput in) throws IOException {
final int size = in.readInt();
if (size < 0) throw new ProtocolException("negative array size");
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = in.readLong();
}
return values;
}
// Copied from NetworkStatsHistory#DataStreamUtils.
private static long[] readVarLongArray(@NonNull DataInput in) throws IOException {
final int size = in.readInt();
if (size == -1) return null;
if (size < 0) throw new ProtocolException("negative array size");
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = readVarLong(in);
}
return values;
}
/**
* Read variable-length {@link Long} using protobuf-style approach.
*/
// Copied from NetworkStatsHistory#DataStreamUtils.
private static long readVarLong(DataInput in) throws IOException {
int shift = 0;
long result = 0;
while (shift < 64) {
byte b = in.readByte();
result |= (long) (b & 0x7F) << shift;
if ((b & 0x80) == 0) {
return result;
}
shift += 7;
}
throw new ProtocolException("malformed var long");
}
// Copied from NetworkIdentitySet.
private static String readOptionalString(DataInput in) throws IOException {
if (in.readByte() != 0) {
return in.readUTF();
} else {
return null;
}
}
/**
* This is copied from NetworkStatsHistory#NetworkStatsHistory(DataInput in). But it is fine
* for OEM to re-write the logic to adapt the legacy file reading.
*/
@NonNull
private static NetworkStatsHistory readPlatformHistory(@NonNull DataInput in)
throws IOException {
final long bucketDuration;
final long[] bucketStart;
final long[] rxBytes;
final long[] rxPackets;
final long[] txBytes;
final long[] txPackets;
final long[] operations;
final int bucketCount;
long[] activeTime = new long[0];
final int version = in.readInt();
switch (version) {
case HistoryVersion.VERSION_INIT: {
bucketDuration = in.readLong();
bucketStart = readFullLongArray(in);
rxBytes = readFullLongArray(in);
rxPackets = new long[bucketStart.length];
txBytes = readFullLongArray(in);
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
break;
}
case HistoryVersion.VERSION_ADD_PACKETS:
case HistoryVersion.VERSION_ADD_ACTIVE: {
bucketDuration = in.readLong();
bucketStart = readVarLongArray(in);
activeTime = (version >= HistoryVersion.VERSION_ADD_ACTIVE)
? readVarLongArray(in)
: new long[bucketStart.length];
rxBytes = readVarLongArray(in);
rxPackets = readVarLongArray(in);
txBytes = readVarLongArray(in);
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
final NetworkStatsHistory.Builder historyBuilder =
new NetworkStatsHistory.Builder(bucketDuration, bucketCount);
for (int i = 0; i < bucketCount; i++) {
final NetworkStatsHistory.Entry entry = new NetworkStatsHistory.Entry(
bucketStart[i], activeTime[i],
rxBytes[i], rxPackets[i], txBytes[i], txPackets[i], operations[i]);
historyBuilder.addEntry(entry);
}
return historyBuilder.build();
}
@NonNull
private static Set<NetworkIdentity> readPlatformNetworkIdentitySet(@NonNull DataInput in)
throws IOException {
final int version = in.readInt();
final int size = in.readInt();
final Set<NetworkIdentity> set = new HashSet<>();
for (int i = 0; i < size; i++) {
if (version <= IdentitySetVersion.VERSION_INIT) {
final int ignored = in.readInt();
}
final int type = in.readInt();
final int ratType = in.readInt();
final String subscriberId = readOptionalString(in);
final String networkId;
if (version >= IdentitySetVersion.VERSION_ADD_NETWORK_ID) {
networkId = readOptionalString(in);
} else {
networkId = null;
}
final boolean roaming;
if (version >= IdentitySetVersion.VERSION_ADD_ROAMING) {
roaming = in.readBoolean();
} else {
roaming = false;
}
final boolean metered;
if (version >= IdentitySetVersion.VERSION_ADD_METERED) {
metered = in.readBoolean();
} else {
// If this is the old data and the type is mobile, treat it as metered. (Note that
// if this is a mobile network, TYPE_MOBILE is the only possible type that could be
// used.)
metered = (type == TYPE_MOBILE);
}
final boolean defaultNetwork;
if (version >= IdentitySetVersion.VERSION_ADD_DEFAULT_NETWORK) {
defaultNetwork = in.readBoolean();
} else {
defaultNetwork = true;
}
final int oemNetCapabilities;
if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) {
oemNetCapabilities = in.readInt();
} else {
oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO;
}
final int subId;
if (version >= IdentitySetVersion.VERSION_ADD_SUB_ID) {
subId = in.readInt();
} else {
subId = INVALID_SUBSCRIPTION_ID;
}
// Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
// releases. For backward compatibility, record them as TYPE_MOBILE instead.
final int collapsedLegacyType = getCollapsedLegacyType(type);
final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
.setType(collapsedLegacyType)
.setSubscriberId(subscriberId)
.setWifiNetworkKey(networkId)
.setRoaming(roaming).setMetered(metered)
.setDefaultNetwork(defaultNetwork)
.setOemManaged(oemNetCapabilities)
.setSubId(subId);
if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) {
builder.setRatType(ratType);
}
set.add(builder.build());
}
return set;
}
private static int getCollapsedLegacyType(int networkType) {
// The constants are referenced from ConnectivityManager#TYPE_MOBILE_*.
switch (networkType) {
case TYPE_MOBILE:
case TYPE_MOBILE_SUPL:
case TYPE_MOBILE_MMS:
case TYPE_MOBILE_DUN:
case TYPE_MOBILE_HIPRI:
case 10 /* TYPE_MOBILE_FOTA */:
case 11 /* TYPE_MOBILE_IMS */:
case 12 /* TYPE_MOBILE_CBS */:
case 14 /* TYPE_MOBILE_IA */:
case 15 /* TYPE_MOBILE_EMERGENCY */:
return TYPE_MOBILE;
}
return networkType;
}
private static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder,
@NonNull File uidFile, boolean onlyTaggedData) throws IOException {
final AtomicFile inputFile = new AtomicFile(uidFile);
DataInputStream in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
try {
readLegacyUid(builder, in, onlyTaggedData);
} finally {
IoUtils.closeQuietly(in);
}
}
/**
* Read legacy Uid statistics file format into the collection.
*
* This is copied from {@code NetworkStatsCollection#readLegacyUid}.
* See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
*
* @param taggedData whether to read only tagged data (true) or only non-tagged data
* (false). For legacy uid files, the tagged data was stored in
* the same binary file with non-tagged data. But in later releases,
* these data should be kept in different recorders.
* @hide
*/
@VisibleForTesting
public static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder,
@NonNull DataInput in, boolean taggedData) throws IOException {
try {
// verify file magic header intact
final int magic = in.readInt();
if (magic != FILE_MAGIC) {
throw new ProtocolException("unexpected magic: " + magic);
}
final int version = in.readInt();
switch (version) {
case CollectionVersion.VERSION_UID_INIT: {
// uid := size *(UID NetworkStatsHistory)
// drop this data version, since we don't have a good
// mapping into NetworkIdentitySet.
break;
}
case CollectionVersion.VERSION_UID_WITH_IDENT: {
// uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
// drop this data version, since this version only existed
// for a short time.
break;
}
case CollectionVersion.VERSION_UID_WITH_TAG:
case CollectionVersion.VERSION_UID_WITH_SET: {
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
final int identSize = in.readInt();
for (int i = 0; i < identSize; i++) {
final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in);
final int size = in.readInt();
for (int j = 0; j < size; j++) {
final int uid = in.readInt();
final int set = (version >= CollectionVersion.VERSION_UID_WITH_SET)
? in.readInt()
: SET_DEFAULT;
final int tag = in.readInt();
final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key(
ident, uid, set, tag);
final NetworkStatsHistory history = readPlatformHistory(in);
if ((tag == TAG_NONE) != taggedData) {
builder.addEntry(key, history);
}
}
}
break;
}
default: {
throw new ProtocolException("unknown version: " + version);
}
}
} catch (FileNotFoundException | ProtocolException e) {
// missing stats is okay, probably first boot
}
}
}