Merge "Make the IP subnet persistent till reboot"
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 336124d..52d59fc 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -616,7 +616,7 @@
         if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
 
         if (enabled) {
-            mIpv4Address = requestIpv4Address();
+            mIpv4Address = requestIpv4Address(true /* useLastAddress */);
         }
 
         if (mIpv4Address == null) {
@@ -661,14 +661,14 @@
         return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
-    private LinkAddress requestIpv4Address() {
+    private LinkAddress requestIpv4Address(final boolean useLastAddress) {
         if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
 
         if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
             return new LinkAddress(BLUETOOTH_IFACE_ADDR);
         }
 
-        return mPrivateAddressCoordinator.requestDownstreamAddress(this);
+        return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
     }
 
     private boolean startIPv6() {
@@ -957,7 +957,7 @@
         }
 
         final LinkAddress deprecatedLinkAddress = mIpv4Address;
-        mIpv4Address = requestIpv4Address();
+        mIpv4Address = requestIpv4Address(false);
         if (mIpv4Address == null) {
             mLog.e("Fail to request a new downstream prefix");
             return;
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index b285849..6276c4e 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -16,7 +16,9 @@
 package com.android.networkstack.tethering;
 
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.util.PrefixUtils.asIpPrefix;
 
 import static java.util.Arrays.asList;
 
@@ -26,9 +28,9 @@
 import android.net.LinkAddress;
 import android.net.Network;
 import android.net.ip.IpServer;
-import android.net.util.PrefixUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.SparseArray;
 
 import androidx.annotation.Nullable;
 
@@ -58,9 +60,6 @@
 
     private static final int MAX_UBYTE = 256;
     private static final int BYTE_MASK = 0xff;
-    // reserved for bluetooth tethering.
-    private static final int BLUETOOTH_RESERVED = 44;
-    private static final int WIFI_P2P_RESERVED = 49;
     private static final byte DEFAULT_ID = (byte) 42;
 
     // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
@@ -75,9 +74,12 @@
     // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
     private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
     private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
+    private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
     private final IpPrefix mTetheringPrefix;
     private final ConnectivityManager mConnectivityMgr;
     private final TetheringConfiguration mConfig;
+    // keyed by downstream type(TetheringManager.TETHERING_*).
+    private final SparseArray<LinkAddress> mCachedAddresses;
 
     public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
         mDownstreams = new ArraySet<>();
@@ -86,6 +88,10 @@
         mConnectivityMgr = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mConfig = config;
+        mCachedAddresses = new SparseArray<>();
+        // Reserved static addresses for bluetooth and wifi p2p.
+        mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
+        mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
     }
 
     /**
@@ -94,7 +100,8 @@
      * UpstreamNetworkState must have an already populated LinkProperties.
      */
     public void updateUpstreamPrefix(final UpstreamNetworkState ns) {
-        // Do not support VPN as upstream
+        // Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null,
+        // but just checking to be sure.
         if (ns.networkCapabilities != null && ns.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
             removeUpstreamPrefix(ns.network);
             return;
@@ -116,7 +123,7 @@
         for (LinkAddress address : linkAddresses) {
             if (!address.isIpv4()) continue;
 
-            list.add(PrefixUtils.asIpPrefix(address));
+            list.add(asIpPrefix(address));
         }
 
         return list;
@@ -155,21 +162,23 @@
         mUpstreamPrefixMap.removeAll(toBeRemoved);
     }
 
-    private boolean isReservedSubnet(final int subnet) {
-        return subnet == BLUETOOTH_RESERVED || subnet == WIFI_P2P_RESERVED;
-    }
-
     /**
      * Pick a random available address and mark its prefix as in use for the provided IpServer,
      * returns null if there is no available address.
      */
     @Nullable
-    public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
+    public LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
         if (mConfig.shouldEnableWifiP2pDedicatedIp()
                 && ipServer.interfaceType() == TETHERING_WIFI_P2P) {
             return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
         }
 
+        final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
+        if (useLastAddress && cachedAddress != null
+                && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
+            return cachedAddress;
+        }
+
         // Address would be 192.168.[subAddress]/24.
         final byte[] bytes = mTetheringPrefix.getRawAddress();
         final int subAddress = getRandomSubAddr();
@@ -177,9 +186,8 @@
         bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
         for (int i = 0; i < MAX_UBYTE; i++) {
             final int newSubNet = (subNet + i) & BYTE_MASK;
-            if (isReservedSubnet(newSubNet)) continue;
-
             bytes[2] = (byte) newSubNet;
+
             final InetAddress addr;
             try {
                 addr = InetAddress.getByAddress(bytes);
@@ -187,20 +195,23 @@
                 throw new IllegalStateException("Invalid address, shouldn't happen.", e);
             }
 
-            final IpPrefix prefix = new IpPrefix(addr, PREFIX_LENGTH);
-            // Check whether this prefix is in use.
-            if (isDownstreamPrefixInUse(prefix)) continue;
-            // Check whether this prefix is conflict with any current upstream network.
-            if (isConflictWithUpstream(prefix)) continue;
+            if (isConflict(new IpPrefix(addr, PREFIX_LENGTH))) continue;
 
             mDownstreams.add(ipServer);
-            return new LinkAddress(addr, PREFIX_LENGTH);
+            final LinkAddress newAddress = new LinkAddress(addr, PREFIX_LENGTH);
+            mCachedAddresses.put(ipServer.interfaceType(), newAddress);
+            return newAddress;
         }
 
         // No available address.
         return null;
     }
 
+    private boolean isConflict(final IpPrefix prefix) {
+        // Check whether this prefix is in use or conflict with any current upstream network.
+        return isDownstreamPrefixInUse(prefix) || isConflictWithUpstream(prefix);
+    }
+
     /** Get random sub address value. Return value is in 0 ~ 0xffff. */
     @VisibleForTesting
     public int getRandomSubAddr() {
@@ -244,13 +255,24 @@
         return prefix1.contains(prefix2.getAddress());
     }
 
-    private boolean isDownstreamPrefixInUse(final IpPrefix source) {
+    // InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last
+    // downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p).
+    private boolean isDownstreamPrefixInUse(final IpPrefix prefix) {
         // This class always generates downstream prefixes with the same prefix length, so
         // prefixes cannot be contained in each other. They can only be equal to each other.
-        for (IpServer downstream : mDownstreams) {
-            final IpPrefix prefix = getDownstreamPrefix(downstream);
-            if (source.equals(prefix)) return true;
+        for (int i = 0; i < mCachedAddresses.size(); i++) {
+            if (prefix.equals(asIpPrefix(mCachedAddresses.valueAt(i)))) return true;
         }
+
+        // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
+        // in mCachedAddresses.
+        for (IpServer downstream : mDownstreams) {
+            final IpPrefix target = getDownstreamPrefix(downstream);
+            if (target == null) continue;
+
+            if (isConflictPrefix(prefix, target)) return true;
+        }
+
         return false;
     }
 
@@ -258,7 +280,7 @@
         final LinkAddress address = downstream.getAddress();
         if (address == null) return null;
 
-        return PrefixUtils.asIpPrefix(address);
+        return asIpPrefix(address);
     }
 
     void dump(final IndentingPrintWriter pw) {
@@ -268,11 +290,19 @@
             pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i));
         }
         pw.decreaseIndent();
+
         pw.println("mDownstreams:");
         pw.increaseIndent();
         for (IpServer ipServer : mDownstreams) {
             pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress());
         }
         pw.decreaseIndent();
+
+        pw.println("mCachedAddresses:");
+        pw.increaseIndent();
+        for (int i = 0; i < mCachedAddresses.size(); i++) {
+            pw.println(mCachedAddresses.keyAt(i) + " - " + mCachedAddresses.valueAt(i));
+        }
+        pw.decreaseIndent();
     }
 }
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 1a976ad..2eb7589 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -47,6 +47,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -230,7 +231,8 @@
             dispatchTetherConnectionChanged(upstreamIface, lp, 0);
         }
         reset(mNetd, mCallback, mAddressCoordinator);
-        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+                mTestAddress);
     }
 
     private void setUpDhcpServer() throws Exception {
@@ -250,7 +252,8 @@
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
-        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+                mTestAddress);
         when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
 
         mBpfCoordinator = spy(new BpfCoordinator(
@@ -372,7 +375,7 @@
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+        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);
@@ -393,7 +396,7 @@
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                   IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -607,7 +610,7 @@
         final ArgumentCaptor<LinkProperties> lpCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
         InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         // One for ipv4 route, one for ipv6 link local route.
         inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -620,11 +623,12 @@
         // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
         // onNewPrefixRequest callback.
         final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
-        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(newAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+                newAddress);
         eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
         mLooper.dispatchAll();
 
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(false));
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
         verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 7b6632c..191eb6e 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -23,6 +23,7 @@
 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.util.PrefixUtils.asIpPrefix;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -40,7 +41,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.ip.IpServer;
-import android.net.util.PrefixUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,7 +65,7 @@
     @Mock private TetheringConfiguration mConfig;
 
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
-    private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+    private final LinkAddress mBluetoothAddress = new LinkAddress("192.168.44.1/24");
     private final LinkAddress mLegacyWifiP2pAddress = new LinkAddress("192.168.49.1/24");
     private final Network mWifiNetwork = new Network(1);
     private final Network mMobileNetwork = new Network(2);
@@ -91,98 +91,119 @@
     }
 
     @Test
-    public void testDownstreamPrefixRequest() throws Exception {
-        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
-        assertNotEquals(hotspotPrefix, mBluetoothPrefix);
+    public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
+        final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
+        final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, false /* useLastAddress */);
+        final IpPrefix hotspotPrefix = asIpPrefix(address);
+        assertNotEquals(hotspotPrefix, bluetoothPrefix);
+        when(mHotspotIpServer.getAddress()).thenReturn(address);
 
-        address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix testDupRequest = PrefixUtils.asIpPrefix(address);
+        final LinkAddress newAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, false /* useLastAddress */);
+        final IpPrefix testDupRequest = asIpPrefix(newAddress);
         assertNotEquals(hotspotPrefix, testDupRequest);
-        assertNotEquals(mBluetoothPrefix, testDupRequest);
+        assertNotEquals(bluetoothPrefix, testDupRequest);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
-        address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer);
-        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
-        assertNotEquals(usbPrefix, mBluetoothPrefix);
+        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, false /* useLastAddress */);
+        final IpPrefix usbPrefix = asIpPrefix(usbAddress);
+        assertNotEquals(usbPrefix, bluetoothPrefix);
         assertNotEquals(usbPrefix, hotspotPrefix);
         mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
     }
 
     @Test
-    public void testRequestDownstreamAddress() throws Exception {
-        LinkAddress expectedAddress = new LinkAddress("192.168.43.42/24");
-        int fakeSubAddr = 0x2b00;
+    public void testSanitizedAddress() throws Exception {
+        int fakeSubAddr = 0x2b00; // 43.0.
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
         LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        assertEquals(actualAddress, expectedAddress);
+                mHotspotIpServer, false /* useLastAddress */);
+        assertEquals(new LinkAddress("192.168.43.42/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
-        fakeSubAddr = 0x2b01;
+        fakeSubAddr = 0x2d01; // 45.1.
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        assertEquals(actualAddress, expectedAddress);
+                mHotspotIpServer, false /* useLastAddress */);
+        assertEquals(new LinkAddress("192.168.45.42/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
-        fakeSubAddr = 0x2bff;
+        fakeSubAddr = 0x2eff; // 46.255.
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        assertEquals(actualAddress, expectedAddress);
+                mHotspotIpServer, false /* useLastAddress */);
+        assertEquals(new LinkAddress("192.168.46.42/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
-        expectedAddress = new LinkAddress("192.168.43.5/24");
-        fakeSubAddr = 0x2b05;
+        fakeSubAddr = 0x2f05; // 47.5.
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        assertEquals(actualAddress, expectedAddress);
-        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
-    }
-
-    private int getBluetoothSubAddress() {
-        final byte[] rawAddress = mBluetoothPrefix.getRawAddress();
-        int bluetoothSubNet = rawAddress[2] & 0xff;
-        return (bluetoothSubNet << 8) + 0x5;
-    }
-
-    @Test
-    public void testReserveBluetoothPrefix() throws Exception {
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(getBluetoothSubAddress());
-        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
-        assertNotEquals("Should not get reserved prefix: ", mBluetoothPrefix, hotspotPrefix);
+                mHotspotIpServer, false /* useLastAddress */);
+        assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
     }
 
     @Test
-    public void testNoConflictDownstreamPrefix() throws Exception {
+    public void testReservedPrefix() throws Exception {
+        // - Test bluetooth prefix is reserved.
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+                getSubAddress(mBluetoothAddress.getAddress().getAddress()));
+        final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, false /* useLastAddress */);
+        final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
+        assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+        // - Test previous enabled hotspot prefix(cached prefix) is reserved.
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+                getSubAddress(hotspotAddress.getAddress().getAddress()));
+        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, false /* useLastAddress */);
+        final IpPrefix usbPrefix = asIpPrefix(usbAddress);
+        assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
+        assertNotEquals(hotspotPrefix, usbPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+
+        // - Test wifi p2p prefix is reserved.
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+                getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
+        final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mEthernetIpServer, false /* useLastAddress */);
+        final IpPrefix etherPrefix = asIpPrefix(etherAddress);
+        assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
+        assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
+        assertNotEquals(hotspotPrefix, etherPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mEthernetIpServer);
+    }
+
+    @Test
+    public void testRequestLastDownstreamAddress() throws Exception {
         final int fakeHotspotSubAddr = 0x2b05;
         final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
-        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
-        assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
-        when(mHotspotIpServer.getAddress()).thenReturn(address);
+        final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true /* useLastAddress */);
+        assertEquals("Wrong wifi prefix: ", predefinedPrefix, asIpPrefix(hotspotAddress));
+        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddress);
 
-        address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer);
-        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
-        assertNotEquals(predefinedPrefix, usbPrefix);
+        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, true /* useLastAddress */);
+        assertNotEquals(predefinedPrefix, asIpPrefix(usbAddress));
 
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
         mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
-        address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer);
-        final IpPrefix allowUseFreePrefix = PrefixUtils.asIpPrefix(address);
-        assertEquals("Fail to reselect available prefix: ", predefinedPrefix, allowUseFreePrefix);
+
+        final int newFakeSubAddr = 0x3c05;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+
+        final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true /* useLastAddress */);
+        assertEquals(hotspotAddress, newHotspotAddress);
+        final LinkAddress newUsbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, true /* useLastAddress */);
+        assertEquals(usbAddress, newUsbAddress);
     }
 
     private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
@@ -215,12 +236,13 @@
         when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
         // - Enable hotspot with prefix 192.168.43.0/24
         final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(hotspotAddr);
+                mHotspotIpServer, true /* useLastAddress */);
+        final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
         assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
         when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr);
-        // - test mobile network with null NetworkCapabilities. Ideally this should not happen,
-        // just make sure no crash in this case.
+        // - test mobile network with null NetworkCapabilities. Ideally this should not happen
+        // because NetworkCapabilities update should always happen before LinkProperties update
+        // and the UpstreamNetworkState update, just make sure no crash in this case.
         final UpstreamNetworkState noCapUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("10.0.0.8/24"), null, null);
         mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
@@ -269,24 +291,24 @@
         // - Restart hotspot again and its prefix is different previous.
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
         final LinkAddress hotspotAddr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix2 = PrefixUtils.asIpPrefix(hotspotAddr2);
+                mHotspotIpServer, true /* useLastAddress */);
+        final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
         assertNotEquals(hotspotPrefix, hotspotPrefix2);
         when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr2);
         mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Usb tethering can be enabled and its prefix is different with conflict one.
         final LinkAddress usbAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer);
-        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(usbAddr);
+                mUsbIpServer, true /* useLastAddress */);
+        final IpPrefix usbPrefix = asIpPrefix(usbAddr);
         assertNotEquals(predefinedPrefix, usbPrefix);
         assertNotEquals(hotspotPrefix2, usbPrefix);
         when(mUsbIpServer.getAddress()).thenReturn(usbAddr);
         // - Disable wifi upstream, then wifi's prefix can be selected again.
         mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
         final LinkAddress ethAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mEthernetIpServer);
-        final IpPrefix ethPrefix = PrefixUtils.asIpPrefix(ethAddr);
+                mEthernetIpServer, true /* useLastAddress */);
+        final IpPrefix ethPrefix = asIpPrefix(ethAddr);
         assertEquals(predefinedPrefix, ethPrefix);
     }
 
@@ -299,9 +321,9 @@
 
     private void assertReseveredWifiP2pPrefix() throws Exception {
         LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer);
-        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
-        final IpPrefix legacyWifiP2pPrefix = PrefixUtils.asIpPrefix(mLegacyWifiP2pAddress);
+                mHotspotIpServer, true /* useLastAddress */);
+        final IpPrefix hotspotPrefix = asIpPrefix(address);
+        final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
         assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
     }
@@ -319,7 +341,7 @@
 
         // If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
         LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mWifiP2pIpServer);
+                mWifiP2pIpServer, true /* useLastAddress */);
         assertEquals(mLegacyWifiP2pAddress, address);
         mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
     }