IpManager: add a provisioning timeout option

Relatedly: remove the provisioning timeout from DhcpClient.

Bug: 17733693
Bug: 24837343
Change-Id: I6d5b835b4ca70ba6fd06df359fc2128a0df46252
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 2b6c916..d0083ab 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -105,26 +105,25 @@
     /* Commands from controller to start/stop DHCP */
     public static final int CMD_START_DHCP                  = PUBLIC_BASE + 1;
     public static final int CMD_STOP_DHCP                   = PUBLIC_BASE + 2;
-    public static final int CMD_RENEW_DHCP                  = PUBLIC_BASE + 3;
 
     /* Notification from DHCP state machine prior to DHCP discovery/renewal */
-    public static final int CMD_PRE_DHCP_ACTION             = PUBLIC_BASE + 4;
+    public static final int CMD_PRE_DHCP_ACTION             = PUBLIC_BASE + 3;
     /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
      * success/failure */
-    public static final int CMD_POST_DHCP_ACTION            = PUBLIC_BASE + 5;
+    public static final int CMD_POST_DHCP_ACTION            = PUBLIC_BASE + 4;
     /* Notification from DHCP state machine before quitting */
-    public static final int CMD_ON_QUIT                     = PUBLIC_BASE + 6;
+    public static final int CMD_ON_QUIT                     = PUBLIC_BASE + 5;
 
     /* Command from controller to indicate DHCP discovery/renewal can continue
      * after pre DHCP action is complete */
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 7;
+    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 6;
 
     /* Command and event notification to/from IpManager requesting the setting
      * (or clearing) of an IPv4 LinkAddress.
      */
-    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 8;
-    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 9;
-    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 10;
+    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 7;
+    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 8;
+    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 9;
 
     /* Message.arg1 arguments to CMD_POST_DHCP notification */
     public static final int DHCP_SUCCESS = 1;
@@ -135,7 +134,7 @@
     private static final int CMD_KICK             = PRIVATE_BASE + 1;
     private static final int CMD_RECEIVED_PACKET  = PRIVATE_BASE + 2;
     private static final int CMD_TIMEOUT          = PRIVATE_BASE + 3;
-    private static final int CMD_ONESHOT_TIMEOUT  = PRIVATE_BASE + 4;
+    private static final int CMD_RENEW_DHCP       = PRIVATE_BASE + 4;
 
     // For message logging.
     private static final Class[] sMessageClasses = { DhcpClient.class };
@@ -177,7 +176,6 @@
     private final WakeupMessage mKickAlarm;
     private final WakeupMessage mTimeoutAlarm;
     private final WakeupMessage mRenewAlarm;
-    private final WakeupMessage mOneshotTimeoutAlarm;
     private final String mIfaceName;
 
     private boolean mRegisteredForPreDhcpNotification;
@@ -243,10 +241,6 @@
         mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
         // Used to schedule DHCP renews.
         mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
-        // Used to tell the caller when its request (CMD_START_DHCP or CMD_RENEW_DHCP) timed out.
-        // TODO: when the legacy DHCP client is gone, make the client fully asynchronous and
-        // remove this.
-        mOneshotTimeoutAlarm = makeWakeupMessage("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
     }
 
     public void registerForPreDhcpNotification() {
@@ -506,29 +500,12 @@
         }
     }
 
-    // The one-shot timeout is used to implement the timeout for CMD_START_DHCP. We can't use a
-    // state timeout to do this because obtaining an IP address involves passing through more than
-    // one state (specifically, it passes at least once through DhcpInitState and once through
-    // DhcpRequestingState). The one-shot timeout is created when CMD_START_DHCP is received, and is
-    // cancelled when exiting DhcpState (either due to a CMD_STOP_DHCP, or because of an error), or
-    // when we get an IP address (when entering DhcpBoundState). If it fires, we send ourselves
-    // CMD_ONESHOT_TIMEOUT and notify the caller that DHCP failed, but we take no other action. For
-    // example, if we're in DhcpInitState and sending DISCOVERs, we continue to do so.
-    //
-    // The one-shot timeout is not used for CMD_RENEW_DHCP because that is implemented using only
-    // one state, so we can just use the state timeout.
-    private void scheduleOneshotTimeout() {
-        final long alarmTime = SystemClock.elapsedRealtime() + DHCP_TIMEOUT_MS;
-        mOneshotTimeoutAlarm.schedule(alarmTime);
-    }
-
     class StoppedState extends LoggingState {
         @Override
         public boolean processMessage(Message message) {
             super.processMessage(message);
             switch (message.what) {
                 case CMD_START_DHCP:
-                    scheduleOneshotTimeout();
                     if (mRegisteredForPreDhcpNotification) {
                         transitionTo(mWaitBeforeStartState);
                     } else {
@@ -571,7 +548,6 @@
 
         @Override
         public void exit() {
-            mOneshotTimeoutAlarm.cancel();
             if (mReceiveThread != null) {
                 mReceiveThread.halt();  // Also closes sockets.
                 mReceiveThread = null;
@@ -586,10 +562,6 @@
                 case CMD_STOP_DHCP:
                     transitionTo(mStoppedState);
                     return HANDLED;
-                case CMD_ONESHOT_TIMEOUT:
-                    if (DBG) Log.d(TAG, "Timed out");
-                    notifyFailure();
-                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -822,7 +794,6 @@
         @Override
         public void enter() {
             super.enter();
-            mOneshotTimeoutAlarm.cancel();
             notifySuccess();
             // TODO: DhcpStateMachine only supported renewing at 50% of the lease time,
             // and did not support rebinding. Now that the legacy DHCP client is gone, fix this.
@@ -888,7 +859,7 @@
         @Override
         protected void timeout() {
             transitionTo(mDhcpInitState);
-            sendMessage(CMD_ONESHOT_TIMEOUT);
+            notifyFailure();
         }
     }
 
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index fd98f46..8ab029c 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -17,6 +17,7 @@
 package android.net.ip;
 
 import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
 
 import android.content.Context;
 import android.net.apf.ApfCapabilities;
@@ -53,6 +54,7 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 import static android.net.metrics.IpConnectivityEvent.IPCE_IPMGR_PROVISIONING_OK;
 import static android.net.metrics.IpConnectivityEvent.IPCE_IPMGR_PROVISIONING_FAIL;
@@ -254,6 +256,7 @@
      *     final ProvisioningConfiguration config =
      *             mIpManager.buildProvisioningConfiguration()
      *                     .withPreDhcpAction()
+     *                     .withProvisioningTimeoutMs(36 * 1000)
      *                     .build();
      *     mIpManager.startProvisioning(config);
      *     ...
@@ -264,6 +267,15 @@
      * must specify the configuration again.
      */
     public static class ProvisioningConfiguration {
+        // TODO: Delete this default timeout once those callers that care are
+        // fixed to pass in their preferred timeout.
+        //
+        // We pick 36 seconds so we can send DHCP requests at
+        //
+        //     t=0, t=2, t=6, t=14, t=30
+        //
+        // allowing for 10% jitter.
+        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
 
         public static class Builder {
             private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
@@ -288,6 +300,11 @@
                 return this;
             }
 
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                mConfig.mProvisioningTimeoutMs = timeoutMs;
+                return this;
+            }
+
             public ProvisioningConfiguration build() {
                 return new ProvisioningConfiguration(mConfig);
             }
@@ -297,6 +314,7 @@
         /* package */ boolean mRequestedPreDhcpAction;
         /* package */ StaticIpConfiguration mStaticIpConfig;
         /* package */ ApfCapabilities mApfCapabilities;
+        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
 
         public ProvisioningConfiguration() {}
 
@@ -305,6 +323,18 @@
             mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
             mStaticIpConfig = other.mStaticIpConfig;
             mApfCapabilities = other.mApfCapabilities;
+            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+        }
+
+        @Override
+        public String toString() {
+            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+                    .add("mRequestedPreDhcpAction: " + mRequestedPreDhcpAction)
+                    .add("mStaticIpConfig: " + mStaticIpConfig)
+                    .add("mApfCapabilities: " + mApfCapabilities)
+                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+                    .toString();
         }
     }
 
@@ -319,6 +349,7 @@
     private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
     private static final int CMD_UPDATE_HTTP_PROXY = 7;
     private static final int CMD_SET_MULTICAST_FILTER = 8;
+    private static final int EVENT_PROVISIONING_TIMEOUT = 9;
 
     private static final int MAX_LOG_RECORDS = 500;
 
@@ -341,6 +372,7 @@
     protected final Callback mCallback;
     private final INetworkManagementService mNwService;
     private final NetlinkTracker mNetlinkTracker;
+    private final WakeupMessage mProvisioningTimeoutAlarm;
     private final LocalLog mLocalLog;
 
     private NetworkInterface mNetworkInterface;
@@ -415,6 +447,9 @@
 
         resetLinkProperties();
 
+        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+
         // Super simple StateMachine.
         addState(mStoppedState);
         addState(mStartedState);
@@ -653,7 +688,6 @@
     }
 
     private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
-        if (mApfFilter != null) mApfFilter.setLinkProperties(newLp);
         switch (delta) {
             case GAINED_PROVISIONING:
                 if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
@@ -674,7 +708,13 @@
         }
     }
 
+    // Updates all IpManager-related state concerned with LinkProperties.
+    // Returns a ProvisioningChange for possibly notifying other interested
+    // parties that are not fronted by IpManager.
     private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+        if (mApfFilter != null) {
+            mApfFilter.setLinkProperties(newLp);
+        }
         if (mIpReachabilityMonitor != null) {
             mIpReachabilityMonitor.updateLinkProperties(newLp);
         }
@@ -682,13 +722,10 @@
         ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
         mLinkProperties = new LinkProperties(newLp);
 
-        if (DBG) {
-            switch (delta) {
-                case GAINED_PROVISIONING:
-                case LOST_PROVISIONING:
-                    Log.d(mTag, "provisioning: " + delta);
-                    break;
-            }
+        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+            // TODO: Add a proper ProvisionedState and cancel the alarm in
+            // its enter() method.
+            mProvisioningTimeoutAlarm.cancel();
         }
 
         return delta;
@@ -802,33 +839,39 @@
             Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
         }
         mCallback.onNewDhcpResults(dhcpResults);
-
         dispatchCallback(delta, newLp);
     }
 
     private void handleIPv4Failure() {
+        // TODO: Investigate deleting this clearIPv4Address() call.
+        //
+        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+        // that could trigger a call to this function. If we missed handling
+        // that message in StartedState for some reason we would still clear
+        // any addresses upon entry to StoppedState.
         clearIPv4Address();
         mDhcpResults = null;
+        if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+        mCallback.onNewDhcpResults(null);
+
+        handleProvisioningFailure();
+    }
+
+    private void handleProvisioningFailure() {
         final LinkProperties newLp = assembleLinkProperties();
         ProvisioningChange delta = setLinkProperties(newLp);
         // If we've gotten here and we're still not provisioned treat that as
         // a total loss of provisioning.
         //
         // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before the DHCPv4 timeout.
+        // there was no usable IPv6 obtained before a non-zero provisioning
+        // timeout expired.
         //
         // Regardless: GAME OVER.
-        //
-        // TODO: Make the DHCP client not time out and just continue in
-        // exponential backoff. Callers such as Wi-Fi which need a timeout
-        // should implement it themselves.
         if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
             delta = ProvisioningChange.LOST_PROVISIONING;
         }
 
-        if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
-        mCallback.onNewDhcpResults(null);
-
         dispatchCallback(delta, newLp);
         if (delta == ProvisioningChange.LOST_PROVISIONING) {
             transitionTo(mStoppingState);
@@ -972,11 +1015,19 @@
                         mInterfaceName);
                 mDhcpClient.registerForPreDhcpNotification();
                 mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+
+                if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                    final long alarmTime = SystemClock.elapsedRealtime() +
+                            mConfiguration.mProvisioningTimeoutMs;
+                    mProvisioningTimeoutAlarm.schedule(alarmTime);
+                }
             }
         }
 
         @Override
         public void exit() {
+            mProvisioningTimeoutAlarm.cancel();
+
             if (mIpReachabilityMonitor != null) {
                 mIpReachabilityMonitor.stop();
                 mIpReachabilityMonitor = null;
@@ -999,7 +1050,7 @@
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case CMD_STOP:
-                    transitionTo(mStoppedState);
+                    transitionTo(mStoppingState);
                     break;
 
                 case CMD_START:
@@ -1027,7 +1078,7 @@
 
                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
                     if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppedState);
+                        transitionTo(mStoppingState);
                     }
                     break;
 
@@ -1053,6 +1104,10 @@
                     break;
                 }
 
+                case EVENT_PROVISIONING_TIMEOUT:
+                    handleProvisioningFailure();
+                    break;
+
                 case DhcpClient.CMD_PRE_DHCP_ACTION:
                     if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
                     if (mConfiguration.mRequestedPreDhcpAction) {