Merge changes from topic "vpn_data_accnt_revert"

* changes:
  Revert "Take all VPN underlying networks into account when migrating traffic for"
  Revert "NetworkStatsService: Fix getDetailedUidStats to take VPNs into account."
  Revert "Addressing comments for http://ag/7700679."
  Revert "Add one more test for VPN usage stats."
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index b27b1c0..9cf582b 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -18,7 +18,6 @@
 
 import static android.os.Process.CLAT_UID;
 
-import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -34,7 +33,6 @@
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.function.Predicate;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -995,33 +993,23 @@
         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);
-            if (predicate.test(entry)) {
-                if (nextOutputEntry != i) {
-                    setValues(nextOutputEntry, 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);
                 nextOutputEntry++;
             }
         }
+
         size = nextOutputEntry;
     }
 
@@ -1187,221 +1175,133 @@
     /**
      * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
      *
-     * <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 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 performs adjustments for one active VPN package and one VPN iface at a 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.
      *
      * @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 underlyingIface the primary underlying network iface used by the VPN application
+     * @return true if it successfully adjusts the accounting for VPN, false otherwise
      */
-    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();
+    public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
+        Entry tunIfaceTotal = new Entry();
+        Entry underlyingIfaceTotal = new Entry();
 
-        for (int i = 0; i < perInterfaceTotal.length; i++) {
-            perInterfaceTotal[i] = new Entry();
-        }
+        tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
 
-        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.
+        // 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.
         // Negative stats should be avoided.
-        final Entry[] moved =
-                addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal,
-                        perInterfaceTotal, underlyingIfacesTotal);
-        deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved);
+        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;
     }
 
     /**
      * Initializes the data used by the migrateTun() method.
      *
-     * <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}
+     * 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.
      */
-    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();
+    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
+            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+        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.tag != TAG_NONE) {
-                // TODO(b/123666283): Take all tags for tunUid into account.
-                continue;
+
+            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
+                    && Objects.equals(underlyingIface, recycle.iface)) {
+                underlyingIfaceTotal.add(recycle);
             }
 
-            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)) {
+            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
+                    && Objects.equals(tunIface, recycle.iface)) {
                 // Add up all tunIface traffic excluding traffic from the vpn app itself.
                 tunIfaceTotal.add(recycle);
             }
         }
     }
 
-    /**
-     * 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 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;
+    }
 
-        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];
-                if (underlyingIfacesTotal.rxBytes > 0) {
-                    tmpEntry.rxBytes =
-                            totalRxBytes
-                                    * perInterfaceTotal[j].rxBytes
-                                    / underlyingIfacesTotal.rxBytes;
+    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;
                 }
-                tmpEntry.rxPackets = 0;
-                if (underlyingIfacesTotal.rxPackets > 0) {
-                    tmpEntry.rxPackets =
-                            totalRxPackets
-                                    * perInterfaceTotal[j].rxPackets
-                                    / underlyingIfacesTotal.rxPackets;
+                if (tunIfaceTotal.rxPackets > 0) {
+                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+                } else {
+                    tmpEntry.rxPackets = 0;
                 }
-                tmpEntry.txBytes = 0;
-                if (underlyingIfacesTotal.txBytes > 0) {
-                    tmpEntry.txBytes =
-                            totalTxBytes
-                                    * perInterfaceTotal[j].txBytes
-                                    / underlyingIfacesTotal.txBytes;
+                if (tunIfaceTotal.txBytes > 0) {
+                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+                } else {
+                    tmpEntry.txBytes = 0;
                 }
-                tmpEntry.txPackets = 0;
-                if (underlyingIfacesTotal.txPackets > 0) {
-                    tmpEntry.txPackets =
-                            totalTxPackets
-                                    * perInterfaceTotal[j].txPackets
-                                    / underlyingIfacesTotal.txPackets;
+                if (tunIfaceTotal.txPackets > 0) {
+                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+                } else {
+                    tmpEntry.txPackets = 0;
                 }
-                tmpEntry.operations = 0;
-                if (underlyingIfacesTotal.operations > 0) {
+                if (tunIfaceTotal.operations > 0) {
                     tmpEntry.operations =
-                            totalOperations
-                                    * perInterfaceTotal[j].operations
-                                    / underlyingIfacesTotal.operations;
+                            pool.operations * operations[i] / tunIfaceTotal.operations;
+                } else {
+                    tmpEntry.operations = 0;
                 }
-                // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying
-                // interface. Add that data usage to this object.
+                tmpEntry.uid = uid[i];
+                tmpEntry.tag = tag[i];
+                tmpEntry.set = set[i];
+                tmpEntry.metered = metered[i];
+                tmpEntry.roaming = roaming[i];
+                tmpEntry.defaultNetwork = defaultNetwork[i];
                 combineValues(tmpEntry);
                 if (tag[i] == TAG_NONE) {
-                    // Add the migrated data to moved so it is deducted from the VPN app later.
-                    moved[j].add(tmpEntry);
+                    moved.add(tmpEntry);
                     // Add debug info
                     tmpEntry.set = SET_DBG_VPN_IN;
                     combineValues(tmpEntry);
@@ -1411,45 +1311,38 @@
         return 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]);
+    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);
 
-            // 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]);
-            }
+        // 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);
+        }
 
-            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]);
-            }
+        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+        if (idxVpnForeground != -1) {
+            tunSubtract(idxVpnForeground, this, moved);
         }
     }
 
-    private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) {
+    private static void tunSubtract(int i, NetworkStats left, Entry right) {
         long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
         left.rxBytes[i] -= rxBytes;
         right.rxBytes -= rxBytes;
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index e74af5e..b1a41287 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -19,8 +19,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Arrays;
-
 /**
  * A lightweight container used to carry information of the ongoing VPN.
  * Internal use only..
@@ -30,14 +28,14 @@
 public class VpnInfo implements Parcelable {
     public int ownerUid;
     public String vpnIface;
-    public String[] underlyingIfaces;
+    public String primaryUnderlyingIface;
 
     @Override
     public String toString() {
         return "VpnInfo{"
                 + "ownerUid=" + ownerUid
                 + ", vpnIface='" + vpnIface + '\''
-                + ", underlyingIfaces='" + Arrays.toString(underlyingIfaces) + '\''
+                + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
                 + '}';
     }
 
@@ -50,7 +48,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(ownerUid);
         dest.writeString(vpnIface);
-        dest.writeStringArray(underlyingIfaces);
+        dest.writeString(primaryUnderlyingIface);
     }
 
     public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
@@ -59,7 +57,7 @@
             VpnInfo info = new VpnInfo();
             info.ownerUid = source.readInt();
             info.vpnIface = source.readString();
-            info.underlyingIfaces = source.readStringArray();
+            info.primaryUnderlyingIface = source.readString();
             return info;
         }
 
diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
index 707d7b3..1b65603 100644
--- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
+++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
@@ -19,22 +19,13 @@
 import com.google.caliper.BeforeExperiment;
 import com.google.caliper.Param;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 public class NetworkStatsBenchmark {
-    private static final String[] UNDERLYING_IFACES = {"wlan0", "rmnet0"};
+    private static final String UNDERLYING_IFACE = "wlan0";
     private static final String TUN_IFACE = "tun0";
     private static final int TUN_UID = 999999999;
 
     @Param({"100", "1000"})
     private int mSize;
-    /**
-     * Should not be more than the length of {@link #UNDERLYING_IFACES}.
-     */
-    @Param({"1", "2"})
-    private int mNumUnderlyingIfaces;
     private NetworkStats mNetworkStats;
 
     @BeforeExperiment
@@ -42,10 +33,8 @@
         mNetworkStats = new NetworkStats(0, mSize + 2);
         int uid = 0;
         NetworkStats.Entry recycle = new NetworkStats.Entry();
-        final List<String> allIfaces = getAllIfacesForBenchmark(); // also contains TUN_IFACE.
-        final int totalIfaces = allIfaces.size();
         for (int i = 0; i < mSize; i++) {
-            recycle.iface = allIfaces.get(i % totalIfaces);
+            recycle.iface = (i < mSize / 2) ? TUN_IFACE : UNDERLYING_IFACE;
             recycle.uid = uid;
             recycle.set = i % 2;
             recycle.tag = NetworkStats.TAG_NONE;
@@ -59,39 +48,22 @@
                 uid++;
             }
         }
-
-        for (int i = 0; i < mNumUnderlyingIfaces; i++) {
-            recycle.iface = UNDERLYING_IFACES[i];
-            recycle.uid = TUN_UID;
-            recycle.set = NetworkStats.SET_FOREGROUND;
-            recycle.tag = NetworkStats.TAG_NONE;
-            recycle.rxBytes = 90000 * mSize;
-            recycle.rxPackets = 40 * mSize;
-            recycle.txBytes = 180000 * mSize;
-            recycle.txPackets = 1200 * mSize;
-            recycle.operations = 0;
-            mNetworkStats.addValues(recycle);
-        }
-    }
-
-    private String[] getVpnUnderlyingIfaces() {
-        return Arrays.copyOf(UNDERLYING_IFACES, mNumUnderlyingIfaces);
-    }
-
-    /**
-     * Same as {@link #getVpnUnderlyingIfaces}, but also contains {@link #TUN_IFACE}.
-     */
-    private List<String> getAllIfacesForBenchmark() {
-        List<String> ifaces = new ArrayList<>();
-        ifaces.add(TUN_IFACE);
-        ifaces.addAll(Arrays.asList(getVpnUnderlyingIfaces()));
-        return ifaces;
+        recycle.iface = UNDERLYING_IFACE;
+        recycle.uid = TUN_UID;
+        recycle.set = NetworkStats.SET_FOREGROUND;
+        recycle.tag = NetworkStats.TAG_NONE;
+        recycle.rxBytes = 90000 * mSize;
+        recycle.rxPackets = 40 * mSize;
+        recycle.txBytes = 180000 * mSize;
+        recycle.txPackets = 1200 * mSize;
+        recycle.operations = 0;
+        mNetworkStats.addValues(recycle);
     }
 
     public void timeMigrateTun(int reps) {
         for (int i = 0; i < reps; i++) {
             NetworkStats stats = mNetworkStats.clone();
-            stats.migrateTun(TUN_UID, TUN_IFACE, getVpnUnderlyingIfaces());
+            stats.migrateTun(TUN_UID, TUN_IFACE, UNDERLYING_IFACE);
         }
     }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 07c9dc1..55b31f2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4374,7 +4374,7 @@
 
     /**
      * @return VPN information for accounting, or null if we can't retrieve all required
-     *         information, e.g underlying ifaces.
+     *         information, e.g primary underlying iface.
      */
     @Nullable
     private VpnInfo createVpnInfo(Vpn vpn) {
@@ -4386,24 +4386,17 @@
         // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
         // the underlyingNetworks list.
         if (underlyingNetworks == null) {
-            NetworkAgentInfo defaultNai = getDefaultNetwork();
-            if (defaultNai != null) {
-                underlyingNetworks = new Network[] { defaultNai.network };
+            NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+            if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
+                info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
+            }
+        } else if (underlyingNetworks.length > 0) {
+            LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
+            if (linkProperties != null) {
+                info.primaryUnderlyingIface = linkProperties.getInterfaceName();
             }
         }
-        if (underlyingNetworks != null && underlyingNetworks.length > 0) {
-            List<String> interfaces = new ArrayList<>();
-            for (Network network : underlyingNetworks) {
-                LinkProperties lp = getLinkProperties(network);
-                if (lp != null && !TextUtils.isEmpty(lp.getInterfaceName())) {
-                    interfaces.add(lp.getInterfaceName());
-                }
-            }
-            if (!interfaces.isEmpty()) {
-                info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]);
-            }
-        }
-        return info.underlyingIfaces == null ? null : info;
+        return info.primaryUnderlyingIface == null ? null : info;
     }
 
     /**
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 2e64965..69efd02 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -263,11 +263,6 @@
         return stats;
     }
 
-    /**
-     * @deprecated Use NetworkStatsService#getDetailedUidStats which also accounts for
-     * VPN traffic
-     */
-    @Deprecated
     public NetworkStats readNetworkStatsDetail() throws IOException {
         return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index bdff500..a2e7e0c 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -41,10 +41,10 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 
-import com.google.android.collect.Sets;
-
 import libcore.io.IoUtils;
 
+import com.google.android.collect.Sets;
+
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -234,7 +234,7 @@
 
         if (vpnArray != null) {
             for (VpnInfo info : vpnArray) {
-                delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
+                delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
             }
         }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 484efd6..f9972dd 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -292,22 +292,6 @@
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
 
-    /**
-     * Snapshot containing most recent network stats for all UIDs across all interfaces and tags
-     * since boot.
-     *
-     * <p>Maintains migrated VPN stats which are result of performing TUN migration on {@link
-     * #mLastUidDetailSnapshot}.
-     */
-    @GuardedBy("mStatsLock")
-    private NetworkStats mTunAdjustedStats;
-    /**
-     * Used by {@link #mTunAdjustedStats} to migrate VPN traffic over delta between this snapshot
-     * and latest snapshot.
-     */
-    @GuardedBy("mStatsLock")
-    private NetworkStats mLastUidDetailSnapshot;
-
     /** Must be set in factory by calling #setHandler. */
     private Handler mHandler;
     private Handler.Callback mHandlerCallback;
@@ -821,39 +805,15 @@
     @Override
     public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
         try {
-            // Get the latest snapshot from NetworkStatsFactory.
-            // TODO: Querying for INTERFACES_ALL may incur performance penalty. Consider restricting
-            // this to limited set of ifaces.
-            NetworkStats uidDetailStats = getNetworkStatsUidDetail(INTERFACES_ALL);
-
-            // Migrate traffic from VPN UID over delta and update mTunAdjustedStats.
-            NetworkStats result;
-            synchronized (mStatsLock) {
-                migrateTunTraffic(uidDetailStats, mVpnInfos);
-                result = mTunAdjustedStats.clone();
-            }
-
-            // Apply filter based on ifacesToQuery.
             final String[] ifacesToQuery =
                     NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
-            result.filter(UID_ALL, ifacesToQuery, TAG_ALL);
-            return result;
+            return getNetworkStatsUidDetail(ifacesToQuery);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
         }
     }
 
-    @VisibleForTesting
-    NetworkStats getTunAdjustedStats() {
-        synchronized (mStatsLock) {
-            if (mTunAdjustedStats == null) {
-                return null;
-            }
-            return mTunAdjustedStats.clone();
-        }
-    }
-
     @Override
     public String[] getMobileIfaces() {
         return mMobileIfaces;
@@ -1328,34 +1288,6 @@
         // a race condition between the service handler thread and the observer's
         mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
                 new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
-
-        migrateTunTraffic(uidSnapshot, vpnArray);
-    }
-
-    /**
-     * Updates {@link #mTunAdjustedStats} with the delta containing traffic migrated off of VPNs.
-     */
-    @GuardedBy("mStatsLock")
-    private void migrateTunTraffic(NetworkStats uidDetailStats, VpnInfo[] vpnInfoArray) {
-        if (mTunAdjustedStats == null) {
-            // Either device booted or system server restarted, hence traffic cannot be migrated
-            // correctly without knowing the past state of VPN's underlying networks.
-            mTunAdjustedStats = uidDetailStats;
-            mLastUidDetailSnapshot = uidDetailStats;
-            return;
-        }
-        // Migrate delta traffic from VPN to other apps.
-        NetworkStats delta = uidDetailStats.subtract(mLastUidDetailSnapshot);
-        for (VpnInfo info : vpnInfoArray) {
-            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
-        }
-        // Filter out debug entries as that may lead to over counting.
-        delta.filterDebugEntries();
-        // Update #mTunAdjustedStats with migrated delta.
-        mTunAdjustedStats.combineAllValues(delta);
-        mTunAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
-        // Update last snapshot.
-        mLastUidDetailSnapshot = uidDetailStats;
     }
 
     /**
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index c16a0f4..b5b0384 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -569,7 +569,7 @@
             .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                     DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
 
-        delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
+        assertTrue(delta.toString(), delta.migrateTun(tunUid, tunIface, underlyingIface));
         assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
@@ -650,7 +650,7 @@
             .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                     DEFAULT_NETWORK_NO,  75500L, 37L, 130000L, 70L, 0L);
 
-        delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
+        assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
         assertEquals(9, delta.size());
 
         // tunIface entries should not be changed.
@@ -813,37 +813,6 @@
     }
 
     @Test
-    public void testFilterDebugEntries() {
-        NetworkStats.Entry entry1 = new NetworkStats.Entry(
-                "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
-
-        NetworkStats.Entry entry2 = new NetworkStats.Entry(
-                "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO,
-                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
-
-        NetworkStats.Entry entry3 = new NetworkStats.Entry(
-                "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
-
-        NetworkStats.Entry entry4 = new NetworkStats.Entry(
-                "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO,
-                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
-
-        NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3)
-                .addValues(entry4);
-
-        stats.filterDebugEntries();
-
-        assertEquals(2, stats.size());
-        assertEquals(entry1, stats.getValues(0, null));
-        assertEquals(entry3, stats.getValues(1, null));
-    }
-
-    @Test
     public void testApply464xlatAdjustments() {
         final String v4Iface = "v4-wlan0";
         final String baseIface = "wlan0";
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 31df9f3..f453f6e 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -56,11 +56,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -216,16 +216,11 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        assertNull(mService.getTunAdjustedStats());
         mService.systemReady();
-        // Verify that system ready fetches realtime stats and initializes tun adjusted stats.
-        verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
-        assertNotNull("failed to initialize TUN adjusted stats", mService.getTunAdjustedStats());
-        assertEquals(0, mService.getTunAdjustedStats().size());
-
         mSession = mService.openSession();
         assertNotNull("openSession() failed", mSession);
 
+
         // catch INetworkManagementEventObserver during systemReady()
         ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
               ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
@@ -738,13 +733,11 @@
 
         NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
 
-        // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations:
-        // 1) NetworkStatsService#systemReady from #setUp.
-        // 2) mService#forceUpdateIfaces in the test above.
-        // 3) Finally, mService#getDetailedUidStats.
-        verify(mNetManager, times(3)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
-        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
-        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
+        verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces ->
+                ifaces != null && ifaces.length == 2
+                        && ArrayUtils.contains(ifaces, TEST_IFACE)
+                        && ArrayUtils.contains(ifaces, stackedIface)));
+
         assertEquals(2, stats.size());
         assertEquals(uidStats, stats.getValues(0, null));
         assertEquals(tetheredStats1, stats.getValues(1, null));
@@ -930,70 +923,11 @@
     }
 
     @Test
-    public void vpnRewriteTrafficThroughItself() throws Exception {
-        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        expectDefaultSettings();
-        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        //
-        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
-        // over VPN.
-        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
-        // over VPN.
-        //
-        // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic
-        // (100 bytes).
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 5)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
-                // VPN rewrites all the packets read from TUN + 100 additional bytes of VPN's
-                // own traffic.
-                .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 0L, 0L, 1600L, 160L, 2L)
-                // VPN sent 1760 bytes over WiFi in foreground (SET_FOREGROUND) i.e. 1600
-                // bytes (160 packets) + 1 byte/packet overhead (=160 bytes).
-                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1760L, 176L, 1L)
-                // VPN received 3300 bytes over WiFi in background (SET_DEFAULT) i.e. 3000 bytes
-                // (300 packets) + 1 byte/packet encryption overhead (=300 bytes).
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        // Verify increased TUN usage by UID_VPN does not get attributed to other apps.
-        NetworkStats tunStats =
-                mService.getDetailedUidStats(new String[] {TUN_IFACE});
-        assertValues(
-                tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 1);
-        assertValues(
-                tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 1);
-        assertValues(
-                tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 2);
-
-        // Verify correct attribution over WiFi.
-        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 260L, 26L, 2);
-    }
-
-    @Test
     public void vpnWithOneUnderlyingIface() throws Exception {
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
         expectDefaultSettings();
         NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -1004,74 +938,23 @@
                 getActiveIface(networkStates));
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
-        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
-        // over VPN.
-        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
-        // over VPN.
-        // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi.
-        // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
-        // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN.
-        // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
-        // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN.
+        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+        // 500 bytes (50 packets) were sent/received by UID_BLUE over VPN.
+        // VPN sent/received 1650 bytes (150 packets) over WiFi.
+        // Of 1650 bytes over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes attributed to
+        // UID_BLUE, and 150 bytes attributed to UID_VPN for both rx/tx traffic.
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
-                // VPN received 3300 bytes over WiFi in background (SET_DEFAULT).
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L)
-                // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND).
-                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L));
+                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
+                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L)
+                .addValues(
+                    TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 1650L, 150L, 2L));
 
         forcePollAndWaitForIdle();
 
-        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 150L, 0L, 2);
-    }
-
-    @Test
-    public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
-        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        expectDefaultSettings();
-        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
-        // over VPN.
-        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
-        // over VPN.
-        // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun
-        // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500
-        // packets) from it. Including overhead that is 6600/5500 bytes.
-        // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi.
-        // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
-        // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN.
-        // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
-        // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
-                .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 5000L, 500L, 6000L, 600L, 1L)
-                // VPN received 8800 bytes over WiFi in background (SET_DEFAULT).
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 8800L, 800L, 0L, 0L, 1L)
-                // VPN sent 8250 bytes over WiFi in foreground (SET_FOREGROUND).
-                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 8250L, 750L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 5800L, 500L, 6750L, 600L, 2);
+        assertUidTotal(sTemplateWifi, UID_RED, 1000L, 100L, 1000L, 100L, 1);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 150L, 0L, 150L, 0L, 2);
     }
 
     @Test
@@ -1079,7 +962,7 @@
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
         expectDefaultSettings();
         NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -1110,136 +993,6 @@
     }
 
     @Test
-    public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is duplicating traffic across both WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN.
-        // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total).
-        // Of 8800 bytes over WiFi/Cell, expect:
-        // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE.
-        // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
-                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L)
-                .addValues(
-                    TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 1200L, 100L, 1200L, 100L, 2);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1200L, 100L, 1200L, 100L, 2);
-    }
-
-    @Test
-    public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over
-        // VPN.
-        // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell.
-        // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell.
-        // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx)
-        // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell.
-        //
-        // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic.
-        // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 500L, 50L, 1000L, 100L, 2L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 330L, 30L, 660L, 60L, 1L)
-              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 220L, 20L, 440L, 40L, 1L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 300L, 30L, 600L, 60L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 30L, 0L, 60L, 0L, 1);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 400L, 40L, 1);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 20L, 0L, 40L, 0L, 1);
-    }
-
-    @Test
-    public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception {
-        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
-        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
-        // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
-        expectDefaultSettings();
-        NetworkState[] networkStates =
-                new NetworkState[] {
-                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
-                };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectBandwidthControlCheck();
-
-        mService.forceUpdateIfaces(
-                new Network[] {WIFI_NETWORK, VPN_NETWORK},
-                vpnInfos,
-                networkStates,
-                getActiveIface(networkStates));
-        // create some traffic (assume 10 bytes of MTU for VPN interface:
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell.
-        // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both
-        // rx/tx.
-        // UID_VPN gets nothing attributed to it (avoiding negative stats).
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 600L, 60L, 600L, 60L, 0L)
-              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 200L, 20L, 200L, 20L, 0L));
-
-        forcePollAndWaitForIdle();
-
-        assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 0);
-        assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0);
-
-        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 200L, 20L, 0);
-        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 0L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
     public void vpnWithIncorrectUnderlyingIface() throws Exception {
         // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
         // but has declared only WiFi (TEST_IFACE) in its underlying network set.
@@ -1248,7 +1001,7 @@
                 new NetworkState[] {
                     buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
                 };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -1277,134 +1030,6 @@
     }
 
     @Test
-    public void recordSnapshot_migratesTunTrafficAndUpdatesTunAdjustedStats() throws Exception {
-        assertEquals(0, mService.getTunAdjustedStats().size());
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        // this should lead to NSS#recordSnapshotLocked
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-
-        // Verify TUN adjusted stats have traffic migrated correctly.
-        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
-        // bytes attributed to UID_VPN.
-        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void getDetailedUidStats_migratesTunTrafficAndUpdatesTunAdjustedStats()
-            throws Exception {
-        assertEquals(0, mService.getTunAdjustedStats().size());
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        mService.getDetailedUidStats(INTERFACES_ALL);
-
-        // Verify internally maintained TUN adjusted stats
-        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
-        // Verify stats for TEST_IFACE (WiFi):
-        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
-        // bytes attributed to UID_VPN.
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-        // Verify stats for TUN_IFACE; only UID_RED should have usage on it.
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
-
-        // lets assume that since last time, VPN received another 1100 bytes (same assumptions as
-        // before i.e. UID_RED downloaded another 1000 bytes).
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        // Note - NetworkStatsFactory returns counters that are monotonically increasing.
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 0L, 0L, 0L));
-
-        mService.getDetailedUidStats(INTERFACES_ALL);
-
-        tunAdjStats = mService.getTunAdjustedStats();
-        // verify TEST_IFACE stats:
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 200L, 0L, 0L, 0L, 0);
-        // verify TUN_IFACE stats:
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
-        assertValues(
-                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
-    public void getDetailedUidStats_returnsCorrectStatsWithVpnRunning() throws Exception {
-        // VPN using WiFi (TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        expectBandwidthControlCheck();
-        mService.forceUpdateIfaces(
-                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
-        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
-        // overhead per packet):
-        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
-        // VPN received 1100 bytes (100 packets) over WiFi.
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
-              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
-
-        // Query realtime stats for TEST_IFACE.
-        NetworkStats queriedStats =
-                mService.getDetailedUidStats(new String[] {TEST_IFACE});
-
-        assertEquals(HOUR_IN_MILLIS, queriedStats.getElapsedRealtime());
-        // verify that returned stats are only for TEST_IFACE and VPN traffic is migrated correctly.
-        assertEquals(new String[] {TEST_IFACE}, queriedStats.getUniqueIfaces());
-        assertValues(
-                queriedStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
-        assertValues(
-                queriedStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
-    }
-
-    @Test
     public void testRegisterUsageCallback() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -1752,11 +1377,11 @@
         return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null);
     }
 
-    private static VpnInfo createVpnInfo(String[] underlyingIfaces) {
+    private static VpnInfo createVpnInfo(String underlyingIface) {
         VpnInfo info = new VpnInfo();
         info.ownerUid = UID_VPN;
         info.vpnIface = TUN_IFACE;
-        info.underlyingIfaces = underlyingIfaces;
+        info.primaryUnderlyingIface = underlyingIface;
         return info;
     }