Merge "Monitor interface added and update bpf interface map"
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index d3d8bba..223bdcd 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -129,6 +129,11 @@
         "src/android/net/EthernetNetworkSpecifier.java",
         "src/android/net/IEthernetManager.aidl",
         "src/android/net/IEthernetServiceListener.aidl",
+        "src/android/net/IInternalNetworkManagementListener.aidl",
+        "src/android/net/InternalNetworkUpdateRequest.java",
+        "src/android/net/InternalNetworkUpdateRequest.aidl",
+        "src/android/net/InternalNetworkManagementException.java",
+        "src/android/net/InternalNetworkManagementException.aidl",
         "src/android/net/ITetheredInterfaceCallback.aidl",
     ],
     path: "src",
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index 4b906c9..8813f98 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -17,6 +17,8 @@
 package android.app.usage;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -55,7 +57,6 @@
 
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Provides access to network usage history and statistics. Usage data is collected in
@@ -125,6 +126,19 @@
     private final Context mContext;
     private final INetworkStatsService mService;
 
+    /**
+     * Type constants for reading different types of Data Usage.
+     * @hide
+     */
+    // @SystemApi(client = MODULE_LIBRARIES)
+    public static final String PREFIX_DEV = "dev";
+    /** @hide */
+    public static final String PREFIX_XT = "xt";
+    /** @hide */
+    public static final String PREFIX_UID = "uid";
+    /** @hide */
+    public static final String PREFIX_UID_TAG = "uid_tag";
+
     /** @hide */
     public static final int FLAG_POLL_ON_OPEN = 1 << 0;
     /** @hide */
@@ -143,6 +157,11 @@
         setAugmentWithSubscriptionPlan(true);
     }
 
+    /** @hide */
+    public INetworkStatsService getBinder() {
+        return mService;
+    }
+
     /**
      * Set poll on open flag to indicate the poll is needed before service gets statistics
      * result. This is default enabled. However, for any non-privileged caller, the poll might
@@ -211,9 +230,10 @@
      */
     @NonNull
     @WorkerThread
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     public Bucket querySummaryForDevice(@NonNull NetworkTemplate template,
             long startTime, long endTime) {
+        Objects.requireNonNull(template);
         try {
             NetworkStats stats =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -385,10 +405,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
             long endTime) throws SecurityException {
+        Objects.requireNonNull(template);
         try {
             NetworkStats result =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -418,10 +439,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime,
             long endTime) throws SecurityException {
+        Objects.requireNonNull(template);
         try {
             NetworkStats result =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -453,10 +475,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template,
             long startTime, long endTime) {
+        Objects.requireNonNull(template);
         try {
             final NetworkStats result =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -580,10 +603,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template,
             long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
+        Objects.requireNonNull(template);
         try {
             final NetworkStats result = new NetworkStats(
                     mContext, template, mFlags, startTime, endTime, mService);
@@ -652,26 +676,49 @@
     }
 
     /**
-     * Query realtime network usage statistics details with interfaces constrains.
-     * Return snapshot of current UID statistics, including any {@link TrafficStats#UID_TETHERING},
-     * video calling data usage and count of network operations that set by
-     * {@link TrafficStats#incrementOperationCount}. The returned data doesn't include any
-     * statistics that is reported by {@link NetworkStatsProvider}.
+     * Query realtime mobile network usage statistics.
      *
-     * @param requiredIfaces A list of interfaces the stats should be restricted to, or
-     *               {@link NetworkStats#INTERFACES_ALL}.
+     * Return a snapshot of current UID network statistics, as it applies
+     * to the mobile radios of the device. The snapshot will include any
+     * tethering traffic, video calling data usage and count of
+     * network operations set by {@link TrafficStats#incrementOperationCount}
+     * made over a mobile radio.
+     * The snapshot will not include any statistics that cannot be seen by
+     * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
      *
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
-    @NonNull public android.net.NetworkStats getDetailedUidStats(
-                @NonNull Set<String> requiredIfaces) {
-        Objects.requireNonNull(requiredIfaces, "requiredIfaces cannot be null");
+    @NonNull public android.net.NetworkStats getMobileUidStats() {
         try {
-            return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0]));
+            return mService.getUidStatsForTransport(TRANSPORT_CELLULAR);
         } catch (RemoteException e) {
-            if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats");
+            if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Query realtime Wi-Fi network usage statistics.
+     *
+     * Return a snapshot of current UID network statistics, as it applies
+     * to the Wi-Fi radios of the device. The snapshot will include any
+     * tethering traffic, video calling data usage and count of
+     * network operations set by {@link TrafficStats#incrementOperationCount}
+     * made over a Wi-Fi radio.
+     * The snapshot will not include any statistics that cannot be seen by
+     * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+    @NonNull public android.net.NetworkStats getWifiUidStats() {
+        try {
+            return mService.getUidStatsForTransport(TRANSPORT_WIFI);
+        } catch (RemoteException e) {
+            if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats");
             throw e.rethrowFromSystemServer();
         }
     }
diff --git a/framework-t/src/android/net/INetworkStatsService.aidl b/framework-t/src/android/net/INetworkStatsService.aidl
index a4babb5..da0aa99 100644
--- a/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/framework-t/src/android/net/INetworkStatsService.aidl
@@ -49,14 +49,8 @@
     @UnsupportedAppUsage
     NetworkStats getDataLayerSnapshotForUid(int uid);
 
-    /** Get a detailed snapshot of stats since boot for all UIDs.
-    *
-    * <p>Results will not always be limited to stats on requiredIfaces when specified: stats for
-    * interfaces stacked on the specified interfaces, or for interfaces on which the specified
-    * interfaces are stacked on, will also be included.
-    * @param requiredIfaces Interface names to get data for, or {@link NetworkStats#INTERFACES_ALL}.
-    */
-    NetworkStats getDetailedUidStats(in String[] requiredIfaces);
+    /** Get the transport NetworkStats for all UIDs since boot. */
+    NetworkStats getUidStatsForTransport(int transport);
 
     /** Return set of any ifaces associated with mobile networks since boot. */
     @UnsupportedAppUsage
diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/framework-t/src/android/net/InternalNetworkManagementException.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.aidl
rename to framework-t/src/android/net/InternalNetworkManagementException.aidl
diff --git a/core/java/android/net/InternalNetworkManagementException.java b/framework-t/src/android/net/InternalNetworkManagementException.java
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.java
rename to framework-t/src/android/net/InternalNetworkManagementException.java
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.aidl
rename to framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/framework-t/src/android/net/InternalNetworkUpdateRequest.java
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.java
rename to framework-t/src/android/net/InternalNetworkUpdateRequest.java
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
index 04d1d68..d3d5a08 100644
--- a/framework-t/src/android/net/NetworkIdentity.java
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -16,18 +16,29 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.net.wifi.WifiInfo;
 import android.service.NetworkIdentityProto;
-import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation;
+import android.telephony.TelephonyManager;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkCapabilitiesUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -37,11 +48,24 @@
  *
  * @hide
  */
-public class NetworkIdentity implements Comparable<NetworkIdentity> {
+@SystemApi(client = MODULE_LIBRARIES)
+public class NetworkIdentity {
     private static final String TAG = "NetworkIdentity";
 
+    /** @hide */
+    // TODO: Remove this after migrating all callers to use
+    //  {@link NetworkTemplate#NETWORK_TYPE_ALL} instead.
     public static final int SUBTYPE_COMBINED = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = {
+            NetworkTemplate.OEM_MANAGED_NO,
+            NetworkTemplate.OEM_MANAGED_PAID,
+            NetworkTemplate.OEM_MANAGED_PRIVATE
+    })
+    public @interface OemManaged{}
+
     /**
      * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}.
      * @hide
@@ -51,29 +75,32 @@
      * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
      * @hide
      */
-    public static final int OEM_PAID = 0x1;
+    public static final int OEM_PAID = 1 << 0;
     /**
      * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
      * @hide
      */
-    public static final int OEM_PRIVATE = 0x2;
+    public static final int OEM_PRIVATE = 1 << 1;
+
+    private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE;
 
     final int mType;
-    final int mSubType;
+    final int mRatType;
     final String mSubscriberId;
-    final String mNetworkId;
+    final String mWifiNetworkKey;
     final boolean mRoaming;
     final boolean mMetered;
     final boolean mDefaultNetwork;
     final int mOemManaged;
 
+    /** @hide */
     public NetworkIdentity(
-            int type, int subType, String subscriberId, String networkId, boolean roaming,
-            boolean metered, boolean defaultNetwork, int oemManaged) {
+            int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
+            boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) {
         mType = type;
-        mSubType = subType;
+        mRatType = ratType;
         mSubscriberId = subscriberId;
-        mNetworkId = networkId;
+        mWifiNetworkKey = wifiNetworkKey;
         mRoaming = roaming;
         mMetered = metered;
         mDefaultNetwork = defaultNetwork;
@@ -82,7 +109,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
+        return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
                 mDefaultNetwork, mOemManaged);
     }
 
@@ -90,9 +117,9 @@
     public boolean equals(@Nullable Object obj) {
         if (obj instanceof NetworkIdentity) {
             final NetworkIdentity ident = (NetworkIdentity) obj;
-            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
+            return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming
                     && Objects.equals(mSubscriberId, ident.mSubscriberId)
-                    && Objects.equals(mNetworkId, ident.mNetworkId)
+                    && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
                     && mMetered == ident.mMetered
                     && mDefaultNetwork == ident.mDefaultNetwork
                     && mOemManaged == ident.mOemManaged;
@@ -104,18 +131,18 @@
     public String toString() {
         final StringBuilder builder = new StringBuilder("{");
         builder.append("type=").append(mType);
-        builder.append(", subType=");
-        if (mSubType == SUBTYPE_COMBINED) {
+        builder.append(", ratType=");
+        if (mRatType == NETWORK_TYPE_ALL) {
             builder.append("COMBINED");
         } else {
-            builder.append(mSubType);
+            builder.append(mRatType);
         }
         if (mSubscriberId != null) {
             builder.append(", subscriberId=")
                     .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
         }
-        if (mNetworkId != null) {
-            builder.append(", networkId=").append(mNetworkId);
+        if (mWifiNetworkKey != null) {
+            builder.append(", wifiNetworkKey=").append(mWifiNetworkKey);
         }
         if (mRoaming) {
             builder.append(", ROAMING");
@@ -153,12 +180,13 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
         proto.write(NetworkIdentityProto.TYPE, mType);
 
-        // Not dumping mSubType, subtypes are no longer supported.
+        // TODO: dump mRatType as well.
 
         proto.write(NetworkIdentityProto.ROAMING, mRoaming);
         proto.write(NetworkIdentityProto.METERED, mMetered);
@@ -168,77 +196,99 @@
         proto.end(start);
     }
 
+    /** Get the network type of this instance. */
     public int getType() {
         return mType;
     }
 
-    public int getSubType() {
-        return mSubType;
+    /** Get the Radio Access Technology(RAT) type of this instance. */
+    public int getRatType() {
+        return mRatType;
     }
 
+    /** Get the Subscriber Id of this instance. */
+    @Nullable
     public String getSubscriberId() {
         return mSubscriberId;
     }
 
-    public String getNetworkId() {
-        return mNetworkId;
+    /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */
+    @Nullable
+    public String getWifiNetworkKey() {
+        return mWifiNetworkKey;
     }
 
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getRoaming() {
         return mRoaming;
     }
 
+    /** Return whether this network is roaming. */
+    public boolean isRoaming() {
+        return mRoaming;
+    }
+
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getMetered() {
         return mMetered;
     }
 
+    /** Return whether this network is metered. */
+    public boolean isMetered() {
+        return mMetered;
+    }
+
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getDefaultNetwork() {
         return mDefaultNetwork;
     }
 
+    /** Return whether this network is the default network. */
+    public boolean isDefaultNetwork() {
+        return mDefaultNetwork;
+    }
+
+    /** Get the OEM managed type of this instance. */
     public int getOemManaged() {
         return mOemManaged;
     }
 
     /**
-     * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and
-     * {@code subType}, assuming that any mobile networks are using the current IMSI.
-     * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_*
-     * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not.
+     * Assemble a {@link NetworkIdentity} from the passed arguments.
+     *
+     * This methods builds an identity based on the capabilities of the network in the
+     * snapshot and other passed arguments. The identity is used as a key to record data usage.
+     *
+     * @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}.
+     * @param defaultNetwork whether the network is a default network.
+     * @param ratType the Radio Access Technology(RAT) type of the network. Or
+     *                {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable.
+     *                See {@code TelephonyManager.NETWORK_TYPE_*}.
+     * @hide
+     * @deprecated See {@link NetworkIdentity.Builder}.
      */
+    // TODO: Remove this after all callers are migrated to use new Api.
+    @Deprecated
+    @NonNull
     public static NetworkIdentity buildNetworkIdentity(Context context,
-            NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) {
-        final int legacyType = snapshot.getLegacyType();
-
-        final String subscriberId = snapshot.getSubscriberId();
-        String networkId = null;
-        boolean roaming = !snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        boolean metered = !(snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                || snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED));
-
-        final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());
-
-        if (legacyType == TYPE_WIFI) {
-            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
-                    .getTransportInfo();
-            if (transportInfo instanceof WifiInfo) {
-                final WifiInfo info = (WifiInfo) transportInfo;
-                networkId = info != null ? info.getCurrentNetworkKey() : null;
-            }
+            @NonNull NetworkStateSnapshot snapshot,
+            boolean defaultNetwork, @Annotation.NetworkType int ratType) {
+        final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
+                .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
+        if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
+            builder.setRatType(ratType);
         }
-
-        return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered,
-                defaultNetwork, oemManaged);
+        return builder.build();
     }
 
     /**
      * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}.
      * @hide
      */
-    public static int getOemBitfield(NetworkCapabilities nc) {
+    public static int getOemBitfield(@NonNull NetworkCapabilities nc) {
         int oemManaged = OEM_NONE;
 
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
@@ -251,30 +301,265 @@
         return oemManaged;
     }
 
-    @Override
-    public int compareTo(NetworkIdentity another) {
-        int res = Integer.compare(mType, another.mType);
+    /** @hide */
+    public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) {
+        Objects.requireNonNull(right);
+        int res = Integer.compare(left.mType, right.mType);
         if (res == 0) {
-            res = Integer.compare(mSubType, another.mSubType);
+            res = Integer.compare(left.mRatType, right.mRatType);
         }
-        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
-            res = mSubscriberId.compareTo(another.mSubscriberId);
+        if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) {
+            res = left.mSubscriberId.compareTo(right.mSubscriberId);
         }
-        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
-            res = mNetworkId.compareTo(another.mNetworkId);
+        if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) {
+            res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey);
         }
         if (res == 0) {
-            res = Boolean.compare(mRoaming, another.mRoaming);
+            res = Boolean.compare(left.mRoaming, right.mRoaming);
         }
         if (res == 0) {
-            res = Boolean.compare(mMetered, another.mMetered);
+            res = Boolean.compare(left.mMetered, right.mMetered);
         }
         if (res == 0) {
-            res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
+            res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork);
         }
         if (res == 0) {
-            res = Integer.compare(mOemManaged, another.mOemManaged);
+            res = Integer.compare(left.mOemManaged, right.mOemManaged);
         }
         return res;
     }
+
+    /**
+     * Builder class for {@link NetworkIdentity}.
+     */
+    public static final class Builder {
+        // Need to be synchronized with ConnectivityManager.
+        // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module.
+        private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST
+        private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
+        private int mType;
+        private int mRatType;
+        private String mSubscriberId;
+        private String mWifiNetworkKey;
+        private boolean mRoaming;
+        private boolean mMetered;
+        private boolean mDefaultNetwork;
+        private int mOemManaged;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+            // Initialize with default values. Will be overwritten by setters.
+            mType = ConnectivityManager.TYPE_NONE;
+            mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+            mSubscriberId = null;
+            mWifiNetworkKey = null;
+            mRoaming = false;
+            mMetered = false;
+            mDefaultNetwork = false;
+            mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
+        }
+
+        /**
+         * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance.
+         * This is a useful shorthand that will read from the snapshot and set the
+         * following fields, if they are set in the snapshot :
+         *  - type
+         *  - subscriberId
+         *  - roaming
+         *  - metered
+         *  - oemManaged
+         *  - wifiNetworkKey
+         *
+         * @param snapshot The target {@link NetworkStateSnapshot} object.
+         * @return The builder object.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) {
+            setType(snapshot.getLegacyType());
+
+            setSubscriberId(snapshot.getSubscriberId());
+            setRoaming(!snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
+            setMetered(!(snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                    || snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)));
+
+            setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
+
+            if (mType == TYPE_WIFI) {
+                final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+                        .getTransportInfo();
+                if (transportInfo instanceof WifiInfo) {
+                    final WifiInfo info = (WifiInfo) transportInfo;
+                    setWifiNetworkKey(info.getNetworkKey());
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Set the network type of the network.
+         *
+         * @param type the network type. See {@link ConnectivityManager#TYPE_*}.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setType(int type) {
+            // Include TYPE_NONE for compatibility, type field might not be filled by some
+            // networks such as test networks.
+            if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type)
+                    && type != ConnectivityManager.TYPE_NONE) {
+                throw new IllegalArgumentException("Invalid network type: " + type);
+            }
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Set the Radio Access Technology(RAT) type of the network.
+         *
+         * No RAT type is specified by default. Call clearRatType to reset.
+         *
+         * @param ratType the Radio Access Technology(RAT) type if applicable. See
+         *                {@code TelephonyManager.NETWORK_TYPE_*}.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setRatType(@Annotation.NetworkType int ratType) {
+            if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
+                    && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                throw new IllegalArgumentException("Invalid ratType " + ratType);
+            }
+            mRatType = ratType;
+            return this;
+        }
+
+        /**
+         * Clear the Radio Access Technology(RAT) type of the network.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder clearRatType() {
+            mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+            return this;
+        }
+
+        /**
+         * Set the Subscriber Id.
+         *
+         * @param subscriberId the Subscriber Id of the network. Or null if not applicable.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setSubscriberId(@Nullable String subscriberId) {
+            mSubscriberId = subscriberId;
+            return this;
+        }
+
+        /**
+         * Set the Wifi Network Key.
+         *
+         * @param wifiNetworkKey Wifi Network Key of the network,
+         *                        see {@link WifiInfo#getNetworkKey()}.
+         *                        Or null if not applicable.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
+            mWifiNetworkKey = wifiNetworkKey;
+            return this;
+        }
+
+        /**
+         * Set whether this network is roaming.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param roaming the roaming status of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setRoaming(boolean roaming) {
+            mRoaming = roaming;
+            return this;
+        }
+
+        /**
+         * Set whether this network is metered.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param metered the meteredness of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setMetered(boolean metered) {
+            mMetered = metered;
+            return this;
+        }
+
+        /**
+         * Set whether this network is the default network.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param defaultNetwork the default network status of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setDefaultNetwork(boolean defaultNetwork) {
+            mDefaultNetwork = defaultNetwork;
+            return this;
+        }
+
+        /**
+         * Set the OEM managed type.
+         *
+         * @param oemManaged Type of OEM managed network or unmanaged networks.
+         *                   See {@code NetworkTemplate#OEM_MANAGED_*}.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setOemManaged(@OemManaged int oemManaged) {
+            // Assert input does not contain illegal oemManage bits.
+            if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) {
+                throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged);
+            }
+            mOemManaged = oemManaged;
+            return this;
+        }
+
+        private void ensureValidParameters() {
+            // Assert non-mobile network cannot have a ratType.
+            if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
+                throw new IllegalArgumentException(
+                        "Invalid ratType " + mRatType + " for type " + mType);
+            }
+
+            // Assert non-wifi network cannot have a wifi network key.
+            if (mType != TYPE_WIFI && mWifiNetworkKey != null) {
+                throw new IllegalArgumentException("Invalid wifi network key for type " + mType);
+            }
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkIdentity}.
+         *
+         * @return the built instance of {@link NetworkIdentity}.
+         */
+        @NonNull
+        public NetworkIdentity build() {
+            ensureValidParameters();
+            return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
+                    mRoaming, mMetered, mDefaultNetwork, mOemManaged);
+        }
+    }
 }
diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java
index abbebef..dfa347f 100644
--- a/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/framework-t/src/android/net/NetworkIdentitySet.java
@@ -18,6 +18,7 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 
+import android.annotation.NonNull;
 import android.service.NetworkIdentitySetProto;
 import android.util.proto.ProtoOutputStream;
 
@@ -25,6 +26,8 @@
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
@@ -32,8 +35,7 @@
  *
  * @hide
  */
-public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements
-        Comparable<NetworkIdentitySet> {
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_ROAMING = 2;
     private static final int VERSION_ADD_NETWORK_ID = 3;
@@ -41,9 +43,19 @@
     private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
     private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
 
+    /**
+     * Construct a {@link NetworkIdentitySet} object.
+     */
     public NetworkIdentitySet() {
+        super();
     }
 
+    /** @hide */
+    public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) {
+        super(ident);
+    }
+
+    /** @hide */
     public NetworkIdentitySet(DataInput in) throws IOException {
         final int version = in.readInt();
         final int size = in.readInt();
@@ -52,7 +64,7 @@
                 final int ignored = in.readInt();
             }
             final int type = in.readInt();
-            final int subType = in.readInt();
+            final int ratType = in.readInt();
             final String subscriberId = readOptionalString(in);
             final String networkId;
             if (version >= VERSION_ADD_NETWORK_ID) {
@@ -91,63 +103,73 @@
                 oemNetCapabilities = NetworkIdentity.OEM_NONE;
             }
 
-            add(new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered,
+            add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
                     defaultNetwork, oemNetCapabilities));
         }
     }
 
     /**
      * Method to serialize this object into a {@code DataOutput}.
+     * @hide
      */
     public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK);
         out.writeInt(size());
         for (NetworkIdentity ident : this) {
             out.writeInt(ident.getType());
-            out.writeInt(ident.getSubType());
+            out.writeInt(ident.getRatType());
             writeOptionalString(out, ident.getSubscriberId());
-            writeOptionalString(out, ident.getNetworkId());
-            out.writeBoolean(ident.getRoaming());
-            out.writeBoolean(ident.getMetered());
-            out.writeBoolean(ident.getDefaultNetwork());
+            writeOptionalString(out, ident.getWifiNetworkKey());
+            out.writeBoolean(ident.isRoaming());
+            out.writeBoolean(ident.isMetered());
+            out.writeBoolean(ident.isDefaultNetwork());
             out.writeInt(ident.getOemManaged());
         }
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered metered. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered metered.
+     * @hide
+     */
     public boolean isAnyMemberMetered() {
         if (isEmpty()) {
             return false;
         }
         for (NetworkIdentity ident : this) {
-            if (ident.getMetered()) {
+            if (ident.isMetered()) {
                 return true;
             }
         }
         return false;
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered roaming.
+     * @hide
+     */
     public boolean isAnyMemberRoaming() {
         if (isEmpty()) {
             return false;
         }
         for (NetworkIdentity ident : this) {
-            if (ident.getRoaming()) {
+            if (ident.isRoaming()) {
                 return true;
             }
         }
         return false;
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered on the default
-            network. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered on the default
+     *         network.
+     * @hide
+     */
     public boolean areAllMembersOnDefaultNetwork() {
         if (isEmpty()) {
             return true;
         }
         for (NetworkIdentity ident : this) {
-            if (!ident.getDefaultNetwork()) {
+            if (!ident.isDefaultNetwork()) {
                 return false;
             }
         }
@@ -171,18 +193,20 @@
         }
     }
 
-    @Override
-    public int compareTo(NetworkIdentitySet another) {
-        if (isEmpty()) return -1;
-        if (another.isEmpty()) return 1;
+    public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
+        Objects.requireNonNull(left);
+        Objects.requireNonNull(right);
+        if (left.isEmpty()) return -1;
+        if (right.isEmpty()) return 1;
 
-        final NetworkIdentity ident = iterator().next();
-        final NetworkIdentity anotherIdent = another.iterator().next();
-        return ident.compareTo(anotherIdent);
+        final NetworkIdentity leftIdent = left.iterator().next();
+        final NetworkIdentity rightIdent = right.iterator().next();
+        return NetworkIdentity.compare(leftIdent, rightIdent);
     }
 
     /**
      * Method to dump this object into proto debug file.
+     * @hide
      */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index 9f9d73f..58ca21f 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -32,6 +32,8 @@
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.service.NetworkStatsCollectionKeyProto;
 import android.service.NetworkStatsCollectionProto;
@@ -70,6 +72,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
@@ -77,6 +80,7 @@
  *
  * @hide
  */
+// @SystemApi(client = MODULE_LIBRARIES)
 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
     private static final String TAG = NetworkStatsCollection.class.getSimpleName();
     /** File header magic number: "ANET" */
@@ -100,15 +104,23 @@
     private long mTotalBytes;
     private boolean mDirty;
 
+    /**
+     * Construct a {@link NetworkStatsCollection} object.
+     *
+     * @param bucketDuration duration of the buckets in this object, in milliseconds.
+     * @hide
+     */
     public NetworkStatsCollection(long bucketDuration) {
         mBucketDuration = bucketDuration;
         reset();
     }
 
+    /** @hide */
     public void clear() {
         reset();
     }
 
+    /** @hide */
     public void reset() {
         mStats.clear();
         mStartMillis = Long.MAX_VALUE;
@@ -117,6 +129,7 @@
         mDirty = false;
     }
 
+    /** @hide */
     public long getStartMillis() {
         return mStartMillis;
     }
@@ -124,6 +137,7 @@
     /**
      * Return first atomic bucket in this collection, which is more conservative
      * than {@link #mStartMillis}.
+     * @hide
      */
     public long getFirstAtomicBucketMillis() {
         if (mStartMillis == Long.MAX_VALUE) {
@@ -133,26 +147,32 @@
         }
     }
 
+    /** @hide */
     public long getEndMillis() {
         return mEndMillis;
     }
 
+    /** @hide */
     public long getTotalBytes() {
         return mTotalBytes;
     }
 
+    /** @hide */
     public boolean isDirty() {
         return mDirty;
     }
 
+    /** @hide */
     public void clearDirty() {
         mDirty = false;
     }
 
+    /** @hide */
     public boolean isEmpty() {
         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
     }
 
+    /** @hide */
     @VisibleForTesting
     public long roundUp(long time) {
         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
@@ -168,6 +188,7 @@
         }
     }
 
+    /** @hide */
     @VisibleForTesting
     public long roundDown(long time) {
         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
@@ -182,10 +203,12 @@
         }
     }
 
+    /** @hide */
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
         return getRelevantUids(accessLevel, Binder.getCallingUid());
     }
 
+    /** @hide */
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
                 final int callerUid) {
         final ArrayList<Integer> uids = new ArrayList<>();
@@ -206,6 +229,7 @@
     /**
      * Combine all {@link NetworkStatsHistory} in this collection which match
      * the requested parameters.
+     * @hide
      */
     public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
             int uid, int set, int tag, int fields, long start, long end,
@@ -331,6 +355,7 @@
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param accessLevel - caller access level.
      * @param callerUid - caller UID.
+     * @hide
      */
     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
@@ -377,6 +402,7 @@
 
     /**
      * Record given {@link android.net.NetworkStats.Entry} into this collection.
+     * @hide
      */
     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
             long end, NetworkStats.Entry entry) {
@@ -387,8 +413,12 @@
 
     /**
      * Record given {@link NetworkStatsHistory} into this collection.
+     *
+     * @hide
      */
-    private void recordHistory(Key key, NetworkStatsHistory history) {
+    public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) {
+        Objects.requireNonNull(key);
+        Objects.requireNonNull(history);
         if (history.size() == 0) return;
         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
 
@@ -403,8 +433,11 @@
     /**
      * Record all {@link NetworkStatsHistory} contained in the given collection
      * into this collection.
+     *
+     * @hide
      */
-    public void recordCollection(NetworkStatsCollection another) {
+    public void recordCollection(@NonNull NetworkStatsCollection another) {
+        Objects.requireNonNull(another);
         for (int i = 0; i < another.mStats.size(); i++) {
             final Key key = another.mStats.keyAt(i);
             final NetworkStatsHistory value = another.mStats.valueAt(i);
@@ -433,6 +466,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void read(InputStream in) throws IOException {
         read((DataInput) new DataInputStream(in));
@@ -472,6 +506,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void write(OutputStream out) throws IOException {
         write((DataOutput) new DataOutputStream(out));
@@ -514,6 +549,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyNetwork(File file) throws IOException {
@@ -559,6 +595,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
@@ -629,6 +666,7 @@
      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
      * moving any {@link NetworkStats#TAG_NONE} series to
      * {@link TrafficStats#UID_REMOVED}.
+     * @hide
      */
     public void removeUids(int[] uids) {
         final ArrayList<Key> knownKeys = new ArrayList<>();
@@ -665,10 +703,11 @@
     private ArrayList<Key> getSortedKeys() {
         final ArrayList<Key> keys = new ArrayList<>();
         keys.addAll(mStats.keySet());
-        Collections.sort(keys);
+        Collections.sort(keys, (left, right) -> Key.compare(left, right));
         return keys;
     }
 
+    /** @hide */
     public void dump(IndentingPrintWriter pw) {
         for (Key key : getSortedKeys()) {
             pw.print("ident="); pw.print(key.ident.toString());
@@ -683,6 +722,7 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
@@ -706,6 +746,7 @@
         proto.end(start);
     }
 
+    /** @hide */
     public void dumpCheckin(PrintWriter pw, long start, long end) {
         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
@@ -768,16 +809,37 @@
         return false;
     }
 
-    private static class Key implements Comparable<Key> {
+    /**
+     * the identifier that associate with the {@link NetworkStatsHistory} object to identify
+     * a certain record in the {@link NetworkStatsCollection} object.
+     */
+    public static class Key {
+        /** @hide */
         public final NetworkIdentitySet ident;
+        /** @hide */
         public final int uid;
+        /** @hide */
         public final int set;
+        /** @hide */
         public final int tag;
 
         private final int mHashCode;
 
-        Key(NetworkIdentitySet ident, int uid, int set, int tag) {
-            this.ident = ident;
+        /**
+         * Construct a {@link Key} object.
+         *
+         * @param ident a Set of {@link NetworkIdentity} that associated with the record.
+         * @param uid Uid of the record.
+         * @param set Set of the record, see {@code NetworkStats#SET_*}.
+         * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}.
+         */
+        public Key(@NonNull Set<NetworkIdentity> ident, int uid, int set, int tag) {
+            this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag);
+        }
+
+        /** @hide */
+        public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) {
+            this.ident = Objects.requireNonNull(ident);
             this.uid = uid;
             this.set = set;
             this.tag = tag;
@@ -790,7 +852,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (obj instanceof Key) {
                 final Key key = (Key) obj;
                 return uid == key.uid && set == key.set && tag == key.tag
@@ -799,20 +861,22 @@
             return false;
         }
 
-        @Override
-        public int compareTo(Key another) {
+        /** @hide */
+        public static int compare(@NonNull Key left, @NonNull Key right) {
+            Objects.requireNonNull(left);
+            Objects.requireNonNull(right);
             int res = 0;
-            if (ident != null && another.ident != null) {
-                res = ident.compareTo(another.ident);
+            if (left.ident != null && right.ident != null) {
+                res = NetworkIdentitySet.compare(left.ident, right.ident);
             }
             if (res == 0) {
-                res = Integer.compare(uid, another.uid);
+                res = Integer.compare(left.uid, right.uid);
             }
             if (res == 0) {
-                res = Integer.compare(set, another.set);
+                res = Integer.compare(left.set, right.set);
             }
             if (res == 0) {
-                res = Integer.compare(tag, another.tag);
+                res = Integer.compare(left.tag, right.tag);
             }
             return res;
         }
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
index 428bc6d..78c1370 100644
--- a/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
@@ -30,6 +31,8 @@
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -50,7 +53,9 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -64,18 +69,25 @@
  *
  * @hide
  */
-public class NetworkStatsHistory implements Parcelable {
+@SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkStatsHistory implements Parcelable {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_PACKETS = 2;
     private static final int VERSION_ADD_ACTIVE = 3;
 
+    /** @hide */
     public static final int FIELD_ACTIVE_TIME = 0x01;
+    /** @hide */
     public static final int FIELD_RX_BYTES = 0x02;
+    /** @hide */
     public static final int FIELD_RX_PACKETS = 0x04;
+    /** @hide */
     public static final int FIELD_TX_BYTES = 0x08;
+    /** @hide */
     public static final int FIELD_TX_PACKETS = 0x10;
+    /** @hide */
     public static final int FIELD_OPERATIONS = 0x20;
-
+    /** @hide */
     public static final int FIELD_ALL = 0xFFFFFFFF;
 
     private long bucketDuration;
@@ -89,34 +101,171 @@
     private long[] operations;
     private long totalBytes;
 
-    public static class Entry {
-        public static final long UNKNOWN = -1;
-
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long bucketDuration;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long bucketStart;
-        public long activeTime;
-        @UnsupportedAppUsage
-        public long rxBytes;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long rxPackets;
-        @UnsupportedAppUsage
-        public long txBytes;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long txPackets;
-        public long operations;
+    /** @hide */
+    public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime,
+            long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets,
+            long[] operations, int bucketCount, long totalBytes) {
+        this.bucketDuration = bucketDuration;
+        this.bucketStart = bucketStart;
+        this.activeTime = activeTime;
+        this.rxBytes = rxBytes;
+        this.rxPackets = rxPackets;
+        this.txBytes = txBytes;
+        this.txPackets = txPackets;
+        this.operations = operations;
+        this.bucketCount = bucketCount;
+        this.totalBytes = totalBytes;
     }
 
+    /**
+     * An instance to represent a single record in a {@link NetworkStatsHistory} object.
+     */
+    public static final class Entry {
+        /** @hide */
+        public static final long UNKNOWN = -1;
+
+        /** @hide */
+        // TODO: Migrate all callers to get duration from the history object and remove this field.
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long bucketDuration;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long bucketStart;
+        /** @hide */
+        public long activeTime;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long rxBytes;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long rxPackets;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long txBytes;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long txPackets;
+        /** @hide */
+        public long operations;
+        /** @hide */
+        Entry() {}
+
+        /**
+         * Construct a {@link Entry} instance to represent a single record in a
+         * {@link NetworkStatsHistory} object.
+         *
+         * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the
+         *                    Unix epoch, see {@link java.lang.System#currentTimeMillis}.
+         * @param activeTime Active time for this {@link Entry}, in milliseconds.
+         * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
+         *                represent the contents of IP packets, including IP headers.
+         * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
+         *                  represent the contents of IP packets, including IP headers.
+         * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
+         *                represent the contents of IP packets, including IP headers.
+         * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
+         *                  represent the contents of IP packets, including IP headers.
+         * @param operations count of network operations performed for this {@link Entry}. This can
+         *                   be used to derive bytes-per-operation.
+         */
+        public Entry(long bucketStart, long activeTime, long rxBytes,
+                long rxPackets, long txBytes, long txPackets, long operations) {
+            this.bucketStart = bucketStart;
+            this.activeTime = activeTime;
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+            this.operations = operations;
+        }
+
+        /**
+         * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch.
+         */
+        public long getBucketStart() {
+            return bucketStart;
+        }
+
+        /**
+         * Get active time of the bucket's time interval, in milliseconds.
+         */
+        public long getActiveTime() {
+            return activeTime;
+        }
+
+        /** Get number of bytes received for this {@link Entry}. */
+        public long getRxBytes() {
+            return rxBytes;
+        }
+
+        /** Get number of packets received for this {@link Entry}. */
+        public long getRxPackets() {
+            return rxPackets;
+        }
+
+        /** Get number of bytes transmitted for this {@link Entry}. */
+        public long getTxBytes() {
+            return txBytes;
+        }
+
+        /** Get number of packets transmitted for this {@link Entry}. */
+        public long getTxPackets() {
+            return txPackets;
+        }
+
+        /** Get count of network operations performed for this {@link Entry}. */
+        public long getOperations() {
+            return operations;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+            Entry entry = (Entry) o;
+            return bucketStart == entry.bucketStart
+                    && activeTime == entry.activeTime && rxBytes == entry.rxBytes
+                    && rxPackets == entry.rxPackets && txBytes == entry.txBytes
+                    && txPackets == entry.txPackets && operations == entry.operations;
+        }
+
+        @Override
+        public int hashCode() {
+            return (int) (bucketStart * 2
+                    + activeTime * 3
+                    + rxBytes * 5
+                    + rxPackets * 7
+                    + txBytes * 11
+                    + txPackets * 13
+                    + operations * 17);
+        }
+
+        @Override
+        public String toString() {
+            return "Entry{"
+                    + "bucketStart=" + bucketStart
+                    + ", activeTime=" + activeTime
+                    + ", rxBytes=" + rxBytes
+                    + ", rxPackets=" + rxPackets
+                    + ", txBytes=" + txBytes
+                    + ", txPackets=" + txPackets
+                    + ", operations=" + operations
+                    + "}";
+        }
+    }
+
+    /** @hide */
     @UnsupportedAppUsage
     public NetworkStatsHistory(long bucketDuration) {
         this(bucketDuration, 10, FIELD_ALL);
     }
 
+    /** @hide */
     public NetworkStatsHistory(long bucketDuration, int initialSize) {
         this(bucketDuration, initialSize, FIELD_ALL);
     }
 
+    /** @hide */
     public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
         this.bucketDuration = bucketDuration;
         bucketStart = new long[initialSize];
@@ -130,11 +279,13 @@
         totalBytes = 0;
     }
 
+    /** @hide */
     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
         recordEntireHistory(existing);
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public NetworkStatsHistory(Parcel in) {
         bucketDuration = in.readLong();
@@ -150,7 +301,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeLong(bucketDuration);
         writeLongArray(out, bucketStart, bucketCount);
         writeLongArray(out, activeTime, bucketCount);
@@ -162,6 +313,7 @@
         out.writeLong(totalBytes);
     }
 
+    /** @hide */
     public NetworkStatsHistory(DataInput in) throws IOException {
         final int version = in.readInt();
         switch (version) {
@@ -204,6 +356,7 @@
         }
     }
 
+    /** @hide */
     public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_ACTIVE);
         out.writeLong(bucketDuration);
@@ -221,15 +374,18 @@
         return 0;
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int size() {
         return bucketCount;
     }
 
+    /** @hide */
     public long getBucketDuration() {
         return bucketDuration;
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public long getStart() {
         if (bucketCount > 0) {
@@ -239,6 +395,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public long getEnd() {
         if (bucketCount > 0) {
@@ -250,6 +407,7 @@
 
     /**
      * Return total bytes represented by this history.
+     * @hide
      */
     public long getTotalBytes() {
         return totalBytes;
@@ -258,6 +416,7 @@
     /**
      * Return index of bucket that contains or is immediately before the
      * requested time.
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int getIndexBefore(long time) {
@@ -273,6 +432,7 @@
     /**
      * Return index of bucket that contains or is immediately after the
      * requested time.
+     * @hide
      */
     public int getIndexAfter(long time) {
         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
@@ -286,6 +446,7 @@
 
     /**
      * Return specific stats entry.
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Entry getValues(int i, Entry recycle) {
@@ -301,6 +462,23 @@
         return entry;
     }
 
+    /**
+     * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance.
+     *
+     * @return
+     */
+    @NonNull
+    public List<Entry> getEntries() {
+        // TODO: Return a wrapper that uses this list instead, to prevent the returned result
+        //  from being changed.
+        final ArrayList<Entry> ret = new ArrayList<>(size());
+        for (int i = 0; i < size(); i++) {
+            ret.add(getValues(i, null /* recycle */));
+        }
+        return ret;
+    }
+
+    /** @hide */
     public void setValues(int i, Entry entry) {
         // Unwind old values
         if (rxBytes != null) totalBytes -= rxBytes[i];
@@ -322,6 +500,7 @@
     /**
      * Record that data traffic occurred in the given time range. Will
      * distribute across internal buckets, creating new buckets as needed.
+     * @hide
      */
     @Deprecated
     public void recordData(long start, long end, long rxBytes, long txBytes) {
@@ -332,6 +511,7 @@
     /**
      * Record that data traffic occurred in the given time range. Will
      * distribute across internal buckets, creating new buckets as needed.
+     * @hide
      */
     public void recordData(long start, long end, NetworkStats.Entry entry) {
         long rxBytes = entry.rxBytes;
@@ -392,6 +572,7 @@
     /**
      * Record an entire {@link NetworkStatsHistory} into this history. Usually
      * for combining together stats for external reporting.
+     * @hide
      */
     @UnsupportedAppUsage
     public void recordEntireHistory(NetworkStatsHistory input) {
@@ -402,6 +583,7 @@
      * Record given {@link NetworkStatsHistory} into this history, copying only
      * buckets that atomically occur in the inclusive time range. Doesn't
      * interpolate across partial buckets.
+     * @hide
      */
     public void recordHistory(NetworkStatsHistory input, long start, long end) {
         final NetworkStats.Entry entry = new NetworkStats.Entry(
@@ -483,6 +665,7 @@
 
     /**
      * Clear all data stored in this object.
+     * @hide
      */
     public void clear() {
         bucketStart = EmptyArray.LONG;
@@ -498,9 +681,10 @@
 
     /**
      * Remove buckets older than requested cutoff.
+     * @hide
      */
-    @Deprecated
     public void removeBucketsBefore(long cutoff) {
+        // TODO: Consider use getIndexBefore.
         int i;
         for (i = 0; i < bucketCount; i++) {
             final long curStart = bucketStart[i];
@@ -522,7 +706,9 @@
             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
             bucketCount -= i;
 
-            // TODO: subtract removed values from totalBytes
+            totalBytes = 0;
+            if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes);
+            if (txBytes != null) totalBytes += CollectionUtils.total(txBytes);
         }
     }
 
@@ -536,6 +722,7 @@
      * @param start - start of the range, timestamp in milliseconds since the epoch.
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param recycle - entry instance for performance, could be null.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, Entry recycle) {
@@ -550,6 +737,7 @@
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param now - current timestamp in milliseconds since the epoch (wall clock).
      * @param recycle - entry instance for performance, could be null.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, long now, Entry recycle) {
@@ -613,6 +801,7 @@
 
     /**
      * @deprecated only for temporary testing
+     * @hide
      */
     @Deprecated
     public void generateRandom(long start, long end, long bytes) {
@@ -631,6 +820,7 @@
 
     /**
      * @deprecated only for temporary testing
+     * @hide
      */
     @Deprecated
     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
@@ -660,12 +850,14 @@
         }
     }
 
+    /** @hide */
     public static long randomLong(Random r, long start, long end) {
         return (long) (start + (r.nextFloat() * (end - start)));
     }
 
     /**
      * Quickly determine if this history intersects with given window.
+     * @hide
      */
     public boolean intersects(long start, long end) {
         final long dataStart = getStart();
@@ -677,6 +869,7 @@
         return false;
     }
 
+    /** @hide */
     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
         pw.print("NetworkStatsHistory: bucketDuration=");
         pw.println(bucketDuration / SECOND_IN_MILLIS);
@@ -700,6 +893,7 @@
         pw.decreaseIndent();
     }
 
+    /** @hide */
     public void dumpCheckin(PrintWriter pw) {
         pw.print("d,");
         pw.print(bucketDuration / SECOND_IN_MILLIS);
@@ -717,6 +911,7 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
@@ -776,6 +971,7 @@
         if (array != null) array[i] += value;
     }
 
+    /** @hide */
     public int estimateResizeBuckets(long newBucketDuration) {
         return (int) (size() * getBucketDuration() / newBucketDuration);
     }
@@ -783,6 +979,7 @@
     /**
      * Utility methods for interacting with {@link DataInputStream} and
      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
+     * @hide
      */
     public static class DataStreamUtils {
         @Deprecated
@@ -857,6 +1054,7 @@
     /**
      * Utility methods for interacting with {@link Parcel} structures, mostly
      * dealing with writing partial arrays.
+     * @hide
      */
     public static class ParcelUtils {
         public static long[] readLongArray(Parcel in) {
@@ -884,4 +1082,80 @@
         }
     }
 
+    /**
+     * Builder class for {@link NetworkStatsHistory}.
+     */
+    public static final class Builder {
+        private final long mBucketDuration;
+        private final List<Long> mBucketStart;
+        private final List<Long> mActiveTime;
+        private final List<Long> mRxBytes;
+        private final List<Long> mRxPackets;
+        private final List<Long> mTxBytes;
+        private final List<Long> mTxPackets;
+        private final List<Long> mOperations;
+
+        /**
+         * Creates a new Builder with given bucket duration and initial capacity to construct
+         * {@link NetworkStatsHistory} objects.
+         *
+         * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+         * @param initialCapacity Estimated number of records.
+         */
+        public Builder(long bucketDuration, int initialCapacity) {
+            mBucketDuration = bucketDuration;
+            mBucketStart = new ArrayList<>(initialCapacity);
+            mActiveTime = new ArrayList<>(initialCapacity);
+            mRxBytes = new ArrayList<>(initialCapacity);
+            mRxPackets = new ArrayList<>(initialCapacity);
+            mTxBytes = new ArrayList<>(initialCapacity);
+            mTxPackets = new ArrayList<>(initialCapacity);
+            mOperations = new ArrayList<>(initialCapacity);
+        }
+
+        /**
+         * Add an {@link Entry} into the {@link NetworkStatsHistory} instance.
+         *
+         * @param entry The target {@link Entry} object.
+         * @return The builder object.
+         */
+        @NonNull
+        public Builder addEntry(@NonNull Entry entry) {
+            mBucketStart.add(entry.bucketStart);
+            mActiveTime.add(entry.activeTime);
+            mRxBytes.add(entry.rxBytes);
+            mRxPackets.add(entry.rxPackets);
+            mTxBytes.add(entry.txBytes);
+            mTxPackets.add(entry.txPackets);
+            mOperations.add(entry.operations);
+            return this;
+        }
+
+        private static long sum(@NonNull List<Long> list) {
+            long sum = 0;
+            for (long entry : list) {
+                sum += entry;
+            }
+            return sum;
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkStatsHistory}.
+         *
+         * @return the built instance of {@link NetworkStatsHistory}.
+         */
+        @NonNull
+        public NetworkStatsHistory build() {
+            return new NetworkStatsHistory(mBucketDuration,
+                    CollectionUtils.toLongArray(mBucketStart),
+                    CollectionUtils.toLongArray(mActiveTime),
+                    CollectionUtils.toLongArray(mRxBytes),
+                    CollectionUtils.toLongArray(mRxPackets),
+                    CollectionUtils.toLongArray(mTxBytes),
+                    CollectionUtils.toLongArray(mTxPackets),
+                    CollectionUtils.toLongArray(mOperations),
+                    mBucketStart.size(),
+                    sum(mRxBytes) + sum(mTxBytes));
+        }
+    }
 }
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index e9084b0..cad8075 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -263,7 +263,7 @@
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
      * given key of the wifi network.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      * @hide
      */
@@ -283,7 +283,7 @@
      * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
      * of key of the wifi network.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      * @param subscriberId the IMSI associated to this wifi network.
      *
@@ -364,7 +364,7 @@
     private final int mMetered;
     private final int mRoaming;
     private final int mDefaultNetwork;
-    private final int mSubType;
+    private final int mRatType;
     /**
      * The subscriber Id match rule defines how the template should match networks with
      * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
@@ -413,18 +413,18 @@
     /** @hide */
     // TODO: Remove it after updating all of the caller.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
-            String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType,
+            String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int ratType,
             int oemManaged) {
         this(matchRule, subscriberId, matchSubscriberIds,
                 wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
-                metered, roaming, defaultNetwork, subType, oemManaged,
+                metered, roaming, defaultNetwork, ratType, oemManaged,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String[] matchWifiNetworkKeys, int metered, int roaming,
-            int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) {
+            int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
         Objects.requireNonNull(matchWifiNetworkKeys);
         mMatchRule = matchRule;
         mSubscriberId = subscriberId;
@@ -435,7 +435,7 @@
         mMetered = metered;
         mRoaming = roaming;
         mDefaultNetwork = defaultNetwork;
-        mSubType = subType;
+        mRatType = ratType;
         mOemManaged = oemManaged;
         mSubscriberIdMatchRule = subscriberIdMatchRule;
         checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
@@ -453,7 +453,7 @@
         mMetered = in.readInt();
         mRoaming = in.readInt();
         mDefaultNetwork = in.readInt();
-        mSubType = in.readInt();
+        mRatType = in.readInt();
         mOemManaged = in.readInt();
         mSubscriberIdMatchRule = in.readInt();
     }
@@ -467,7 +467,7 @@
         dest.writeInt(mMetered);
         dest.writeInt(mRoaming);
         dest.writeInt(mDefaultNetwork);
-        dest.writeInt(mSubType);
+        dest.writeInt(mRatType);
         dest.writeInt(mOemManaged);
         dest.writeInt(mSubscriberIdMatchRule);
     }
@@ -500,8 +500,8 @@
             builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString(
                     mDefaultNetwork));
         }
-        if (mSubType != NETWORK_TYPE_ALL) {
-            builder.append(", subType=").append(mSubType);
+        if (mRatType != NETWORK_TYPE_ALL) {
+            builder.append(", ratType=").append(mRatType);
         }
         if (mOemManaged != OEM_MANAGED_ALL) {
             builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
@@ -514,7 +514,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
-                mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
+                mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule);
     }
 
     @Override
@@ -526,7 +526,7 @@
                     && mMetered == other.mMetered
                     && mRoaming == other.mRoaming
                     && mDefaultNetwork == other.mDefaultNetwork
-                    && mSubType == other.mSubType
+                    && mRatType == other.mRatType
                     && mOemManaged == other.mOemManaged
                     && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
                     && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
@@ -593,7 +593,7 @@
 
     /**
      * Get the set of Wifi Network Keys of the template.
-     * See {@link WifiInfo#getCurrentNetworkKey()}.
+     * See {@link WifiInfo#getNetworkKey()}.
      */
     @NonNull
     public Set<String> getWifiNetworkKeys() {
@@ -635,7 +635,7 @@
      * Get the Radio Access Technology(RAT) type filter of the template.
      */
     public int getRatType() {
-        return mSubType;
+        return mRatType;
     }
 
     /**
@@ -708,8 +708,8 @@
     }
 
     private boolean matchesCollapsedRatType(NetworkIdentity ident) {
-        return mSubType == NETWORK_TYPE_ALL
-                || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType);
+        return mRatType == NETWORK_TYPE_ALL
+                || getCollapsedRatType(mRatType) == getCollapsedRatType(ident.mRatType);
     }
 
     /**
@@ -729,7 +729,7 @@
      * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is
      * empty.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      */
     private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
@@ -837,7 +837,7 @@
         switch (ident.mType) {
             case TYPE_WIFI:
                 return matchesSubscriberId(ident.mSubscriberId)
-                        && matchesWifiNetworkKey(ident.mNetworkId);
+                        && matchesWifiNetworkKey(ident.mWifiNetworkKey);
             default:
                 return false;
         }
@@ -1059,9 +1059,9 @@
          * the intention of matching any Wifi Network Key.
          *
          * @param wifiNetworkKeys the list of Wifi Network Key,
-         *                        see {@link WifiInfo#getCurrentNetworkKey()}.
+         *                        see {@link WifiInfo#getNetworkKey()}.
          *                        Or an empty list to match all networks.
-         *                        Note that {@code getCurrentNetworkKey()} might get null key
+         *                        Note that {@code getNetworkKey()} might get null key
          *                        when wifi disconnects. However, the caller should never invoke
          *                        this function with a null Wifi Network Key since such statistics
          *                        never exists.
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index 1af32bf..c803a72 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -27,8 +26,8 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.MediaPlayer;
+import android.os.Binder;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.RemoteException;
 
 import com.android.server.NetworkManagementSocketTagger;
@@ -37,8 +36,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.net.DatagramSocket;
 import java.net.Socket;
 import java.net.SocketException;
@@ -177,25 +174,12 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private synchronized static INetworkStatsService getStatsService() {
         if (sStatsService == null) {
-            sStatsService = getStatsBinder();
+            throw new IllegalStateException("TrafficStats not initialized, uid="
+                    + Binder.getCallingUid());
         }
         return sStatsService;
     }
 
-    @Nullable
-    private static INetworkStatsService getStatsBinder() {
-        try {
-            final Method getServiceMethod = Class.forName("android.os.ServiceManager")
-                    .getDeclaredMethod("getService", new Class[]{String.class});
-            final IBinder binder = (IBinder) getServiceMethod.invoke(
-                    null, Context.NETWORK_STATS_SERVICE);
-            return INetworkStatsService.Stub.asInterface(binder);
-        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException
-                | InvocationTargetException e) {
-            throw new NullPointerException("Cannot get INetworkStatsService: " + e);
-        }
-    }
-
     /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
      * session started, or {@code null} if no session active.
@@ -210,6 +194,26 @@
     private static final String LOOPBACK_IFACE = "lo";
 
     /**
+     * Initialization {@link TrafficStats} with the context, to
+     * allow {@link TrafficStats} to fetch the needed binder.
+     *
+     * @param context a long-lived context, such as the application context or system
+     *                server context.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("VisiblySynchronized")
+    public static synchronized void init(@NonNull final Context context) {
+        if (sStatsService != null) {
+            throw new IllegalStateException("TrafficStats is already initialized, uid="
+                    + Binder.getCallingUid());
+        }
+        final NetworkStatsManager statsManager =
+                context.getSystemService(NetworkStatsManager.class);
+        sStatsService = statsManager.getBinder();
+    }
+
+    /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * <p>
diff --git a/service-t/Sources.bp b/service-t/Sources.bp
index 3fd3d8c..24bc91d 100644
--- a/service-t/Sources.bp
+++ b/service-t/Sources.bp
@@ -68,6 +68,7 @@
 filegroup {
     name: "services.connectivity-ethernet-sources",
     srcs: [
+        "src/com/android/server/net/DelayedDiskWrite.java",
         "src/com/android/server/net/IpConfigStore.java",
     ],
     path: "src",
diff --git a/services/core/java/com/android/server/net/DelayedDiskWrite.java b/service-t/src/com/android/server/net/DelayedDiskWrite.java
similarity index 82%
rename from services/core/java/com/android/server/net/DelayedDiskWrite.java
rename to service-t/src/com/android/server/net/DelayedDiskWrite.java
index 8f09eb7..35dc455 100644
--- a/services/core/java/com/android/server/net/DelayedDiskWrite.java
+++ b/service-t/src/com/android/server/net/DelayedDiskWrite.java
@@ -26,21 +26,37 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+/**
+ * This class provides APIs to do a delayed data write to a given {@link OutputStream}.
+ */
 public class DelayedDiskWrite {
+    private static final String TAG = "DelayedDiskWrite";
+
     private HandlerThread mDiskWriteHandlerThread;
     private Handler mDiskWriteHandler;
     /* Tracks multiple writes on the same thread */
     private int mWriteSequence = 0;
-    private final String TAG = "DelayedDiskWrite";
 
+    /**
+     * Used to do a delayed data write to a given {@link OutputStream}.
+     */
     public interface Writer {
-        public void onWriteCalled(DataOutputStream out) throws IOException;
+        /**
+         * write data to a given {@link OutputStream}.
+         */
+        void onWriteCalled(DataOutputStream out) throws IOException;
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w) {
         write(filePath, w, true);
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w, final boolean open) {
         if (TextUtils.isEmpty(filePath)) {
             throw new IllegalArgumentException("empty file path");
@@ -77,7 +93,7 @@
             if (out != null) {
                 try {
                     out.close();
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
 
             // Quit if no more writes sent
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index ad15c3c..4c78dcb 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -19,12 +19,16 @@
 import static android.Manifest.permission.NETWORK_STATS_PROVIDER;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
+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.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.net.NetworkIdentity.SUBTYPE_COMBINED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.IFACE_VT;
@@ -102,7 +106,6 @@
 import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
@@ -230,11 +233,6 @@
 
     private PendingIntent mPollIntent;
 
-    private static final String PREFIX_DEV = "dev";
-    private static final String PREFIX_XT = "xt";
-    private static final String PREFIX_UID = "uid";
-    private static final String PREFIX_UID_TAG = "uid_tag";
-
     /**
      * Settings that can be changed externally.
      */
@@ -244,9 +242,9 @@
         boolean getSampleEnabled();
         boolean getAugmentEnabled();
         /**
-         * When enabled, all mobile data is reported under {@link NetworkIdentity#SUBTYPE_COMBINED}.
-         * When disabled, mobile data is broken down by a granular subtype representative of the
-         * actual subtype. {@see NetworkTemplate#getCollapsedRatType}.
+         * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}.
+         * When disabled, mobile data is broken down by a granular ratType representative of the
+         * actual ratType. {@see NetworkTemplate#getCollapsedRatType}.
          * Enabling this decreases the level of detail but saves performance, disk space and
          * amount of data logged.
          */
@@ -293,6 +291,9 @@
     /** Set of any ifaces associated with mobile networks since boot. */
     private volatile String[] mMobileIfaces = new String[0];
 
+    /** Set of any ifaces associated with wifi networks since boot. */
+    private volatile String[] mWifiIfaces = new String[0];
+
     /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
     @GuardedBy("mStatsLock")
     private Network[] mDefaultNetworks = new Network[0];
@@ -451,7 +452,7 @@
         handlerThread.start();
         mHandler = new NetworkStatsHandler(handlerThread.getLooper());
         mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext,
-                new HandlerExecutor(mHandler), this);
+                (command) -> mHandler.post(command) , this);
         mContentResolver = mContext.getContentResolver();
         mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
                 mNetworkStatsSubscriptionsMonitor);
@@ -567,7 +568,7 @@
         // watch for tethering changes
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
         tetheringManager.registerTetheringEventCallback(
-                new HandlerExecutor(mHandler), mTetherListener);
+                (command) -> mHandler.post(command), mTetherListener);
 
         // listen for periodic polling events
         final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
@@ -1019,11 +1020,15 @@
     }
 
     @Override
-    public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
+    public NetworkStats getUidStatsForTransport(int transport) {
         enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
         try {
+            final String[] relevantIfaces =
+                    transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+            // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
+            // interfaces, so this is not useful, remove it.
             final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
+                    mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
             return getNetworkStatsUidDetail(ifacesToQuery);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
@@ -1382,16 +1387,18 @@
 
         final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
         final ArraySet<String> mobileIfaces = new ArraySet<>();
+        final ArraySet<String> wifiIfaces = new ArraySet<>();
         for (NetworkStateSnapshot snapshot : snapshots) {
             final int displayTransport =
                     getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
             final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
+            final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport);
             final boolean isDefault = CollectionUtils.contains(
                     mDefaultNetworks, snapshot.getNetwork());
-            final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED
-                    : getSubTypeForStateSnapshot(snapshot);
+            final int ratType = combineSubtypeEnabled ? NetworkTemplate.NETWORK_TYPE_ALL
+                    : getRatTypeForStateSnapshot(snapshot);
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
-                    isDefault, subType);
+                    isDefault, ratType);
 
             // Traffic occurring on the base interface is always counted for
             // both total usage and UID details.
@@ -1406,12 +1413,12 @@
                 // VT is considered always metered in framework's layer. If VT is not metered
                 // per carrier's policy, modem will report 0 usage for VT calls.
                 if (snapshot.getNetworkCapabilities().hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) {
+                        NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) {
 
                     // Copy the identify from IMS one but mark it as metered.
                     NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(),
-                            ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
-                            ident.getRoaming(), true /* metered */,
+                            ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(),
+                            ident.isRoaming(), true /* metered */,
                             true /* onDefaultNetwork */, ident.getOemManaged());
                     final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
                     findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
@@ -1421,6 +1428,9 @@
                 if (isMobile) {
                     mobileIfaces.add(baseIface);
                 }
+                if (isWifi) {
+                    wifiIfaces.add(baseIface);
+                }
             }
 
             // Traffic occurring on stacked interfaces is usually clatd.
@@ -1462,6 +1472,9 @@
                     if (isMobile) {
                         mobileIfaces.add(iface);
                     }
+                    if (isWifi) {
+                        wifiIfaces.add(iface);
+                    }
 
                     mStatsFactory.noteStackedIface(iface, baseIface);
                 }
@@ -1469,11 +1482,16 @@
         }
 
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
+        mWifiIfaces = wifiIfaces.toArray(new String[0]);
         // TODO (b/192758557): Remove debug log.
         if (CollectionUtils.contains(mMobileIfaces, null)) {
             throw new NullPointerException(
                     "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
         }
+        if (CollectionUtils.contains(mWifiIfaces, null)) {
+            throw new NullPointerException(
+                    "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
+        }
     }
 
     private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -1491,11 +1509,11 @@
     }
 
     /**
-     * For networks with {@code TRANSPORT_CELLULAR}, get subType that was obtained through
+     * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through
      * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different
      * transport types do not actually fill this value.
      */
-    private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
+    private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
         if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
             return 0;
         }