Loading core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java 0 → 100644 +520 −0 Original line number Diff line number Diff line /* * 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.app.usage.NetworkStatsManager.PREFIX_UID; import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; import static android.app.usage.NetworkStatsManager.PREFIX_XT; 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 android.annotation.NonNull; 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.FastDataInput; 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.net.ProtocolException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; 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 { private static final HashMap<String, String> sPrefixLegacyFileNameMap = new HashMap<String, String>() {{ put(PREFIX_XT, "netstats_xt.bin"); put(PREFIX_UID, "netstats_uid.bin"); put(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; } /** * 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 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 String prefix) { final ArrayList<File> list = new ArrayList<>(); final File platformFiles = new File(getPlatformBaseDir(), "netstats"); if (platformFiles.exists()) { for (String name : platformFiles.list()) { // 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 a default * implementation to read persisted network stats from two different locations. * And this is intended to be modified by OEM to read from 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 readPlatformCollectionLocked( @NonNull 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 FastDataInput dataIn = new FastDataInput(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 = NetworkIdentity.OEM_NONE; } // 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); 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 tagged data. 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 } } } Loading
core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java 0 → 100644 +520 −0 Original line number Diff line number Diff line /* * 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.app.usage.NetworkStatsManager.PREFIX_UID; import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; import static android.app.usage.NetworkStatsManager.PREFIX_XT; 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 android.annotation.NonNull; 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.FastDataInput; 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.net.ProtocolException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; 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 { private static final HashMap<String, String> sPrefixLegacyFileNameMap = new HashMap<String, String>() {{ put(PREFIX_XT, "netstats_xt.bin"); put(PREFIX_UID, "netstats_uid.bin"); put(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; } /** * 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 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 String prefix) { final ArrayList<File> list = new ArrayList<>(); final File platformFiles = new File(getPlatformBaseDir(), "netstats"); if (platformFiles.exists()) { for (String name : platformFiles.list()) { // 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 a default * implementation to read persisted network stats from two different locations. * And this is intended to be modified by OEM to read from 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 readPlatformCollectionLocked( @NonNull 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 FastDataInput dataIn = new FastDataInput(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 = NetworkIdentity.OEM_NONE; } // 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); 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 tagged data. 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 } } }