DO NOT MERGE - Merge pie-platform-release (PPRL.190705.004) into master

Bug: 136196576
Change-Id: I481d824726ae0260b42cd7a4acc1c6fce593c324
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 9f46f20..59ae334 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
 import android.app.usage.NetworkStats.Bucket;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -121,6 +122,7 @@
     /**
      * {@hide}
      */
+    @UnsupportedAppUsage
     public NetworkStatsManager(Context context) throws ServiceNotFoundException {
         this(context, INetworkStatsService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)));
diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java
index ecccda5..7256502 100644
--- a/core/java/android/net/EthernetManager.java
+++ b/core/java/android/net/EthernetManager.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
@@ -66,6 +67,7 @@
          * @param iface Ethernet interface name
          * @param isAvailable {@code true} if Ethernet port exists.
          */
+        @UnsupportedAppUsage
         void onAvailabilityChanged(String iface, boolean isAvailable);
     }
 
@@ -84,6 +86,7 @@
      * Get Ethernet configuration.
      * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
      */
+    @UnsupportedAppUsage
     public IpConfiguration getConfiguration(String iface) {
         try {
             return mService.getConfiguration(iface);
@@ -95,6 +98,7 @@
     /**
      * Set Ethernet configuration.
      */
+    @UnsupportedAppUsage
     public void setConfiguration(String iface, IpConfiguration config) {
         try {
             mService.setConfiguration(iface, config);
@@ -106,6 +110,7 @@
     /**
      * Indicates whether the system currently has one or more Ethernet interfaces.
      */
+    @UnsupportedAppUsage
     public boolean isAvailable() {
         return getAvailableInterfaces().length > 0;
     }
@@ -115,6 +120,7 @@
      *
      * @param iface Ethernet interface name
      */
+    @UnsupportedAppUsage
     public boolean isAvailable(String iface) {
         try {
             return mService.isAvailable(iface);
@@ -128,6 +134,7 @@
      * @param listener A {@link Listener} to add.
      * @throws IllegalArgumentException If the listener is null.
      */
+    @UnsupportedAppUsage
     public void addListener(Listener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
@@ -145,6 +152,7 @@
     /**
      * Returns an array of available Ethernet interface names.
      */
+    @UnsupportedAppUsage
     public String[] getAvailableInterfaces() {
         try {
             return mService.getAvailableInterfaces();
@@ -158,6 +166,7 @@
      * @param listener A {@link Listener} to remove.
      * @throws IllegalArgumentException If the listener is null.
      */
+    @UnsupportedAppUsage
     public void removeListener(Listener listener) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 148b25d..9994f9f 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -31,6 +31,7 @@
 interface INetworkStatsService {
 
     /** Start a statistics query session. */
+    @UnsupportedAppUsage
     INetworkStatsSession openSession();
 
     /** Start a statistics query session. If calling package is profile or device owner then it is
@@ -39,9 +40,11 @@
      *  PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted
      *  READ_NETWORK_USAGE_STATS is checked for.
      */
+    @UnsupportedAppUsage
     INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage);
 
     /** Return data layer snapshot of UID network usage. */
+    @UnsupportedAppUsage
     NetworkStats getDataLayerSnapshotForUid(int uid);
 
     /** Get a detailed snapshot of stats since boot for all UIDs.
@@ -54,6 +57,7 @@
     NetworkStats getDetailedUidStats(in String[] requiredIfaces);
 
     /** Return set of any ifaces associated with mobile networks since boot. */
+    @UnsupportedAppUsage
     String[] getMobileIfaces();
 
     /** Increment data layer count of operations performed for UID and tag. */
@@ -62,10 +66,11 @@
     /** Force update of ifaces. */
     void forceUpdateIfaces(
          in Network[] defaultNetworks,
-         in VpnInfo[] vpnArray,
          in NetworkState[] networkStates,
-         in String activeIface);
+         in String activeIface,
+         in VpnInfo[] vpnInfos);
     /** Force update of statistics. */
+    @UnsupportedAppUsage
     void forceUpdate();
 
     /** Registers a callback on data usage. */
diff --git a/core/java/android/net/INetworkStatsSession.aidl b/core/java/android/net/INetworkStatsSession.aidl
index 5229a3b..f13f2cb 100644
--- a/core/java/android/net/INetworkStatsSession.aidl
+++ b/core/java/android/net/INetworkStatsSession.aidl
@@ -27,13 +27,17 @@
     NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end);
 
     /** Return network layer usage summary for traffic that matches template. */
+    @UnsupportedAppUsage
     NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
     /** Return historical network layer stats for traffic that matches template. */
+    @UnsupportedAppUsage
     NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
 
     /** Return network layer usage summary per UID for traffic that matches template. */
+    @UnsupportedAppUsage
     NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
     /** Return historical network layer stats for specific UID traffic that matches template. */
+    @UnsupportedAppUsage
     NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
     /** Return historical network layer stats for specific UID traffic that matches template. */
     NetworkStatsHistory getHistoryIntervalForUid(in NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end);
@@ -41,6 +45,7 @@
     /** Return array of uids that have stats and are accessible to the calling user */
     int[] getRelevantUids();
 
+    @UnsupportedAppUsage
     void close();
 
 }
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 8599f47..43c8ff2 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -15,6 +15,7 @@
  */
 package android.net;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -65,10 +66,13 @@
     // An interval, in seconds between the NattKeepalive packets
     private int mNattKeepaliveInterval;
 
-    // XFRM mark and mask
+    // XFRM mark and mask; defaults to 0 (no mark/mask)
     private int mMarkValue;
     private int mMarkMask;
 
+    // XFRM interface id
+    private int mXfrmInterfaceId;
+
     /** Set the mode for this IPsec transform */
     public void setMode(int mode) {
         mMode = mode;
@@ -125,14 +129,30 @@
         mNattKeepaliveInterval = interval;
     }
 
+    /**
+     * Sets the mark value
+     *
+     * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+     * ignored.
+     */
     public void setMarkValue(int mark) {
         mMarkValue = mark;
     }
 
+    /**
+     * Sets the mark mask
+     *
+     * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+     * ignored.
+     */
     public void setMarkMask(int mask) {
         mMarkMask = mask;
     }
 
+    public void setXfrmInterfaceId(int xfrmInterfaceId) {
+        mXfrmInterfaceId = xfrmInterfaceId;
+    }
+
     // Transport or Tunnel
     public int getMode() {
         return mMode;
@@ -190,6 +210,10 @@
         return mMarkMask;
     }
 
+    public int getXfrmInterfaceId() {
+        return mXfrmInterfaceId;
+    }
+
     // Parcelable Methods
 
     @Override
@@ -213,6 +237,7 @@
         out.writeInt(mNattKeepaliveInterval);
         out.writeInt(mMarkValue);
         out.writeInt(mMarkMask);
+        out.writeInt(mXfrmInterfaceId);
     }
 
     @VisibleForTesting
@@ -235,6 +260,7 @@
         mNattKeepaliveInterval = c.mNattKeepaliveInterval;
         mMarkValue = c.mMarkValue;
         mMarkMask = c.mMarkMask;
+        mXfrmInterfaceId = c.mXfrmInterfaceId;
     }
 
     private IpSecConfig(Parcel in) {
@@ -255,6 +281,7 @@
         mNattKeepaliveInterval = in.readInt();
         mMarkValue = in.readInt();
         mMarkMask = in.readInt();
+        mXfrmInterfaceId = in.readInt();
     }
 
     @Override
@@ -289,6 +316,8 @@
                 .append(mMarkValue)
                 .append(", mMarkMask=")
                 .append(mMarkMask)
+                .append(", mXfrmInterfaceId=")
+                .append(mXfrmInterfaceId)
                 .append("}");
 
         return strBuilder.toString();
@@ -305,25 +334,25 @@
                 }
             };
 
-    @VisibleForTesting
-    /** Equals method used for testing */
-    public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
-        if (lhs == null || rhs == null) return (lhs == rhs);
-        return (lhs.mMode == rhs.mMode
-                && lhs.mSourceAddress.equals(rhs.mSourceAddress)
-                && lhs.mDestinationAddress.equals(rhs.mDestinationAddress)
-                && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
-                        || (lhs.mNetwork == rhs.mNetwork))
-                && lhs.mEncapType == rhs.mEncapType
-                && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
-                && lhs.mEncapRemotePort == rhs.mEncapRemotePort
-                && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
-                && lhs.mSpiResourceId == rhs.mSpiResourceId
-                && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
-                && IpSecAlgorithm.equals(
-                        lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
-                && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)
-                && lhs.mMarkValue == rhs.mMarkValue
-                && lhs.mMarkMask == rhs.mMarkMask);
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof IpSecConfig)) return false;
+        final IpSecConfig rhs = (IpSecConfig) other;
+        return (mMode == rhs.mMode
+                && mSourceAddress.equals(rhs.mSourceAddress)
+                && mDestinationAddress.equals(rhs.mDestinationAddress)
+                && ((mNetwork != null && mNetwork.equals(rhs.mNetwork))
+                        || (mNetwork == rhs.mNetwork))
+                && mEncapType == rhs.mEncapType
+                && mEncapSocketResourceId == rhs.mEncapSocketResourceId
+                && mEncapRemotePort == rhs.mEncapRemotePort
+                && mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+                && mSpiResourceId == rhs.mSpiResourceId
+                && IpSecAlgorithm.equals(mEncryption, rhs.mEncryption)
+                && IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+                && IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication)
+                && mMarkValue == rhs.mMarkValue
+                && mMarkMask == rhs.mMarkMask
+                && mXfrmInterfaceId == rhs.mXfrmInterfaceId);
     }
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index d4c3edc..889e9bc 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -941,7 +941,8 @@
             throw new IllegalArgumentException(sse);
         } else if (sse.errorCode == OsConstants.EAGAIN) {
             throw new IllegalStateException(sse);
-        } else if (sse.errorCode == OsConstants.EOPNOTSUPP) {
+        } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+                || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
             throw new UnsupportedOperationException(sse);
         }
     }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index a12df28..93ae4f1 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -148,15 +148,13 @@
     }
 
     /**
-     * Equals method used for testing
-     *
-     * @hide
+     * Standard equals.
      */
-    @VisibleForTesting
-    public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) {
-        if (lhs == null || rhs == null) return (lhs == rhs);
-        return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig())
-                && lhs.mResourceId == rhs.mResourceId;
+    public boolean equals(Object other) {
+        if (this == other) return true;
+        if (!(other instanceof IpSecTransform)) return false;
+        final IpSecTransform rhs = (IpSecTransform) other;
+        return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
     }
 
     /**
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 4bcc245..14a0cbf 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,10 +16,13 @@
 
 package android.net;
 
+import static android.os.Process.CLAT_UID;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +36,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Predicate;
 
 /**
  * Collection of active network statistics. Can contain summary details across
@@ -43,6 +47,7 @@
  *
  * @hide
  */
+// @NotThreadSafe
 public class NetworkStats implements Parcelable {
     private static final String TAG = "NetworkStats";
     /** {@link #iface} value when interface details unavailable. */
@@ -110,25 +115,43 @@
      * generated.
      */
     private long elapsedRealtime;
+    @UnsupportedAppUsage
     private int size;
+    @UnsupportedAppUsage
     private int capacity;
+    @UnsupportedAppUsage
     private String[] iface;
+    @UnsupportedAppUsage
     private int[] uid;
+    @UnsupportedAppUsage
     private int[] set;
+    @UnsupportedAppUsage
     private int[] tag;
+    @UnsupportedAppUsage
     private int[] metered;
+    @UnsupportedAppUsage
     private int[] roaming;
+    @UnsupportedAppUsage
     private int[] defaultNetwork;
+    @UnsupportedAppUsage
     private long[] rxBytes;
+    @UnsupportedAppUsage
     private long[] rxPackets;
+    @UnsupportedAppUsage
     private long[] txBytes;
+    @UnsupportedAppUsage
     private long[] txPackets;
+    @UnsupportedAppUsage
     private long[] operations;
 
     public static class Entry {
+        @UnsupportedAppUsage
         public String iface;
+        @UnsupportedAppUsage
         public int uid;
+        @UnsupportedAppUsage
         public int set;
+        @UnsupportedAppUsage
         public int tag;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
@@ -148,12 +171,17 @@
          * getSummary().
          */
         public int defaultNetwork;
+        @UnsupportedAppUsage
         public long rxBytes;
+        @UnsupportedAppUsage
         public long rxPackets;
+        @UnsupportedAppUsage
         public long txBytes;
+        @UnsupportedAppUsage
         public long txPackets;
         public long operations;
 
+        @UnsupportedAppUsage
         public Entry() {
             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
         }
@@ -240,6 +268,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
@@ -263,6 +292,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     public NetworkStats(Parcel parcel) {
         elapsedRealtime = parcel.readLong();
         size = parcel.readInt();
@@ -399,6 +429,7 @@
     /**
      * Return specific stats entry.
      */
+    @UnsupportedAppUsage
     public Entry getValues(int i, Entry recycle) {
         final Entry entry = recycle != null ? recycle : new Entry();
         entry.iface = iface[i];
@@ -416,6 +447,26 @@
         return entry;
     }
 
+    /**
+     * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index
+     * @{code dest}.
+     */
+    private void maybeCopyEntry(int dest, int src) {
+        if (dest == src) return;
+        iface[dest] = iface[src];
+        uid[dest] = uid[src];
+        set[dest] = set[src];
+        tag[dest] = tag[src];
+        metered[dest] = metered[src];
+        roaming[dest] = roaming[src];
+        defaultNetwork[dest] = defaultNetwork[src];
+        rxBytes[dest] = rxBytes[src];
+        rxPackets[dest] = rxPackets[src];
+        txBytes[dest] = txBytes[src];
+        txPackets[dest] = txPackets[src];
+        operations[dest] = operations[src];
+    }
+
     public long getElapsedRealtime() {
         return elapsedRealtime;
     }
@@ -432,6 +483,7 @@
         return SystemClock.elapsedRealtime() - elapsedRealtime;
     }
 
+    @UnsupportedAppUsage
     public int size() {
         return size;
     }
@@ -460,6 +512,7 @@
      * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
      * also be used to subtract values from existing rows.
      */
+    @UnsupportedAppUsage
     public NetworkStats combineValues(Entry entry) {
         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
                 entry.roaming, entry.defaultNetwork);
@@ -479,6 +532,7 @@
     /**
      * Combine all values from another {@link NetworkStats} into this object.
      */
+    @UnsupportedAppUsage
     public void combineAllValues(NetworkStats another) {
         NetworkStats.Entry entry = null;
         for (int i = 0; i < another.size; i++) {
@@ -564,6 +618,7 @@
     /**
      * Return list of unique UIDs known by this data structure.
      */
+    @UnsupportedAppUsage
     public int[] getUniqueUids() {
         final SparseBooleanArray uids = new SparseBooleanArray();
         for (int uid : this.uid) {
@@ -582,6 +637,7 @@
      * Return total bytes represented by this snapshot object, usually used when
      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
      */
+    @UnsupportedAppUsage
     public long getTotalBytes() {
         final Entry entry = getTotal(null);
         return entry.rxBytes + entry.txBytes;
@@ -590,6 +646,7 @@
     /**
      * Return total of all fields represented by this snapshot object.
      */
+    @UnsupportedAppUsage
     public Entry getTotal(Entry recycle) {
         return getTotal(recycle, null, UID_ALL, false);
     }
@@ -598,6 +655,7 @@
      * Return total of all fields represented by this snapshot object matching
      * the requested {@link #uid}.
      */
+    @UnsupportedAppUsage
     public Entry getTotal(Entry recycle, int limitUid) {
         return getTotal(recycle, null, limitUid, false);
     }
@@ -610,6 +668,7 @@
         return getTotal(recycle, limitIface, UID_ALL, false);
     }
 
+    @UnsupportedAppUsage
     public Entry getTotalIncludingTags(Entry recycle) {
         return getTotal(recycle, null, UID_ALL, true);
     }
@@ -772,13 +831,15 @@
      *
      * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
      * packet on the stacked interface, and once as translated to an IPv6 packet on the
-     * base interface. For correct stats accounting on the base interface, every 464xlat
-     * packet needs to be subtracted from the root UID on the base interface both for tx
-     * and rx traffic (http://b/12249687, http:/b/33681750).
+     * base interface. For correct stats accounting on the base interface, if using xt_qtaguid,
+     * every rx 464xlat packet needs to be subtracted from the root UID on the base interface
+     * (http://b/12249687, http:/b/33681750), and every tx 464xlat packet which was counted onto
+     * clat uid should be ignored.
      *
      * As for eBPF, the per uid stats is collected by different hook, the rx packets on base
-     * interface will not be counted. Thus, the adjustment on root uid is only needed in tx
-     * direction.
+     * interface will not be counted. Thus, the adjustment on root uid is not needed. However, the
+     * tx traffic counted in the same way xt_qtaguid does, so the traffic on clat uid still
+     * needs to be ignored.
      *
      * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
      * {@code ConcurrentHashMap}
@@ -806,17 +867,14 @@
             if (baseIface == null) {
                 continue;
             }
-            // Subtract any 464lat traffic seen for the root UID on the current base interface.
-            // However, for eBPF, the per uid stats is collected by different hook, the rx packets
-            // on base interface will not be counted. Thus, the adjustment on root uid is only
-            // needed in tx direction.
+            // Subtract xt_qtaguid 464lat rx traffic seen for the root UID on the current base
+            // interface. As for eBPF, the per uid stats is collected by different hook, the rx
+            // packets on base interface will not be counted.
             adjust.iface = baseIface;
             if (!useBpfStats) {
                 adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
                 adjust.rxPackets = -entry.rxPackets;
             }
-            adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
-            adjust.txPackets = -entry.txPackets;
             adjustments.combineValues(adjust);
 
             // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
@@ -828,6 +886,9 @@
             stackedTraffic.setValues(i, entry);
         }
 
+        // Traffic on clat uid is v6 tx traffic that is already counted with app uid on the stacked
+        // v4 interface, so it needs to be removed to avoid double-counting.
+        baseTraffic.removeUids(new int[] {CLAT_UID});
         baseTraffic.combineAllValues(adjustments);
     }
 
@@ -906,21 +967,18 @@
     }
 
     /**
-     * Return all rows except those attributed to the requested UID; doesn't
-     * mutate the original structure.
+     * Remove all rows that match one of specified UIDs.
      */
-    public NetworkStats withoutUids(int[] uids) {
-        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
-
-        Entry entry = new Entry();
+    public void removeUids(int[] uids) {
+        int nextOutputEntry = 0;
         for (int i = 0; i < size; i++) {
-            entry = getValues(i, entry);
-            if (!ArrayUtils.contains(uids, entry.uid)) {
-                stats.addValues(entry);
+            if (!ArrayUtils.contains(uids, uid[i])) {
+                maybeCopyEntry(nextOutputEntry, i);
+                nextOutputEntry++;
             }
         }
 
-        return stats;
+        size = nextOutputEntry;
     }
 
     /**
@@ -936,23 +994,33 @@
         if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
             return;
         }
+        filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
+                && (limitTag == TAG_ALL || limitTag == e.tag)
+                && (limitIfaces == INTERFACES_ALL
+                    || ArrayUtils.contains(limitIfaces, e.iface)));
+    }
 
+    /**
+     * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
+     *
+     * <p>This mutates the original structure in place.
+     */
+    public void filterDebugEntries() {
+        filter(e -> e.set < SET_DEBUG_START);
+    }
+
+    private void filter(Predicate<Entry> predicate) {
         Entry entry = new Entry();
         int nextOutputEntry = 0;
         for (int i = 0; i < size; i++) {
             entry = getValues(i, entry);
-            final boolean matches =
-                    (limitUid == UID_ALL || limitUid == entry.uid)
-                    && (limitTag == TAG_ALL || limitTag == entry.tag)
-                    && (limitIfaces == INTERFACES_ALL
-                            || ArrayUtils.contains(limitIfaces, entry.iface));
-
-            if (matches) {
-                setValues(nextOutputEntry, entry);
+            if (predicate.test(entry)) {
+                if (nextOutputEntry != i) {
+                    setValues(nextOutputEntry, entry);
+                }
                 nextOutputEntry++;
             }
         }
-
         size = nextOutputEntry;
     }
 
@@ -1095,6 +1163,7 @@
         return 0;
     }
 
+    @UnsupportedAppUsage
     public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
         @Override
         public NetworkStats createFromParcel(Parcel in) {
@@ -1117,133 +1186,221 @@
     /**
      * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
      *
-     * This method should only be called on delta NetworkStats. Do not call this method on a
-     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
-     * change over time.
+     * <p>This method should only be called on delta NetworkStats. Do not call this method on a
+     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change
+     * over time.
      *
-     * This method performs adjustments for one active VPN package and one VPN iface at a time.
-     *
-     * It is possible for the VPN software to use multiple underlying networks. This method
-     * only migrates traffic for the primary underlying network.
+     * <p>This method performs adjustments for one active VPN package and one VPN iface at a time.
      *
      * @param tunUid uid of the VPN application
      * @param tunIface iface of the vpn tunnel
-     * @param underlyingIface the primary underlying network iface used by the VPN application
-     * @return true if it successfully adjusts the accounting for VPN, false otherwise
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
      */
-    public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
-        Entry tunIfaceTotal = new Entry();
-        Entry underlyingIfaceTotal = new Entry();
+    public void migrateTun(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces) {
+        // Combined usage by all apps using VPN.
+        final Entry tunIfaceTotal = new Entry();
+        // Usage by VPN, grouped by its {@code underlyingIfaces}.
+        final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.length];
+        // Usage by VPN, summed across all its {@code underlyingIfaces}.
+        final Entry underlyingIfacesTotal = new Entry();
 
-        tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
+        for (int i = 0; i < perInterfaceTotal.length; i++) {
+            perInterfaceTotal[i] = new Entry();
+        }
 
-        // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
-        // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
+        tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal,
+                underlyingIfacesTotal);
+
+        // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app.
+        // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression.
         // Negative stats should be avoided.
-        Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
-        if (pool.isEmpty()) {
-            return true;
-        }
-        Entry moved =
-                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
-        deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
-
-        if (!moved.isEmpty()) {
-            Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
-                    + moved);
-            return false;
-        }
-        return true;
+        final Entry[] moved =
+                addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal,
+                        perInterfaceTotal, underlyingIfacesTotal);
+        deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved);
     }
 
     /**
      * Initializes the data used by the migrateTun() method.
      *
-     * This is the first pass iteration which does the following work:
-     * (1) Adds up all the traffic through the tunUid's underlyingIface
-     *     (both foreground and background).
-     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+     * <p>This is the first pass iteration which does the following work:
+     *
+     * <ul>
+     *   <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and
+     *       background).
+     *   <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+     * </ul>
+     *
+     * @param tunUid uid of the VPN application
+     * @param tunIface iface of the vpn tunnel
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN
+     * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code
+     *     underlyingIfaces}
+     * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its
+     *     {@code underlyingIfaces}
      */
-    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
-            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
-        Entry recycle = new Entry();
+    private void tunAdjustmentInit(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+        final Entry recycle = new Entry();
         for (int i = 0; i < size; i++) {
             getValues(i, recycle);
             if (recycle.uid == UID_ALL) {
                 throw new IllegalStateException(
                         "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
-            } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
+            }
+            if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
                 throw new IllegalStateException(
                         "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
             }
-
-            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
-                    && Objects.equals(underlyingIface, recycle.iface)) {
-                underlyingIfaceTotal.add(recycle);
+            if (recycle.tag != TAG_NONE) {
+                // TODO(b/123666283): Take all tags for tunUid into account.
+                continue;
             }
 
-            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
-                    && Objects.equals(tunIface, recycle.iface)) {
+            if (recycle.uid == tunUid) {
+                // Add up traffic through tunUid's underlying interfaces.
+                for (int j = 0; j < underlyingIfaces.length; j++) {
+                    if (Objects.equals(underlyingIfaces[j], recycle.iface)) {
+                        perInterfaceTotal[j].add(recycle);
+                        underlyingIfacesTotal.add(recycle);
+                        break;
+                    }
+                }
+            } else if (tunIface.equals(recycle.iface)) {
                 // Add up all tunIface traffic excluding traffic from the vpn app itself.
                 tunIfaceTotal.add(recycle);
             }
         }
     }
 
-    private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
-        Entry pool = new Entry();
-        pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
-        pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
-        pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
-        pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
-        pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
-        return pool;
-    }
+    /**
+     * Distributes traffic across apps that are using given {@code tunIface}, and returns the total
+     * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}.
+     *
+     * @param tunUid uid of the VPN application
+     * @param tunIface iface of the vpn tunnel
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @param tunIfaceTotal combined data usage across all apps using {@code tunIface}
+     * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces}
+     * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code
+     *     underlyingIfaces}
+     */
+    private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+        // Traffic that should be moved off of each underlying interface for tunUid (see
+        // deductTrafficFromVpnApp below).
+        final Entry[] moved = new Entry[underlyingIfaces.length];
+        for (int i = 0; i < underlyingIfaces.length; i++) {
+            moved[i] = new Entry();
+        }
 
-    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
-            Entry tunIfaceTotal, Entry pool) {
-        Entry moved = new Entry();
-        Entry tmpEntry = new Entry();
-        tmpEntry.iface = underlyingIface;
-        for (int i = 0; i < size; i++) {
-            // the vpn app is excluded from the redistribution but all moved traffic will be
-            // deducted from the vpn app (see deductTrafficFromVpnApp below).
-            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
-                if (tunIfaceTotal.rxBytes > 0) {
-                    tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
-                } else {
-                    tmpEntry.rxBytes = 0;
-                }
-                if (tunIfaceTotal.rxPackets > 0) {
-                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
-                } else {
-                    tmpEntry.rxPackets = 0;
-                }
-                if (tunIfaceTotal.txBytes > 0) {
-                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
-                } else {
-                    tmpEntry.txBytes = 0;
-                }
-                if (tunIfaceTotal.txPackets > 0) {
-                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
-                } else {
-                    tmpEntry.txPackets = 0;
-                }
-                if (tunIfaceTotal.operations > 0) {
-                    tmpEntry.operations =
-                            pool.operations * operations[i] / tunIfaceTotal.operations;
-                } else {
-                    tmpEntry.operations = 0;
-                }
-                tmpEntry.uid = uid[i];
-                tmpEntry.tag = tag[i];
+        final Entry tmpEntry = new Entry();
+        final int origSize = size;
+        for (int i = 0; i < origSize; i++) {
+            if (!Objects.equals(iface[i], tunIface)) {
+                // Consider only entries that go onto the VPN interface.
+                continue;
+            }
+            if (uid[i] == tunUid) {
+                // Exclude VPN app from the redistribution, as it can choose to create packet
+                // streams by writing to itself.
+                continue;
+            }
+            tmpEntry.uid = uid[i];
+            tmpEntry.tag = tag[i];
+            tmpEntry.metered = metered[i];
+            tmpEntry.roaming = roaming[i];
+            tmpEntry.defaultNetwork = defaultNetwork[i];
+
+            // In a first pass, compute this entry's total share of data across all
+            // underlyingIfaces. This is computed on the basis of the share of this entry's usage
+            // over tunIface.
+            // TODO: Consider refactoring first pass into a separate helper method.
+            long totalRxBytes = 0;
+            if (tunIfaceTotal.rxBytes > 0) {
+                // Note - The multiplication below should not overflow since NetworkStatsService
+                // processes this every time device has transmitted/received amount equivalent to
+                // global threshold alert (~ 2MB) across all interfaces.
+                final long rxBytesAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
+                // app must not be blamed for more than it consumed on tunIface
+                totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces);
+            }
+            long totalRxPackets = 0;
+            if (tunIfaceTotal.rxPackets > 0) {
+                final long rxPacketsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+                totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces);
+            }
+            long totalTxBytes = 0;
+            if (tunIfaceTotal.txBytes > 0) {
+                final long txBytesAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+                totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces);
+            }
+            long totalTxPackets = 0;
+            if (tunIfaceTotal.txPackets > 0) {
+                final long txPacketsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+                totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces);
+            }
+            long totalOperations = 0;
+            if (tunIfaceTotal.operations > 0) {
+                final long operationsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.operations * operations[i] / tunIfaceTotal.operations;
+                totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces);
+            }
+            // In a second pass, distribute these values across interfaces in the proportion that
+            // each interface represents of the total traffic of the underlying interfaces.
+            for (int j = 0; j < underlyingIfaces.length; j++) {
+                tmpEntry.iface = underlyingIfaces[j];
+                tmpEntry.rxBytes = 0;
+                // Reset 'set' to correct value since it gets updated when adding debug info below.
                 tmpEntry.set = set[i];
-                tmpEntry.metered = metered[i];
-                tmpEntry.roaming = roaming[i];
-                tmpEntry.defaultNetwork = defaultNetwork[i];
+                if (underlyingIfacesTotal.rxBytes > 0) {
+                    tmpEntry.rxBytes =
+                            totalRxBytes
+                                    * perInterfaceTotal[j].rxBytes
+                                    / underlyingIfacesTotal.rxBytes;
+                }
+                tmpEntry.rxPackets = 0;
+                if (underlyingIfacesTotal.rxPackets > 0) {
+                    tmpEntry.rxPackets =
+                            totalRxPackets
+                                    * perInterfaceTotal[j].rxPackets
+                                    / underlyingIfacesTotal.rxPackets;
+                }
+                tmpEntry.txBytes = 0;
+                if (underlyingIfacesTotal.txBytes > 0) {
+                    tmpEntry.txBytes =
+                            totalTxBytes
+                                    * perInterfaceTotal[j].txBytes
+                                    / underlyingIfacesTotal.txBytes;
+                }
+                tmpEntry.txPackets = 0;
+                if (underlyingIfacesTotal.txPackets > 0) {
+                    tmpEntry.txPackets =
+                            totalTxPackets
+                                    * perInterfaceTotal[j].txPackets
+                                    / underlyingIfacesTotal.txPackets;
+                }
+                tmpEntry.operations = 0;
+                if (underlyingIfacesTotal.operations > 0) {
+                    tmpEntry.operations =
+                            totalOperations
+                                    * perInterfaceTotal[j].operations
+                                    / underlyingIfacesTotal.operations;
+                }
+                // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying
+                // interface. Add that data usage to this object.
                 combineValues(tmpEntry);
                 if (tag[i] == TAG_NONE) {
-                    moved.add(tmpEntry);
+                    // Add the migrated data to moved so it is deducted from the VPN app later.
+                    moved[j].add(tmpEntry);
                     // Add debug info
                     tmpEntry.set = SET_DBG_VPN_IN;
                     combineValues(tmpEntry);
@@ -1253,38 +1410,45 @@
         return moved;
     }
 
-    private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
-        // Add debug info
-        moved.uid = tunUid;
-        moved.set = SET_DBG_VPN_OUT;
-        moved.tag = TAG_NONE;
-        moved.iface = underlyingIface;
-        moved.metered = METERED_ALL;
-        moved.roaming = ROAMING_ALL;
-        moved.defaultNetwork = DEFAULT_NETWORK_ALL;
-        combineValues(moved);
+    private void deductTrafficFromVpnApp(
+            int tunUid,
+            @NonNull String[] underlyingIfaces,
+            @NonNull Entry[] moved) {
+        for (int i = 0; i < underlyingIfaces.length; i++) {
+            moved[i].uid = tunUid;
+            // Add debug info
+            moved[i].set = SET_DBG_VPN_OUT;
+            moved[i].tag = TAG_NONE;
+            moved[i].iface = underlyingIfaces[i];
+            moved[i].metered = METERED_ALL;
+            moved[i].roaming = ROAMING_ALL;
+            moved[i].defaultNetwork = DEFAULT_NETWORK_ALL;
+            combineValues(moved[i]);
 
-        // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
-        // the TAG_NONE traffic.
-        //
-        // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
-        // which should be the case as it comes directly from the /proc file. We only blend in the
-        // roaming data after applying these adjustments, by checking the NetworkIdentity of the
-        // underlying iface.
-        int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
-        if (idxVpnBackground != -1) {
-            tunSubtract(idxVpnBackground, this, moved);
-        }
+            // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+            // the TAG_NONE traffic.
+            //
+            // Relies on the fact that the underlying traffic only has state ROAMING_NO and
+            // METERED_NO, which should be the case as it comes directly from the /proc file.
+            // We only blend in the roaming data after applying these adjustments, by checking the
+            // NetworkIdentity of the underlying iface.
+            final int idxVpnBackground = findIndex(underlyingIfaces[i], tunUid, SET_DEFAULT,
+                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+            if (idxVpnBackground != -1) {
+                // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed
+                // from foreground usage.
+                tunSubtract(idxVpnBackground, this, moved[i]);
+            }
 
-        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
-        if (idxVpnForeground != -1) {
-            tunSubtract(idxVpnForeground, this, moved);
+            final int idxVpnForeground = findIndex(underlyingIfaces[i], tunUid, SET_FOREGROUND,
+                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+            if (idxVpnForeground != -1) {
+                tunSubtract(idxVpnForeground, this, moved[i]);
+            }
         }
     }
 
-    private static void tunSubtract(int i, NetworkStats left, Entry right) {
+    private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) {
         long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
         left.rxBytes[i] -= rxBytes;
         right.rxBytes -= rxBytes;
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index a13ad65..d53e032 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -30,6 +30,7 @@
 
 import static com.android.internal.util.ArrayUtils.total;
 
+import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.NetworkStatsHistoryBucketProto;
@@ -89,16 +90,23 @@
     public static class Entry {
         public static final long UNKNOWN = -1;
 
+        @UnsupportedAppUsage
         public long bucketDuration;
+        @UnsupportedAppUsage
         public long bucketStart;
         public long activeTime;
+        @UnsupportedAppUsage
         public long rxBytes;
+        @UnsupportedAppUsage
         public long rxPackets;
+        @UnsupportedAppUsage
         public long txBytes;
+        @UnsupportedAppUsage
         public long txPackets;
         public long operations;
     }
 
+    @UnsupportedAppUsage
     public NetworkStatsHistory(long bucketDuration) {
         this(bucketDuration, 10, FIELD_ALL);
     }
@@ -125,6 +133,7 @@
         recordEntireHistory(existing);
     }
 
+    @UnsupportedAppUsage
     public NetworkStatsHistory(Parcel in) {
         bucketDuration = in.readLong();
         bucketStart = readLongArray(in);
@@ -210,6 +219,7 @@
         return 0;
     }
 
+    @UnsupportedAppUsage
     public int size() {
         return bucketCount;
     }
@@ -218,6 +228,7 @@
         return bucketDuration;
     }
 
+    @UnsupportedAppUsage
     public long getStart() {
         if (bucketCount > 0) {
             return bucketStart[0];
@@ -226,6 +237,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     public long getEnd() {
         if (bucketCount > 0) {
             return bucketStart[bucketCount - 1] + bucketDuration;
@@ -245,6 +257,7 @@
      * Return index of bucket that contains or is immediately before the
      * requested time.
      */
+    @UnsupportedAppUsage
     public int getIndexBefore(long time) {
         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
         if (index < 0) {
@@ -272,6 +285,7 @@
     /**
      * Return specific stats entry.
      */
+    @UnsupportedAppUsage
     public Entry getValues(int i, Entry recycle) {
         final Entry entry = recycle != null ? recycle : new Entry();
         entry.bucketStart = bucketStart[i];
@@ -373,6 +387,7 @@
      * Record an entire {@link NetworkStatsHistory} into this history. Usually
      * for combining together stats for external reporting.
      */
+    @UnsupportedAppUsage
     public void recordEntireHistory(NetworkStatsHistory input) {
         recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
     }
@@ -509,6 +524,7 @@
      * Return interpolated data usage across the requested range. Interpolates
      * across buckets, so values may be rounded slightly.
      */
+    @UnsupportedAppUsage
     public Entry getValues(long start, long end, Entry recycle) {
         return getValues(start, end, Long.MAX_VALUE, recycle);
     }
@@ -517,6 +533,7 @@
      * Return interpolated data usage across the requested range. Interpolates
      * across buckets, so values may be rounded slightly.
      */
+    @UnsupportedAppUsage
     public Entry getValues(long start, long end, long now, Entry recycle) {
         final Entry entry = recycle != null ? recycle : new Entry();
         entry.bucketDuration = end - start;
@@ -701,6 +718,7 @@
         return writer.toString();
     }
 
+    @UnsupportedAppUsage
     public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
         @Override
         public NetworkStatsHistory createFromParcel(Parcel in) {
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 74233fd..60ae352 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -34,11 +34,13 @@
 import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.wifi.WifiInfo.removeDoubleQuotes;
 
+import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.BackupUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.ByteArrayOutputStream;
@@ -88,14 +90,27 @@
 
     private static boolean sForceAllNetworkTypes = false;
 
+    /**
+     * Results in matching against all mobile network types.
+     *
+     * <p>See {@link #matchesMobile} and {@link matchesMobileWildcard}.
+     */
+    @VisibleForTesting
     public static void forceAllNetworkTypes() {
         sForceAllNetworkTypes = true;
     }
 
+    /** Resets the affect of {@link #forceAllNetworkTypes}. */
+    @VisibleForTesting
+    public static void resetForceAllNetworkTypes() {
+        sForceAllNetworkTypes = false;
+    }
+
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
      */
+    @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
         return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
     }
@@ -104,6 +119,7 @@
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks,
      * regardless of IMSI.
      */
+    @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateMobileWildcard() {
         return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
     }
@@ -112,11 +128,13 @@
      * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
      * regardless of SSID.
      */
+    @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateWifiWildcard() {
         return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
     }
 
     @Deprecated
+    @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateWifi() {
         return buildTemplateWifiWildcard();
     }
@@ -133,6 +151,7 @@
      * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
      * networks together.
      */
+    @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateEthernet() {
         return new NetworkTemplate(MATCH_ETHERNET, null, null);
     }
@@ -173,6 +192,7 @@
     private final int mRoaming;
     private final int mDefaultNetwork;
 
+    @UnsupportedAppUsage
     public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
         this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
     }
@@ -293,10 +313,12 @@
         }
     }
 
+    @UnsupportedAppUsage
     public int getMatchRule() {
         return mMatchRule;
     }
 
+    @UnsupportedAppUsage
     public String getSubscriberId() {
         return mSubscriberId;
     }
@@ -460,6 +482,7 @@
      * active merge set [A,B], we'd return a new template that primarily matches
      * A, but also matches B.
      */
+    @UnsupportedAppUsage
     public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
         if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
             // Requested template subscriber is part of the merge group; return
@@ -471,6 +494,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
         @Override
         public NetworkTemplate createFromParcel(Parcel in) {
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index f033268..1c6a484 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -19,11 +19,13 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
 import android.app.DownloadManager;
 import android.app.backup.BackupManager;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.media.MediaPlayer;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.DataUnit;
@@ -89,6 +91,42 @@
     public static final int UID_TETHERING = -5;
 
     /**
+     * Tag values in this range are reserved for the network stack. The network stack is
+     * running as UID {@link android.os.Process.NETWORK_STACK_UID} when in the mainline
+     * module separate process, and as the system UID otherwise.
+     */
+    /** @hide */
+    @SystemApi
+    public static final int TAG_NETWORK_STACK_RANGE_START = 0xFFFFFD00;
+    /** @hide */
+    @SystemApi
+    public static final int TAG_NETWORK_STACK_RANGE_END = 0xFFFFFEFF;
+
+    /**
+     * Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services
+     * like DownloadManager when performing traffic on behalf of an application.
+     */
+    // Please note there is no enforcement of these constants, so do not rely on them to
+    // determine that the caller is a system caller.
+    /** @hide */
+    @SystemApi
+    public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = 0xFFFFFF00;
+    /** @hide */
+    @SystemApi
+    public static final int TAG_SYSTEM_IMPERSONATION_RANGE_END = 0xFFFFFF0F;
+
+    /**
+     * Tag values between these ranges are reserved for the network stack to do traffic
+     * on behalf of applications. It is a subrange of the range above.
+     */
+    /** @hide */
+    @SystemApi
+    public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = 0xFFFFFF80;
+    /** @hide */
+    @SystemApi
+    public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = 0xFFFFFF8F;
+
+    /**
      * Default tag value for {@link DownloadManager} traffic.
      *
      * @hide
@@ -126,21 +164,13 @@
      */
     public static final int TAG_SYSTEM_APP = 0xFFFFFF05;
 
-    /** @hide */
-    public static final int TAG_SYSTEM_DHCP = 0xFFFFFF40;
-    /** @hide */
-    public static final int TAG_SYSTEM_NTP = 0xFFFFFF41;
+    // TODO : remove this constant when Wifi code is updated
     /** @hide */
     public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
-    /** @hide */
-    public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFF43;
-    /** @hide */
-    public static final int TAG_SYSTEM_GPS = 0xFFFFFF44;
-    /** @hide */
-    public static final int TAG_SYSTEM_PAC = 0xFFFFFF45;
 
     private static INetworkStatsService sStatsService;
 
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private synchronized static INetworkStatsService getStatsService() {
         if (sStatsService == null) {
             sStatsService = INetworkStatsService.Stub.asInterface(
@@ -270,7 +300,6 @@
      * Changes only take effect during subsequent calls to
      * {@link #tagSocket(Socket)}.
      */
-    @SystemApi
     @SuppressLint("Doclava125")
     public static void setThreadStatsUid(int uid) {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
@@ -309,7 +338,6 @@
      *
      * @see #setThreadStatsUid(int)
      */
-    @SystemApi
     @SuppressLint("Doclava125")
     public static void clearThreadStatsUid() {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
@@ -536,6 +564,7 @@
     }
 
     /** {@hide} */
+    @UnsupportedAppUsage
     public static long getMobileTcpRxPackets() {
         long total = 0;
         for (String iface : getMobileIfaces()) {
@@ -551,6 +580,7 @@
     }
 
     /** {@hide} */
+    @UnsupportedAppUsage
     public static long getMobileTcpTxPackets() {
         long total = 0;
         for (String iface : getMobileIfaces()) {
@@ -584,6 +614,7 @@
     }
 
     /** {@hide} */
+    @UnsupportedAppUsage
     public static long getTxBytes(String iface) {
         try {
             return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES);
@@ -593,6 +624,7 @@
     }
 
     /** {@hide} */
+    @UnsupportedAppUsage
     public static long getRxBytes(String iface) {
         try {
             return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES);
@@ -948,6 +980,7 @@
      * Interfaces are never removed from this list, so counters should always be
      * monotonic.
      */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private static String[] getMobileIfaces() {
         try {
             return getStatsService().getMobileIfaces();
diff --git a/core/java/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl
new file mode 100644
index 0000000..f70fc8e
--- /dev/null
+++ b/core/java/android/net/UidRange.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * {@hide}
+ */
+parcelable UidRange;
\ No newline at end of file
diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl
index 3361a7b..9484c74 100644
--- a/core/java/android/net/nsd/INsdManager.aidl
+++ b/core/java/android/net/nsd/INsdManager.aidl
@@ -25,6 +25,7 @@
  */
 interface INsdManager
 {
+    @UnsupportedAppUsage
     Messenger getMessenger();
     void setEnabled(boolean enable);
 }
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java
index bccaf60..9ba17ed 100644
--- a/core/java/android/net/nsd/NsdServiceInfo.java
+++ b/core/java/android/net/nsd/NsdServiceInfo.java
@@ -17,6 +17,7 @@
 package android.net.nsd;
 
 import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.text.TextUtils;
@@ -185,6 +186,7 @@
     }
 
     /** @hide */
+    @UnsupportedAppUsage
     public void setAttribute(String key, byte[] value) {
         if (TextUtils.isEmpty(key)) {
             throw new IllegalArgumentException("Key cannot be empty");
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 60f1877..b4e1c32 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -19,15 +19,18 @@
 import static android.Manifest.permission.DUMP;
 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
 import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
+import android.content.pm.PackageManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
@@ -44,7 +47,6 @@
 import android.net.TrafficStats;
 import android.net.util.NetdService;
 import android.os.Binder;
-import android.os.DeadSystemException;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -62,17 +64,19 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
+import libcore.io.IoUtils;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
 
-import libcore.io.IoUtils;
-
 /**
  * A service to manage multiple clients that want to access the IpSec API. The service is
  * responsible for maintaining a list of clients and managing the resources (and related quotas)
@@ -89,14 +93,14 @@
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String NETD_SERVICE_NAME = "netd";
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN};
-    private static final String[] WILDCARD_ADDRESSES = new String[]{"0.0.0.0", "::"};
+    private static final int[] ADDRESS_FAMILIES =
+            new int[] {OsConstants.AF_INET, OsConstants.AF_INET6};
 
     private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
-    private static final int MAX_PORT_BIND_ATTEMPTS = 10;
     private static final InetAddress INADDR_ANY;
 
+    @VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
+
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -205,6 +209,7 @@
                     mBinder.linkToDeath(this, 0);
                 } catch (RemoteException e) {
                     binderDied();
+                    e.rethrowFromSystemServer();
                 }
             }
         }
@@ -615,12 +620,13 @@
                 mSrvConfig
                         .getNetdInstance()
                         .ipSecDeleteSecurityAssociation(
-                                mResourceId,
+                                uid,
                                 mConfig.getSourceAddress(),
                                 mConfig.getDestinationAddress(),
                                 spi,
                                 mConfig.getMarkValue(),
-                                mConfig.getMarkMask());
+                                mConfig.getMarkMask(),
+                                mConfig.getXfrmInterfaceId());
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId, e);
             }
@@ -682,7 +688,8 @@
                     mSrvConfig
                             .getNetdInstance()
                             .ipSecDeleteSecurityAssociation(
-                                    mResourceId, mSourceAddress, mDestinationAddress, mSpi, 0, 0);
+                                    uid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
+                                    0 /* mask */, 0 /* if_id */);
                 }
             } catch (ServiceSpecificException | RemoteException e) {
                 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId, e);
@@ -794,6 +801,8 @@
         private final int mIkey;
         private final int mOkey;
 
+        private final int mIfId;
+
         TunnelInterfaceRecord(
                 int resourceId,
                 String interfaceName,
@@ -801,7 +810,8 @@
                 String localAddr,
                 String remoteAddr,
                 int ikey,
-                int okey) {
+                int okey,
+                int intfId) {
             super(resourceId);
 
             mInterfaceName = interfaceName;
@@ -810,6 +820,7 @@
             mRemoteAddress = remoteAddr;
             mIkey = ikey;
             mOkey = okey;
+            mIfId = intfId;
         }
 
         /** always guarded by IpSecService#this */
@@ -819,16 +830,24 @@
             //       Teardown VTI
             //       Delete global policies
             try {
-                mSrvConfig.getNetdInstance().removeVirtualTunnelInterface(mInterfaceName);
+                final INetd netd = mSrvConfig.getNetdInstance();
+                netd.ipSecRemoveTunnelInterface(mInterfaceName);
 
-                for(String wildcardAddr : WILDCARD_ADDRESSES) {
-                    for (int direction : DIRECTIONS) {
-                        int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey;
-                        mSrvConfig
-                                .getNetdInstance()
-                                .ipSecDeleteSecurityPolicy(
-                                        0, direction, wildcardAddr, wildcardAddr, mark, 0xffffffff);
-                    }
+                for (int selAddrFamily : ADDRESS_FAMILIES) {
+                    netd.ipSecDeleteSecurityPolicy(
+                            uid,
+                            selAddrFamily,
+                            IpSecManager.DIRECTION_OUT,
+                            mOkey,
+                            0xffffffff,
+                            mIfId);
+                    netd.ipSecDeleteSecurityPolicy(
+                            uid,
+                            selAddrFamily,
+                            IpSecManager.DIRECTION_IN,
+                            mIkey,
+                            0xffffffff,
+                            mIfId);
                 }
             } catch (ServiceSpecificException | RemoteException e) {
                 Log.e(
@@ -870,6 +889,10 @@
             return mOkey;
         }
 
+        public int getIfId() {
+            return mIfId;
+        }
+
         @Override
         protected ResourceTracker getResourceTracker() {
             return getUserRecord().mTunnelQuotaTracker;
@@ -1080,7 +1103,8 @@
         }
         checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
 
-        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
         final int resourceId = mNextResourceId++;
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -1093,7 +1117,7 @@
             spi =
                     mSrvConfig
                             .getNetdInstance()
-                            .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi);
+                            .ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
             Log.d(TAG, "Allocated SPI " + spi);
             userRecord.mSpiRecords.put(
                     resourceId,
@@ -1215,7 +1239,8 @@
                     OsConstants.UDP_ENCAP,
                     OsConstants.UDP_ENCAP_ESPINUDP);
 
-            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(
+                        new ParcelFileDescriptor(sockFd), callingUid);
             if (port != 0) {
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
@@ -1261,7 +1286,8 @@
         // TODO: Check that underlying network exists, and IP addresses not assigned to a different
         //       network (b/72316676).
 
-        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        int callerUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callerUid);
         if (!userRecord.mTunnelQuotaTracker.isAvailable()) {
             return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
@@ -1276,25 +1302,31 @@
             //       Create VTI
             //       Add inbound/outbound global policies
             //              (use reqid = 0)
-            mSrvConfig
-                    .getNetdInstance()
-                    .addVirtualTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey);
+            final INetd netd = mSrvConfig.getNetdInstance();
+            netd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
 
-            for(String wildcardAddr : WILDCARD_ADDRESSES) {
-                for (int direction : DIRECTIONS) {
-                    int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey;
-
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecAddSecurityPolicy(
-                                0, // Use 0 for reqId
-                                direction,
-                                wildcardAddr,
-                                wildcardAddr,
-                                0,
-                                mark,
-                                0xffffffff);
-                }
+            for (int selAddrFamily : ADDRESS_FAMILIES) {
+                // Always send down correct local/remote addresses for template.
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_OUT,
+                        localAddr,
+                        remoteAddr,
+                        0,
+                        okey,
+                        0xffffffff,
+                        resourceId);
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_IN,
+                        remoteAddr,
+                        localAddr,
+                        0,
+                        ikey,
+                        0xffffffff,
+                        resourceId);
             }
 
             userRecord.mTunnelInterfaceRecords.put(
@@ -1307,7 +1339,8 @@
                                     localAddr,
                                     remoteAddr,
                                     ikey,
-                                    okey),
+                                    okey,
+                                    resourceId),
                             binder));
             return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
         } catch (RemoteException e) {
@@ -1417,6 +1450,17 @@
                         + "or Encryption algorithms");
     }
 
+    private int getFamily(String inetAddress) {
+        int family = AF_UNSPEC;
+        InetAddress checkAddress = NetworkUtils.numericToInetAddress(inetAddress);
+        if (checkAddress instanceof Inet4Address) {
+            family = AF_INET;
+        } else if (checkAddress instanceof Inet6Address) {
+            family = AF_INET6;
+        }
+        return family;
+    }
+
     /**
      * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
      * IllegalArgumentException if they are not.
@@ -1470,6 +1514,26 @@
         // Require a valid source address for all transforms.
         checkInetAddress(config.getSourceAddress());
 
+        // Check to ensure source and destination have the same address family.
+        String sourceAddress = config.getSourceAddress();
+        String destinationAddress = config.getDestinationAddress();
+        int sourceFamily = getFamily(sourceAddress);
+        int destinationFamily = getFamily(destinationAddress);
+        if (sourceFamily != destinationFamily) {
+            throw new IllegalArgumentException(
+                    "Source address ("
+                            + sourceAddress
+                            + ") and destination address ("
+                            + destinationAddress
+                            + ") have different address families.");
+        }
+
+        // Throw an error if UDP Encapsulation is not used in IPv4.
+        if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
+            throw new IllegalArgumentException(
+                    "UDP Encapsulation is not supported for this address family");
+        }
+
         switch (config.getMode()) {
             case IpSecTransform.MODE_TRANSPORT:
                 break;
@@ -1479,13 +1543,16 @@
                 throw new IllegalArgumentException(
                         "Invalid IpSecTransform.mode: " + config.getMode());
         }
+
+        config.setMarkValue(0);
+        config.setMarkMask(0);
     }
 
+    private static final String TUNNEL_OP = AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS;
+
     private void enforceTunnelPermissions(String callingPackage) {
         checkNotNull(callingPackage, "Null calling package cannot create IpSec tunnels");
-        switch (getAppOpsManager().noteOp(
-                    AppOpsManager.OP_MANAGE_IPSEC_TUNNELS,
-                    Binder.getCallingUid(), callingPackage)) {
+        switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
             case AppOpsManager.MODE_DEFAULT:
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
@@ -1521,7 +1588,7 @@
         mSrvConfig
                 .getNetdInstance()
                 .ipSecAddSecurityAssociation(
-                        resourceId,
+                        Binder.getCallingUid(),
                         c.getMode(),
                         c.getSourceAddress(),
                         c.getDestinationAddress(),
@@ -1540,7 +1607,8 @@
                         (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
                         encapType,
                         encapLocalPort,
-                        encapRemotePort);
+                        encapRemotePort,
+                        c.getXfrmInterfaceId());
     }
 
     /**
@@ -1612,13 +1680,14 @@
     @Override
     public synchronized void applyTransportModeTransform(
             ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
-        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
         checkDirection(direction);
         // Get transform record; if no transform is found, will throw IllegalArgumentException
         TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
 
         // TODO: make this a function.
-        if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
+        if (info.pid != getCallingPid() || info.uid != callingUid) {
             throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
         }
 
@@ -1631,8 +1700,8 @@
         mSrvConfig
                 .getNetdInstance()
                 .ipSecApplyTransportModeTransform(
-                        socket.getFileDescriptor(),
-                        resourceId,
+                        socket,
+                        callingUid,
                         direction,
                         c.getSourceAddress(),
                         c.getDestinationAddress(),
@@ -1650,7 +1719,7 @@
             throws RemoteException {
         mSrvConfig
                 .getNetdInstance()
-                .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
+                .ipSecRemoveTransportModeTransform(socket);
     }
 
     /**
@@ -1664,7 +1733,8 @@
         enforceTunnelPermissions(callingPackage);
         checkDirection(direction);
 
-        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
 
         // Get transform record; if no transform is found, will throw IllegalArgumentException
         TransformRecord transformInfo =
@@ -1689,31 +1759,53 @@
         SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId());
 
         int mark =
-                (direction == IpSecManager.DIRECTION_IN)
-                        ? tunnelInterfaceInfo.getIkey()
-                        : tunnelInterfaceInfo.getOkey();
+                (direction == IpSecManager.DIRECTION_OUT)
+                        ? tunnelInterfaceInfo.getOkey()
+                        : tunnelInterfaceInfo.getIkey();
 
         try {
-            c.setMarkValue(mark);
-            c.setMarkMask(0xffffffff);
+            // Default to using the invalid SPI of 0 for inbound SAs. This allows policies to skip
+            // SPI matching as part of the template resolution.
+            int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+            c.setXfrmInterfaceId(tunnelInterfaceInfo.getIfId());
+
+            // TODO: enable this when UPDSA supports updating marks. Adding kernel support upstream
+            //     (and backporting) would allow us to narrow the mark space, and ensure that the SA
+            //     and SPs have matching marks (as VTI are meant to be built).
+            // Currently update does nothing with marks. Leave empty (defaulting to 0) to ensure the
+            //     config matches the actual allocated resources in the kernel.
+            // All SAs will have zero marks (from creation time), and any policy that matches the
+            //     same src/dst could match these SAs. Non-IpSecService governed processes that
+            //     establish floating policies with the same src/dst may result in undefined
+            //     behavior. This is generally limited to vendor code due to the permissions
+            //     (CAP_NET_ADMIN) required.
+            //
+            // c.setMarkValue(mark);
+            // c.setMarkMask(0xffffffff);
 
             if (direction == IpSecManager.DIRECTION_OUT) {
                 // Set output mark via underlying network (output only)
                 c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork());
 
-                // If outbound, also add SPI to the policy.
-                for(String wildcardAddr : WILDCARD_ADDRESSES) {
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecUpdateSecurityPolicy(
-                                    0, // Use 0 for reqId
-                                    direction,
-                                    wildcardAddr,
-                                    wildcardAddr,
-                                    transformInfo.getSpiRecord().getSpi(),
-                                    mark,
-                                    0xffffffff);
-                }
+                // Set outbound SPI only. We want inbound to use any valid SA (old, new) on rekeys,
+                // but want to guarantee outbound packets are sent over the new SA.
+                spi = transformInfo.getSpiRecord().getSpi();
+            }
+
+            // Always update the policy with the relevant XFRM_IF_ID
+            for (int selAddrFamily : ADDRESS_FAMILIES) {
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecUpdateSecurityPolicy(
+                                callingUid,
+                                selAddrFamily,
+                                direction,
+                                transformInfo.getConfig().getSourceAddress(),
+                                transformInfo.getConfig().getDestinationAddress(),
+                                spi, // If outbound, also add SPI to the policy.
+                                mark, // Must always set policy mark; ikey/okey for VTIs
+                                0xffffffff,
+                                c.getXfrmInterfaceId());
             }
 
             // Update SA with tunnel mark (ikey or okey based on direction)
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index cebc472..7c1c1c7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -109,7 +109,7 @@
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
         boolean hasCarrierPrivileges = tm != null &&
-                tm.checkCarrierPrivilegesForPackage(callingPackage) ==
+                tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) ==
                         TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
                 DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
similarity index 61%
rename from core/java/com/android/internal/net/NetworkStatsFactory.java
rename to services/core/java/com/android/server/net/NetworkStatsFactory.java
index b3cf389..3ca1803 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -14,29 +14,34 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package com.android.server.net;
 
+import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+
 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
 
 import android.annotation.Nullable;
+import android.net.INetd;
 import android.net.NetworkStats;
+import android.net.util.NetdService;
+import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ProcFileReader;
 
 import libcore.io.IoUtils;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.net.ProtocolException;
 import java.util.Arrays;
@@ -63,25 +68,59 @@
     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
     private final File mStatsXtUid;
 
-    private boolean mUseBpfStats;
+    private final boolean mUseBpfStats;
 
-    // TODO: only do adjustments in NetworkStatsService and remove this.
+    private INetd mNetdService;
+
+    /**
+     * Guards persistent data access in this class
+     *
+     * <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
+     * to other code that will acquire other locks within the system server. See b/134244752.
+     */
+    private final Object mPersistentDataLock = new Object();
+
+    /** Set containing info about active VPNs and their underlying networks. */
+    private volatile VpnInfo[] mVpnInfos = new VpnInfo[0];
+
+    // A persistent snapshot of cumulative stats since device start
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats mPersistSnapshot;
+
+    // The persistent snapshot of tun and 464xlat adjusted stats since device start
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats mTunAnd464xlatAdjustedStats;
+
     /**
      * (Stacked interface) -> (base interface) association for all connected ifaces since boot.
      *
      * Because counters must never roll backwards, once a given interface is stacked on top of an
      * underlying interface, the stacked interface can never be stacked on top of
      * another interface. */
-    private static final ConcurrentHashMap<String, String> sStackedIfaces
+    private final ConcurrentHashMap<String, String> mStackedIfaces
             = new ConcurrentHashMap<>();
 
-    public static void noteStackedIface(String stackedIface, String baseIface) {
+    /** Informs the factory of a new stacked interface. */
+    public void noteStackedIface(String stackedIface, String baseIface) {
         if (stackedIface != null && baseIface != null) {
-            sStackedIfaces.put(stackedIface, baseIface);
+            mStackedIfaces.put(stackedIface, baseIface);
         }
     }
 
     /**
+     * Set active VPN information for data usage migration purposes
+     *
+     * <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
+     * app's UID. This method is used to support migration of VPN data usage, ensuring data is
+     * accurately billed to the real owner of the traffic.
+     *
+     * @param vpnArray The snapshot of the currently-running VPNs.
+     */
+    public void updateVpnInfos(VpnInfo[] vpnArray) {
+        mVpnInfos = vpnArray.clone();
+    }
+
+    /**
      * Get a set of interfaces containing specified ifaces and stacked interfaces.
      *
      * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
@@ -89,7 +128,7 @@
      * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
      * is called are guaranteed to be included.
      */
-    public static String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
+    public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
         if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
             return null;
         }
@@ -99,7 +138,7 @@
         // elements as they existed upon construction exactly once, and may
         // (but are not guaranteed to) reflect any modifications subsequent to construction".
         // This is enough here.
-        for (Map.Entry<String, String> entry : sStackedIfaces.entrySet()) {
+        for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
             if (relatedIfaces.contains(entry.getKey())) {
                 relatedIfaces.add(entry.getValue());
             } else if (relatedIfaces.contains(entry.getValue())) {
@@ -115,19 +154,14 @@
      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean)
      */
-    public static void apply464xlatAdjustments(NetworkStats baseTraffic,
+    public void apply464xlatAdjustments(NetworkStats baseTraffic,
             NetworkStats stackedTraffic, boolean useBpfStats) {
-        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces,
+        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces,
                 useBpfStats);
     }
 
-    @VisibleForTesting
-    public static void clearStackedIfaces() {
-        sStackedIfaces.clear();
-    }
-
     public NetworkStatsFactory() {
-        this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists());
+        this(new File("/proc/"), new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists());
     }
 
     @VisibleForTesting
@@ -136,6 +170,10 @@
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
         mUseBpfStats = useBpfStats;
+        synchronized (mPersistentDataLock) {
+            mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+            mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+        }
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -195,7 +233,7 @@
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
-            throw new ProtocolException("problem parsing stats", e);
+            throw protocolExceptionWithCause("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
             StrictMode.setThreadPolicy(savedPolicy);
@@ -245,7 +283,7 @@
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
-            throw new ProtocolException("problem parsing stats", e);
+            throw protocolExceptionWithCause("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
             StrictMode.setThreadPolicy(savedPolicy);
@@ -254,44 +292,109 @@
     }
 
     public NetworkStats readNetworkStatsDetail() throws IOException {
-        return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
+        return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
     }
 
-    public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
-            NetworkStats lastStats) throws IOException {
-        final NetworkStats stats =
-              readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
-
-        // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
-        // TODO: remove this and only apply adjustments in NetworkStatsService.
-        stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats);
-
-        return stats;
-    }
-
-    private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
-            int limitTag, NetworkStats lastStats) throws IOException {
-        if (USE_NATIVE_PARSING) {
-            final NetworkStats stats;
-            if (lastStats != null) {
-                stats = lastStats;
-                stats.setElapsedRealtime(SystemClock.elapsedRealtime());
-            } else {
-                stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
-            }
-            if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
-                    limitIfaces, limitTag, mUseBpfStats) != 0) {
-                throw new IOException("Failed to parse network stats");
-            }
-            if (SANITY_CHECK_NATIVE) {
-                final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
-                        limitIfaces, limitTag);
-                assertEquals(javaStats, stats);
-            }
-            return stats;
-        } else {
-            return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
+    @GuardedBy("mPersistentDataLock")
+    private void requestSwapActiveStatsMapLocked() throws RemoteException {
+        // Ask netd to do a active map stats swap. When the binder call successfully returns,
+        // the system server should be able to safely read and clean the inactive map
+        // without race problem.
+        if (mNetdService == null) {
+            mNetdService = NetdService.getInstance();
         }
+        mNetdService.trafficSwapActiveStatsMap();
+    }
+
+    /**
+     * Reads the detailed UID stats based on the provided parameters
+     *
+     * @param limitUid the UID to limit this query to
+     * @param limitIfaces the interfaces to limit this query to. Use {@link
+     *     NetworkStats.INTERFACES_ALL} to select all interfaces
+     * @param limitTag the tags to limit this query to
+     * @return the NetworkStats instance containing network statistics at the present time.
+     */
+    public NetworkStats readNetworkStatsDetail(
+            int limitUid, String[] limitIfaces, int limitTag) throws IOException {
+        // In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
+        // code that will acquire other locks within the system server. See b/134244752.
+        synchronized (mPersistentDataLock) {
+            // Take a reference. If this gets swapped out, we still have the old reference.
+            final VpnInfo[] vpnArray = mVpnInfos;
+            // Take a defensive copy. mPersistSnapshot is mutated in some cases below
+            final NetworkStats prev = mPersistSnapshot.clone();
+
+            if (USE_NATIVE_PARSING) {
+                final NetworkStats stats =
+                        new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */);
+                if (mUseBpfStats) {
+                    try {
+                        requestSwapActiveStatsMapLocked();
+                    } catch (RemoteException e) {
+                        throw new IOException(e);
+                    }
+                    // Stats are always read from the inactive map, so they must be read after the
+                    // swap
+                    if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+                            INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
+                        throw new IOException("Failed to parse network stats");
+                    }
+
+                    // BPF stats are incremental; fold into mPersistSnapshot.
+                    mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+                    mPersistSnapshot.combineAllValues(stats);
+                } else {
+                    if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+                            INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
+                        throw new IOException("Failed to parse network stats");
+                    }
+                    if (SANITY_CHECK_NATIVE) {
+                        final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid,
+                                UID_ALL, INTERFACES_ALL, TAG_ALL);
+                        assertEquals(javaStats, stats);
+                    }
+
+                    mPersistSnapshot = stats;
+                }
+            } else {
+                mPersistSnapshot = javaReadNetworkStatsDetail(mStatsXtUid, UID_ALL, INTERFACES_ALL,
+                        TAG_ALL);
+            }
+
+            NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
+
+            // Filter return values
+            adjustedStats.filter(limitUid, limitIfaces, limitTag);
+            return adjustedStats;
+        }
+    }
+
+    @GuardedBy("mPersistentDataLock")
+    private NetworkStats adjustForTunAnd464Xlat(
+            NetworkStats uidDetailStats, NetworkStats previousStats, VpnInfo[] vpnArray) {
+        // Calculate delta from last snapshot
+        final NetworkStats delta = uidDetailStats.subtract(previousStats);
+
+        // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
+        // network, the overhead is their fault.
+        // No locking here: apply464xlatAdjustments behaves fine with an add-only
+        // ConcurrentHashMap.
+        delta.apply464xlatAdjustments(mStackedIfaces, mUseBpfStats);
+
+        // Migrate data usage over a VPN to the TUN network.
+        for (VpnInfo info : vpnArray) {
+            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
+        }
+
+        // Filter out debug entries as that may lead to over counting.
+        delta.filterDebugEntries();
+
+        // Update mTunAnd464xlatAdjustedStats with migrated delta.
+        mTunAnd464xlatAdjustedStats.combineAllValues(delta);
+        mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
+
+        return mTunAnd464xlatAdjustedStats.clone();
     }
 
     /**
@@ -342,7 +445,7 @@
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
-            throw new ProtocolException("problem parsing idx " + idx, e);
+            throw protocolExceptionWithCause("problem parsing idx " + idx, e);
         } finally {
             IoUtils.closeQuietly(reader);
             StrictMode.setThreadPolicy(savedPolicy);
@@ -379,4 +482,10 @@
 
     @VisibleForTesting
     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
+
+    private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) {
+        ProtocolException pe = new ProtocolException(message);
+        pe.initCause(cause);
+        return pe;
+    }
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java
index d840873..2564dae 100644
--- a/services/core/java/com/android/server/net/NetworkStatsObservers.java
+++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java
@@ -39,7 +39,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -104,9 +103,9 @@
     public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
-                VpnInfo[] vpnArray, long currentTime) {
+                long currentTime) {
         StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
-                activeUidIfaces, vpnArray, currentTime);
+                activeUidIfaces, currentTime);
         getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
     }
 
@@ -354,7 +353,7 @@
             // thread will update it. We pass a null VPN array because usage is aggregated by uid
             // for this snapshot, so VPN traffic can't be reattributed to responsible apps.
             mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
-                    null /* vpnArray */, statsContext.mCurrentTime);
+                    statsContext.mCurrentTime);
         }
 
         /**
@@ -396,7 +395,7 @@
             // thread will update it. We pass the VPN info so VPN traffic is reattributed to
             // responsible apps.
             mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
-                    statsContext.mVpnArray, statsContext.mCurrentTime);
+                    statsContext.mCurrentTime);
         }
 
         /**
@@ -427,18 +426,16 @@
         NetworkStats mUidSnapshot;
         ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
         ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
-        VpnInfo[] mVpnArray;
         long mCurrentTime;
 
         StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
                 ArrayMap<String, NetworkIdentitySet> activeIfaces,
                 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
-                VpnInfo[] vpnArray, long currentTime) {
+                long currentTime) {
             mXtSnapshot = xtSnapshot;
             mUidSnapshot = uidSnapshot;
             mActiveIfaces = activeIfaces;
             mActiveUidIfaces = activeUidIfaces;
-            mVpnArray = vpnArray;
             mCurrentTime = currentTime;
         }
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index a16dcf3..06ec341 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -23,7 +23,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.Nullable;
 import android.net.NetworkStats;
 import android.net.NetworkStats.NonMonotonicObserver;
 import android.net.NetworkStatsHistory;
@@ -37,14 +36,13 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Sets;
 
+import libcore.io.IoUtils;
+
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -202,18 +200,12 @@
     }
 
     /**
-     * Record any delta that occurred since last {@link NetworkStats} snapshot,
-     * using the given {@link Map} to identify network interfaces. First
-     * snapshot is considered bootstrap, and is not counted as delta.
-     *
-     * @param vpnArray Optional info about the currently active VPN, if any. This is used to
-     *                 redistribute traffic from the VPN app to the underlying responsible apps.
-     *                 This should always be set to null if the provided snapshot is aggregated
-     *                 across all UIDs (e.g. contains UID_ALL buckets), regardless of VPN state.
+     * Record any delta that occurred since last {@link NetworkStats} snapshot, using the given
+     * {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is
+     * not counted as delta.
      */
     public void recordSnapshotLocked(NetworkStats snapshot,
-            Map<String, NetworkIdentitySet> ifaceIdent, @Nullable VpnInfo[] vpnArray,
-            long currentTimeMillis) {
+            Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
         final HashSet<String> unknownIfaces = Sets.newHashSet();
 
         // skip recording when snapshot missing
@@ -232,12 +224,6 @@
         final long end = currentTimeMillis;
         final long start = end - delta.getElapsedRealtime();
 
-        if (vpnArray != null) {
-            for (VpnInfo info : vpnArray) {
-                delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
-            }
-        }
-
         NetworkStats.Entry entry = null;
         for (int i = 0; i < delta.size(); i++) {
             entry = delta.getValues(i, entry);
@@ -352,7 +338,7 @@
 
         // Clear UID from current stats snapshot
         if (mLastSnapshot != null) {
-            mLastSnapshot = mLastSnapshot.withoutUids(uids);
+            mLastSnapshot.removeUids(uids);
         }
 
         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 2fef560..90dc700 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.ConnectivityManager.isNetworkTypeMobile;
+import static android.net.NetworkStack.checkNetworkStackPermission;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.INTERFACES_ALL;
@@ -129,7 +130,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -181,6 +181,7 @@
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
+    private final NetworkStatsFactory mStatsFactory;
     private final AlarmManager mAlarmManager;
     private final Clock mClock;
     private final TelephonyManager mTeleManager;
@@ -266,10 +267,6 @@
     @GuardedBy("mStatsLock")
     private Network[] mDefaultNetworks = new Network[0];
 
-    /** Set containing info about active VPNs and their underlying networks. */
-    @GuardedBy("mStatsLock")
-    private VpnInfo[] mVpnInfos = new VpnInfo[0];
-
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
@@ -334,8 +331,9 @@
 
         NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
                 wakeLock, getDefaultClock(), TelephonyManager.getDefault(),
-                new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
-                getDefaultSystemDir(), getDefaultBaseDir());
+                new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(),
+                new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir());
+        service.registerLocalService();
 
         HandlerThread handlerThread = new HandlerThread(TAG);
         Handler.Callback callback = new HandlerCallback(service);
@@ -345,11 +343,14 @@
         return service;
     }
 
+    // This must not be called outside of tests, even within the same package, as this constructor
+    // does not register the local service. Use the create() helper above.
     @VisibleForTesting
     NetworkStatsService(Context context, INetworkManagementService networkManager,
             AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock,
             TelephonyManager teleManager, NetworkStatsSettings settings,
-            NetworkStatsObservers statsObservers, File systemDir, File baseDir) {
+            NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir,
+            File baseDir) {
         mContext = checkNotNull(context, "missing Context");
         mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
         mAlarmManager = checkNotNull(alarmManager, "missing AlarmManager");
@@ -357,11 +358,14 @@
         mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
         mTeleManager = checkNotNull(teleManager, "missing TelephonyManager");
         mWakeLock = checkNotNull(wakeLock, "missing WakeLock");
+        mStatsFactory = checkNotNull(factory, "missing factory");
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
-        mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
+        mUseBpfTrafficStats = new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists();
+    }
 
+    private void registerLocalService() {
         LocalServices.addService(NetworkStatsManagerInternal.class,
                 new NetworkStatsManagerInternalImpl());
     }
@@ -373,14 +377,9 @@
     }
 
     public void systemReady() {
-        mSystemReady = true;
-
-        if (!isBandwidthControlEnabled()) {
-            Slog.w(TAG, "bandwidth controls disabled, unable to track stats");
-            return;
-        }
-
         synchronized (mStatsLock) {
+            mSystemReady = true;
+
             // create data recorders along with historical rotators
             mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
             mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
@@ -426,7 +425,14 @@
             // ignored; service lives in system_server
         }
 
-        registerPollAlarmLocked();
+        //  schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}.
+        final PendingIntent pollIntent =
+                PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+        final long currentRealtime = SystemClock.elapsedRealtime();
+        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+                mSettings.getPollInterval(), pollIntent);
+
         registerGlobalAlert();
     }
 
@@ -487,23 +493,6 @@
     }
 
     /**
-     * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
-     * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
-     */
-    private void registerPollAlarmLocked() {
-        if (mPollIntent != null) {
-            mAlarmManager.cancel(mPollIntent);
-        }
-
-        mPollIntent = PendingIntent.getBroadcast(
-                mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
-
-        final long currentRealtime = SystemClock.elapsedRealtime();
-        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
-                mSettings.getPollInterval(), mPollIntent);
-    }
-
-    /**
      * Register for a global alert that is delivered through
      * {@link INetworkManagementEventObserver} once a threshold amount of data
      * has been transferred.
@@ -548,8 +537,6 @@
     }
 
     private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
-        assertBandwidthControlEnabled();
-
         final int callingUid = Binder.getCallingUid();
         final int usedFlags = isRateLimitedForPoll(callingUid)
                 ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
@@ -742,7 +729,6 @@
 
     private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
         assertSystemReady();
-        assertBandwidthControlEnabled();
 
         // NOTE: if callers want to get non-augmented data, they should go
         // through the public API
@@ -753,7 +739,6 @@
 
     private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
         assertSystemReady();
-        assertBandwidthControlEnabled();
 
         final NetworkStatsCollection uidComplete;
         synchronized (mStatsLock) {
@@ -768,18 +753,10 @@
         if (Binder.getCallingUid() != uid) {
             mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
         }
-        assertBandwidthControlEnabled();
 
         // TODO: switch to data layer stats once kernel exports
         // for now, read network layer stats and flatten across all ifaces
-        final long token = Binder.clearCallingIdentity();
-        final NetworkStats networkLayer;
-        try {
-            networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid,
-                    NetworkStats.INTERFACES_ALL);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
 
         // splice in operation counts
         networkLayer.spliceOperationsFrom(mUidOperations);
@@ -801,7 +778,7 @@
     public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
         try {
             final String[] ifacesToQuery =
-                    NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
+                    mStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
             return getNetworkStatsUidDetail(ifacesToQuery);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
@@ -852,24 +829,27 @@
     @Override
     public void forceUpdateIfaces(
             Network[] defaultNetworks,
-            VpnInfo[] vpnArray,
             NetworkState[] networkStates,
-            String activeIface) {
-        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-        assertBandwidthControlEnabled();
+            String activeIface,
+            VpnInfo[] vpnInfos) {
+        checkNetworkStackPermission(mContext);
 
         final long token = Binder.clearCallingIdentity();
         try {
-            updateIfaces(defaultNetworks, vpnArray, networkStates, activeIface);
+            updateIfaces(defaultNetworks, networkStates, activeIface);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+
+        // Update the VPN underlying interfaces only after the poll is made and tun data has been
+        // migrated. Otherwise the migration would use the new interfaces instead of the ones that
+        // were current when the polled data was transferred.
+        mStatsFactory.updateVpnInfos(vpnInfos);
     }
 
     @Override
     public void forceUpdate() {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-        assertBandwidthControlEnabled();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -880,8 +860,6 @@
     }
 
     private void advisePersistThreshold(long thresholdBytes) {
-        assertBandwidthControlEnabled();
-
         // clamp threshold into safe range
         mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
         if (LOGV) {
@@ -952,18 +930,64 @@
 
     @Override
     public long getIfaceStats(String iface, int type) {
-        // eBPF code doesn't provide per-interface TCP counters. Use xt_qtaguid for now.
-        // TODO: delete getMobileTcp(Rx|Tx)Packets entirely. See b/110443385 .
-        if (type == TYPE_TCP_TX_PACKETS || type == TYPE_TCP_RX_PACKETS) {
-            return nativeGetIfaceStat(iface, type, false);
+        long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+        if (nativeIfaceStats == -1) {
+            return nativeIfaceStats;
         } else {
-            return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+            // When tethering offload is in use, nativeIfaceStats does not contain usage from
+            // offload, add it back here.
+            // When tethering offload is not in use, nativeIfaceStats contains tethering usage.
+            // this does not cause double-counting of tethering traffic, because
+            // NetdTetheringStatsProvider returns zero NetworkStats
+            // when called with STATS_PER_IFACE.
+            return nativeIfaceStats + getTetherStats(iface, type);
         }
     }
 
     @Override
     public long getTotalStats(int type) {
-        return nativeGetTotalStat(type, checkBpfStatsEnable());
+        long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable());
+        if (nativeTotalStats == -1) {
+            return nativeTotalStats;
+        } else {
+            // Refer to comment in getIfaceStats
+            return nativeTotalStats + getTetherStats(IFACE_ALL, type);
+        }
+    }
+
+    private long getTetherStats(String iface, int type) {
+        final NetworkStats tetherSnapshot;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error get TetherStats: " + e);
+            return 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        HashSet<String> limitIfaces;
+        if (iface == IFACE_ALL) {
+            limitIfaces = null;
+        } else {
+            limitIfaces = new HashSet<String>();
+            limitIfaces.add(iface);
+        }
+        NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces);
+        if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type +
+                " entry=" + entry);
+        switch (type) {
+            case 0: // TYPE_RX_BYTES
+                return entry.rxBytes;
+            case 1: // TYPE_RX_PACKETS
+                return entry.rxPackets;
+            case 2: // TYPE_TX_BYTES
+                return entry.txBytes;
+            case 3: // TYPE_TX_PACKETS
+                return entry.txPackets;
+            default:
+                return 0;
+        }
     }
 
     private boolean checkBpfStatsEnable() {
@@ -1080,13 +1104,11 @@
 
     private void updateIfaces(
             Network[] defaultNetworks,
-            VpnInfo[] vpnArray,
             NetworkState[] networkStates,
             String activeIface) {
         synchronized (mStatsLock) {
             mWakeLock.acquire();
             try {
-                mVpnInfos = vpnArray;
                 mActiveIface = activeIface;
                 updateIfacesLocked(defaultNetworks, networkStates);
             } finally {
@@ -1096,10 +1118,9 @@
     }
 
     /**
-     * Inspect all current {@link NetworkState} to derive mapping from {@code
-     * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
-     * are active on a single {@code iface}, they are combined under a single
-     * {@link NetworkIdentitySet}.
+     * Inspect all current {@link NetworkState} to derive mapping from {@code iface} to {@link
+     * NetworkStatsHistory}. When multiple {@link NetworkInfo} are active on a single {@code iface},
+     * they are combined under a single {@link NetworkIdentitySet}.
      */
     @GuardedBy("mStatsLock")
     private void updateIfacesLocked(Network[] defaultNetworks, NetworkState[] states) {
@@ -1159,20 +1180,28 @@
                     }
                 }
 
-                // Traffic occurring on stacked interfaces is usually clatd,
-                // which is already accounted against its final egress interface
-                // by the kernel. Thus, we only need to collect stacked
-                // interface stats at the UID level.
+                // Traffic occurring on stacked interfaces is usually clatd.
+                // UID stats are always counted on the stacked interface and never
+                // on the base interface, because the packets on the base interface
+                // do not actually match application sockets until they are translated.
+                //
+                // Interface stats are more complicated. Packets subject to BPF offload
+                // never appear on the base interface and only appear on the stacked
+                // interface, so to ensure those packets increment interface stats, interface
+                // stats from stacked interfaces must be collected.
                 final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks();
                 for (LinkProperties stackedLink : stackedLinks) {
                     final String stackedIface = stackedLink.getInterfaceName();
                     if (stackedIface != null) {
+                        if (mUseBpfTrafficStats) {
+                            findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident);
+                        }
                         findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident);
                         if (isMobile) {
                             mobileIfaces.add(stackedIface);
                         }
 
-                        NetworkStatsFactory.noteStackedIface(stackedIface, baseIface);
+                        mStatsFactory.noteStackedIface(stackedIface, baseIface);
                     }
                 }
             }
@@ -1202,7 +1231,7 @@
         final NetworkStats xtSnapshot = getNetworkStatsXt();
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
-        final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
+        final NetworkStats devSnapshot = readNetworkStatsSummaryDev();
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // Tethering snapshot for dev and xt stats. Counts per-interface data from tethering stats
@@ -1216,27 +1245,24 @@
         // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
-        mDevRecorder.recordSnapshotLocked(
-                devSnapshot, mActiveIfaces, null /* vpnArray */, currentTime);
+        mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordXt");
-        mXtRecorder.recordSnapshotLocked(
-                xtSnapshot, mActiveIfaces, null /* vpnArray */, currentTime);
+        mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // For per-UID stats, pass the VPN info so VPN traffic is reattributed to responsible apps.
-        VpnInfo[] vpnArray = mVpnInfos;
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordUid");
-        mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+        mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordUidTag");
-        mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+        mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
 
         // We need to make copies of member fields that are sent to the observer to avoid
         // a race condition between the service handler thread and the observer's
         mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
-                new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
+                new ArrayMap<>(mActiveUidIfaces), currentTime);
     }
 
     /**
@@ -1599,6 +1625,30 @@
         }
     }
 
+    private NetworkStats readNetworkStatsSummaryDev() {
+        try {
+            return mStatsFactory.readNetworkStatsSummaryDev();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private NetworkStats readNetworkStatsSummaryXt() {
+        try {
+            return mStatsFactory.readNetworkStatsSummaryXt();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private NetworkStats readNetworkStatsUidDetail(int uid, String[] ifaces, int tag) {
+        try {
+            return mStatsFactory.readNetworkStatsDetail(uid, ifaces, tag);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
     /**
      * Return snapshot of current UID statistics, including any
      * {@link TrafficStats#UID_TETHERING}, video calling data usage, and {@link #mUidOperations}
@@ -1609,15 +1659,12 @@
      */
     private NetworkStats getNetworkStatsUidDetail(String[] ifaces)
             throws RemoteException {
-
-        // TODO: remove 464xlat adjustments from NetworkStatsFactory and apply all at once here.
-        final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL,
-                ifaces);
+        final NetworkStats uidSnapshot = readNetworkStatsUidDetail(UID_ALL,  ifaces, TAG_ALL);
 
         // fold tethering stats and operations into uid snapshot
         final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
         tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
-        NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
+        mStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
                 mUseBpfTrafficStats);
         uidSnapshot.combineAllValues(tetherSnapshot);
 
@@ -1628,7 +1675,7 @@
         final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
         if (vtStats != null) {
             vtStats.filter(UID_ALL, ifaces, TAG_ALL);
-            NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
+            mStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
                     mUseBpfTrafficStats);
             uidSnapshot.combineAllValues(vtStats);
         }
@@ -1642,7 +1689,7 @@
      * Return snapshot of current XT statistics with video calling data usage statistics.
      */
     private NetworkStats getNetworkStatsXt() throws RemoteException {
-        final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
+        final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
 
         final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
@@ -1702,24 +1749,6 @@
         }
     }
 
-    private void assertBandwidthControlEnabled() {
-        if (!isBandwidthControlEnabled()) {
-            throw new IllegalStateException("Bandwidth module disabled");
-        }
-    }
-
-    private boolean isBandwidthControlEnabled() {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return mNetworkManager.isBandwidthControlEnabled();
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
         @Override
         public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
similarity index 86%
rename from core/jni/com_android_internal_net_NetworkStatsFactory.cpp
rename to services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
index de3a78a..9cd743b 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -21,9 +21,9 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <core_jni_helpers.h>
 #include <jni.h>
 
+#include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
@@ -32,10 +32,9 @@
 #include <utils/misc.h>
 
 #include "android-base/unique_fd.h"
-#include "bpf/BpfNetworkStats.h"
 #include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
 
-using android::bpf::hasBpfSupport;
 using android::bpf::parseBpfNetworkStatsDetail;
 using android::bpf::stats_line;
 
@@ -96,7 +95,7 @@
 static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines,
                                         const std::vector<std::string>& limitIfaces,
                                         int limitTag, int limitUid, const char* path) {
-    FILE* fp = fopen(path, "r");
+    FILE* fp = fopen(path, "re");
     if (fp == NULL) {
         return -1;
     }
@@ -285,10 +284,6 @@
 static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path,
                                   jint limitUid, jobjectArray limitIfacesObj, jint limitTag,
                                   jboolean useBpfStats) {
-    ScopedUtfChars path8(env, path);
-    if (path8.c_str() == NULL) {
-        return -1;
-    }
 
     std::vector<std::string> limitIfaces;
     if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
@@ -308,6 +303,11 @@
         if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
             return -1;
     } else {
+        ScopedUtfChars path8(env, path);
+        if (path8.c_str() == NULL) {
+            ALOGE("the qtaguid legacy path is invalid: %s", path8.c_str());
+            return -1;
+        }
         if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag,
                                          limitUid, path8.c_str()) < 0)
             return -1;
@@ -333,29 +333,27 @@
                 (void*) readNetworkStatsDev },
 };
 
-int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) {
-    int err = RegisterMethodsOrDie(env,
-            "com/android/internal/net/NetworkStatsFactory", gMethods,
+int register_android_server_net_NetworkStatsFactory(JNIEnv* env) {
+    int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods,
             NELEM(gMethods));
+    gStringClass = env->FindClass("java/lang/String");
+    gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass));
 
-    gStringClass = FindClassOrDie(env, "java/lang/String");
-    gStringClass = MakeGlobalRefOrDie(env, gStringClass);
-
-    jclass clazz = FindClassOrDie(env, "android/net/NetworkStats");
-    gNetworkStatsClassInfo.size = GetFieldIDOrDie(env, clazz, "size", "I");
-    gNetworkStatsClassInfo.capacity = GetFieldIDOrDie(env, clazz, "capacity", "I");
-    gNetworkStatsClassInfo.iface = GetFieldIDOrDie(env, clazz, "iface", "[Ljava/lang/String;");
-    gNetworkStatsClassInfo.uid = GetFieldIDOrDie(env, clazz, "uid", "[I");
-    gNetworkStatsClassInfo.set = GetFieldIDOrDie(env, clazz, "set", "[I");
-    gNetworkStatsClassInfo.tag = GetFieldIDOrDie(env, clazz, "tag", "[I");
-    gNetworkStatsClassInfo.metered = GetFieldIDOrDie(env, clazz, "metered", "[I");
-    gNetworkStatsClassInfo.roaming = GetFieldIDOrDie(env, clazz, "roaming", "[I");
-    gNetworkStatsClassInfo.defaultNetwork = GetFieldIDOrDie(env, clazz, "defaultNetwork", "[I");
-    gNetworkStatsClassInfo.rxBytes = GetFieldIDOrDie(env, clazz, "rxBytes", "[J");
-    gNetworkStatsClassInfo.rxPackets = GetFieldIDOrDie(env, clazz, "rxPackets", "[J");
-    gNetworkStatsClassInfo.txBytes = GetFieldIDOrDie(env, clazz, "txBytes", "[J");
-    gNetworkStatsClassInfo.txPackets = GetFieldIDOrDie(env, clazz, "txPackets", "[J");
-    gNetworkStatsClassInfo.operations = GetFieldIDOrDie(env, clazz, "operations", "[J");
+    jclass clazz = env->FindClass("android/net/NetworkStats");
+    gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
+    gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I");
+    gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
+    gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
+    gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
+    gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
+    gNetworkStatsClassInfo.metered = env->GetFieldID(clazz, "metered", "[I");
+    gNetworkStatsClassInfo.roaming = env->GetFieldID(clazz, "roaming", "[I");
+    gNetworkStatsClassInfo.defaultNetwork = env->GetFieldID(clazz, "defaultNetwork", "[I");
+    gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
+    gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
+    gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
+    gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
+    gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
 
     return err;
 }
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 3302dea..4d4a7b4 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -30,11 +30,10 @@
 #include <utils/Log.h>
 
 #include "android-base/unique_fd.h"
-#include "bpf/BpfNetworkStats.h"
 #include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
 
 using android::bpf::Stats;
-using android::bpf::hasBpfSupport;
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
 
diff --git a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java
index 9f4b754..7767a28 100644
--- a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java
@@ -27,9 +27,10 @@
 import android.net.NetworkUtils;
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
-import android.support.test.runner.AndroidJUnit4;
 import android.util.ArrayMap;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;