Merge "Ability to specify which all applications fall under enterprise slice."
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 4c64e98..72c83fa 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -63,6 +63,7 @@
     ],
     canned_fs_config: "canned_fs_config",
     bpfs: [
+        "clatd.o_mainline",
         "netd.o_mainline",
         "offload.o",
         "test.o",
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b4228da..2bb19db 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -614,10 +614,8 @@
             return false;
         }
 
-        if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
-            // BT configures the interface elsewhere: only start DHCP.
-            // TODO: make all tethering types behave the same way, and delete the bluetooth
-            // code that calls into NetworkManagementService directly.
+        if (shouldNotConfigureBluetoothInterface()) {
+            // Interface was already configured elsewhere, only start DHCP.
             return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
         }
 
@@ -651,12 +649,15 @@
         return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
+    private boolean shouldNotConfigureBluetoothInterface() {
+        // Before T, bluetooth tethering configures the interface elsewhere.
+        return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
+    }
+
     private LinkAddress requestIpv4Address(final boolean useLastAddress) {
         if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
 
-        if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
-            return new LinkAddress(BLUETOOTH_IFACE_ADDR);
-        }
+        if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
 
         return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
     }
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 55c24d3..db9a64f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -134,7 +134,12 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.tethering.util.InterfaceSet;
 import com.android.networkstack.tethering.util.PrefixUtils;
 import com.android.networkstack.tethering.util.TetheringUtils;
@@ -265,8 +270,11 @@
     private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
 
     private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
+    private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
     private String mConfiguredEthernetIface;
+    private String mConfiguredBluetoothIface;
     private EthernetCallback mEthernetCallback;
+    private TetheredInterfaceCallbackShim mBluetoothCallback;
     private SettingsObserver mSettingsObserver;
     private BluetoothPan mBluetoothPan;
     private PanServiceListener mBluetoothPanListener;
@@ -533,14 +541,16 @@
         }
     }
 
-    // This method needs to exist because TETHERING_BLUETOOTH and TETHERING_WIGIG can't use
-    // enableIpServing.
+    // This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
+    // can't use enableIpServing.
     private void processInterfaceStateChange(final String iface, boolean enabled) {
         // Do not listen to USB interface state changes or USB interface add/removes. USB tethering
         // is driven only by USB_ACTION broadcasts.
         final int type = ifaceNameToType(iface);
         if (type == TETHERING_USB || type == TETHERING_NCM) return;
 
+        if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
+
         if (enabled) {
             ensureIpServerStarted(iface);
         } else {
@@ -769,6 +779,9 @@
                             TETHERING_BLUETOOTH);
                 }
                 mPendingPanRequests.clear();
+                mBluetoothIfaceRequest = null;
+                mBluetoothCallback = null;
+                maybeDisableBluetoothIpServing();
             });
         }
 
@@ -779,7 +792,11 @@
 
     private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
             final boolean enable, final IIntResultListener listener) {
-        bluetoothPan.setBluetoothTethering(enable);
+        if (SdkLevel.isAtLeastT()) {
+            changeBluetoothTetheringSettings(bluetoothPan, enable);
+        } else {
+            changeBluetoothTetheringSettingsPreT(bluetoothPan, enable);
+        }
 
         // Enabling bluetooth tethering settings can silently fail. Send internal error if the
         // result is not expected.
@@ -788,6 +805,68 @@
         sendTetherResult(listener, result, TETHERING_BLUETOOTH);
     }
 
+    private void changeBluetoothTetheringSettingsPreT(@NonNull final BluetoothPan bluetoothPan,
+            final boolean enable) {
+        bluetoothPan.setBluetoothTethering(enable);
+    }
+
+    private void changeBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
+            final boolean enable) {
+        final BluetoothPanShim panShim = mDeps.getBluetoothPanShim(bluetoothPan);
+        if (enable) {
+            if (mBluetoothIfaceRequest != null) {
+                Log.d(TAG, "Bluetooth tethering settings already enabled");
+                return;
+            }
+
+            mBluetoothCallback = new BluetoothCallback();
+            try {
+                mBluetoothIfaceRequest = panShim.requestTetheredInterface(mExecutor,
+                        mBluetoothCallback);
+            } catch (UnsupportedApiLevelException e) {
+                Log.wtf(TAG, "Use unsupported API, " + e);
+            }
+        } else {
+            if (mBluetoothIfaceRequest == null) {
+                Log.d(TAG, "Bluetooth tethering settings already disabled");
+                return;
+            }
+
+            mBluetoothIfaceRequest.release();
+            mBluetoothIfaceRequest = null;
+            mBluetoothCallback = null;
+            // If bluetooth request is released, tethering won't able to receive
+            // onUnavailable callback, explicitly disable bluetooth IpServer manually.
+            maybeDisableBluetoothIpServing();
+        }
+    }
+
+    // BluetoothCallback is only called after T. Before T, PanService would call tether/untether to
+    // notify bluetooth interface status.
+    private class BluetoothCallback implements TetheredInterfaceCallbackShim {
+        @Override
+        public void onAvailable(String iface) {
+            if (this != mBluetoothCallback) return;
+
+            enableIpServing(TETHERING_BLUETOOTH, iface, getRequestedState(TETHERING_BLUETOOTH));
+            mConfiguredBluetoothIface = iface;
+        }
+
+        @Override
+        public void onUnavailable() {
+            if (this != mBluetoothCallback) return;
+
+            maybeDisableBluetoothIpServing();
+        }
+    }
+
+    private void maybeDisableBluetoothIpServing() {
+        if (mConfiguredBluetoothIface == null) return;
+
+        ensureIpServerStopped(mConfiguredBluetoothIface);
+        mConfiguredBluetoothIface = null;
+    }
+
     private int setEthernetTethering(final boolean enable) {
         final EthernetManager em = (EthernetManager) mContext.getSystemService(
                 Context.ETHERNET_SERVICE);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 7df9475..c1a747e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -18,6 +18,7 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
 import android.content.Context;
 import android.net.INetd;
 import android.net.ip.IpServer;
@@ -31,6 +32,8 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.apishim.BluetoothPanShimImpl;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
 
 import java.util.ArrayList;
 
@@ -158,4 +161,13 @@
             TetheringConfiguration cfg) {
         return new PrivateAddressCoordinator(ctx, cfg);
     }
+
+    /**
+     * Get BluetoothPanShim object to enable/disable bluetooth tethering.
+     *
+     * TODO: use BluetoothPan directly when mainline module is built with API 32.
+     */
+    public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+        return BluetoothPanShimImpl.newInstance(pan);
+    }
 }
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 2f2cde0..267c376 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,6 +16,7 @@
 
 package android.net.ip;
 
+import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
@@ -33,6 +34,7 @@
 import static android.net.ip.IpServer.STATE_UNAVAILABLE;
 import static android.system.OsConstants.ETH_P_IPV6;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
 import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
@@ -400,11 +402,16 @@
     }
 
     @Test
-    public void canBeTethered() throws Exception {
+    public void canBeTetheredAsBluetooth() throws Exception {
         initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNetd);
+        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        if (isAtLeastT()) {
+            inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+            inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                    IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+        }
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         // One for ipv4 route, one for ipv6 link local route.
@@ -426,7 +433,13 @@
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
-        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+        // happen after T. Before T, the interface configuration control in bluetooth side.
+        if (isAtLeastT()) {
+            inOrder.verify(mNetd).interfaceSetCfg(
+                    argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
+        }
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
         inOrder.verify(mAddressCoordinator).releaseDownstream(any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
@@ -443,7 +456,7 @@
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
         inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
-                  IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+                IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -587,7 +600,8 @@
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
-        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         inOrder.verify(mAddressCoordinator).releaseDownstream(any());
         inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
         inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
@@ -683,7 +697,11 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        assertDhcpStarted(mBluetoothPrefix);
+        if (isAtLeastT()) {
+            assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
+        } else {
+            assertDhcpStarted(mBluetoothPrefix);
+        }
     }
 
     @Test
@@ -1371,7 +1389,6 @@
         for (String flag : flags) {
             if (flag.equals(match)) return true;
         }
-        fail("Missing flag: " + match);
         return false;
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 40d133a..e4dbc7d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -62,6 +62,7 @@
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
@@ -81,6 +82,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Matchers.anyInt;
@@ -182,6 +185,10 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.CollectionUtils;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
 import com.android.testutils.MiscAsserts;
 
@@ -261,6 +268,8 @@
     @Mock private PackageManager mPackageManager;
     @Mock private BluetoothAdapter mBluetoothAdapter;
     @Mock private BluetoothPan mBluetoothPan;
+    @Mock private BluetoothPanShim mBluetoothPanShim;
+    @Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -285,6 +294,7 @@
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
     private SoftApCallback mSoftApCallback;
     private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
 
     private TestConnectivityManager mCm;
 
@@ -483,13 +493,23 @@
             return false;
         }
 
-
         @Override
         public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
                 TetheringConfiguration cfg) {
             mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
             return mPrivateAddressCoordinator;
         }
+
+        @Override
+        public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+            try {
+                when(mBluetoothPanShim.requestTetheredInterface(
+                        any(), any())).thenReturn(mTetheredInterfaceRequestShim);
+            } catch (UnsupportedApiLevelException e) {
+                fail("BluetoothPan#requestTetheredInterface is not supported");
+            }
+            return mBluetoothPanShim;
+        }
     }
 
     private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@@ -2557,6 +2577,44 @@
 
     @Test
     public void testBluetoothTethering() throws Exception {
+        // Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+        assumeTrue(isAtLeastT());
+
+        final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
+        mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+        mLooper.dispatchAll();
+        verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
+        result.assertHasResult();
+
+        mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+        mLooper.dispatchAll();
+        verifyNetdCommandForBtSetup();
+
+        // If PAN disconnect, tethering should also be stopped.
+        mTetheredInterfaceCallbackShim.onUnavailable();
+        mLooper.dispatchAll();
+        verifyNetdCommandForBtTearDown();
+
+        // Tethering could restart if PAN reconnect.
+        mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+        mLooper.dispatchAll();
+        verifyNetdCommandForBtSetup();
+
+        // Pretend that bluetooth tethering was disabled.
+        mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
+        mTethering.stopTethering(TETHERING_BLUETOOTH);
+        mLooper.dispatchAll();
+        verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
+
+        verifyNetdCommandForBtTearDown();
+    }
+
+    @Test
+    public void testBluetoothTetheringBeforeT() throws Exception {
+        // Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+        assumeFalse(isAtLeastT());
+
         final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
         mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
@@ -2610,12 +2668,17 @@
         mTethering.interfaceAdded(TEST_BT_IFNAME);
         mLooper.dispatchAll();
 
-        mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
-        mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
-        final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
-        mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
-        mLooper.dispatchAll();
-        tetherResult.assertHasResult();
+        if (isAtLeastT()) {
+            mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+            mLooper.dispatchAll();
+        } else {
+            mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
+            mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
+            final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+            mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
+            mLooper.dispatchAll();
+            tetherResult.assertHasResult();
+        }
 
         verifyNetdCommandForBtSetup();
 
@@ -2632,6 +2695,10 @@
     }
 
     private void verifyNetdCommandForBtSetup() throws Exception {
+        if (isAtLeastT()) {
+            verify(mNetd).interfaceSetCfg(argThat(cfg -> TEST_BT_IFNAME.equals(cfg.ifName)
+                    && assertContainsFlag(cfg.flags, INetd.IF_STATE_UP)));
+        }
         verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
         verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
         verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
@@ -2644,19 +2711,30 @@
         reset(mNetd);
     }
 
+    private boolean assertContainsFlag(String[] flags, String match) {
+        for (String flag : flags) {
+            if (flag.equals(match)) return true;
+        }
+        return false;
+    }
+
     private void verifyNetdCommandForBtTearDown() throws Exception {
         verify(mNetd).tetherApplyDnsInterfaces();
         verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
         verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
-        verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+        // happen after T. Before T, the interface configuration control in bluetooth side.
+        verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+                any(InterfaceConfigurationParcel.class));
         verify(mNetd).tetherStop();
         verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
+        reset(mNetd);
     }
 
     // If bindToPanService is true, this function would return ServiceListener which could notify
     // PanService is connected or disconnected.
     private ServiceListener verifySetBluetoothTethering(final boolean enable,
-            final boolean bindToPanService) {
+            final boolean bindToPanService) throws Exception {
         ServiceListener listener = null;
         verify(mBluetoothAdapter).isEnabled();
         if (bindToPanService) {
@@ -2671,7 +2749,19 @@
             verify(mBluetoothAdapter, never()).getProfileProxy(eq(mServiceContext), any(),
                     anyInt());
         }
-        verify(mBluetoothPan).setBluetoothTethering(enable);
+
+        if (isAtLeastT()) {
+            if (enable) {
+                final ArgumentCaptor<TetheredInterfaceCallbackShim> callbackCaptor =
+                        ArgumentCaptor.forClass(TetheredInterfaceCallbackShim.class);
+                verify(mBluetoothPanShim).requestTetheredInterface(any(), callbackCaptor.capture());
+                mTetheredInterfaceCallbackShim = callbackCaptor.getValue();
+            } else {
+                verify(mTetheredInterfaceRequestShim).release();
+            }
+        } else {
+            verify(mBluetoothPan).setBluetoothTethering(enable);
+        }
         verify(mBluetoothPan).isTetheringOn();
         verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
         reset(mBluetoothAdapter, mBluetoothPan);
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 4fa288b..bb9f5ead6 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -73,6 +73,19 @@
 }
 
 bpf {
+    name: "clatd.o_mainline",
+    srcs: ["clatd.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        "frameworks/libs/net/common/netd/libnetdutils/include",
+    ],
+    sub_dir: "net_shared",
+}
+
+bpf {
     name: "netd.o_mainline",
     srcs: ["netd.c"],
     cflags: [
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
new file mode 100644
index 0000000..dc646c3
--- /dev/null
+++ b/bpf_progs/clatd.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/swab.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000  // Flag: "Don't Fragment"
+
+DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
+
+static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+    const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+    void* data = (void*)(long)skb->data;
+    const void* data_end = (void*)(long)skb->data_end;
+    const struct ethhdr* const eth = is_ethernet ? data : NULL;  // used iff is_ethernet
+    const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+    // Require ethernet dst mac address to be our unicast address.
+    if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+    // Must be meta-ethernet IPv6 frame
+    if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
+
+    // Must have (ethernet and) ipv6 header
+    if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_PIPE;
+
+    // Ethertype - if present - must be IPv6
+    if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_PIPE;
+
+    // IP version must be 6
+    if (ip6->version != 6) return TC_ACT_PIPE;
+
+    // Maximum IPv6 payload length that can be translated to IPv4
+    if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
+
+    switch (ip6->nexthdr) {
+        case IPPROTO_TCP:  // For TCP & UDP the checksum neutrality of the chosen IPv6
+        case IPPROTO_UDP:  // address means there is no need to update their checksums.
+        case IPPROTO_GRE:  // We do not need to bother looking at GRE/ESP headers,
+        case IPPROTO_ESP:  // since there is never a checksum to update.
+            break;
+
+        default:  // do not know how to handle anything else
+            return TC_ACT_PIPE;
+    }
+
+    ClatIngress6Key k = {
+            .iif = skb->ifindex,
+            .pfx96.in6_u.u6_addr32 =
+                    {
+                            ip6->saddr.in6_u.u6_addr32[0],
+                            ip6->saddr.in6_u.u6_addr32[1],
+                            ip6->saddr.in6_u.u6_addr32[2],
+                    },
+            .local6 = ip6->daddr,
+    };
+
+    ClatIngress6Value* v = bpf_clat_ingress6_map_lookup_elem(&k);
+
+    if (!v) return TC_ACT_PIPE;
+
+    struct ethhdr eth2;  // used iff is_ethernet
+    if (is_ethernet) {
+        eth2 = *eth;                     // Copy over the ethernet header (src/dst mac)
+        eth2.h_proto = htons(ETH_P_IP);  // But replace the ethertype
+    }
+
+    struct iphdr ip = {
+            .version = 4,                                                      // u4
+            .ihl = sizeof(struct iphdr) / sizeof(__u32),                       // u4
+            .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4),             // u8
+            .tot_len = htons(ntohs(ip6->payload_len) + sizeof(struct iphdr)),  // u16
+            .id = 0,                                                           // u16
+            .frag_off = htons(IP_DF),                                          // u16
+            .ttl = ip6->hop_limit,                                             // u8
+            .protocol = ip6->nexthdr,                                          // u8
+            .check = 0,                                                        // u16
+            .saddr = ip6->saddr.in6_u.u6_addr32[3],                            // u32
+            .daddr = v->local4.s_addr,                                         // u32
+    };
+
+    // Calculate the IPv4 one's complement checksum of the IPv4 header.
+    __wsum sum4 = 0;
+    for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+        sum4 += ((__u16*)&ip)[i];
+    }
+    // Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse u32 into range 1 .. 0x1FFFE
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse any potential carry into u16
+    ip.check = (__u16)~sum4;                // sum4 cannot be zero, so this is never 0xFFFF
+
+    // Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header.
+    __wsum sum6 = 0;
+    // We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits)
+    for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
+        sum6 += ~((__u16*)ip6)[i];  // note the bitwise negation
+    }
+
+    // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+    // of the ipv6 address chosen by netd's ClatdController.
+
+    // Packet mutations begin - point of no return, but if this first modification fails
+    // the packet is probably still pristine, so let clatd handle it.
+    if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_PIPE;
+
+    // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+    //
+    // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+    // thus we need to subtract out the ipv6 header's sum, and add in the ipv4 header's sum.
+    // However, by construction of ip.check above the checksum of an ipv4 header is zero.
+    // Thus we only need to subtract the ipv6 header's sum, which is the same as adding
+    // in the sum of the bitwise negation of the ipv6 header.
+    //
+    // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+    // (-ENOTSUPP) if it isn't.  So we just ignore the return code.
+    //
+    // if (skb->ip_summed == CHECKSUM_COMPLETE)
+    //   return (skb->csum = csum_add(skb->csum, csum));
+    // else
+    //   return -ENOTSUPP;
+    bpf_csum_update(skb, sum6);
+
+    // bpf_skb_change_proto() invalidates all pointers - reload them.
+    data = (void*)(long)skb->data;
+    data_end = (void*)(long)skb->data_end;
+
+    // I cannot think of any valid way for this error condition to trigger, however I do
+    // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+    if (data + l2_header_size + sizeof(struct iphdr) > data_end) return TC_ACT_SHOT;
+
+    if (is_ethernet) {
+        struct ethhdr* new_eth = data;
+
+        // Copy over the updated ethernet header
+        *new_eth = eth2;
+
+        // Copy over the new ipv4 header.
+        *(struct iphdr*)(new_eth + 1) = ip;
+    } else {
+        // Copy over the new ipv4 header without an ethernet header.
+        *(struct iphdr*)data = ip;
+    }
+
+    // Redirect, possibly back to same interface, so tcpdump sees packet twice.
+    if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
+
+    // Just let it through, tcpdump will not see IPv4 packet.
+    return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether)
+(struct __sk_buff* skb) {
+    return nat64(skb, true);
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip)
+(struct __sk_buff* skb) {
+    return nat64(skb, false);
+}
+
+DEFINE_BPF_MAP_GRW(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16, AID_SYSTEM)
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_ether)
+(struct __sk_buff* skb) {
+    return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_rawip)
+(struct __sk_buff* skb) {
+    void* data = (void*)(long)skb->data;
+    const void* data_end = (void*)(long)skb->data_end;
+    const struct iphdr* const ip4 = data;
+
+    // Must be meta-ethernet IPv4 frame
+    if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+    // Must have ipv4 header
+    if (data + sizeof(*ip4) > data_end) return TC_ACT_PIPE;
+
+    // IP version must be 4
+    if (ip4->version != 4) return TC_ACT_PIPE;
+
+    // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+    if (ip4->ihl != 5) return TC_ACT_PIPE;
+
+    // Calculate the IPv4 one's complement checksum of the IPv4 header.
+    __wsum sum4 = 0;
+    for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
+        sum4 += ((__u16*)ip4)[i];
+    }
+    // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse u32 into range 1 .. 0x1FFFE
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse any potential carry into u16
+    // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+    if (sum4 != 0xFFFF) return TC_ACT_PIPE;
+
+    // Minimum IPv4 total length is the size of the header
+    if (ntohs(ip4->tot_len) < sizeof(*ip4)) return TC_ACT_PIPE;
+
+    // We are incapable of dealing with IPv4 fragments
+    if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE;
+
+    switch (ip4->protocol) {
+        case IPPROTO_TCP:  // For TCP & UDP the checksum neutrality of the chosen IPv6
+        case IPPROTO_GRE:  // address means there is no need to update their checksums.
+        case IPPROTO_ESP:  // We do not need to bother looking at GRE/ESP headers,
+            break;         // since there is never a checksum to update.
+
+        case IPPROTO_UDP:  // See above comment, but must also have UDP header...
+            if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE;
+            const struct udphdr* uh = (const struct udphdr*)(ip4 + 1);
+            // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the
+            // checksum.  Otherwise the network or more likely the NAT64 gateway might
+            // drop the packet because in most cases IPv6/UDP packets with a zero checksum
+            // are invalid. See RFC 6935.  TODO: calculate checksum via bpf_csum_diff()
+            if (!uh->check) return TC_ACT_PIPE;
+            break;
+
+        default:  // do not know how to handle anything else
+            return TC_ACT_PIPE;
+    }
+
+    ClatEgress4Key k = {
+            .iif = skb->ifindex,
+            .local4.s_addr = ip4->saddr,
+    };
+
+    ClatEgress4Value* v = bpf_clat_egress4_map_lookup_elem(&k);
+
+    if (!v) return TC_ACT_PIPE;
+
+    // Translating without redirecting doesn't make sense.
+    if (!v->oif) return TC_ACT_PIPE;
+
+    // This implementation is currently limited to rawip.
+    if (v->oifIsEthernet) return TC_ACT_PIPE;
+
+    struct ipv6hdr ip6 = {
+            .version = 6,                                    // __u8:4
+            .priority = ip4->tos >> 4,                       // __u8:4
+            .flow_lbl = {(ip4->tos & 0xF) << 4, 0, 0},       // __u8[3]
+            .payload_len = htons(ntohs(ip4->tot_len) - 20),  // __be16
+            .nexthdr = ip4->protocol,                        // __u8
+            .hop_limit = ip4->ttl,                           // __u8
+            .saddr = v->local6,                              // struct in6_addr
+            .daddr = v->pfx96,                               // struct in6_addr
+    };
+    ip6.daddr.in6_u.u6_addr32[3] = ip4->daddr;
+
+    // Calculate the IPv6 16-bit one's complement checksum of the IPv6 header.
+    __wsum sum6 = 0;
+    // We'll end up with a non-zero sum due to ip6.version == 6
+    for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
+        sum6 += ((__u16*)&ip6)[i];
+    }
+
+    // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+    // of the ipv6 address chosen by netd's ClatdController.
+
+    // Packet mutations begin - point of no return, but if this first modification fails
+    // the packet is probably still pristine, so let clatd handle it.
+    if (bpf_skb_change_proto(skb, htons(ETH_P_IPV6), 0)) return TC_ACT_PIPE;
+
+    // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+    //
+    // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+    // thus we need to subtract out the ipv4 header's sum, and add in the ipv6 header's sum.
+    // However, we've already verified the ipv4 checksum is correct and thus 0.
+    // Thus we only need to add the ipv6 header's sum.
+    //
+    // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+    // (-ENOTSUPP) if it isn't.  So we just ignore the return code (see above for more details).
+    bpf_csum_update(skb, sum6);
+
+    // bpf_skb_change_proto() invalidates all pointers - reload them.
+    data = (void*)(long)skb->data;
+    data_end = (void*)(long)skb->data_end;
+
+    // I cannot think of any valid way for this error condition to trigger, however I do
+    // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+    if (data + sizeof(ip6) > data_end) return TC_ACT_SHOT;
+
+    // Copy over the new ipv6 header without an ethernet header.
+    *(struct ipv6hdr*)data = ip6;
+
+    // Redirect to non v4-* interface.  Tcpdump only sees packet after this redirect.
+    return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index b1ffc92..6ad8b06 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -74,12 +74,12 @@
     }
 
     @Test
-    fun testGetMetered() {
+    fun testIsMetered() {
         // Verify network is metered.
         val netIdent1 = NetworkIdentity.buildNetworkIdentity(mockContext,
                 buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI),
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
-        assertTrue(netIdent1.getMetered())
+        assertTrue(netIdent1.isMetered())
 
         // Verify network is not metered because it has NET_CAPABILITY_NOT_METERED capability.
         val capsNotMetered = NetworkCapabilities.Builder().apply {
@@ -88,7 +88,7 @@
         val netIdent2 = NetworkIdentity.buildNetworkIdentity(mockContext,
                 buildMobileNetworkStateSnapshot(capsNotMetered, TEST_IMSI),
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
-        assertFalse(netIdent2.getMetered())
+        assertFalse(netIdent2.isMetered())
 
         // Verify network is not metered because it has NET_CAPABILITY_TEMPORARILY_NOT_METERED
         // capability .
@@ -98,6 +98,34 @@
         val netIdent3 = NetworkIdentity.buildNetworkIdentity(mockContext,
                 buildMobileNetworkStateSnapshot(capsTempNotMetered, TEST_IMSI),
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
-        assertFalse(netIdent3.getMetered())
+        assertFalse(netIdent3.isMetered())
+    }
+
+    @Test
+    fun testBuilder() {
+        val oemPrivateRoamingNotMeteredCap = NetworkCapabilities().apply {
+            addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)
+            addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+        }
+        val identFromSnapshot = NetworkIdentity.Builder().setNetworkStateSnapshot(
+                buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI))
+                .setDefaultNetwork(true)
+                .setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+                .build()
+        val identFromLegacyBuild = NetworkIdentity.buildNetworkIdentity(mockContext,
+                buildMobileNetworkStateSnapshot(oemPrivateRoamingNotMeteredCap, TEST_IMSI),
+                true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+        val identFromConstructor = NetworkIdentity(TYPE_MOBILE,
+                TelephonyManager.NETWORK_TYPE_UMTS,
+                TEST_IMSI,
+                null /* wifiNetworkKey */,
+                true /* roaming */,
+                false /* metered */,
+                true /* defaultNetwork */,
+                NetworkTemplate.OEM_MANAGED_PRIVATE)
+        assertEquals(identFromLegacyBuild, identFromSnapshot)
+        assertEquals(identFromConstructor, identFromSnapshot)
+
+        // TODO: Add test cases for wifiNetworkKey and ratType.
     }
 }
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 3765bf0..5e1699a 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -73,10 +73,8 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -119,7 +117,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.net.module.util.LocationPermissionChecker;
 import com.android.server.net.NetworkStatsService.AlertObserver;
@@ -996,7 +993,7 @@
     }
 
     @Test
-    public void testDetailedUidStats() throws Exception {
+    public void testUidStatsForTransport() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();
         NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
@@ -1022,7 +1019,7 @@
                 .insertEntry(entry3));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
-        NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
+        NetworkStats stats = mService.getUidStatsForTransport(NetworkCapabilities.TRANSPORT_WIFI);
 
         assertEquals(3, stats.size());
         entry1.operations = 1;
@@ -1033,68 +1030,6 @@
     }
 
     @Test
-    public void testDetailedUidStats_Filtered() throws Exception {
-        // pretend that network comes online
-        expectDefaultSettings();
-
-        final String stackedIface = "stacked-test0";
-        final LinkProperties stackedProp = new LinkProperties();
-        stackedProp.setInterfaceName(stackedIface);
-        final NetworkStateSnapshot wifiState = buildWifiState();
-        wifiState.getLinkProperties().addStackedLink(stackedProp);
-        NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState};
-
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
-
-        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
-                new UnderlyingNetworkInfo[0]);
-
-        NetworkStats.Entry uidStats = new NetworkStats.Entry(
-                TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L);
-        // Stacked on matching interface
-        NetworkStats.Entry tetheredStats1 = new NetworkStats.Entry(
-                stackedIface, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L);
-        TetherStatsParcel tetherStatsParcel1 =
-                buildTetherStatsParcel(stackedIface, 1024L, 8L, 512L, 4L, 0);
-        // Different interface
-        TetherStatsParcel tetherStatsParcel2 =
-                buildTetherStatsParcel("otherif", 1024L, 8L, 512L, 4L, 0);
-
-        final String[] ifaceFilter = new String[] { TEST_IFACE };
-        final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE };
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
-        when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter)))
-                .thenReturn(augmentedIfaceFilter);
-        when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
-                .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
-                        .insertEntry(uidStats));
-        final TetherStatsParcel[] tetherStatsParcels =  {tetherStatsParcel1, tetherStatsParcel2};
-        when(mNetd.tetherGetStats()).thenReturn(tetherStatsParcels);
-
-        NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
-
-        // mStatsFactory#readNetworkStatsDetail() has the following invocations:
-        // 1) NetworkStatsService#systemReady from #setUp.
-        // 2) mService#notifyNetworkStatus in the test above.
-        //
-        // Additionally, we should have one call from the above call to mService#getDetailedUidStats
-        // with the augmented ifaceFilter.
-        verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
-        verify(mStatsFactory, times(1)).readNetworkStatsDetail(
-                eq(UID_ALL),
-                eq(augmentedIfaceFilter),
-                eq(TAG_ALL));
-        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
-        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
-        assertEquals(2, stats.size());
-        assertEquals(uidStats, stats.getValues(0, null));
-        assertEquals(tetheredStats1, stats.getValues(1, null));
-    }
-
-    @Test
     public void testForegroundBackground() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();