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();