diff --git a/api/system-current.txt b/api/system-current.txt index 697166d33812d..f017bb0228795 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -26021,15 +26021,16 @@ package android.net.metrics { } 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 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 @@ package android.net.metrics { 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 @@ package android.net.metrics { 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 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 aabd09b1ec07f..a67589af2563a 100644 --- a/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java +++ b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java @@ -29,17 +29,21 @@ public final class CaptivePortalStateChangeEvent extends IpConnectivityEvent imp 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 final class CaptivePortalStateChangeEvent extends IpConnectivityEvent imp } }; - 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 d455a0fcdaa5d..0b5e3544fe8c6 100644 --- a/core/java/android/net/metrics/IpConnectivityEvent.java +++ b/core/java/android/net/metrics/IpConnectivityEvent.java @@ -50,6 +50,9 @@ public abstract class IpConnectivityEvent { 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 0000000000000..23712661b9d5f --- /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 CREATOR + = new Parcelable.Creator() { + 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 d33075634ca04..381a110dd20b4 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.TrafficStats; 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 @@ import java.util.Random; */ 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { @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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { @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 @@ public class NetworkMonitor extends StateMachine { @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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 0000000000000..cb15ee580a73e --- /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; + } +}