Merge "Have DhcpClient transit to StoppedState on lease renew failure."
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 8e0e9d3..6433e04 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -1450,7 +1450,7 @@
                 case CMD_EXPIRE_DHCP:
                     Log.d(TAG, "Lease expired!");
                     notifyFailure();
-                    transitionTo(mDhcpInitState);
+                    transitionTo(mStoppedState);
                     return HANDLED;
                 default:
                     return NOT_HANDLED;
@@ -1830,7 +1830,7 @@
                     if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
                         Log.d(TAG, "Renewed lease not for our current IP address!");
                         notifyFailure();
-                        transitionTo(mDhcpInitState);
+                        transitionTo(mStoppedState);
                         return;
                     }
                     setDhcpLeaseExpiry(packet);
@@ -1844,9 +1844,9 @@
                     transitionTo(mDhcpBoundState);
                 }
             } else if (packet instanceof DhcpNakPacket) {
-                Log.d(TAG, "Received NAK, returning to INIT");
+                Log.d(TAG, "Received NAK, returning to StoppedState");
                 notifyFailure();
-                transitionTo(mDhcpInitState);
+                transitionTo(mStoppedState);
             }
         }
     }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 47abaf7..378c733 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -855,10 +855,10 @@
                 captivePortalApiUrl, null /* ipv6OnlyWaitTime */);
     }
 
-    private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet) {
+    private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet, final String message) {
         return DhcpPacket.buildNakPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
             SERVER_ADDR /* serverIp */, INADDR_ANY /* relayIp */, packet.getClientMac(),
-            false /* broadcast */, "duplicated request IP address");
+            false /* broadcast */, message);
     }
 
     private void sendArpReply(final byte[] clientMac) throws IOException {
@@ -996,7 +996,7 @@
                 final ByteBuffer byteBuffer = isSuccessLease
                         ? buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec, (short) mtu,
                                 false /* rapidCommit */, captivePortalApiUrl)
-                        : buildDhcpNakPacket(packet);
+                        : buildDhcpNakPacket(packet, "duplicated request IP address");
                 mPacketReader.sendResponse(byteBuffer);
             } else {
                 fail("invalid DHCP packet");
@@ -2459,7 +2459,8 @@
     }
 
     private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
-            final MacAddress bssid, final boolean expectRoaming) throws Exception {
+            final MacAddress bssid, final boolean expectRoaming,
+            final boolean shouldReplyNakOnRoam) throws Exception {
         long currentTime = System.currentTimeMillis();
         final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER, bssid);
 
@@ -2501,11 +2502,16 @@
         assertNull(packet.mRequestedIp);                // requested IP option
         assertNull(packet.mServerIdentifier);           // server ID
 
-        mPacketReader.sendResponse(buildDhcpAckPacket(packet,
-                hasMismatchedIpAddress ? CLIENT_ADDR_NEW : CLIENT_ADDR, TEST_LEASE_DURATION_S,
-                (short) TEST_DEFAULT_MTU, false /* rapidcommit */, null /* captivePortalUrl */));
+        final ByteBuffer packetBuffer = shouldReplyNakOnRoam
+                ? buildDhcpNakPacket(packet, "request IP on a wrong subnet")
+                : buildDhcpAckPacket(packet,
+                        hasMismatchedIpAddress ? CLIENT_ADDR_NEW : CLIENT_ADDR,
+                        TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU,
+                        false /* rapidCommit */, null /* captivePortalApiUrl */);
+        mPacketReader.sendResponse(packetBuffer);
         HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
-        if (hasMismatchedIpAddress) {
+
+        if (shouldReplyNakOnRoam || hasMismatchedIpAddress) {
             // notifyFailure
             ArgumentCaptor<DhcpResultsParcelable> captor =
                     ArgumentCaptor.forClass(DhcpResultsParcelable.class);
@@ -2513,9 +2519,9 @@
             DhcpResults lease = fromStableParcelable(captor.getValue());
             assertNull(lease);
 
-            // roll back to INIT state.
-            packet = getNextDhcpPacket();
-            assertTrue(packet instanceof DhcpDiscoverPacket);
+            // DhcpClient rolls back to StoppedState instead of INIT state after calling
+            // notifyFailure, DHCPDISCOVER should not be sent out.
+            assertNull(getNextDhcpPacket(TEST_TIMEOUT_MS));
         } else {
             assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
                     TEST_DEFAULT_MTU);
@@ -2525,31 +2531,42 @@
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */,
+                false /* shouldReplyNakOnRoam */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidBssid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                MacAddress.fromString(TEST_DHCP_ROAM_BSSID), false /* expectRoaming */);
+                MacAddress.fromString(TEST_DHCP_ROAM_BSSID), false /* expectRoaming */,
+                false/* shouldReplyNakOnRoam */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_nullBssid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                null /* BSSID */, false /* expectRoaming */);
+                null /* BSSID */, false /* expectRoaming */, false /* shouldReplyNakOnRoam */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidDisplayName() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"test-ssid\"" /* display name */,
-                MacAddress.fromString(TEST_DEFAULT_BSSID), false /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), false /* expectRoaming */,
+                false /* shouldReplyNakOnRoam */);
     }
 
     @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_mismatchedLeasedIpAddress() throws Exception {
         doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
-                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
+                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */,
+                false /* shouldReplyNakOnRoam */);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDhcpRoaming_failureLeaseOnNak() throws Exception {
+        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
+                MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */,
+                true /* shouldReplyNakOnRoam */);
     }
 
     private void performDualStackProvisioning() throws Exception {