Expose measurement results to interested callers.

With this it's possible for callers to:

    Network network = ...
    LinkProperties lp = ...

    NetworkDiagnostics netdiag = new NetworkDiagnostics(network, lp, 5000);
    netdiag.startMeasurements();
    netdiag.waitForMeasurements();

    boolean result = true;
    for (Measurement m : netdiag.getMeasurements()) {
        if (!m.checkSucceeded()) {
            result = false;
            // Additional error handling here.
        }
    }

    return result;

Bug: 21832299
Bug: 26780890
Change-Id: Iaf7ff029e2c6a998b574c23d0dcde8d57a467a22
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 5fd39c0..dc62609 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -49,7 +49,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
@@ -107,27 +109,33 @@
     // so callers can wait for completion.
     private final CountDownLatch mCountDownLatch;
 
-    private class Measurement {
+    public class Measurement {
         private static final String SUCCEEDED = "SUCCEEDED";
         private static final String FAILED = "FAILED";
 
-        // TODO: Refactor to make these private for better encapsulation.
-        public String description = "";
-        public long startTime;
-        public long finishTime;
-        public String result = "";
-        public Thread thread;
+        private boolean succeeded;
 
-        public void recordSuccess(String msg) {
+        // Package private.  TODO: investigate better encapsulation.
+        String description = "";
+        long startTime;
+        long finishTime;
+        String result = "";
+        Thread thread;
+
+        public boolean checkSucceeded() { return succeeded; }
+
+        void recordSuccess(String msg) {
             maybeFixupTimes();
+            succeeded = true;
             result = SUCCEEDED + ": " + msg;
             if (mCountDownLatch != null) {
                 mCountDownLatch.countDown();
             }
         }
 
-        public void recordFailure(String msg) {
+        void recordFailure(String msg) {
             maybeFixupTimes();
+            succeeded = false;
             result = FAILED + ": " + msg;
             if (mCountDownLatch != null) {
                 mCountDownLatch.countDown();
@@ -265,6 +273,51 @@
         } catch (InterruptedException ignored) {}
     }
 
+    public List<Measurement> getMeasurements() {
+        // TODO: Consider moving waitForMeasurements() in here to minimize the
+        // chance of caller errors.
+
+        ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
+
+        // Sort measurements IPv4 first.
+        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
+                mExplicitSourceIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+
+        // IPv6 measurements second.
+        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
+                mExplicitSourceIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+
+        return measurements;
+    }
+
     public void dump(IndentingPrintWriter pw) {
         pw.println(TAG + ":" + mDescription);
         final long unfinished = mCountDownLatch.getCount();
@@ -276,30 +329,13 @@
         }
 
         pw.increaseIndent();
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
-                pw.println(entry.getValue().toString());
-            }
+
+        String prefix;
+        for (Measurement m : getMeasurements()) {
+            prefix = m.checkSucceeded() ? "." : "F";
+            pw.println(prefix + "  " + m.toString());
         }
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
-        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
-                mExplicitSourceIcmpChecks.entrySet()) {
-            pw.println(entry.getValue().toString());
-        }
-        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
-        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
+
         pw.decreaseIndent();
     }