Merge "IpConn metrics: correctly read RA lifetimes" into nyc-mr1-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index 9d07e84..b0fb9f3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26063,6 +26063,7 @@
     method public static void logStateEvent(java.lang.String, java.lang.String);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.metrics.DhcpClientEvent> CREATOR;
+    field public final int durationMs;
     field public final java.lang.String ifName;
     field public final java.lang.String msg;
   }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 5ac24d5..847d82b 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
  * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
@@ -264,7 +266,7 @@
         dest.writeParcelable(networkCapabilities, flags);
         dest.writeInt(legacyType);
         dest.writeInt(requestId);
-        // type intentionally not preserved across process boundaries.
+        dest.writeString(type.name());
     }
     public static final Creator<NetworkRequest> CREATOR =
         new Creator<NetworkRequest>() {
@@ -272,8 +274,8 @@
                 NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
                 int legacyType = in.readInt();
                 int requestId = in.readInt();
-                // type intentionally not preserved across process boundaries.
-                NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, Type.NONE);
+                Type type = Type.valueOf(in.readString());  // IllegalArgumentException if invalid.
+                NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, type);
                 return result;
             }
             public NetworkRequest[] newArray(int size) {
@@ -311,13 +313,10 @@
         return (that.legacyType == this.legacyType &&
                 that.requestId == this.requestId &&
                 that.type == this.type &&
-                ((that.networkCapabilities == null && this.networkCapabilities == null) ||
-                 (that.networkCapabilities != null &&
-                  that.networkCapabilities.equals(this.networkCapabilities))));
+                Objects.equals(that.networkCapabilities, this.networkCapabilities));
     }
 
     public int hashCode() {
-        return requestId + (legacyType * 1013) +
-                (networkCapabilities.hashCode() * 1051) + type.hashCode() * 17;
+        return Objects.hash(requestId, legacyType, networkCapabilities, type);
     }
 }
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 3fe68b4..169f571 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -21,36 +21,43 @@
 import android.os.Parcelable;
 
 /**
+ *  An event recorded when a DhcpClient state machine transitions to a new state.
  * {@hide}
  */
 @SystemApi
 public final class DhcpClientEvent implements Parcelable {
     public final String ifName;
     public final String msg;
+    public final int durationMs;
 
     /** {@hide} */
-    public DhcpClientEvent(String ifName, String msg) {
+    public DhcpClientEvent(String ifName, String msg, int durationMs) {
         this.ifName = ifName;
         this.msg = msg;
+        this.durationMs = durationMs;
     }
 
     private DhcpClientEvent(Parcel in) {
         this.ifName = in.readString();
         this.msg = in.readString();
+        this.durationMs = in.readInt();
     }
 
+    @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(ifName);
         out.writeString(msg);
+        out.writeInt(durationMs);
     }
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
     public String toString() {
-        return String.format("DhcpClientEvent(%s, %s)", ifName, msg);
+        return String.format("DhcpClientEvent(%s, %s, %dms)", ifName, msg, durationMs);
     }
 
     public static final Parcelable.Creator<DhcpClientEvent> CREATOR
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3fb35d6..fb5b3f8 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3836,6 +3836,13 @@
         }
     }
 
+    private void ensureNetworkRequestHasType(NetworkRequest request) {
+        if (request.type == NetworkRequest.Type.NONE) {
+            throw new IllegalArgumentException(
+                    "All NetworkRequests in ConnectivityService must have a type");
+        }
+    }
+
     /**
      * Tracks info about the requester.
      * Also used to notice when the calling process dies so we can self-expire
@@ -3851,7 +3858,7 @@
 
         NetworkRequestInfo(NetworkRequest r, PendingIntent pi) {
             request = r;
-            ensureRequestHasType();
+            ensureNetworkRequestHasType(request);
             mPendingIntent = pi;
             messenger = null;
             mBinder = null;
@@ -3864,7 +3871,7 @@
             super();
             messenger = m;
             request = r;
-            ensureRequestHasType();
+            ensureNetworkRequestHasType(request);
             mBinder = binder;
             mPid = getCallingPid();
             mUid = getCallingUid();
@@ -3878,13 +3885,6 @@
             }
         }
 
-        private void ensureRequestHasType() {
-            if (request.type == NetworkRequest.Type.NONE) {
-                throw new IllegalArgumentException(
-                        "All NetworkRequests in ConnectivityService must have a type");
-            }
-        }
-
         private void enforceRequestCountLimit() {
             synchronized (mUidToNetworkRequestCount) {
                 int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
@@ -4138,6 +4138,7 @@
 
     @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
+        ensureNetworkRequestHasType(networkRequest);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
                 0, networkRequest));
     }
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 5852626..83cfc01 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -492,10 +492,19 @@
     }
 
     abstract class LoggingState extends State {
+        private long mEnterTimeMs;
+
         @Override
         public void enter() {
             if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
-            mMetricsLog.log(new DhcpClientEvent(mIfaceName, getName()));
+            mEnterTimeMs = SystemClock.elapsedRealtime();
+            // TODO: record time for Init -> Bound and Bound -> Renewing -> Bound
+        }
+
+        @Override
+        public void exit() {
+            long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs;
+            mMetricsLog.log(new DhcpClientEvent(mIfaceName, getName(), (int) durationMs));
         }
 
         private String messageName(int what) {
@@ -520,6 +529,13 @@
             }
             return NOT_HANDLED;
         }
+
+        @Override
+        public String getName() {
+            // All DhcpClient's states are inner classes with a well defined name.
+            // Use getSimpleName() and avoid super's getName() creating new String instances.
+            return getClass().getSimpleName();
+        }
     }
 
     // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
@@ -546,10 +562,9 @@
         }
     }
 
-    class StoppedState extends LoggingState {
+    class StoppedState extends State {
         @Override
         public boolean processMessage(Message message) {
-            super.processMessage(message);
             switch (message.what) {
                 case CMD_START_DHCP:
                     if (mRegisteredForPreDhcpNotification) {
@@ -578,10 +593,9 @@
         }
     }
 
-    class DhcpState extends LoggingState {
+    class DhcpState extends State {
         @Override
         public void enter() {
-            super.enter();
             clearDhcpState();
             if (initInterface() && initSockets()) {
                 mReceiveThread = new ReceiveThread();
@@ -679,7 +693,9 @@
             }
         }
 
+        @Override
         public void exit() {
+            super.exit();
             mKickAlarm.cancel();
             mTimeoutAlarm.cancel();
         }
@@ -784,15 +800,9 @@
         }
     }
 
-    class DhcpHaveLeaseState extends LoggingState {
-        @Override
-        public void enter() {
-            super.enter();
-        }
-
+    class DhcpHaveLeaseState extends State {
         @Override
         public boolean processMessage(Message message) {
-            super.processMessage(message);
             switch (message.what) {
                 case CMD_EXPIRE_DHCP:
                     Log.d(TAG, "Lease expired!");
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 14744d0..d424717 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -1043,6 +1043,11 @@
      * received. assertNoCallback may be called at any time.
      */
     private class TestNetworkCallback extends NetworkCallback {
+        // Chosen to be much less than the linger timeout. This ensures that we can distinguish
+        // between a LOST callback that arrives immediately and a LOST callback that arrives after
+        // the linger timeout.
+        private final static int TIMEOUT_MS = 50;
+
         private class CallbackInfo {
             public final CallbackState state;
             public final Network network;
@@ -1297,7 +1302,7 @@
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
-        // Cell is lingered because it would not satisfy any reques, even if it validated.
+        // Cell is lingered because it would not satisfy any request, even if it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
@@ -1327,6 +1332,29 @@
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // The current code has a bug: if a network is lingering, and we add and then remove a
+        // request from it, we forget that the network was lingering and tear it down immediately.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+
+        NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build();
+        NetworkCallback noopCallback = new NetworkCallback();
+        mCm.requestNetwork(cellRequest, noopCallback);
+        mCm.unregisterNetworkCallback(noopCallback);
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);