[automerger skipped] Merge "Use CompletableFuture instead of a latch." into rvc-dev am: 303e9bdddf -s ours

am skip reason: Change-Id Ia73ba8a121a3744a5e36795d2d2bff2f099c1394 with SHA-1 0a61e95319 is in history

Change-Id: I70e18ba92ea1ac35b678dbee6d8a44cfce493f79
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 3509801..cc095a0 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -571,9 +571,8 @@
             /**
              * Configure tethering with static IPv4 assignment.
              *
-             * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be
-             * started, but will only be able to offer the client address. The two addresses must
-             * be in the same prefix.
+             * A DHCP server will be started, but will only be able to offer the client address.
+             * The two addresses must be in the same prefix.
              *
              * @param localIPv4Address The preferred local IPv4 link address to use.
              * @param clientAddress The static client address.
@@ -584,10 +583,7 @@
                     @NonNull final LinkAddress clientAddress) {
                 Objects.requireNonNull(localIPv4Address);
                 Objects.requireNonNull(clientAddress);
-                if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength()
-                        || !localIPv4Address.isIpv4() || !clientAddress.isIpv4()
-                        || !new IpPrefix(localIPv4Address.toString()).equals(
-                        new IpPrefix(clientAddress.toString()))) {
+                if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) {
                     throw new IllegalArgumentException("Invalid server or client addresses");
                 }
 
@@ -657,6 +653,19 @@
         }
 
         /**
+         * Check whether the two addresses are ipv4 and in the same prefix.
+         * @hide
+         */
+        public static boolean checkStaticAddressConfiguration(
+                @NonNull final LinkAddress localIPv4Address,
+                @NonNull final LinkAddress clientAddress) {
+            return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength()
+                    && localIPv4Address.isIpv4() && clientAddress.isIpv4()
+                    && new IpPrefix(localIPv4Address.toString()).equals(
+                    new IpPrefix(clientAddress.toString()));
+        }
+
+        /**
          * Get a TetheringRequestParcel from the configuration
          * @hide
          */
diff --git a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index d6bc063..82a26be 100644
--- a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -18,10 +18,12 @@
 
 import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
-import android.annotation.NonNull;
 import android.net.LinkAddress;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.net.Inet4Address;
 import java.util.Collection;
 import java.util.Collections;
@@ -160,6 +162,17 @@
         return this;
     }
 
+    /**
+     * Set the client address to tell DHCP server only offer this address.
+     * The client's prefix length is the same as server's.
+     *
+     * <p>If not set, the default value is null.
+     */
+    public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) {
+        this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr);
+        return this;
+    }
+
     private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
         int[] res = new int[addrs.size()];
         int i = 0;
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 5b6fe91..1dac5b7 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -18,6 +18,7 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.util.NetworkConstants.FF;
@@ -511,17 +512,24 @@
         }
     }
 
-    private boolean startDhcp(Inet4Address addr, int prefixLen) {
+    private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) {
         if (mUsingLegacyDhcp) {
             return true;
         }
+
+        final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress();
+        final int prefixLen = serverLinkAddr.getPrefixLength();
+        final Inet4Address clientAddr = clientLinkAddr == null ? null :
+                (Inet4Address) clientLinkAddr.getAddress();
+
         final DhcpServingParamsParcel params;
         params = new DhcpServingParamsParcelExt()
                 .setDefaultRouters(addr)
                 .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                 .setDnsServers(addr)
-                .setServerAddr(new LinkAddress(addr, prefixLen))
-                .setMetered(true);
+                .setServerAddr(serverLinkAddr)
+                .setMetered(true)
+                .setSingleClientAddr(clientAddr);
         // TODO: also advertise link MTU
 
         mDhcpServerStartIndex++;
@@ -556,9 +564,10 @@
         }
     }
 
-    private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+    private boolean configureDhcp(boolean enable, final LinkAddress serverAddr,
+            final LinkAddress clientAddr) {
         if (enable) {
-            return startDhcp(addr, prefixLen);
+            return startDhcp(serverAddr, clientAddr);
         } else {
             stopDhcp();
             return true;
@@ -606,7 +615,7 @@
                 // code that calls into NetworkManagementService directly.
                 srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
                 mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
-                return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+                return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
             }
             mIpv4Address = new LinkAddress(srvAddr, prefixLen);
         } catch (IllegalArgumentException e) {
@@ -643,7 +652,7 @@
             mLinkProperties.removeRoute(route);
         }
 
-        return configureDhcp(enabled, srvAddr, prefixLen);
+        return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
     private String getRandomWifiIPv4Address() {
@@ -962,7 +971,14 @@
     }
 
     private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
-        if (request == null) return;
+        // Ignore static address configuration if they are invalid or null. In theory, static
+        // addresses should not be invalid here because TetheringManager do not allow caller to
+        // specify invalid static address configuration.
+        if (request == null || request.localIPv4Address == null
+                || request.staticClientAddress == null || !checkStaticAddressConfiguration(
+                request.localIPv4Address, request.staticClientAddress)) {
+            return;
+        }
 
         mStaticIpv4ServerAddr = request.localIPv4Address;
         mStaticIpv4ClientAddr = request.staticClientAddress;
diff --git a/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index bd60594..639cf65 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -20,6 +20,7 @@
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
 import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_INVALID;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
@@ -537,6 +538,7 @@
     private static boolean isValidDownstreamType(int type) {
         switch (type) {
             case TETHERING_BLUETOOTH:
+            case TETHERING_ETHERNET:
             case TETHERING_USB:
             case TETHERING_WIFI:
                 return true;
@@ -650,6 +652,11 @@
 
     private void handleRequestLatestTetheringEntitlementValue(int downstream,
             ResultReceiver receiver, boolean showEntitlementUi) {
+        if (!isValidDownstreamType(downstream)) {
+            receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null);
+            return;
+        }
+
         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
         if (!isTetherProvisioningRequired(config)) {
             receiver.send(TETHER_ERROR_NO_ERROR, null);
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 492ce3d..dbd68ef 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -57,6 +58,7 @@
 
 import java.io.FileDescriptor;
 import java.net.Inet4Address;
+import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
@@ -66,6 +68,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -110,7 +113,7 @@
     }
 
     private void cleanUp() throws Exception {
-        mTm.stopTethering(TetheringManager.TETHERING_ETHERNET);
+        mTm.stopTethering(TETHERING_ETHERNET);
         if (mTetheringEventCallback != null) {
             mTetheringEventCallback.awaitInterfaceUntethered();
             mTetheringEventCallback.unregister();
@@ -177,6 +180,49 @@
     }
 
     @Test
+    public void testStaticIpv4() throws Exception {
+        assumeFalse(mEm.isAvailable());
+
+        mEm.setIncludeTestInterfaces(true);
+
+        mTestIface = createTestInterface();
+
+        final String iface = mTetheredInterfaceRequester.getInterface();
+        assertEquals("TetheredInterfaceCallback for unexpected interface",
+                mTestIface.getInterfaceName(), iface);
+
+        assertInvalidStaticIpv4Request(iface, null, null);
+        assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
+        assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
+        assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
+        assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
+
+        final String localAddr = "192.0.2.3/28";
+        final String clientAddr = "192.0.2.2/28";
+        mTetheringEventCallback = enableEthernetTethering(iface,
+                requestWithStaticIpv4(localAddr, clientAddr));
+
+        mTetheringEventCallback.awaitInterfaceTethered();
+        assertInterfaceHasIpAddress(iface, clientAddr);
+
+        byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
+        byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
+
+        FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
+        mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+        DhcpResults dhcpResults = runDhcp(fd, client1);
+        assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
+
+        try {
+            runDhcp(fd, client2);
+            fail("Only one client should get an IP address");
+        } catch (TimeoutException expected) { }
+
+    }
+
+    @Test
     public void testPhysicalEthernet() throws Exception {
         assumeTrue(mEm.isAvailable());
 
@@ -271,7 +317,8 @@
         }
     }
 
-    private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
+    private MyTetheringEventCallback enableEthernetTethering(String iface,
+            TetheringRequest request) throws Exception {
         MyTetheringEventCallback callback = new MyTetheringEventCallback(mTm, iface);
         mTm.registerTetheringEventCallback(mHandler::post, callback);
 
@@ -282,34 +329,37 @@
             }
         };
         Log.d(TAG, "Starting Ethernet tethering");
-        mTm.startTethering(
-                new TetheringRequest.Builder(TetheringManager.TETHERING_ETHERNET).build(),
-                mHandler::post /* executor */,  startTetheringCallback);
+        mTm.startTethering(request, mHandler::post /* executor */,  startTetheringCallback);
         callback.awaitInterfaceTethered();
         return callback;
     }
 
+    private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
+        return enableEthernetTethering(iface,
+                new TetheringRequest.Builder(TETHERING_ETHERNET).build());
+    }
+
     private int getMTU(TestNetworkInterface iface) throws SocketException {
         NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName());
         assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif);
         return nif.getMTU();
     }
 
+    private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
+        final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
+        mHandler.post(() -> reader.start());
+        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
+        return reader;
+    }
+
     private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception {
         FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
-        mTapPacketReader = new TapPacketReader(mHandler, fd, mtu);
-        mHandler.post(() -> mTapPacketReader.start());
-        HandlerUtilsKt.waitForIdle(mHandler, TIMEOUT_MS);
-
+        mTapPacketReader = makePacketReader(fd, mtu);
         mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName());
         checkTetheredClientCallbacks(fd);
     }
 
-    private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception {
-        // Create a fake client.
-        byte[] clientMacAddr = new byte[6];
-        new Random().nextBytes(clientMacAddr);
-
+    private DhcpResults runDhcp(FileDescriptor fd, byte[] clientMacAddr) throws Exception {
         // We have to retransmit DHCP requests because IpServer declares itself to be ready before
         // its DhcpServer is actually started. TODO: fix this race and remove this loop.
         DhcpPacket offerPacket = null;
@@ -319,13 +369,25 @@
             offerPacket = getNextDhcpPacket();
             if (offerPacket instanceof DhcpOfferPacket) break;
         }
-        assertTrue("No DHCPOFFER received on interface within timeout",
-                offerPacket instanceof DhcpOfferPacket);
+        if (!(offerPacket instanceof DhcpOfferPacket)) {
+            throw new TimeoutException("No DHCPOFFER received on interface within timeout");
+        }
 
         sendDhcpRequest(fd, offerPacket, clientMacAddr);
         DhcpPacket ackPacket = getNextDhcpPacket();
-        assertTrue("No DHCPACK received on interface within timeout",
-                ackPacket instanceof DhcpAckPacket);
+        if (!(ackPacket instanceof DhcpAckPacket)) {
+            throw new TimeoutException("No DHCPACK received on interface within timeout");
+        }
+
+        return ackPacket.toDhcpResults();
+    }
+
+    private void checkTetheredClientCallbacks(FileDescriptor fd) throws Exception {
+        // Create a fake client.
+        byte[] clientMacAddr = new byte[6];
+        new Random().nextBytes(clientMacAddr);
+
+        DhcpResults dhcpResults = runDhcp(fd, clientMacAddr);
 
         final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
         assertEquals(1, clients.size());
@@ -333,7 +395,7 @@
 
         // Check the MAC address.
         assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
-        assertEquals(TetheringManager.TETHERING_ETHERNET, client.getTetheringType());
+        assertEquals(TETHERING_ETHERNET, client.getTetheringType());
 
         // Check the hostname.
         assertEquals(1, client.getAddresses().size());
@@ -341,7 +403,6 @@
         assertEquals(DHCP_HOSTNAME, info.getHostname());
 
         // Check the address is the one that was handed out in the DHCP ACK.
-        DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
 
         // Check that the lifetime is correct +/- 10s.
@@ -441,6 +502,34 @@
         assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
     }
 
+    private TetheringRequest requestWithStaticIpv4(String local, String client) {
+        LinkAddress localAddr = local == null ? null : new LinkAddress(local);
+        LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
+        return new TetheringRequest.Builder(TETHERING_ETHERNET)
+                .setStaticIpv4Addresses(localAddr, clientAddr).build();
+    }
+
+    private void assertInvalidStaticIpv4Request(String iface, String local, String client)
+            throws Exception {
+        try {
+            enableEthernetTethering(iface, requestWithStaticIpv4(local, client));
+            fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
+        } catch (IllegalArgumentException | NullPointerException expected) { }
+    }
+
+    private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
+        LinkAddress expectedAddr = new LinkAddress(expected);
+        NetworkInterface nif = NetworkInterface.getByName(iface);
+        for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
+            final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
+            if (expectedAddr.equals(addr)) {
+                return;
+            }
+        }
+        fail("Expected " + iface + " to have IP address " + expected + ", found "
+                + nif.getInterfaceAddresses());
+    }
+
     private TestNetworkInterface createTestInterface() throws Exception {
         TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
         TestNetworkInterface iface = tnm.createTapInterface();
diff --git a/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e8add98..f8eb147 100644
--- a/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -42,7 +42,9 @@
 @SmallTest
 public class DhcpServingParamsParcelExtTest {
     private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123");
+    private static final Inet4Address TEST_CLIENT_ADDRESS = inet4Addr("192.168.0.42");
     private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b;
+    private static final int TEST_CLIENT_ADDRESS_PARCELED = 0xc0a8002a;
     private static final int TEST_PREFIX_LENGTH = 17;
     private static final int TEST_LEASE_TIME_SECS = 120;
     private static final int TEST_MTU = 1000;
@@ -105,6 +107,12 @@
         assertFalse(mParcel.metered);
     }
 
+    @Test
+    public void testSetClientAddr() {
+        mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS);
+        assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr);
+    }
+
     private static Inet4Address inet4Addr(String addr) {
         return (Inet4Address) parseNumericAddress(addr);
     }
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 0a7850b..b3a30ab 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -17,8 +17,10 @@
 package com.android.server.connectivity.tethering;
 
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
@@ -353,6 +355,20 @@
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
+        // 8. Test get value for invalid downstream type.
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        receiver = new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
+                mCallbacklatch.countDown();
+            }
+        };
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
+        mLooper.dispatchAll();
+        callbackTimeoutHelper(mCallbacklatch);
+        assertEquals(0, mEnMgr.uiProvisionCount);
+        mEnMgr.reset();
     }
 
     void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {
@@ -471,6 +487,22 @@
         mLooper.dispatchAll();
         assertEquals(0, mEnMgr.uiProvisionCount);
         assertEquals(3, mEnMgr.silentProvisionCount);
+        assertFalse(mEnMgr.isCellularUpstreamPermitted());
+        mEnMgr.reset();
+        // 7. start ui provisioning, upstream is mobile, downstream is ethernet
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        mEnMgr.startProvisioningIfNeeded(TETHERING_ETHERNET, true);
+        mLooper.dispatchAll();
+        assertEquals(1, mEnMgr.uiProvisionCount);
+        assertEquals(0, mEnMgr.silentProvisionCount);
+        assertTrue(mEnMgr.isCellularUpstreamPermitted());
+        mEnMgr.reset();
+        // 8. downstream is invalid
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI_P2P, true);
+        mLooper.dispatchAll();
+        assertEquals(0, mEnMgr.uiProvisionCount);
+        assertEquals(0, mEnMgr.silentProvisionCount);
         mEnMgr.reset();
     }
 
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/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a59c6fd..7532cdd 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
@@ -38,6 +38,7 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -1654,10 +1655,13 @@
     }
 
     @Test
-    public void testRequestStaticServerIp() throws Exception {
-        final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
-        final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
-        final String serverAddr = "192.168.20.1";
+    public void testRequestStaticIp() throws Exception {
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24");
+        final String serverAddr = "192.168.0.123";
+        final int clientAddrParceled = 0xc0a8002a;
+        final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
+                ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
                   serverLinkAddr, clientLinkAddr), null);
         mLooper.dispatchAll();
@@ -1666,8 +1670,12 @@
         sendUsbBroadcast(true, true, true, TETHERING_USB);
         mLooper.dispatchAll();
         verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
-
-        // TODO: test static client address.
+        verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
+                any());
+        final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue();
+        assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress());
+        assertEquals(24, params.serverAddrPrefixLength);
+        assertEquals(clientAddrParceled, params.clientAddr);
     }
 
     // TODO: Test that a request for hotspot mode doesn't interfere with an