Merge "Fix TetheringIntegrationTests failure"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 0cfd884..27297c4 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -25,7 +25,7 @@
],
static_libs: [
"androidx.annotation_annotation",
- "netd_aidl_interface-unstable-java",
+ "netd_aidl_interface-V3-java",
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.config-V1.0-java",
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 82b17ac..1dac5b7 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,6 +34,7 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
+import android.net.TetherOffloadRuleParcel;
import android.net.TetheredClient;
import android.net.TetheringManager;
import android.net.TetheringRequestParcel;
@@ -41,7 +42,7 @@
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.DhcpServingParamsParcelExt;
-import android.net.dhcp.IDhcpLeaseCallbacks;
+import android.net.dhcp.IDhcpEventCallbacks;
import android.net.dhcp.IDhcpServer;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
@@ -280,6 +281,19 @@
return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
dstMac);
}
+
+ // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
+ // would be error-prone due to generated stable AIDL classes not having a copy constructor.
+ public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
+ final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
+ parcel.inputInterfaceIndex = upstreamIfindex;
+ parcel.outputInterfaceIndex = downstreamIfindex;
+ parcel.destination = address.getAddress();
+ parcel.prefixLength = 128;
+ parcel.srcL2Address = srcMac.toByteArray();
+ parcel.dstL2Address = dstMac.toByteArray();
+ return parcel;
+ }
}
private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
new LinkedHashMap<>();
@@ -449,7 +463,7 @@
}
}
- private class DhcpLeaseCallback extends IDhcpLeaseCallbacks.Stub {
+ private class DhcpLeaseCallback extends IDhcpEventCallbacks.Stub {
@Override
public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) {
final ArrayList<TetheredClient> leases = new ArrayList<>();
@@ -483,6 +497,11 @@
}
@Override
+ public void onNewPrefixRequest(IpPrefix currentPrefix) {
+ //TODO: add specific implementation.
+ }
+
+ @Override
public int getInterfaceVersion() {
return this.VERSION;
}
@@ -824,9 +843,7 @@
private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
try {
- mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex,
- rule.address.getAddress(), mInterfaceParams.macAddr.toByteArray(),
- rule.dstMac.toByteArray());
+ mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
mIpv6ForwardingRules.put(rule.address, rule);
} catch (RemoteException | ServiceSpecificException e) {
mLog.e("Could not add IPv6 downstream rule: ", e);
@@ -835,7 +852,7 @@
private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
try {
- mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress());
+ mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
if (removeFromMap) {
mIpv6ForwardingRules.remove(rule.address);
}
diff --git a/Tethering/tests/unit/AndroidManifest.xml b/Tethering/tests/unit/AndroidManifest.xml
index 530bc07..4ff1d37 100644
--- a/Tethering/tests/unit/AndroidManifest.xml
+++ b/Tethering/tests/unit/AndroidManifest.xml
@@ -20,7 +20,16 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <service
+ android:name="com.android.server.connectivity.tethering.MockTetheringService"
+ android:permission="android.permission.TETHER_PRIVILEGED"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.server.connectivity.tethering.TetheringService"/>
+ </intent-filter>
+ </service>
</application>
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.networkstack.tethering.tests.unit"
android:label="Tethering service tests">
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 3106e0e..fdfdae8 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -43,7 +43,6 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -66,6 +65,7 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
+import android.net.TetherOffloadRuleParcel;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServer;
import android.net.dhcp.IDhcpServerCallbacks;
@@ -85,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -92,6 +93,7 @@
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -514,6 +516,65 @@
mLooper.dispatchAll();
}
+ /**
+ * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable
+ * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as:
+ *
+ * private void checkFooCalled(StableParcelable p, ...) {
+ * ArgumentCaptor<FooParam> captor = ArgumentCaptor.forClass(FooParam.class);
+ * verify(mMock).foo(captor.capture());
+ * Foo foo = captor.getValue();
+ * assertFooMatchesExpectations(foo);
+ * }
+ *
+ * almost works, but not quite. This is because if the code under test calls foo() twice, the
+ * first call to checkFooCalled() matches both the calls, putting both calls into the captor,
+ * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito
+ * features such as never(), inOrder(), etc.
+ *
+ * This approach isn't great because if the match fails, the error message is unhelpful
+ * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does
+ * work.
+ *
+ * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the
+ * TooManyActualInvocations problem described above by forcing the caller of the custom assert
+ * method to specify all expected invocations in one call. This is useful when the stable
+ * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and
+ * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here
+ * because there is no such object.
+ */
+ private static class TetherOffloadRuleParcelMatcher implements
+ ArgumentMatcher<TetherOffloadRuleParcel> {
+ public final int upstreamIfindex;
+ public final InetAddress dst;
+ public final MacAddress dstMac;
+
+ TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) {
+ this.upstreamIfindex = upstreamIfindex;
+ this.dst = dst;
+ this.dstMac = dstMac;
+ }
+
+ public boolean matches(TetherOffloadRuleParcel parcel) {
+ return upstreamIfindex == parcel.inputInterfaceIndex
+ && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex)
+ && Arrays.equals(dst.getAddress(), parcel.destination)
+ && (128 == parcel.prefixLength)
+ && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address)
+ && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address);
+ }
+
+ public String toString() {
+ return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s",
+ upstreamIfindex, dst.getHostAddress(), dstMac);
+ }
+ }
+
+ private TetherOffloadRuleParcel matches(
+ int upstreamIfindex, InetAddress dst, MacAddress dstMac) {
+ return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac));
+ }
+
@Test
public void addRemoveipv6ForwardingRules() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
@@ -537,13 +598,11 @@
// Events on this interface are received and sent to netd.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
- verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
- eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
reset(mNetd);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
- eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
reset(mNetd);
// Link-local and multicast neighbors are ignored.
@@ -554,12 +613,12 @@
// A neighbor that is no longer valid causes the rule to be removed.
recvNewNeigh(myIfindex, neighA, NUD_FAILED, macA);
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
reset(mNetd);
// A neighbor that is deleted causes the rule to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
reset(mNetd);
// Upstream changes result in deleting and re-adding the rules.
@@ -571,22 +630,16 @@
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp);
- inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
- eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
- inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
- eq(neighA.getAddress()));
- inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
- eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
- inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
- eq(neighB.getAddress()));
+ inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA));
+ inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
+ inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB));
+ inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
reset(mNetd);
// When the upstream is lost, rules are removed.
dispatchTetherConnectionChanged(null, null);
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
- eq(neighA.getAddress()));
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
- eq(neighB.getAddress()));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB));
reset(mNetd);
// If the upstream is IPv4-only, no rules are added.
@@ -599,31 +652,27 @@
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
- eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
- verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(),
- eq(neighA.getAddress()), any(), any());
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
+ verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
// If upstream IPv6 connectivity is lost, rules are removed.
reset(mNetd);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null);
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
// When the interface goes down, rules are removed.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
- eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
- verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
- eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
reset(mNetd);
mIpServer.stop();
mLooper.dispatchAll();
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
- verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
reset(mNetd);
}
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java
new file mode 100644
index 0000000..9121243
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinatorTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
+import static android.net.ip.IpServer.STATE_TETHERED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.RouteInfo;
+import android.net.ip.IpServer;
+import android.net.util.SharedLog;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IPv6TetheringCoordinatorTest {
+ private static final String TEST_DNS_SERVER = "2001:4860:4860::8888";
+ private static final String TEST_INTERFACE = "test_rmnet0";
+ private static final String TEST_IPV6_ADDRESS = "2001:db8::1/64";
+ private static final String TEST_IPV4_ADDRESS = "192.168.100.1/24";
+
+ private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
+ private ArrayList<IpServer> mNotifyList;
+
+ @Mock private SharedLog mSharedLog;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
+ mNotifyList = new ArrayList<IpServer>();
+ mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mSharedLog);
+ }
+
+ private UpstreamNetworkState createDualStackUpstream(final int transportType) {
+ final Network network = mock(Network.class);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities.Builder().addTransportType(transportType).build();
+ final InetAddress dns = InetAddresses.parseNumericAddress(TEST_DNS_SERVER);
+ final LinkProperties linkProp = new LinkProperties();
+ linkProp.setInterfaceName(TEST_INTERFACE);
+ linkProp.addLinkAddress(new LinkAddress(TEST_IPV6_ADDRESS));
+ linkProp.addLinkAddress(new LinkAddress(TEST_IPV4_ADDRESS));
+ linkProp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, TEST_INTERFACE, RTN_UNICAST));
+ linkProp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, TEST_INTERFACE,
+ RTN_UNICAST));
+ linkProp.addDnsServer(dns);
+ return new UpstreamNetworkState(linkProp, netCap, network);
+ }
+
+ private void assertOnlyOneV6AddressAndNoV4(LinkProperties lp) {
+ assertEquals(lp.getInterfaceName(), TEST_INTERFACE);
+ assertFalse(lp.hasIpv4Address());
+ final List<LinkAddress> addresses = lp.getLinkAddresses();
+ assertEquals(addresses.size(), 1);
+ final LinkAddress v6Address = addresses.get(0);
+ assertEquals(v6Address, new LinkAddress(TEST_IPV6_ADDRESS));
+ }
+
+ @Test
+ public void testUpdateIpv6Upstream() throws Exception {
+ // 1. Add first IpServer.
+ final IpServer firstServer = mock(IpServer.class);
+ mNotifyList.add(firstServer);
+ mIPv6TetheringCoordinator.addActiveDownstream(firstServer, STATE_TETHERED);
+ verify(firstServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null);
+ verifyNoMoreInteractions(firstServer);
+
+ // 2. Add second IpServer and it would not have ipv6 tethering.
+ final IpServer secondServer = mock(IpServer.class);
+ mNotifyList.add(secondServer);
+ mIPv6TetheringCoordinator.addActiveDownstream(secondServer, STATE_LOCAL_ONLY);
+ verifyNoMoreInteractions(secondServer);
+ reset(firstServer, secondServer);
+
+ // 3. No upstream.
+ mIPv6TetheringCoordinator.updateUpstreamNetworkState(null);
+ verify(secondServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null);
+ reset(firstServer, secondServer);
+
+ // 4. Update ipv6 mobile upstream.
+ final UpstreamNetworkState mobileUpstream = createDualStackUpstream(TRANSPORT_CELLULAR);
+ final ArgumentCaptor<LinkProperties> lp = ArgumentCaptor.forClass(LinkProperties.class);
+ mIPv6TetheringCoordinator.updateUpstreamNetworkState(mobileUpstream);
+ verify(firstServer).sendMessage(eq(IpServer.CMD_IPV6_TETHER_UPDATE), eq(0), eq(0),
+ lp.capture());
+ final LinkProperties v6OnlyLink = lp.getValue();
+ assertOnlyOneV6AddressAndNoV4(v6OnlyLink);
+ verifyNoMoreInteractions(firstServer);
+ verifyNoMoreInteractions(secondServer);
+ reset(firstServer, secondServer);
+
+ // 5. Remove first IpServer.
+ mNotifyList.remove(firstServer);
+ mIPv6TetheringCoordinator.removeActiveDownstream(firstServer);
+ verify(firstServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null);
+ verify(secondServer).sendMessage(eq(IpServer.CMD_IPV6_TETHER_UPDATE), eq(0), eq(0),
+ lp.capture());
+ final LinkProperties localOnlyLink = lp.getValue();
+ assertNotNull(localOnlyLink);
+ assertNotEquals(localOnlyLink, v6OnlyLink);
+ reset(firstServer, secondServer);
+
+ // 6. Remove second IpServer.
+ mNotifyList.remove(secondServer);
+ mIPv6TetheringCoordinator.removeActiveDownstream(secondServer);
+ verifyNoMoreInteractions(firstServer);
+ verify(secondServer).sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null);
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
new file mode 100644
index 0000000..355ece9
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.connectivity.tethering;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Intent;
+import android.net.ITetheringConnector;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class MockTetheringService extends TetheringService {
+ private final Tethering mTethering = mock(Tethering.class);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new MockTetheringConnector(super.onBind(intent));
+ }
+
+ @Override
+ public Tethering makeTethering(TetheringDependencies deps) {
+ return mTethering;
+ }
+
+ public Tethering getTethering() {
+ return mTethering;
+ }
+
+ public class MockTetheringConnector extends Binder {
+ final IBinder mBase;
+ MockTetheringConnector(IBinder base) {
+ mBase = base;
+ }
+
+ public ITetheringConnector getTetheringConnector() {
+ return ITetheringConnector.Stub.asInterface(mBase);
+ }
+
+ public MockTetheringService getService() {
+ return MockTetheringService.this;
+ }
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
new file mode 100644
index 0000000..d9d3e73
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.IIntResultListener;
+import android.net.ITetheringConnector;
+import android.net.ITetheringEventCallback;
+import android.net.TetheringRequestParcel;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringServiceTest {
+ private static final String TEST_IFACE_NAME = "test_wlan0";
+ private static final String TEST_CALLER_PKG = "test_pkg";
+ @Mock private ITetheringEventCallback mITetheringEventCallback;
+ @Rule public ServiceTestRule mServiceTestRule;
+ private Tethering mTethering;
+ private Intent mMockServiceIntent;
+ private ITetheringConnector mTetheringConnector;
+
+ private class TestTetheringResult extends IIntResultListener.Stub {
+ private int mResult = -1; // Default value that does not match any result code.
+ @Override
+ public void onResult(final int resultCode) {
+ mResult = resultCode;
+ }
+
+ public void assertResult(final int expected) {
+ assertEquals(expected, mResult);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mServiceTestRule = new ServiceTestRule();
+ mMockServiceIntent = new Intent(
+ InstrumentationRegistry.getTargetContext(),
+ MockTetheringService.class);
+ final MockTetheringConnector mockConnector =
+ (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
+ mTetheringConnector = mockConnector.getTetheringConnector();
+ final MockTetheringService service = mockConnector.getService();
+ mTethering = service.getTethering();
+ verify(mTethering).startStateMachineUpdaters();
+ when(mTethering.hasTetherableConfiguration()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mServiceTestRule.unbindService();
+ }
+
+ @Test
+ public void testTether() throws Exception {
+ when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).tether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testUntether() throws Exception {
+ when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untether(TEST_IFACE_NAME);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testSetUsbTethering() throws Exception {
+ when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).setUsbTethering(true /* enable */);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testStartTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_WIFI;
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).startTethering(eq(request), eq(result));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).stopTethering(TETHERING_WIFI);
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testRequestLatestTetheringEntitlementResult() throws Exception {
+ final ResultReceiver result = new ResultReceiver(null);
+ mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
+ true /* showEntitlementUi */, TEST_CALLER_PKG);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
+ eq(result), eq(true) /* showEntitlementUi */);
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testRegisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testUnregisterTetheringEventCallback() throws Exception {
+ mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback,
+ TEST_CALLER_PKG);
+ verify(mTethering).unregisterTetheringEventCallback(
+ eq(mITetheringEventCallback));
+ verifyNoMoreInteractions(mTethering);
+ }
+
+ @Test
+ public void testStopAllTethering() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verify(mTethering).untetherAll();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void testIsTetheringSupported() throws Exception {
+ final TestTetheringResult result = new TestTetheringResult();
+ mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+ verify(mTethering).hasTetherableConfiguration();
+ verifyNoMoreInteractions(mTethering);
+ result.assertResult(TETHER_ERROR_NO_ERROR);
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a418c4a..38059fc 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -141,7 +141,9 @@
import com.android.testutils.MiscAssertsKt;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -440,6 +442,18 @@
return buildMobileUpstreamState(false, true, true);
}
+ // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
+ // after use.
+ @BeforeClass
+ public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);