Merge "Add NetworkStack metrics to system API"
diff --git a/api/system-current.txt b/api/system-current.txt
index 83c9884..1320d14 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3179,6 +3179,146 @@
 
 }
 
+package android.net.metrics {
+
+  public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class ApfProgramEvent.Builder {
+    ctor public ApfProgramEvent.Builder();
+    method public android.net.metrics.ApfProgramEvent build();
+    method public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
+    method public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
+    method public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
+    method public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
+    method public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
+    method public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
+  }
+
+  public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class ApfStats.Builder {
+    ctor public ApfStats.Builder();
+    method public android.net.metrics.ApfStats build();
+    method public android.net.metrics.ApfStats.Builder setDroppedRas(int);
+    method public android.net.metrics.ApfStats.Builder setDurationMs(long);
+    method public android.net.metrics.ApfStats.Builder setMatchingRas(int);
+    method public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
+    method public android.net.metrics.ApfStats.Builder setParseErrors(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
+    method public android.net.metrics.ApfStats.Builder setReceivedRas(int);
+    method public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
+  }
+
+  public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class DhcpClientEvent.Builder {
+    ctor public DhcpClientEvent.Builder();
+    method public android.net.metrics.DhcpClientEvent build();
+    method public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
+    method public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
+  }
+
+  public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public DhcpErrorEvent(int);
+    method public static int errorCodeWithOption(int, int);
+    field public static final int BOOTP_TOO_SHORT;
+    field public static final int BUFFER_UNDERFLOW;
+    field public static final int DHCP_BAD_MAGIC_COOKIE;
+    field public static final int DHCP_ERROR = 4; // 0x4
+    field public static final int DHCP_INVALID_OPTION_LENGTH;
+    field public static final int DHCP_NO_COOKIE;
+    field public static final int DHCP_NO_MSG_TYPE;
+    field public static final int DHCP_UNKNOWN_MSG_TYPE;
+    field public static final int L2_ERROR = 1; // 0x1
+    field public static final int L2_TOO_SHORT;
+    field public static final int L2_WRONG_ETH_TYPE;
+    field public static final int L3_ERROR = 2; // 0x2
+    field public static final int L3_INVALID_IP;
+    field public static final int L3_NOT_IPV4;
+    field public static final int L3_TOO_SHORT;
+    field public static final int L4_ERROR = 3; // 0x3
+    field public static final int L4_NOT_UDP;
+    field public static final int L4_WRONG_PORT;
+    field public static final int MISC_ERROR = 5; // 0x5
+    field public static final int PARSING_ERROR;
+    field public static final int RECEIVE_ERROR;
+  }
+
+  public class IpConnectivityLog {
+    method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(android.net.metrics.IpConnectivityLog.Event);
+  }
+
+  public static interface IpConnectivityLog.Event extends android.os.Parcelable {
+  }
+
+  public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public IpManagerEvent(int, long);
+    field public static final int COMPLETE_LIFECYCLE = 3; // 0x3
+    field public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
+    field public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
+    field public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
+    field public static final int ERROR_STARTING_IPV4 = 4; // 0x4
+    field public static final int ERROR_STARTING_IPV6 = 5; // 0x5
+    field public static final int PROVISIONING_FAIL = 2; // 0x2
+    field public static final int PROVISIONING_OK = 1; // 0x1
+  }
+
+  public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public IpReachabilityEvent(int);
+    field public static final int NUD_FAILED = 512; // 0x200
+    field public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
+    field public static final int PROBE = 256; // 0x100
+    field public static final int PROVISIONING_LOST = 768; // 0x300
+    field public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
+  }
+
+  public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public NetworkEvent(int, long);
+    ctor public NetworkEvent(int);
+    field public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
+    field public static final int NETWORK_CONNECTED = 1; // 0x1
+    field public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
+    field public static final int NETWORK_DISCONNECTED = 7; // 0x7
+    field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
+    field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
+    field public static final int NETWORK_LINGER = 5; // 0x5
+    field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
+    field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
+    field public static final int NETWORK_UNLINGER = 6; // 0x6
+    field public static final int NETWORK_VALIDATED = 2; // 0x2
+    field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
+  }
+
+  public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method public static String getProbeName(int);
+    field public static final int DNS_FAILURE = 0; // 0x0
+    field public static final int DNS_SUCCESS = 1; // 0x1
+    field public static final int PROBE_DNS = 0; // 0x0
+    field public static final int PROBE_FALLBACK = 4; // 0x4
+    field public static final int PROBE_HTTP = 1; // 0x1
+    field public static final int PROBE_HTTPS = 2; // 0x2
+    field public static final int PROBE_PAC = 3; // 0x3
+    field public static final int PROBE_PRIVDNS = 5; // 0x5
+  }
+
+  public static class ValidationProbeEvent.Builder {
+    ctor public ValidationProbeEvent.Builder();
+    method public android.net.metrics.ValidationProbeEvent build();
+    method public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
+    method public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
+    method public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
+  }
+
+}
+
 package android.net.wifi {
 
   @Deprecated public class RttManager {
diff --git a/api/test-current.txt b/api/test-current.txt
index 6f90b97..46f98e5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -645,6 +645,146 @@
 
 }
 
+package android.net.metrics {
+
+  public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class ApfProgramEvent.Builder {
+    ctor public ApfProgramEvent.Builder();
+    method public android.net.metrics.ApfProgramEvent build();
+    method public android.net.metrics.ApfProgramEvent.Builder setActualLifetime(long);
+    method public android.net.metrics.ApfProgramEvent.Builder setCurrentRas(int);
+    method public android.net.metrics.ApfProgramEvent.Builder setFilteredRas(int);
+    method public android.net.metrics.ApfProgramEvent.Builder setFlags(boolean, boolean);
+    method public android.net.metrics.ApfProgramEvent.Builder setLifetime(long);
+    method public android.net.metrics.ApfProgramEvent.Builder setProgramLength(int);
+  }
+
+  public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class ApfStats.Builder {
+    ctor public ApfStats.Builder();
+    method public android.net.metrics.ApfStats build();
+    method public android.net.metrics.ApfStats.Builder setDroppedRas(int);
+    method public android.net.metrics.ApfStats.Builder setDurationMs(long);
+    method public android.net.metrics.ApfStats.Builder setMatchingRas(int);
+    method public android.net.metrics.ApfStats.Builder setMaxProgramSize(int);
+    method public android.net.metrics.ApfStats.Builder setParseErrors(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdates(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdatesAll(int);
+    method public android.net.metrics.ApfStats.Builder setProgramUpdatesAllowingMulticast(int);
+    method public android.net.metrics.ApfStats.Builder setReceivedRas(int);
+    method public android.net.metrics.ApfStats.Builder setZeroLifetimeRas(int);
+  }
+
+  public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+  }
+
+  public static class DhcpClientEvent.Builder {
+    ctor public DhcpClientEvent.Builder();
+    method public android.net.metrics.DhcpClientEvent build();
+    method public android.net.metrics.DhcpClientEvent.Builder setDurationMs(int);
+    method public android.net.metrics.DhcpClientEvent.Builder setMsg(String);
+  }
+
+  public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public DhcpErrorEvent(int);
+    method public static int errorCodeWithOption(int, int);
+    field public static final int BOOTP_TOO_SHORT;
+    field public static final int BUFFER_UNDERFLOW;
+    field public static final int DHCP_BAD_MAGIC_COOKIE;
+    field public static final int DHCP_ERROR = 4; // 0x4
+    field public static final int DHCP_INVALID_OPTION_LENGTH;
+    field public static final int DHCP_NO_COOKIE;
+    field public static final int DHCP_NO_MSG_TYPE;
+    field public static final int DHCP_UNKNOWN_MSG_TYPE;
+    field public static final int L2_ERROR = 1; // 0x1
+    field public static final int L2_TOO_SHORT;
+    field public static final int L2_WRONG_ETH_TYPE;
+    field public static final int L3_ERROR = 2; // 0x2
+    field public static final int L3_INVALID_IP;
+    field public static final int L3_NOT_IPV4;
+    field public static final int L3_TOO_SHORT;
+    field public static final int L4_ERROR = 3; // 0x3
+    field public static final int L4_NOT_UDP;
+    field public static final int L4_WRONG_PORT;
+    field public static final int MISC_ERROR = 5; // 0x5
+    field public static final int PARSING_ERROR;
+    field public static final int RECEIVE_ERROR;
+  }
+
+  public class IpConnectivityLog {
+    method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event);
+    method public boolean log(android.net.metrics.IpConnectivityLog.Event);
+  }
+
+  public static interface IpConnectivityLog.Event extends android.os.Parcelable {
+  }
+
+  public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public IpManagerEvent(int, long);
+    field public static final int COMPLETE_LIFECYCLE = 3; // 0x3
+    field public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
+    field public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
+    field public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; // 0x6
+    field public static final int ERROR_STARTING_IPV4 = 4; // 0x4
+    field public static final int ERROR_STARTING_IPV6 = 5; // 0x5
+    field public static final int PROVISIONING_FAIL = 2; // 0x2
+    field public static final int PROVISIONING_OK = 1; // 0x1
+  }
+
+  public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public IpReachabilityEvent(int);
+    field public static final int NUD_FAILED = 512; // 0x200
+    field public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
+    field public static final int PROBE = 256; // 0x100
+    field public static final int PROVISIONING_LOST = 768; // 0x300
+    field public static final int PROVISIONING_LOST_ORGANIC = 1280; // 0x500
+  }
+
+  public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
+    ctor public NetworkEvent(int, long);
+    ctor public NetworkEvent(int);
+    field public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
+    field public static final int NETWORK_CONNECTED = 1; // 0x1
+    field public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
+    field public static final int NETWORK_DISCONNECTED = 7; // 0x7
+    field public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10; // 0xa
+    field public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8; // 0x8
+    field public static final int NETWORK_LINGER = 5; // 0x5
+    field public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11; // 0xb
+    field public static final int NETWORK_REVALIDATION_SUCCESS = 9; // 0x9
+    field public static final int NETWORK_UNLINGER = 6; // 0x6
+    field public static final int NETWORK_VALIDATED = 2; // 0x2
+    field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
+  }
+
+  public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method public static String getProbeName(int);
+    field public static final int DNS_FAILURE = 0; // 0x0
+    field public static final int DNS_SUCCESS = 1; // 0x1
+    field public static final int PROBE_DNS = 0; // 0x0
+    field public static final int PROBE_FALLBACK = 4; // 0x4
+    field public static final int PROBE_HTTP = 1; // 0x1
+    field public static final int PROBE_HTTPS = 2; // 0x2
+    field public static final int PROBE_PAC = 3; // 0x3
+    field public static final int PROBE_PRIVDNS = 5; // 0x5
+  }
+
+  public static class ValidationProbeEvent.Builder {
+    ctor public ValidationProbeEvent.Builder();
+    method public android.net.metrics.ValidationProbeEvent build();
+    method public android.net.metrics.ValidationProbeEvent.Builder setDurationMs(long);
+    method public android.net.metrics.ValidationProbeEvent.Builder setProbeType(int, boolean);
+    method public android.net.metrics.ValidationProbeEvent.Builder setReturnCode(int);
+  }
+
+}
+
 package android.os {
 
   public static class Build.VERSION {
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index 5dabf35..8601005 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -36,11 +38,15 @@
  * the APF program in place with a new APF program.
  * {@hide}
  */
-public final class ApfProgramEvent implements Parcelable {
+@TestApi
+@SystemApi
+public final class ApfProgramEvent implements IpConnectivityLog.Event {
 
     // Bitflag constants describing what an Apf program filters.
     // Bits are indexeds from LSB to MSB, starting at index 0.
+    /** @hide */
     public static final int FLAG_MULTICAST_FILTER_ON = 0;
+    /** @hide */
     public static final int FLAG_HAS_IPV4_ADDRESS    = 1;
 
     /** {@hide} */
@@ -48,21 +54,33 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flags {}
 
+    /** @hide */
     @UnsupportedAppUsage
-    public long lifetime;       // Maximum computed lifetime of the program in seconds
+    public final long lifetime;       // Maximum computed lifetime of the program in seconds
+    /** @hide */
     @UnsupportedAppUsage
-    public long actualLifetime; // Effective program lifetime in seconds
+    public final long actualLifetime; // Effective program lifetime in seconds
+    /** @hide */
     @UnsupportedAppUsage
-    public int filteredRas;     // Number of RAs filtered by the APF program
+    public final int filteredRas;     // Number of RAs filtered by the APF program
+    /** @hide */
     @UnsupportedAppUsage
-    public int currentRas;      // Total number of current RAs at generation time
+    public final int currentRas;      // Total number of current RAs at generation time
+    /** @hide */
     @UnsupportedAppUsage
-    public int programLength;   // Length of the APF program in bytes
+    public final int programLength;   // Length of the APF program in bytes
+    /** @hide */
     @UnsupportedAppUsage
-    public int flags;           // Bitfield compound of FLAG_* constants
+    public final int flags;           // Bitfield compound of FLAG_* constants
 
-    @UnsupportedAppUsage
-    public ApfProgramEvent() {
+    private ApfProgramEvent(long lifetime, long actualLifetime, int filteredRas, int currentRas,
+            int programLength, int flags) {
+        this.lifetime = lifetime;
+        this.actualLifetime = actualLifetime;
+        this.filteredRas = filteredRas;
+        this.currentRas = currentRas;
+        this.programLength = programLength;
+        this.flags = flags;
     }
 
     private ApfProgramEvent(Parcel in) {
@@ -74,6 +92,75 @@
         this.flags = in.readInt();
     }
 
+    /**
+     * Utility to create an instance of {@link ApfProgramEvent}.
+     */
+    public static class Builder {
+        private long mLifetime;
+        private long mActualLifetime;
+        private int mFilteredRas;
+        private int mCurrentRas;
+        private int mProgramLength;
+        private int mFlags;
+
+        /**
+         * Set the maximum computed lifetime of the program in seconds.
+         */
+        public Builder setLifetime(long lifetime) {
+            mLifetime = lifetime;
+            return this;
+        }
+
+        /**
+         * Set the effective program lifetime in seconds.
+         */
+        public Builder setActualLifetime(long lifetime) {
+            mActualLifetime = lifetime;
+            return this;
+        }
+
+        /**
+         * Set the number of RAs filtered by the APF program.
+         */
+        public Builder setFilteredRas(int filteredRas) {
+            mFilteredRas = filteredRas;
+            return this;
+        }
+
+        /**
+         * Set the total number of current RAs at generation time.
+         */
+        public Builder setCurrentRas(int currentRas) {
+            mCurrentRas = currentRas;
+            return this;
+        }
+
+        /**
+         * Set the length of the APF program in bytes.
+         */
+        public Builder setProgramLength(int programLength) {
+            mProgramLength = programLength;
+            return this;
+        }
+
+        /**
+         * Set the flags describing what an Apf program filters.
+         */
+        public Builder setFlags(boolean hasIPv4, boolean multicastFilterOn) {
+            mFlags = flagsFor(hasIPv4, multicastFilterOn);
+            return this;
+        }
+
+        /**
+         * Build a new {@link ApfProgramEvent}.
+         */
+        public ApfProgramEvent build() {
+            return new ApfProgramEvent(mLifetime, mActualLifetime, mFilteredRas, mCurrentRas,
+                    mProgramLength, mFlags);
+        }
+    }
+
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(lifetime);
@@ -84,6 +171,7 @@
         out.writeInt(flags);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
@@ -96,6 +184,7 @@
                 programLength, actualLifetime, lifetimeString, namesOf(flags));
     }
 
+    /** @hide */
     public static final Parcelable.Creator<ApfProgramEvent> CREATOR
             = new Parcelable.Creator<ApfProgramEvent>() {
         public ApfProgramEvent createFromParcel(Parcel in) {
@@ -107,6 +196,7 @@
         }
     };
 
+    /** @hide */
     @UnsupportedAppUsage
     public static @Flags int flagsFor(boolean hasIPv4, boolean multicastFilterOn) {
         int bitfield = 0;
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index bb2a35c..844a134 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -16,6 +16,8 @@
 
 package android.net.metrics;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -24,42 +26,70 @@
  * An event logged for an interface with APF capabilities when its IpClient state machine exits.
  * {@hide}
  */
-public final class ApfStats implements Parcelable {
+@SystemApi
+@TestApi
+public final class ApfStats implements IpConnectivityLog.Event {
 
-    /** time interval in milliseconds these stastistics covers. */
+    /**
+     * time interval in milliseconds these stastistics covers.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public long durationMs;
-    /** number of received RAs. */
+    public final long durationMs;
+    /**
+     * number of received RAs.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int receivedRas;
-    /** number of received RAs matching a known RA. */
+    public final int receivedRas;
+    /**
+     * number of received RAs matching a known RA.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int matchingRas;
-    /** number of received RAs ignored due to the MAX_RAS limit. */
+    public final int matchingRas;
+    /**
+     * number of received RAs ignored due to the MAX_RAS limit.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int droppedRas;
-    /** number of received RAs with a minimum lifetime of 0. */
+    public final int droppedRas;
+    /**
+     * number of received RAs with a minimum lifetime of 0.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int zeroLifetimeRas;
-    /** number of received RAs that could not be parsed. */
+    public final int zeroLifetimeRas;
+    /**
+     * number of received RAs that could not be parsed.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int parseErrors;
-    /** number of APF program updates from receiving RAs.. */
+    public final int parseErrors;
+    /**
+     * number of APF program updates from receiving RAs.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int programUpdates;
-    /** total number of APF program updates. */
+    public final int programUpdates;
+    /**
+     * total number of APF program updates.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int programUpdatesAll;
-    /** number of APF program updates from allowing multicast traffic. */
+    public final int programUpdatesAll;
+    /**
+     * number of APF program updates from allowing multicast traffic.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int programUpdatesAllowingMulticast;
-    /** maximum APF program size advertised by hardware. */
+    public final int programUpdatesAllowingMulticast;
+    /**
+     * maximum APF program size advertised by hardware.
+     * @hide
+     */
     @UnsupportedAppUsage
-    public int maxProgramSize;
-
-    @UnsupportedAppUsage
-    public ApfStats() {
-    }
+    public final int maxProgramSize;
 
     private ApfStats(Parcel in) {
         this.durationMs = in.readLong();
@@ -74,6 +104,130 @@
         this.maxProgramSize = in.readInt();
     }
 
+    private ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas,
+            int zeroLifetimeRas, int parseErrors, int programUpdates, int programUpdatesAll,
+            int programUpdatesAllowingMulticast, int maxProgramSize) {
+        this.durationMs = durationMs;
+        this.receivedRas = receivedRas;
+        this.matchingRas = matchingRas;
+        this.droppedRas = droppedRas;
+        this.zeroLifetimeRas = zeroLifetimeRas;
+        this.parseErrors = parseErrors;
+        this.programUpdates = programUpdates;
+        this.programUpdatesAll = programUpdatesAll;
+        this.programUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+        this.maxProgramSize = maxProgramSize;
+    }
+
+    /**
+     * Utility to create an instance of {@link ApfStats}.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static class Builder {
+        private long mDurationMs;
+        private int mReceivedRas;
+        private int mMatchingRas;
+        private int mDroppedRas;
+        private int mZeroLifetimeRas;
+        private int mParseErrors;
+        private int mProgramUpdates;
+        private int mProgramUpdatesAll;
+        private int mProgramUpdatesAllowingMulticast;
+        private int mMaxProgramSize;
+
+        /**
+         * Set the time interval in milliseconds these statistics covers.
+         */
+        public Builder setDurationMs(long durationMs) {
+            mDurationMs = durationMs;
+            return this;
+        }
+
+        /**
+         * Set the number of received RAs.
+         */
+        public Builder setReceivedRas(int receivedRas) {
+            mReceivedRas = receivedRas;
+            return this;
+        }
+
+        /**
+         * Set the number of received RAs matching a known RA.
+         */
+        public Builder setMatchingRas(int matchingRas) {
+            mMatchingRas = matchingRas;
+            return this;
+        }
+
+        /**
+         * Set the number of received RAs ignored due to the MAX_RAS limit.
+         */
+        public Builder setDroppedRas(int droppedRas) {
+            mDroppedRas = droppedRas;
+            return this;
+        }
+
+        /**
+         * Set the number of received RAs with a minimum lifetime of 0.
+         */
+        public Builder setZeroLifetimeRas(int zeroLifetimeRas) {
+            mZeroLifetimeRas = zeroLifetimeRas;
+            return this;
+        }
+
+        /**
+         * Set the number of received RAs that could not be parsed.
+         */
+        public Builder setParseErrors(int parseErrors) {
+            mParseErrors = parseErrors;
+            return this;
+        }
+
+        /**
+         * Set the number of APF program updates from receiving RAs.
+         */
+        public Builder setProgramUpdates(int programUpdates) {
+            mProgramUpdates = programUpdates;
+            return this;
+        }
+
+        /**
+         * Set the total number of APF program updates.
+         */
+        public Builder setProgramUpdatesAll(int programUpdatesAll) {
+            mProgramUpdatesAll = programUpdatesAll;
+            return this;
+        }
+
+        /**
+         * Set the number of APF program updates from allowing multicast traffic.
+         */
+        public Builder setProgramUpdatesAllowingMulticast(int programUpdatesAllowingMulticast) {
+            mProgramUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+            return this;
+        }
+
+        /**
+         * Set the maximum APF program size advertised by hardware.
+         */
+        public Builder setMaxProgramSize(int maxProgramSize) {
+            mMaxProgramSize = maxProgramSize;
+            return this;
+        }
+
+        /**
+         * Create a new {@link ApfStats}.
+         */
+        public ApfStats build() {
+            return new ApfStats(mDurationMs, mReceivedRas, mMatchingRas, mDroppedRas,
+                    mZeroLifetimeRas, mParseErrors, mProgramUpdates, mProgramUpdatesAll,
+                    mProgramUpdatesAllowingMulticast, mMaxProgramSize);
+        }
+    }
+
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(durationMs);
@@ -88,6 +242,7 @@
         out.writeInt(maxProgramSize);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
@@ -108,6 +263,7 @@
                 .toString();
     }
 
+    /** @hide */
     public static final Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
         public ApfStats createFromParcel(Parcel in) {
             return new ApfStats(in);
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 98a7d7e..2a942ee 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -16,6 +16,8 @@
 
 package android.net.metrics;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -24,7 +26,9 @@
  * An event recorded when a DhcpClient state machine transitions to a new state.
  * {@hide}
  */
-public final class DhcpClientEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class DhcpClientEvent implements IpConnectivityLog.Event {
 
     // Names for recording DhcpClient pseudo-state transitions.
     /** {@hide} Represents transitions from DhcpInitState to DhcpBoundState */
@@ -32,11 +36,13 @@
     /** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */
     public static final String RENEWING_BOUND = "RenewingBoundState";
 
+    /** @hide */
     public final String msg;
+    /** @hide */
     public final int durationMs;
 
     @UnsupportedAppUsage
-    public DhcpClientEvent(String msg, int durationMs) {
+    private DhcpClientEvent(String msg, int durationMs) {
         this.msg = msg;
         this.durationMs = durationMs;
     }
@@ -46,12 +52,45 @@
         this.durationMs = in.readInt();
     }
 
+    /**
+     * Utility to create an instance of {@link ApfProgramEvent}.
+     */
+    public static class Builder {
+        private String mMsg;
+        private int mDurationMs;
+
+        /**
+         * Set the message of the event.
+         */
+        public Builder setMsg(String msg) {
+            mMsg = msg;
+            return this;
+        }
+
+        /**
+         * Set the duration of the event in milliseconds.
+         */
+        public Builder setDurationMs(int durationMs) {
+            mDurationMs = durationMs;
+            return this;
+        }
+
+        /**
+         * Create a new {@link DhcpClientEvent}.
+         */
+        public DhcpClientEvent build() {
+            return new DhcpClientEvent(mMsg, mDurationMs);
+        }
+    }
+
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(msg);
         out.writeInt(durationMs);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
@@ -62,6 +101,7 @@
         return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
+    /** @hide */
     public static final Parcelable.Creator<DhcpClientEvent> CREATOR
         = new Parcelable.Creator<DhcpClientEvent>() {
         public DhcpClientEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index c6c06f0..18fde80 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -16,7 +16,8 @@
 
 package android.net.metrics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -27,48 +28,34 @@
  * Event class used to record error events when parsing DHCP response packets.
  * {@hide}
  */
-public final class DhcpErrorEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class DhcpErrorEvent implements IpConnectivityLog.Event {
     public static final int L2_ERROR   = 1;
     public static final int L3_ERROR   = 2;
     public static final int L4_ERROR   = 3;
     public static final int DHCP_ERROR = 4;
     public static final int MISC_ERROR = 5;
 
-    @UnsupportedAppUsage
     public static final int L2_TOO_SHORT               = makeErrorCode(L2_ERROR, 1);
-    @UnsupportedAppUsage
     public static final int L2_WRONG_ETH_TYPE          = makeErrorCode(L2_ERROR, 2);
 
-    @UnsupportedAppUsage
     public static final int L3_TOO_SHORT               = makeErrorCode(L3_ERROR, 1);
-    @UnsupportedAppUsage
     public static final int L3_NOT_IPV4                = makeErrorCode(L3_ERROR, 2);
-    @UnsupportedAppUsage
     public static final int L3_INVALID_IP              = makeErrorCode(L3_ERROR, 3);
 
-    @UnsupportedAppUsage
     public static final int L4_NOT_UDP                 = makeErrorCode(L4_ERROR, 1);
-    @UnsupportedAppUsage
     public static final int L4_WRONG_PORT              = makeErrorCode(L4_ERROR, 2);
 
-    @UnsupportedAppUsage
     public static final int BOOTP_TOO_SHORT            = makeErrorCode(DHCP_ERROR, 1);
-    @UnsupportedAppUsage
     public static final int DHCP_BAD_MAGIC_COOKIE      = makeErrorCode(DHCP_ERROR, 2);
-    @UnsupportedAppUsage
     public static final int DHCP_INVALID_OPTION_LENGTH = makeErrorCode(DHCP_ERROR, 3);
-    @UnsupportedAppUsage
     public static final int DHCP_NO_MSG_TYPE           = makeErrorCode(DHCP_ERROR, 4);
-    @UnsupportedAppUsage
     public static final int DHCP_UNKNOWN_MSG_TYPE      = makeErrorCode(DHCP_ERROR, 5);
-    @UnsupportedAppUsage
     public static final int DHCP_NO_COOKIE             = makeErrorCode(DHCP_ERROR, 6);
 
-    @UnsupportedAppUsage
     public static final int BUFFER_UNDERFLOW           = makeErrorCode(MISC_ERROR, 1);
-    @UnsupportedAppUsage
     public static final int RECEIVE_ERROR              = makeErrorCode(MISC_ERROR, 2);
-    @UnsupportedAppUsage
     public static final int PARSING_ERROR              = makeErrorCode(MISC_ERROR, 3);
 
     // error code byte format (MSB to LSB):
@@ -76,9 +63,9 @@
     // byte 1: error subtype
     // byte 2: unused
     // byte 3: optional code
+    /** @hide */
     public final int errorCode;
 
-    @UnsupportedAppUsage
     public DhcpErrorEvent(int errorCode) {
         this.errorCode = errorCode;
     }
@@ -87,16 +74,19 @@
         this.errorCode = in.readInt();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(errorCode);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public static final Parcelable.Creator<DhcpErrorEvent> CREATOR
         = new Parcelable.Creator<DhcpErrorEvent>() {
         public DhcpErrorEvent createFromParcel(Parcel in) {
@@ -108,7 +98,6 @@
         }
     };
 
-    @UnsupportedAppUsage
     public static int errorCodeWithOption(int errorCode, int option) {
         return (0xFFFF0000 & errorCode) | (0xFF & option);
     }
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 998f4ba..c04fcdc 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -16,6 +16,8 @@
 
 package android.net.metrics;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
@@ -23,6 +25,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
 
@@ -30,18 +33,28 @@
  * Class for logging IpConnectvity events with IpConnectivityMetrics
  * {@hide}
  */
+@SystemApi
+@TestApi
 public class IpConnectivityLog {
     private static final String TAG = IpConnectivityLog.class.getSimpleName();
     private static final boolean DBG = false;
 
+    /** @hide */
     public static final String SERVICE_NAME = "connmetrics";
 
     private IIpConnectivityMetrics mService;
 
+    /**
+     * An event to be logged.
+     */
+    public interface Event extends Parcelable {}
+
+    /** @hide */
     @UnsupportedAppUsage
     public IpConnectivityLog() {
     }
 
+    /** @hide */
     @VisibleForTesting
     public IpConnectivityLog(IIpConnectivityMetrics service) {
         mService = service;
@@ -67,6 +80,7 @@
      * @param ev the event to log. If the event timestamp is 0,
      * the timestamp is set to the current time in milliseconds.
      * @return true if the event was successfully logged.
+     * @hide
      */
     public boolean log(ConnectivityMetricsEvent ev) {
         if (!checkLoggerService()) {
@@ -94,7 +108,7 @@
      * @param data is a Parcelable instance representing the event.
      * @return true if the event was successfully logged.
      */
-    public boolean log(long timestamp, Parcelable data) {
+    public boolean log(long timestamp, Event data) {
         ConnectivityMetricsEvent ev = makeEv(data);
         ev.timestamp = timestamp;
         return log(ev);
@@ -106,8 +120,7 @@
      * @param data is a Parcelable instance representing the event.
      * @return true if the event was successfully logged.
      */
-    @UnsupportedAppUsage
-    public boolean log(String ifname, Parcelable data) {
+    public boolean log(String ifname, Event data) {
         ConnectivityMetricsEvent ev = makeEv(data);
         ev.ifname = ifname;
         return log(ev);
@@ -121,7 +134,7 @@
      * @param data is a Parcelable instance representing the event.
      * @return true if the event was successfully logged.
      */
-    public boolean log(int netid, int[] transports, Parcelable data) {
+    public boolean log(int netid, int[] transports, Event data) {
         ConnectivityMetricsEvent ev = makeEv(data);
         ev.netId = netid;
         ev.transports = BitUtils.packBits(transports);
@@ -133,12 +146,11 @@
      * @param data is a Parcelable instance representing the event.
      * @return true if the event was successfully logged.
      */
-    @UnsupportedAppUsage
-    public boolean log(Parcelable data) {
+    public boolean log(Event data) {
         return log(makeEv(data));
     }
 
-    private static ConnectivityMetricsEvent makeEv(Parcelable data) {
+    private static ConnectivityMetricsEvent makeEv(Event data) {
         ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
         ev.data = data;
         return ev;
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index c47650f..013e353 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -17,7 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -28,11 +29,13 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * An event recorded by IpManager when IP provisioning completes for a network or
+ * An event recorded by IpClient when IP provisioning completes for a network or
  * when a network disconnects.
  * {@hide}
  */
-public final class IpManagerEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class IpManagerEvent implements IpConnectivityLog.Event {
 
     public static final int PROVISIONING_OK                       = 1;
     public static final int PROVISIONING_FAIL                     = 2;
@@ -43,6 +46,7 @@
     public static final int ERROR_INVALID_PROVISIONING            = 7;
     public static final int ERROR_INTERFACE_NOT_FOUND             = 8;
 
+    /** @hide */
     @IntDef(value = {
             PROVISIONING_OK, PROVISIONING_FAIL, COMPLETE_LIFECYCLE,
             ERROR_STARTING_IPV4, ERROR_STARTING_IPV6, ERROR_STARTING_IPREACHABILITYMONITOR,
@@ -51,10 +55,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
 
+    /** @hide */
     public final @EventType int eventType;
+    /** @hide */
     public final long durationMs;
 
-    @UnsupportedAppUsage
     public IpManagerEvent(@EventType int eventType, long duration) {
         this.eventType = eventType;
         this.durationMs = duration;
@@ -65,17 +70,20 @@
         this.durationMs = in.readLong();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(eventType);
         out.writeLong(durationMs);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public static final Parcelable.Creator<IpManagerEvent> CREATOR
         = new Parcelable.Creator<IpManagerEvent>() {
         public IpManagerEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index 715c95e..c736297 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -16,7 +16,8 @@
 
 package android.net.metrics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -28,7 +29,9 @@
  * a neighbor probe result.
  * {@hide}
  */
-public final class IpReachabilityEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class IpReachabilityEvent implements IpConnectivityLog.Event {
 
     // Event types.
     /** A probe forced by IpReachabilityMonitor. */
@@ -47,9 +50,9 @@
     // byte 1: unused
     // byte 2: type of event: PROBE, NUD_FAILED, PROVISIONING_LOST
     // byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
+    /** @hide */
     public final int eventType;
 
-    @UnsupportedAppUsage
     public IpReachabilityEvent(int eventType) {
         this.eventType = eventType;
     }
@@ -58,16 +61,19 @@
         this.eventType = in.readInt();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(eventType);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public static final Parcelable.Creator<IpReachabilityEvent> CREATOR
         = new Parcelable.Creator<IpReachabilityEvent>() {
         public IpReachabilityEvent createFromParcel(Parcel in) {
@@ -79,18 +85,6 @@
         }
     };
 
-    /**
-     * Returns the NUD failure event type code corresponding to the given conditions.
-     */
-    @UnsupportedAppUsage
-    public static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
-        if (isFromProbe) {
-            return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
-        } else {
-            return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
-        }
-    }
-
     @Override
     public String toString() {
         int hi = eventType & 0xff00;
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index cb82fbe..f5b2ff1 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -29,7 +31,9 @@
 /**
  * {@hide}
  */
-public final class NetworkEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class NetworkEvent implements IpConnectivityLog.Event {
 
     public static final int NETWORK_CONNECTED            = 1;
     public static final int NETWORK_VALIDATED            = 2;
@@ -46,6 +50,7 @@
 
     public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12;
 
+    /** @hide */
     @IntDef(value = {
             NETWORK_CONNECTED,
             NETWORK_VALIDATED,
@@ -63,7 +68,9 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
 
+    /** @hide */
     public final @EventType int eventType;
+    /** @hide */
     public final long durationMs;
 
     public NetworkEvent(@EventType int eventType, long durationMs) {
@@ -80,17 +87,20 @@
         durationMs = in.readLong();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(eventType);
         out.writeLong(durationMs);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public static final Parcelable.Creator<NetworkEvent> CREATOR
         = new Parcelable.Creator<NetworkEvent>() {
         public NetworkEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index c41881c..d308246 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -24,7 +24,7 @@
  * An event logged when the APF packet socket receives an RA packet.
  * {@hide}
  */
-public final class RaEvent implements Parcelable {
+public final class RaEvent implements IpConnectivityLog.Event {
 
     public static final long NO_LIFETIME = -1L;
 
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 12c2305..42e8aa6 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseArray;
@@ -30,7 +32,9 @@
  * An event recorded by NetworkMonitor when sending a probe for finding captive portals.
  * {@hide}
  */
-public final class ValidationProbeEvent implements Parcelable {
+@SystemApi
+@TestApi
+public final class ValidationProbeEvent implements IpConnectivityLog.Event {
 
     public static final int PROBE_DNS       = 0;
     public static final int PROBE_HTTP      = 1;
@@ -45,20 +49,27 @@
     private static final int FIRST_VALIDATION  = 1 << 8;
     private static final int REVALIDATION      = 2 << 8;
 
+    /** @hide */
     @IntDef(value = {DNS_FAILURE, DNS_SUCCESS})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReturnCode {}
 
-    public long durationMs;
+    /** @hide */
+    public final long durationMs;
     // probeType byte format (MSB to LSB):
     // byte 0: unused
     // byte 1: unused
     // byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
     // byte 3: PROBE_* constant
-    public int probeType;
-    public @ReturnCode int returnCode;
+    /** @hide */
+    public final int probeType;
+    /** @hide */
+    public final @ReturnCode int returnCode;
 
-    public ValidationProbeEvent() {
+    private ValidationProbeEvent(long durationMs, int probeType, int returnCode) {
+        this.durationMs = durationMs;
+        this.probeType = probeType;
+        this.returnCode = returnCode;
     }
 
     private ValidationProbeEvent(Parcel in) {
@@ -67,6 +78,47 @@
         returnCode = in.readInt();
     }
 
+    /**
+     * Utility to create an instance of {@link ApfProgramEvent}.
+     */
+    public static class Builder {
+        private long mDurationMs;
+        private int mProbeType;
+        private int mReturnCode;
+
+        /**
+         * Set the duration of the probe in milliseconds.
+         */
+        public Builder setDurationMs(long durationMs) {
+            mDurationMs = durationMs;
+            return this;
+        }
+
+        /**
+         * Set the probe type based on whether it was the first validation.
+         */
+        public Builder setProbeType(int probeType, boolean firstValidation) {
+            mProbeType = makeProbeType(probeType, firstValidation);
+            return this;
+        }
+
+        /**
+         * Set the return code of the probe.
+         */
+        public Builder setReturnCode(int returnCode) {
+            mReturnCode = returnCode;
+            return this;
+        }
+
+        /**
+         * Create a new {@link ValidationProbeEvent}.
+         */
+        public ValidationProbeEvent build() {
+            return new ValidationProbeEvent(mDurationMs, mProbeType, mReturnCode);
+        }
+    }
+
+    /** @hide */
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(durationMs);
@@ -74,11 +126,13 @@
         out.writeInt(returnCode);
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public static final Parcelable.Creator<ValidationProbeEvent> CREATOR
         = new Parcelable.Creator<ValidationProbeEvent>() {
         public ValidationProbeEvent createFromParcel(Parcel in) {
@@ -90,7 +144,7 @@
         }
     };
 
-    public static int makeProbeType(int probeType, boolean firstValidation) {
+    private static int makeProbeType(int probeType, boolean firstValidation) {
         return (probeType & 0xff) | (firstValidation ? FIRST_VALIDATION : REVALIDATION);
     }
 
@@ -98,7 +152,7 @@
         return Decoder.constants.get(probeType & 0xff, "PROBE_???");
     }
 
-    public static String getValidationStage(int probeType) {
+    private static String getValidationStage(int probeType) {
         return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
     }
 
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 4077d93..c8a8e1f 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -1598,10 +1598,11 @@
     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
         int[] transports = mNetworkCapabilities.getTransportTypes();
         boolean isFirstValidation = validationStage().mIsFirstValidation;
-        ValidationProbeEvent ev = new ValidationProbeEvent();
-        ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
-        ev.returnCode = probeResult;
-        ev.durationMs = durationMs;
+        ValidationProbeEvent ev = new ValidationProbeEvent.Builder()
+                .setProbeType(probeType, isFirstValidation)
+                .setReturnCode(probeResult)
+                .setDurationMs(durationMs)
+                .build();
         mMetricsLog.log(mNetId, transports, ev);
     }
 
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index d8e2869..3351b25 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -188,7 +188,13 @@
         private final byte[] mPacket = new byte[1514];
         private final FileDescriptor mSocket;
         private final long mStart = SystemClock.elapsedRealtime();
-        private final ApfStats mStats = new ApfStats();
+
+        private int mReceivedRas = 0;
+        private int mMatchingRas = 0;
+        private int mDroppedRas = 0;
+        private int mParseErrors = 0;
+        private int mZeroLifetimeRas = 0;
+        private int mProgramUpdates = 0;
 
         private volatile boolean mStopped;
 
@@ -221,26 +227,26 @@
         }
 
         private void updateStats(ProcessRaResult result) {
-            mStats.receivedRas++;
+            mReceivedRas++;
             switch(result) {
                 case MATCH:
-                    mStats.matchingRas++;
+                    mMatchingRas++;
                     return;
                 case DROPPED:
-                    mStats.droppedRas++;
+                    mDroppedRas++;
                     return;
                 case PARSE_ERROR:
-                    mStats.parseErrors++;
+                    mParseErrors++;
                     return;
                 case ZERO_LIFETIME:
-                    mStats.zeroLifetimeRas++;
+                    mZeroLifetimeRas++;
                     return;
                 case UPDATE_EXPIRY:
-                    mStats.matchingRas++;
-                    mStats.programUpdates++;
+                    mMatchingRas++;
+                    mProgramUpdates++;
                     return;
                 case UPDATE_NEW_RA:
-                    mStats.programUpdates++;
+                    mProgramUpdates++;
                     return;
             }
         }
@@ -248,11 +254,19 @@
         private void logStats() {
             final long nowMs = SystemClock.elapsedRealtime();
             synchronized (this) {
-                mStats.durationMs = nowMs - mStart;
-                mStats.maxProgramSize = mApfCapabilities.maximumApfProgramSize;
-                mStats.programUpdatesAll = mNumProgramUpdates;
-                mStats.programUpdatesAllowingMulticast = mNumProgramUpdatesAllowingMulticast;
-                mMetricsLog.log(mStats);
+                final ApfStats stats = new ApfStats.Builder()
+                        .setReceivedRas(mReceivedRas)
+                        .setMatchingRas(mMatchingRas)
+                        .setDroppedRas(mDroppedRas)
+                        .setParseErrors(mParseErrors)
+                        .setZeroLifetimeRas(mZeroLifetimeRas)
+                        .setProgramUpdates(mProgramUpdates)
+                        .setDurationMs(nowMs - mStart)
+                        .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize)
+                        .setProgramUpdatesAll(mNumProgramUpdates)
+                        .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast)
+                        .build();
+                mMetricsLog.log(stats);
                 logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
             }
         }
@@ -863,7 +877,7 @@
     @GuardedBy("this")
     private long mLastInstalledProgramMinLifetime;
     @GuardedBy("this")
-    private ApfProgramEvent mLastInstallEvent;
+    private ApfProgramEvent.Builder mLastInstallEvent;
 
     // For debugging only. The last program installed.
     @GuardedBy("this")
@@ -1295,12 +1309,12 @@
         }
         mIpClientCallback.installPacketFilter(program);
         logApfProgramEventLocked(now);
-        mLastInstallEvent = new ApfProgramEvent();
-        mLastInstallEvent.lifetime = programMinLifetime;
-        mLastInstallEvent.filteredRas = rasToFilter.size();
-        mLastInstallEvent.currentRas = mRas.size();
-        mLastInstallEvent.programLength = program.length;
-        mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter);
+        mLastInstallEvent = new ApfProgramEvent.Builder()
+                .setLifetime(programMinLifetime)
+                .setFilteredRas(rasToFilter.size())
+                .setCurrentRas(mRas.size())
+                .setProgramLength(program.length)
+                .setFlags(mIPv4Address != null, mMulticastFilter);
     }
 
     @GuardedBy("this")
@@ -1308,13 +1322,14 @@
         if (mLastInstallEvent == null) {
             return;
         }
-        ApfProgramEvent ev = mLastInstallEvent;
+        ApfProgramEvent.Builder ev = mLastInstallEvent;
         mLastInstallEvent = null;
-        ev.actualLifetime = now - mLastTimeInstalledProgram;
-        if (ev.actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
+        final long actualLifetime = now - mLastTimeInstalledProgram;
+        ev.setActualLifetime(actualLifetime);
+        if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
             return;
         }
-        mMetricsLog.log(ev);
+        mMetricsLog.log(ev.build());
     }
 
     /**
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 8ae689b..15acc0e 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1043,6 +1043,10 @@
     }
 
     private void logState(String name, int durationMs) {
-        mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
+        final DhcpClientEvent event = new DhcpClientEvent.Builder()
+                .setMsg(name)
+                .setDurationMs(durationMs)
+                .build();
+        mMetricsLog.log(mIfaceName, event);
     }
 }
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 7e02a28..8b02156 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -16,11 +16,14 @@
 
 package android.net.ip;
 
+import static android.net.metrics.IpReachabilityEvent.NUD_FAILED;
+import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
+import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
+import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+
 import android.content.Context;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LinkProperties.ProvisioningChange;
-import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.metrics.IpConnectivityLog;
@@ -33,28 +36,19 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
-import android.system.ErrnoException;
-import android.system.OsConstants;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.DumpUtils.Dump;
 
-import java.io.InterruptedIOException;
 import java.io.PrintWriter;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 
 /**
@@ -380,7 +374,18 @@
         long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
         boolean isFromProbe = (duration < getProbeWakeLockDuration());
         boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
-        int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
+        int eventType = nudFailureEventType(isFromProbe, isProvisioningLost);
         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
     }
+
+    /**
+     * Returns the NUD failure event type code corresponding to the given conditions.
+     */
+    private static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) {
+        if (isFromProbe) {
+            return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED;
+        } else {
+            return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC;
+        }
+    }
 }
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index aaed659..3c3e7ce 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -1522,7 +1522,8 @@
     }
 
     private void verifyRaEvent(RaEvent expected) {
-        ArgumentCaptor<Parcelable> captor = ArgumentCaptor.forClass(Parcelable.class);
+        ArgumentCaptor<IpConnectivityLog.Event> captor =
+                ArgumentCaptor.forClass(IpConnectivityLog.Event.class);
         verify(mLog, atLeastOnce()).log(captor.capture());
         RaEvent got = lastRaEvent(captor.getAllValues());
         if (!raEventEquals(expected, got)) {
@@ -1530,7 +1531,7 @@
         }
     }
 
-    private RaEvent lastRaEvent(List<Parcelable> events) {
+    private RaEvent lastRaEvent(List<IpConnectivityLog.Event> events) {
         RaEvent got = null;
         for (Parcelable ev : events) {
             if (ev instanceof RaEvent) {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8359fe2..1a0cb74 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,14 +18,15 @@
 
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -34,12 +35,11 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.RouteInfo;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.RouteInfo;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
-import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
@@ -55,6 +55,13 @@
 import com.android.internal.util.BitUtils;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 
+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.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Collections;
@@ -62,13 +69,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpConnectivityMetricsTest {
@@ -154,7 +154,7 @@
     @Test
     public void testRateLimiting() {
         final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
-        final ApfProgramEvent ev = new ApfProgramEvent();
+        final ApfProgramEvent ev = new ApfProgramEvent.Builder().build();
         final long fakeTimestamp = 1;
 
         int attempt = 100; // More than burst quota, but less than buffer size.
@@ -304,26 +304,31 @@
         when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
         when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
 
-        ApfStats apfStats = new ApfStats();
-        apfStats.durationMs = 45000;
-        apfStats.receivedRas = 10;
-        apfStats.matchingRas = 2;
-        apfStats.droppedRas = 2;
-        apfStats.parseErrors = 2;
-        apfStats.zeroLifetimeRas = 1;
-        apfStats.programUpdates = 4;
-        apfStats.programUpdatesAll = 7;
-        apfStats.programUpdatesAllowingMulticast = 3;
-        apfStats.maxProgramSize = 2048;
+        ApfStats apfStats = new ApfStats.Builder()
+                .setDurationMs(45000)
+                .setReceivedRas(10)
+                .setMatchingRas(2)
+                .setDroppedRas(2)
+                .setParseErrors(2)
+                .setZeroLifetimeRas(1)
+                .setProgramUpdates(4)
+                .setProgramUpdatesAll(7)
+                .setProgramUpdatesAllowingMulticast(3)
+                .setMaxProgramSize(2048)
+                .build();
 
-        ValidationProbeEvent validationEv = new ValidationProbeEvent();
-        validationEv.durationMs = 40730;
-        validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
-        validationEv.returnCode = 204;
+        final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder()
+                .setDurationMs(40730)
+                .setProbeType(ValidationProbeEvent.PROBE_HTTP, true)
+                .setReturnCode(204)
+                .build();
 
+        final DhcpClientEvent event = new DhcpClientEvent.Builder()
+                .setMsg("SomeState")
+                .setDurationMs(192)
+                .build();
         Parcelable[] events = {
-            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
-            new DhcpClientEvent("SomeState", 192),
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event,
             new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
             validationEv,
             apfStats,
@@ -424,7 +429,7 @@
                 "  validation_probe_event <",
                 "    latency_ms: 40730",
                 "    probe_result: 204",
-                "    probe_type: 1",
+                "    probe_type: 257",
                 "  >",
                 ">",
                 "events <",