Merge "Expand NetworkMonitor metrics" into nyc-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index 697166d..f017bb0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26021,15 +26021,16 @@
   }
 
   public final class CaptivePortalStateChangeEvent extends android.net.metrics.IpConnectivityEvent implements android.os.Parcelable {
-    ctor public CaptivePortalStateChangeEvent(int);
+    ctor public CaptivePortalStateChangeEvent(int, int);
     ctor public CaptivePortalStateChangeEvent(android.os.Parcel);
     method public int describeContents();
-    method public static void logEvent(int);
+    method public static void logEvent(int, int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.metrics.CaptivePortalStateChangeEvent> CREATOR;
     field public static final int NETWORK_MONITOR_CONNECTED = 0; // 0x0
     field public static final int NETWORK_MONITOR_DISCONNECTED = 1; // 0x1
     field public static final int NETWORK_MONITOR_VALIDATED = 2; // 0x2
+    field public final int netId;
     field public final int state;
   }
 
@@ -26116,8 +26117,11 @@
     field public static final int IPCE_IPRM_PROBE_STARTED = 0; // 0x0
     field public static final int IPCE_IPRM_PROVISIONING_LOST = 3; // 0x3
     field public static final int IPCE_NETMON_BASE = 2048; // 0x800
+    field public static final int IPCE_NETMON_CAPPORT_FOUND = 2052; // 0x804
     field public static final int IPCE_NETMON_CHECK_RESULT = 2049; // 0x801
+    field public static final int IPCE_NETMON_PORTAL_PROBE = 2051; // 0x803
     field public static final int IPCE_NETMON_STATE_CHANGE = 2048; // 0x800
+    field public static final int IPCE_NETMON_VALIDATED = 2050; // 0x802
   }
 
   public final class IpManagerEvent extends android.net.metrics.IpConnectivityEvent implements android.os.Parcelable {
@@ -26143,6 +26147,19 @@
     field public final java.lang.String ifName;
   }
 
+  public final class NetworkMonitorEvent extends android.net.metrics.IpConnectivityEvent implements android.os.Parcelable {
+    ctor public NetworkMonitorEvent(android.os.Parcel);
+    method public int describeContents();
+    method public static void logCaptivePortalFound(int, long);
+    method public static void logPortalProbeEvent(int, long, int);
+    method public static void logValidated(int, long);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.metrics.NetworkMonitorEvent> CREATOR;
+    field public final long durationMs;
+    field public final int netId;
+    field public final int returnCode;
+  }
+
 }
 
 package android.net.nsd {
diff --git a/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
index aabd09b..a67589a 100644
--- a/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
+++ b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
@@ -29,17 +29,21 @@
     public static final int NETWORK_MONITOR_DISCONNECTED = 1;
     public static final int NETWORK_MONITOR_VALIDATED    = 2;
 
+    public final int netId;
     public final int state;
 
-    public CaptivePortalStateChangeEvent(int state) {
+    public CaptivePortalStateChangeEvent(int netId, int state) {
+        this.netId = netId;
         this.state = state;
     }
 
     public CaptivePortalStateChangeEvent(Parcel in) {
+        netId = in.readInt();
         state = in.readInt();
     }
 
     public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(netId);
         out.writeInt(state);
     }
 
@@ -58,7 +62,7 @@
         }
     };
 
-    public static void logEvent(int state) {
-        logEvent(IPCE_NETMON_STATE_CHANGE, new CaptivePortalStateChangeEvent(state));
+    public static void logEvent(int netId, int state) {
+        logEvent(IPCE_NETMON_STATE_CHANGE, new CaptivePortalStateChangeEvent(netId, state));
     }
 };
diff --git a/core/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
index d455a0f..0b5e354 100644
--- a/core/java/android/net/metrics/IpConnectivityEvent.java
+++ b/core/java/android/net/metrics/IpConnectivityEvent.java
@@ -50,6 +50,9 @@
 
     public static final int IPCE_NETMON_STATE_CHANGE       = IPCE_NETMON_BASE + 0;
     public static final int IPCE_NETMON_CHECK_RESULT       = IPCE_NETMON_BASE + 1;
+    public static final int IPCE_NETMON_VALIDATED          = IPCE_NETMON_BASE + 2;
+    public static final int IPCE_NETMON_PORTAL_PROBE       = IPCE_NETMON_BASE + 3;
+    public static final int IPCE_NETMON_CAPPORT_FOUND      = IPCE_NETMON_BASE + 4;
 
     public static final int IPCE_CONSRV_DEFAULT_NET_CHANGE = IPCE_CONSRV_BASE + 0;
 
diff --git a/core/java/android/net/metrics/NetworkMonitorEvent.java b/core/java/android/net/metrics/NetworkMonitorEvent.java
new file mode 100644
index 0000000..2371266
--- /dev/null
+++ b/core/java/android/net/metrics/NetworkMonitorEvent.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@hide}
+ */
+@SystemApi
+public final class NetworkMonitorEvent extends IpConnectivityEvent implements Parcelable {
+    public final int netId;
+    public final long durationMs;
+    public final int returnCode;
+
+    private NetworkMonitorEvent(int netId, long durationMs, int returnCode) {
+        this.netId = netId;
+        this.durationMs = durationMs;
+        this.returnCode = returnCode;
+    }
+
+    public NetworkMonitorEvent(Parcel in) {
+        netId = in.readInt();
+        durationMs = in.readLong();
+        returnCode = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(netId);
+        out.writeLong(durationMs);
+        out.writeInt(returnCode);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<NetworkMonitorEvent> CREATOR
+        = new Parcelable.Creator<NetworkMonitorEvent>() {
+        public NetworkMonitorEvent createFromParcel(Parcel in) {
+            return new NetworkMonitorEvent(in);
+        }
+
+        public NetworkMonitorEvent[] newArray(int size) {
+            return new NetworkMonitorEvent[size];
+        }
+    };
+
+    private static void logEvent(int eventType, int netId, long durationMs, int returnCode) {
+        logEvent(eventType, new NetworkMonitorEvent(netId, durationMs, returnCode));
+    }
+
+    public static void logValidated(int netId, long durationMs) {
+        logEvent(IPCE_NETMON_VALIDATED, netId, durationMs, 0);
+    }
+
+    public static void logPortalProbeEvent(int netId, long durationMs, int returnCode) {
+        logEvent(IPCE_NETMON_PORTAL_PROBE, netId, durationMs, returnCode);
+    }
+
+    public static void logCaptivePortalFound(int netId, long durationMs) {
+        logEvent(IPCE_NETMON_CAPPORT_FOUND, netId, durationMs, 0);
+    }
+};
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index d330756..381a110 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -36,8 +36,10 @@
 import android.net.Uri;
 import android.net.metrics.CaptivePortalCheckResultEvent;
 import android.net.metrics.CaptivePortalStateChangeEvent;
+import android.net.metrics.NetworkMonitorEvent;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.util.Stopwatch;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
@@ -79,7 +81,7 @@
  */
 public class NetworkMonitor extends StateMachine {
     private static final boolean DBG = false;
-    private static final String TAG = "NetworkMonitor";
+    private static final String TAG = NetworkMonitor.class.getSimpleName();
     private static final String DEFAULT_SERVER = "connectivitycheck.gstatic.com";
     private static final int SOCKET_TIMEOUT_MS = 10000;
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
@@ -221,6 +223,7 @@
     private final Context mContext;
     private final Handler mConnectivityServiceHandler;
     private final NetworkAgentInfo mNetworkAgentInfo;
+    private final int mNetId;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
     private final AlarmManager mAlarmManager;
@@ -246,6 +249,8 @@
 
     private final LocalLog validationLogs = new LocalLog(20); // 20 lines
 
+    private final Stopwatch mEvaluationTimer = new Stopwatch();
+
     public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
             NetworkRequest defaultRequest) {
         // Add suffix indicating which NetworkMonitor we're talking about.
@@ -254,6 +259,7 @@
         mContext = context;
         mConnectivityServiceHandler = handler;
         mNetworkAgentInfo = networkAgentInfo;
+        mNetId = mNetworkAgentInfo.network.netId;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -300,12 +306,12 @@
                     transitionTo(mLingeringState);
                     return HANDLED;
                 case CMD_NETWORK_CONNECTED:
-                    CaptivePortalStateChangeEvent.logEvent(
+                    CaptivePortalStateChangeEvent.logEvent(mNetId,
                             CaptivePortalStateChangeEvent.NETWORK_MONITOR_CONNECTED);
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_NETWORK_DISCONNECTED:
-                    CaptivePortalStateChangeEvent.logEvent(
+                    CaptivePortalStateChangeEvent.logEvent(mNetId,
                             CaptivePortalStateChangeEvent.NETWORK_MONITOR_DISCONNECTED);
                     if (mLaunchCaptivePortalAppBroadcastReceiver != null) {
                         mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver);
@@ -336,7 +342,7 @@
                             mUserDoesNotWant = true;
                             mConnectivityServiceHandler.sendMessage(obtainMessage(
                                     EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID,
-                                    mNetworkAgentInfo.network.netId, null));
+                                    mNetId, null));
                             // TODO: Should teardown network.
                             mUidResponsibleForReeval = 0;
                             transitionTo(mEvaluatingState);
@@ -356,7 +362,11 @@
     private class ValidatedState extends State {
         @Override
         public void enter() {
-            CaptivePortalStateChangeEvent.logEvent(
+            if (mEvaluationTimer.isRunning()) {
+                NetworkMonitorEvent.logValidated(mNetId, mEvaluationTimer.stop());
+                mEvaluationTimer.reset();
+            }
+            CaptivePortalStateChangeEvent.logEvent(mNetId,
                    CaptivePortalStateChangeEvent.NETWORK_MONITOR_VALIDATED);
             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
                     NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
@@ -436,6 +446,12 @@
 
         @Override
         public void enter() {
+            // If we have already started to track time spent in EvaluatingState
+            // don't reset the timer due simply to, say, commands or events that
+            // cause us to exit and re-enter EvaluatingState.
+            if (!mEvaluationTimer.isStarted()) {
+                mEvaluationTimer.start();
+            }
             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
             if (mUidResponsibleForReeval != INVALID_UID) {
                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
@@ -481,22 +497,20 @@
                     // will be unresponsive. isCaptivePortal() could be executed on another Thread
                     // if this is found to cause problems.
                     CaptivePortalProbeResult probeResult = isCaptivePortal();
-                    CaptivePortalCheckResultEvent.logEvent(mNetworkAgentInfo.network.netId,
-                            probeResult.mHttpResponseCode);
+                    CaptivePortalCheckResultEvent.logEvent(mNetId, probeResult.mHttpResponseCode);
                     if (probeResult.mHttpResponseCode == 204) {
                         transitionTo(mValidatedState);
                     } else if (probeResult.mHttpResponseCode >= 200 &&
                             probeResult.mHttpResponseCode <= 399) {
                         mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
-                                NETWORK_TEST_RESULT_INVALID, mNetworkAgentInfo.network.netId,
-                                probeResult.mRedirectUrl));
+                                NETWORK_TEST_RESULT_INVALID, mNetId, probeResult.mRedirectUrl));
                         transitionTo(mCaptivePortalState);
                     } else {
                         final Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
                         sendMessageDelayed(msg, mReevaluateDelayMs);
                         mConnectivityServiceHandler.sendMessage(obtainMessage(
-                                EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID,
-                                mNetworkAgentInfo.network.netId, probeResult.mRedirectUrl));
+                                EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId,
+                                probeResult.mRedirectUrl));
                         if (mAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
                             // Don't continue to blame UID forever.
                             TrafficStats.clearThreadStatsUid();
@@ -511,7 +525,7 @@
                     // Before IGNORE_REEVALUATE_ATTEMPTS attempts are made,
                     // ignore any re-evaluation requests. After, restart the
                     // evaluation process via EvaluatingState#enter.
-                    return mAttempts < IGNORE_REEVALUATE_ATTEMPTS ? HANDLED : NOT_HANDLED;
+                    return (mAttempts < IGNORE_REEVALUATE_ATTEMPTS) ? HANDLED : NOT_HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -553,6 +567,10 @@
 
         @Override
         public void enter() {
+            if (mEvaluationTimer.isRunning()) {
+                NetworkMonitorEvent.logCaptivePortalFound(mNetId, mEvaluationTimer.stop());
+                mEvaluationTimer.reset();
+            }
             // Don't annoy user with sign-in notifications.
             if (mDontDisplaySigninNotification) return;
             // Create a CustomIntentReceiver that sends us a
@@ -593,7 +611,8 @@
 
         @Override
         public void enter() {
-            final String cmdName = ACTION_LINGER_EXPIRED + "." + mNetworkAgentInfo.network.netId;
+            mEvaluationTimer.reset();
+            final String cmdName = ACTION_LINGER_EXPIRED + "." + mNetId;
             mWakeupMessage = makeWakeupMessage(mContext, getHandler(), cmdName, CMD_LINGER_EXPIRED);
             long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs;
             mWakeupMessage.schedule(wakeupTime);
@@ -663,6 +682,7 @@
         HttpURLConnection urlConnection = null;
         int httpResponseCode = 599;
         String redirectUrl = null;
+        final Stopwatch probeTimer = new Stopwatch().start();
         try {
             URL url = new URL(getCaptivePortalServerUrl(mContext));
             // On networks with a PAC instead of fetching a URL that should result in a 204
@@ -759,6 +779,7 @@
                 urlConnection.disconnect();
             }
         }
+        NetworkMonitorEvent.logPortalProbeEvent(mNetId, probeTimer.stop(), httpResponseCode);
         return new CaptivePortalProbeResult(httpResponseCode, redirectUrl);
     }
 
diff --git a/services/net/java/android/net/util/Stopwatch.java b/services/net/java/android/net/util/Stopwatch.java
new file mode 100644
index 0000000..cb15ee5
--- /dev/null
+++ b/services/net/java/android/net/util/Stopwatch.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.os.SystemClock;
+
+
+/**
+ * @hide
+ */
+public class Stopwatch {
+    private long mStartTimeMs;
+    private long mStopTimeMs;
+
+    public boolean isStarted() {
+        return (mStartTimeMs > 0);
+    }
+
+    public boolean isStopped() {
+        return (mStopTimeMs > 0);
+    }
+
+    public boolean isRunning() {
+        return (isStarted() && !isStopped());
+    }
+
+    // Returning |this| makes possible the following usage pattern:
+    //
+    //     Stopwatch s = new Stopwatch().start();
+    public Stopwatch start() {
+        if (!isStarted()) {
+            mStartTimeMs = SystemClock.elapsedRealtime();
+        }
+        return this;
+    }
+
+    // Returns the total time recorded, in milliseconds, or 0 if not started.
+    public long stop() {
+        if (isRunning()) {
+            mStopTimeMs = SystemClock.elapsedRealtime();
+        }
+        // Return either the delta after having stopped, or 0.
+        return (mStopTimeMs - mStartTimeMs);
+    }
+
+    // Returns the total time recorded to date, in milliseconds.
+    // If the Stopwatch is not running, returns the same value as stop(),
+    // i.e. either the total time recorded before stopping or 0.
+    public long lap() {
+        if (isRunning()) {
+            return (SystemClock.elapsedRealtime() - mStartTimeMs);
+        } else {
+            return stop();
+        }
+    }
+
+    public void reset() {
+        mStartTimeMs = 0;
+        mStopTimeMs = 0;
+    }
+}