Merge "Implement Ikev2VpnRunner"
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index fb35b4b..8afeb30 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,7 +15,9 @@
*/
package android.net;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.IBinder;
@@ -23,6 +25,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
* activity to indicate to the system different outcomes of captive portal sign in. This class is
@@ -76,6 +80,17 @@
private final IBinder mBinder;
/** @hide */
+ @IntDef(value = {
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS,
+ MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR,
+ })
+ public @interface EventId {
+ }
+
+ /** @hide */
public CaptivePortal(@NonNull IBinder binder) {
mBinder = binder;
}
@@ -153,6 +168,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void reevaluateNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
@@ -168,7 +184,7 @@
*/
@SystemApi
@TestApi
- public void logEvent(int eventId, @NonNull String packageName) {
+ public void logEvent(@EventId int eventId, @NonNull String packageName) {
try {
ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
} catch (RemoteException e) {
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index b128ea7..d009144 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -202,7 +202,7 @@
*/
@NetworkProbe
public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK =
- "networkProbesAttemped";
+ "networkProbesAttempted";
/** @hide */
@StringDef(prefix = {"KEY_"}, value = {
@@ -252,8 +252,8 @@
@NonNull PersistableBundle additionalInfo) {
mNetwork = network;
mReportTimestamp = reportTimestamp;
- mLinkProperties = linkProperties;
- mNetworkCapabilities = networkCapabilities;
+ mLinkProperties = new LinkProperties(linkProperties);
+ mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
mAdditionalInfo = additionalInfo;
}
@@ -433,6 +433,12 @@
/** The detection method used to identify the suspected data stall */
@DetectionMethod private final int mDetectionMethod;
+ /** LinkProperties available on the Network at the reported timestamp */
+ @NonNull private final LinkProperties mLinkProperties;
+
+ /** NetworkCapabilities available on the Network at the reported timestamp */
+ @NonNull private final NetworkCapabilities mNetworkCapabilities;
+
/** PersistableBundle that may contain additional information on the suspected data stall */
@NonNull private final PersistableBundle mStallDetails;
@@ -446,16 +452,23 @@
* @param network The Network for which this DataStallReport applies
* @param reportTimestamp The timestamp for the report
* @param detectionMethod The detection method used to identify this data stall
+ * @param linkProperties The LinkProperties available on network at reportTimestamp
+ * @param networkCapabilities The NetworkCapabilities available on network at
+ * reportTimestamp
* @param stallDetails A PersistableBundle that may contain additional info about the report
*/
public DataStallReport(
@NonNull Network network,
long reportTimestamp,
@DetectionMethod int detectionMethod,
+ @NonNull LinkProperties linkProperties,
+ @NonNull NetworkCapabilities networkCapabilities,
@NonNull PersistableBundle stallDetails) {
mNetwork = network;
mReportTimestamp = reportTimestamp;
mDetectionMethod = detectionMethod;
+ mLinkProperties = new LinkProperties(linkProperties);
+ mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
mStallDetails = stallDetails;
}
@@ -488,6 +501,26 @@
}
/**
+ * Returns the LinkProperties available when this report was taken.
+ *
+ * @return LinkProperties available on the Network at the reported timestamp
+ */
+ @NonNull
+ public LinkProperties getLinkProperties() {
+ return new LinkProperties(mLinkProperties);
+ }
+
+ /**
+ * Returns the NetworkCapabilities when this report was taken.
+ *
+ * @return NetworkCapabilities available on the Network at the reported timestamp
+ */
+ @NonNull
+ public NetworkCapabilities getNetworkCapabilities() {
+ return new NetworkCapabilities(mNetworkCapabilities);
+ }
+
+ /**
* Returns a PersistableBundle with additional info for this report.
*
* <p>Gets a bundle with details about the suspected data stall including information
@@ -513,12 +546,20 @@
return mReportTimestamp == that.mReportTimestamp
&& mDetectionMethod == that.mDetectionMethod
&& mNetwork.equals(that.mNetwork)
+ && mLinkProperties.equals(that.mLinkProperties)
+ && mNetworkCapabilities.equals(that.mNetworkCapabilities)
&& persistableBundleEquals(mStallDetails, that.mStallDetails);
}
@Override
public int hashCode() {
- return Objects.hash(mNetwork, mReportTimestamp, mDetectionMethod, mStallDetails);
+ return Objects.hash(
+ mNetwork,
+ mReportTimestamp,
+ mDetectionMethod,
+ mLinkProperties,
+ mNetworkCapabilities,
+ mStallDetails);
}
/** {@inheritDoc} */
@@ -533,6 +574,8 @@
dest.writeParcelable(mNetwork, flags);
dest.writeLong(mReportTimestamp);
dest.writeInt(mDetectionMethod);
+ dest.writeParcelable(mLinkProperties, flags);
+ dest.writeParcelable(mNetworkCapabilities, flags);
dest.writeParcelable(mStallDetails, flags);
}
@@ -544,6 +587,8 @@
in.readParcelable(null),
in.readLong(),
in.readInt(),
+ in.readParcelable(null),
+ in.readParcelable(null),
in.readParcelable(null));
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 94eda01..212c716 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3341,7 +3341,6 @@
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
-
try {
return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
} catch (RemoteException e) {
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index aae9fd4..61a1484 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -262,32 +262,60 @@
*/
public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
- // TODO : remove these two constructors. They are a stopgap measure to help sheperding a number
- // of dependent changes that would conflict throughout the automerger graph. Having these
- // temporarily helps with the process of going through with all these dependent changes across
- // the entire tree.
- /** @hide TODO: decide which of these to expose. */
+ /** @hide TODO: remove and replace usage with the public constructor. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
}
- /** @hide TODO: decide which of these to expose. */
+ /** @hide TODO: remove and replace usage with the public constructor. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
}
- /** @hide TODO: decide which of these to expose. */
+ /** @hide TODO: remove and replace usage with the public constructor. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
this(looper, context, logTag, ni, nc, lp, score, null, providerId);
}
- /** @hide TODO: decide which of these to expose. */
+ /** @hide TODO: remove and replace usage with the public constructor. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
int providerId) {
+ this(looper, context, logTag, nc, lp, score, config, providerId, ni);
+ }
+
+ private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
+ // The subtype can be changed with (TODO) setLegacySubtype, but it starts
+ // with the type and an empty description.
+ return new NetworkInfo(config.legacyType, config.legacyType, config.legacyTypeName, "");
+ }
+
+ /**
+ * Create a new network agent.
+ * @param context a {@link Context} to get system services from.
+ * @param looper the {@link Looper} on which to invoke the callbacks.
+ * @param logTag the tag for logs
+ * @param nc the initial {@link NetworkCapabilities} of this network. Update with
+ * sendNetworkCapabilities.
+ * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties.
+ * @param score the initial score of this network. Update with sendNetworkScore.
+ * @param config an immutable {@link NetworkAgentConfig} for this agent.
+ * @param provider the {@link NetworkProvider} managing this agent.
+ */
+ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+ @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
+ @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+ this(looper, context, logTag, nc, lp, score, config,
+ provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
+ getLegacyNetworkInfo(config));
+ }
+
+ private NetworkAgent(Looper looper, Context context, String logTag, NetworkCapabilities nc,
+ LinkProperties lp, int score, NetworkAgentConfig config, int providerId,
+ NetworkInfo ni) {
mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mContext = context;
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
index abc6b67..2c5a113 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -21,12 +21,10 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
/**
* Allows a network transport to provide the system with policy and configuration information about
- * a particular network when registering a {@link NetworkAgent}. This information cannot change once
- * the agent is registered.
+ * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered.
*
* @hide
*/
@@ -120,6 +118,19 @@
}
/**
+ * The legacy type of this network agent, or TYPE_NONE if unset.
+ * @hide
+ */
+ public int legacyType = ConnectivityManager.TYPE_NONE;
+
+ /**
+ * @return the legacy type
+ */
+ public int getLegacyType() {
+ return legacyType;
+ }
+
+ /**
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
@@ -127,6 +138,21 @@
*/
public boolean hasShownBroken;
+ /**
+ * The name of the legacy network type. It's a free-form string used in logging.
+ * @hide
+ */
+ @NonNull
+ public String legacyTypeName = "";
+
+ /**
+ * @return the name of the legacy network type. It's a free-form string used in logging.
+ */
+ @NonNull
+ public String getLegacyTypeName() {
+ return legacyTypeName;
+ }
+
/** @hide */
public NetworkAgentConfig() {
}
@@ -140,6 +166,8 @@
subscriberId = nac.subscriberId;
provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
skip464xlat = nac.skip464xlat;
+ legacyType = nac.legacyType;
+ legacyTypeName = nac.legacyTypeName;
}
}
@@ -185,6 +213,29 @@
}
/**
+ * Sets the legacy type for this network.
+ *
+ * @param legacyType the type
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacyType(int legacyType) {
+ mConfig.legacyType = legacyType;
+ return this;
+ }
+
+ /**
+ * Sets the name of the legacy type of the agent. It's a free-form string used in logging.
+ * @param legacyTypeName the name
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacyTypeName(@NonNull String legacyTypeName) {
+ mConfig.legacyTypeName = legacyTypeName;
+ return this;
+ }
+
+ /**
* Returns the constructed {@link NetworkAgentConfig} object.
*/
@NonNull
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 71f751c..e2b8ad9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -68,6 +68,7 @@
import android.net.CaptivePortal;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
import android.net.ICaptivePortal;
import android.net.IConnectivityDiagnosticsCallback;
@@ -154,7 +155,6 @@
import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
@@ -194,6 +194,7 @@
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Vpn;
@@ -230,6 +231,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
+import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
@@ -578,6 +580,7 @@
final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler;
private final DnsManager mDnsManager;
+ private final NetworkRanker mNetworkRanker;
private boolean mSystemReady;
private Intent mInitialBroadcast;
@@ -726,9 +729,9 @@
private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
boolean isDefaultNetwork) {
if (DBG) {
- log("Sending " + state +
- " broadcast for type " + type + " " + nai.name() +
- " isDefaultNetwork=" + isDefaultNetwork);
+ log("Sending " + state
+ + " broadcast for type " + type + " " + nai.toShortString()
+ + " isDefaultNetwork=" + isDefaultNetwork);
}
}
@@ -808,14 +811,6 @@
}
}
- private String naiToString(NetworkAgentInfo nai) {
- String name = nai.name();
- String state = (nai.networkInfo != null) ?
- nai.networkInfo.getState() + "/" + nai.networkInfo.getDetailedState() :
- "???/???";
- return name + " " + state;
- }
-
public void dump(IndentingPrintWriter pw) {
pw.println("mLegacyTypeTracker:");
pw.increaseIndent();
@@ -830,7 +825,7 @@
for (int type = 0; type < mTypeLists.length; type++) {
if (mTypeLists[type] == null || mTypeLists[type].isEmpty()) continue;
for (NetworkAgentInfo nai : mTypeLists[type]) {
- pw.println(type + " " + naiToString(nai));
+ pw.println(type + " " + nai.toShortString());
}
}
}
@@ -965,6 +960,7 @@
mMetricsLog = logger;
mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
+ mNetworkRanker = new NetworkRanker();
NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
mNetworkRequests.put(mDefaultRequest, defaultNRI);
mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
@@ -2785,7 +2781,7 @@
nai.everCaptivePortalDetected |= visible;
if (nai.lastCaptivePortalDetected &&
Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
- if (DBG) log("Avoiding captive portal network: " + nai.name());
+ if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
nai.asyncChannel.sendMessage(
NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
teardownUnneededNetwork(nai);
@@ -2844,7 +2840,7 @@
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
- log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
+ log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
}
if (valid != nai.lastValidated) {
if (wasDefault) {
@@ -3009,6 +3005,21 @@
}
@Override
+ public void notifyDataStallSuspected(
+ long timestampMillis, int detectionMethod, PersistableBundle extras) {
+ final Message msg =
+ mConnectivityDiagnosticsHandler.obtainMessage(
+ ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED,
+ detectionMethod, mNetId, timestampMillis);
+ msg.setData(new Bundle(extras));
+
+ // NetworkStateTrackerHandler currently doesn't take any actions based on data
+ // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid
+ // the cost of going through two handlers.
+ mConnectivityDiagnosticsHandler.sendMessage(msg);
+ }
+
+ @Override
public int getInterfaceVersion() {
return this.VERSION;
}
@@ -3097,23 +3108,32 @@
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
- private void updateLingerState(NetworkAgentInfo nai, long now) {
+ /**
+ * Updates the linger state from the network requests inside the NAI.
+ * @param nai the agent info to update
+ * @param now the timestamp of the event causing this update
+ * @return whether the network was lingered as a result of this update
+ */
+ private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) {
// 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
// 2. If the network was lingering and there are now requests, unlinger it.
// 3. If this network is unneeded (which implies it is not lingering), and there is at least
// one lingered request, start lingering.
nai.updateLingerTimer();
if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) {
- if (DBG) log("Unlingering " + nai.name());
+ if (DBG) log("Unlingering " + nai.toShortString());
nai.unlinger();
logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
} else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) {
- int lingerTime = (int) (nai.getLingerExpiry() - now);
- if (DBG) log("Lingering " + nai.name() + " for " + lingerTime + "ms");
+ if (DBG) {
+ final int lingerTime = (int) (nai.getLingerExpiry() - now);
+ log("Lingering " + nai.toShortString() + " for " + lingerTime + "ms");
+ }
nai.linger();
logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+ return true;
}
+ return false;
}
private void handleAsyncChannelHalfConnect(Message msg) {
@@ -3171,7 +3191,7 @@
private void disconnectAndDestroyNetwork(NetworkAgentInfo nai) {
ensureRunningOnConnectivityServiceThread();
if (DBG) {
- log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests());
+ log(nai.toShortString() + " disconnected, was satisfying " + nai.numNetworkRequests());
}
// Clear all notifications of this network.
mNotifier.clearNotification(nai.network.netId);
@@ -3229,7 +3249,7 @@
mDefaultNetworkNai = null;
updateDataActivityTracking(null /* newNetwork */, nai);
notifyLockdownVpn(nai);
- ensureNetworkTransitionWakelock(nai.name());
+ ensureNetworkTransitionWakelock(nai.toShortString());
}
mLegacyTypeTracker.remove(nai, wasDefault);
if (!nai.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
@@ -3452,14 +3472,17 @@
boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.request.requestId);
if (VDBG || DDBG) {
- log(" Removing from current network " + nai.name() +
- ", leaving " + nai.numNetworkRequests() + " requests.");
+ log(" Removing from current network " + nai.toShortString()
+ + ", leaving " + nai.numNetworkRequests() + " requests.");
}
// If there are still lingered requests on this network, don't tear it down,
// but resume lingering instead.
- updateLingerState(nai, SystemClock.elapsedRealtime());
+ final long now = SystemClock.elapsedRealtime();
+ if (updateLingerState(nai, now)) {
+ notifyNetworkLosing(nai, now);
+ }
if (unneeded(nai, UnneededFor.TEARDOWN)) {
- if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
+ if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
teardownUnneededNetwork(nai);
} else {
wasKept = true;
@@ -3716,6 +3739,7 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+ checkNetworkStackPermission();
nm.forceReevaluation(Binder.getCallingUid());
}
}
@@ -3794,7 +3818,7 @@
pw.increaseIndent();
for (NetworkAgentInfo nai : networksSortedById()) {
if (nai.avoidUnvalidated) {
- pw.println(nai.name());
+ pw.println(nai.toShortString());
}
}
pw.decreaseIndent();
@@ -3906,7 +3930,7 @@
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
NetworkCapabilities nc = nai.networkCapabilities;
- if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
+ if (DBG) log("handleNetworkUnvalidated " + nai.toShortString() + " cap=" + nc);
if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return;
@@ -4182,6 +4206,19 @@
final int connectivityInfo = encodeBool(hasConnectivity);
mHandler.sendMessage(
mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+
+ final NetworkAgentInfo nai;
+ if (network == null) {
+ nai = getDefaultNetwork();
+ } else {
+ nai = getNetworkAgentInfoForNetwork(network);
+ }
+ if (nai != null) {
+ mConnectivityDiagnosticsHandler.sendMessage(
+ mConnectivityDiagnosticsHandler.obtainMessage(
+ ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
+ connectivityInfo, 0, nai));
+ }
}
private void handleReportNetworkConnectivity(
@@ -5307,7 +5344,7 @@
detail = reason;
}
log(String.format("updateSignalStrengthThresholds: %s, sending %s to %s",
- detail, Arrays.toString(thresholdsArray.toArray()), nai.name()));
+ detail, Arrays.toString(thresholdsArray.toArray()), nai.toShortString()));
}
nai.asyncChannel.sendMessage(
@@ -6231,9 +6268,9 @@
// newLp is already a defensive copy.
newLp.ensureDirectlyConnectedRoutes();
if (VDBG || DDBG) {
- log("Update of LinkProperties for " + nai.name() +
- "; created=" + nai.created +
- "; everConnected=" + nai.everConnected);
+ log("Update of LinkProperties for " + nai.toShortString()
+ + "; created=" + nai.created
+ + "; everConnected=" + nai.everConnected);
}
updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
}
@@ -6403,7 +6440,7 @@
loge("Unknown NetworkAgentInfo in handleLingerComplete");
return;
}
- if (DBG) log("handleLingerComplete for " + oldNetwork.name());
+ if (DBG) log("handleLingerComplete for " + oldNetwork.toShortString());
// If we get here it means that the last linger timeout for this network expired. So there
// must be no other active linger timers, and we must stop lingering.
@@ -6476,18 +6513,7 @@
}
// An accumulator class to gather the list of changes that result from a rematch.
- // TODO : enrich to represent an entire set of changes to apply.
private static class NetworkReassignment {
- static class NetworkBgStatePair {
- @NonNull final NetworkAgentInfo mNetwork;
- final boolean mOldBackground;
- NetworkBgStatePair(@NonNull final NetworkAgentInfo network,
- final boolean oldBackground) {
- mNetwork = network;
- mOldBackground = oldBackground;
- }
- }
-
static class RequestReassignment {
@NonNull public final NetworkRequestInfo mRequest;
@Nullable public final NetworkAgentInfo mOldNetwork;
@@ -6499,44 +6525,34 @@
mOldNetwork = oldNetwork;
mNewNetwork = newNetwork;
}
+
+ public String toString() {
+ return mRequest.request.requestId + " : "
+ + (null != mOldNetwork ? mOldNetwork.network.netId : "null")
+ + " → " + (null != mNewNetwork ? mNewNetwork.network.netId : "null");
+ }
}
- @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>();
- @NonNull private final Map<NetworkRequestInfo, RequestReassignment> mReassignments =
- new ArrayMap<>();
-
- @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() {
- return mRematchedNetworks;
- }
+ @NonNull private final ArrayList<RequestReassignment> mReassignments = new ArrayList<>();
@NonNull Iterable<RequestReassignment> getRequestReassignments() {
- return mReassignments.values();
+ return mReassignments;
}
void addRequestReassignment(@NonNull final RequestReassignment reassignment) {
- final RequestReassignment oldChange = mReassignments.get(reassignment.mRequest);
- if (null == oldChange) {
- mReassignments.put(reassignment.mRequest, reassignment);
- return;
+ if (!Build.IS_USER) {
+ // The code is never supposed to add two reassignments of the same request. Make
+ // sure this stays true, but without imposing this expensive check on all
+ // reassignments on all user devices.
+ for (final RequestReassignment existing : mReassignments) {
+ if (existing.mRequest.equals(reassignment.mRequest)) {
+ throw new IllegalStateException("Trying to reassign ["
+ + reassignment + "] but already have ["
+ + existing + "]");
+ }
+ }
}
- if (oldChange.mNewNetwork != reassignment.mOldNetwork) {
- throw new IllegalArgumentException("Reassignment <" + reassignment.mRequest + "> ["
- + reassignment.mOldNetwork + " -> " + reassignment.mNewNetwork
- + "] conflicts with ["
- + oldChange.mOldNetwork + " -> " + oldChange.mNewNetwork + "]");
- }
- // There was already a note to reassign this request from a network A to a network B,
- // and a reassignment is added from network B to some other network C. The following
- // synthesizes the merged reassignment that goes A -> C. An interesting (but not
- // special) case to think about is when B is null, which can happen when the rematch
- // loop notices the current satisfier doesn't satisfy the request any more, but
- // hasn't yet encountered another network that could.
- mReassignments.put(reassignment.mRequest, new RequestReassignment(reassignment.mRequest,
- oldChange.mOldNetwork, reassignment.mNewNetwork));
- }
-
- void addRematchedNetwork(@NonNull final NetworkBgStatePair network) {
- mRematchedNetworks.add(network);
+ mReassignments.add(reassignment);
}
// Will return null if this reassignment does not change the network assigned to
@@ -6548,121 +6564,80 @@
}
return null;
}
- }
- // TODO : remove this when it's useless
- @NonNull private NetworkReassignment computeInitialReassignment() {
- final NetworkReassignment change = new NetworkReassignment();
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- change.addRequestReassignment(new NetworkReassignment.RequestReassignment(nri,
- nri.mSatisfier, nri.mSatisfier));
+ public String toString() {
+ final StringJoiner sj = new StringJoiner(", " /* delimiter */,
+ "NetReassign [" /* prefix */, "]" /* suffix */);
+ if (mReassignments.isEmpty()) return sj.add("no changes").toString();
+ for (final RequestReassignment rr : getRequestReassignments()) {
+ sj.add(rr.toString());
+ }
+ return sj.toString();
}
- return change;
+
+ public String debugString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("NetworkReassignment :");
+ if (mReassignments.isEmpty()) return sb.append(" no changes").toString();
+ for (final RequestReassignment rr : getRequestReassignments()) {
+ sb.append("\n ").append(rr);
+ }
+ return sb.append("\n").toString();
+ }
}
- private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork(
- @NonNull final NetworkAgentInfo newNetwork) {
- final int score = newNetwork.getCurrentScore();
- final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests = new ArrayMap<>();
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- // Process requests in the first pass and listens in the second pass. This allows us to
- // change a network's capabilities depending on which requests it has. This is only
- // correct if the change in capabilities doesn't affect whether the network satisfies
- // requests or not, and doesn't affect the network's score.
- if (nri.request.isListen()) continue;
-
- final NetworkAgentInfo currentNetwork = nri.mSatisfier;
- final boolean satisfies = newNetwork.satisfies(nri.request);
- if (newNetwork == currentNetwork && satisfies) continue;
-
- // check if it satisfies the NetworkCapabilities
- if (VDBG) log(" checking if request is satisfied: " + nri.request);
- if (satisfies) {
- // next check if it's better than any current network we're using for
- // this request
+ private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
+ @Nullable final NetworkAgentInfo previousSatisfier,
+ @Nullable final NetworkAgentInfo newSatisfier,
+ final long now) {
+ if (newSatisfier != null) {
+ if (VDBG) log("rematch for " + newSatisfier.toShortString());
+ if (previousSatisfier != null) {
if (VDBG || DDBG) {
- log("currentScore = "
- + (currentNetwork != null ? currentNetwork.getCurrentScore() : 0)
- + ", newScore = " + score);
+ log(" accepting network in place of " + previousSatisfier.toShortString());
}
- if (currentNetwork == null || currentNetwork.getCurrentScore() < score) {
- reassignedRequests.put(nri, newNetwork);
- }
- } else if (newNetwork == currentNetwork) {
- reassignedRequests.put(nri, null);
+ previousSatisfier.removeRequest(nri.request.requestId);
+ previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
+ } else {
+ if (VDBG || DDBG) log(" accepting network in place of null");
}
+ newSatisfier.unlingerRequest(nri.request);
+ if (!newSatisfier.addRequest(nri.request)) {
+ Slog.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
+ + nri.request);
+ }
+ } else {
+ if (DBG) {
+ log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
+ + " request " + nri.request.requestId);
+ }
+ previousSatisfier.removeRequest(nri.request.requestId);
}
- return reassignedRequests;
+ nri.mSatisfier = newSatisfier;
}
- // Handles a network appearing or improving its score.
- //
- // - Evaluates all current NetworkRequests that can be
- // satisfied by newNetwork, and reassigns to newNetwork
- // any such requests for which newNetwork is the best.
- //
- // - Writes into the passed reassignment object all changes that should be done for
- // rematching this network with all requests, to be applied later.
- //
- // TODO : stop writing to the passed reassignment. This is temporarily more useful, but
- // it's unidiomatic Java and it's hard to read.
- //
- // @param changes a currently-building list of changes to write to
- // @param newNetwork is the network to be matched against NetworkRequests.
- // @param now the time the rematch starts, as returned by SystemClock.elapsedRealtime();
- private void rematchNetworkAndRequests(@NonNull final NetworkReassignment changes,
- @NonNull final NetworkAgentInfo newNetwork, final long now) {
+ @NonNull
+ private NetworkReassignment computeNetworkReassignment() {
ensureRunningOnConnectivityServiceThread();
- if (!newNetwork.everConnected) return;
+ final NetworkReassignment changes = new NetworkReassignment();
- changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork,
- newNetwork.isBackgroundNetwork()));
-
- if (VDBG || DDBG) log("rematching " + newNetwork.name());
-
- final ArrayMap<NetworkRequestInfo, NetworkAgentInfo> reassignedRequests =
- computeRequestReassignmentForNetwork(newNetwork);
-
- // Find and migrate to this Network any NetworkRequests for
- // which this network is now the best.
- for (final Map.Entry<NetworkRequestInfo, NetworkAgentInfo> entry :
- reassignedRequests.entrySet()) {
- final NetworkRequestInfo nri = entry.getKey();
- final NetworkAgentInfo previousSatisfier = nri.mSatisfier;
- final NetworkAgentInfo newSatisfier = entry.getValue();
- changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
- nri, previousSatisfier, newSatisfier));
- if (newSatisfier != null) {
- if (VDBG) log("rematch for " + newSatisfier.name());
- if (previousSatisfier != null) {
- if (VDBG || DDBG) {
- log(" accepting network in place of " + previousSatisfier.name());
- }
- previousSatisfier.removeRequest(nri.request.requestId);
- previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs);
- } else {
- if (VDBG || DDBG) log(" accepting network in place of null");
- }
- newSatisfier.unlingerRequest(nri.request);
- if (!newSatisfier.addRequest(nri.request)) {
- Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
- }
- } else {
- // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",
- // mark it as no longer satisfying "nri". Because networks are processed by
- // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will
- // match "newNetwork" before this loop will encounter a "currentNetwork" with higher
- // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".
- // This means this code doesn't have to handle the case where "currentNetwork" no
- // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork".
- if (DBG) {
- log("Network " + newNetwork.name() + " stopped satisfying" +
- " request " + nri.request.requestId);
- }
- newNetwork.removeRequest(nri.request.requestId);
- }
- nri.mSatisfier = newSatisfier;
+ // Gather the list of all relevant agents and sort them by score.
+ final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ if (!nai.everConnected) continue;
+ nais.add(nai);
}
+
+ for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.request.isListen()) continue;
+ final NetworkAgentInfo bestNetwork = mNetworkRanker.getBestNetwork(nri.request, nais);
+ if (bestNetwork != nri.mSatisfier) {
+ // bestNetwork may be null if no network can satisfy this request.
+ changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
+ nri, nri.mSatisfier, bestNetwork));
+ }
+ }
+ return changes;
}
/**
@@ -6670,25 +6645,38 @@
* being disconnected.
*/
private void rematchAllNetworksAndRequests() {
- // TODO: This may be slow, and should be optimized. Unfortunately at this moment the
- // processing is network-major instead of request-major (the code iterates through all
- // networks, then for each it iterates for all requests), which is a problem for re-scoring
- // requests. Once the code has switched to a request-major iteration style, this can
- // be optimized to only do the processing needed.
+ // TODO: This may be slow, and should be optimized.
final long now = SystemClock.elapsedRealtime();
- final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
+ final NetworkReassignment changes = computeNetworkReassignment();
+ if (VDBG || DDBG) {
+ log(changes.debugString());
+ } else if (DBG) {
+ log(changes.toString()); // Shorter form, only one line of log
+ }
+ applyNetworkReassignment(changes, now);
+ }
- final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
- new NetworkAgentInfo[mNetworkAgentInfos.size()]);
- // Rematch higher scoring networks first to prevent requests first matching a lower
- // scoring network and then a higher scoring network, which could produce multiple
- // callbacks.
- Arrays.sort(nais);
- final NetworkReassignment changes = computeInitialReassignment();
+ private void applyNetworkReassignment(@NonNull final NetworkReassignment changes,
+ final long now) {
+ final Collection<NetworkAgentInfo> nais = mNetworkAgentInfos.values();
+
+ // Since most of the time there are only 0 or 1 background networks, it would probably
+ // be more efficient to just use an ArrayList here. TODO : measure performance
+ final ArraySet<NetworkAgentInfo> oldBgNetworks = new ArraySet<>();
for (final NetworkAgentInfo nai : nais) {
- rematchNetworkAndRequests(changes, nai, now);
+ if (nai.isBackgroundNetwork()) oldBgNetworks.add(nai);
}
+ // First, update the lists of satisfied requests in the network agents. This is necessary
+ // because some code later depends on this state to be correct, most prominently computing
+ // the linger status.
+ for (final NetworkReassignment.RequestReassignment event :
+ changes.getRequestReassignments()) {
+ updateSatisfiersForRematchRequest(event.mRequest, event.mOldNetwork,
+ event.mNewNetwork, now);
+ }
+
+ final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
final NetworkRequestInfo defaultRequestInfo = mNetworkRequests.get(mDefaultRequest);
final NetworkReassignment.RequestReassignment reassignment =
changes.getReassignment(defaultRequestInfo);
@@ -6713,8 +6701,6 @@
// before LegacyTypeTracker sends legacy broadcasts
for (final NetworkReassignment.RequestReassignment event :
changes.getRequestReassignments()) {
- if (event.mOldNetwork == event.mNewNetwork) continue;
-
// Tell NetworkProviders about the new score, so they can stop
// trying to connect if they know they cannot match it.
// TODO - this could get expensive if there are a lot of outstanding requests for this
@@ -6729,24 +6715,37 @@
}
}
- for (final NetworkReassignment.NetworkBgStatePair event : changes.getRematchedNetworks()) {
- // Process listen requests and update capabilities if the background state has
- // changed for this network. For consistency with previous behavior, send onLost
- // callbacks before onAvailable.
- processNewlyLostListenRequests(event.mNetwork);
- if (event.mOldBackground != event.mNetwork.isBackgroundNetwork()) {
- applyBackgroundChangeForRematch(event.mNetwork);
- }
- processNewlySatisfiedListenRequests(event.mNetwork);
- }
-
+ // Update the linger state before processing listen callbacks, because the background
+ // computation depends on whether the network is lingering. Don't send the LOSING callbacks
+ // just yet though, because they have to be sent after the listens are processed to keep
+ // backward compatibility.
+ final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>();
for (final NetworkAgentInfo nai : nais) {
// Rematching may have altered the linger state of some networks, so update all linger
// timers. updateLingerState reads the state from the network agent and does nothing
// if the state has not changed : the source of truth is controlled with
// NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been
// called while rematching the individual networks above.
- updateLingerState(nai, now);
+ if (updateLingerState(nai, now)) {
+ lingeredNetworks.add(nai);
+ }
+ }
+
+ for (final NetworkAgentInfo nai : nais) {
+ if (!nai.everConnected) continue;
+ final boolean oldBackground = oldBgNetworks.contains(nai);
+ // Process listen requests and update capabilities if the background state has
+ // changed for this network. For consistency with previous behavior, send onLost
+ // callbacks before onAvailable.
+ processNewlyLostListenRequests(nai);
+ if (oldBackground != nai.isBackgroundNetwork()) {
+ applyBackgroundChangeForRematch(nai);
+ }
+ processNewlySatisfiedListenRequests(nai);
+ }
+
+ for (final NetworkAgentInfo nai : lingeredNetworks) {
+ notifyNetworkLosing(nai, now);
}
updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
@@ -6762,9 +6761,11 @@
// and became unneeded due to another network improving its score to the
// point where this network will no longer be able to satisfy any requests
// even if it validates.
- updateLingerState(nai, now);
+ if (updateLingerState(nai, now)) {
+ notifyNetworkLosing(nai, now);
+ }
} else {
- if (DBG) log("Reaping " + nai.name());
+ if (DBG) log("Reaping " + nai.toShortString());
teardownUnneededNetwork(nai);
}
}
@@ -6792,7 +6793,7 @@
private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
@Nullable final NetworkAgentInfo oldDefaultNetwork,
@Nullable final NetworkAgentInfo newDefaultNetwork,
- @NonNull final NetworkAgentInfo[] nais) {
+ @NonNull final Collection<NetworkAgentInfo> nais) {
if (oldDefaultNetwork != newDefaultNetwork) {
// Maintain the illusion : since the legacy API only understands one network at a time,
// if the default network changed, apps should see a disconnected broadcast for the
@@ -6831,7 +6832,9 @@
// they may get old info. Reverse this after the old startUsing api is removed.
// This is on top of the multiple intent sequencing referenced in the todo above.
for (NetworkAgentInfo nai : nais) {
- addNetworkToLegacyTypeTracker(nai);
+ if (nai.everConnected) {
+ addNetworkToLegacyTypeTracker(nai);
+ }
}
}
@@ -6912,8 +6915,8 @@
notifyLockdownVpn(networkAgent);
if (DBG) {
- log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
- oldInfo.getState() + " to " + state);
+ log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
+ + oldInfo.getState() + " to " + state);
}
if (!networkAgent.created
@@ -6931,7 +6934,7 @@
networkAgent.everConnected = true;
if (networkAgent.linkProperties == null) {
- Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties");
+ Slog.wtf(TAG, networkAgent.toShortString() + " connected with null LinkProperties");
}
// NetworkCapabilities need to be set before sending the private DNS config to
@@ -6991,7 +6994,7 @@
}
private void updateNetworkScore(NetworkAgentInfo nai, NetworkScore ns) {
- if (VDBG || DDBG) log("updateNetworkScore for " + nai.name() + " to " + ns);
+ if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + ns);
nai.setNetworkScore(ns);
rematchAllNetworksAndRequests();
sendUpdatedScoreToFactories(nai);
@@ -7015,6 +7018,12 @@
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
}
+ // Notify the requests on this NAI that the network is now lingered.
+ private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
+ final int lingerTime = (int) (nai.getLingerExpiry() - now);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+ }
+
/**
* Notify of the blocked state apps with a registered callback matching a given NAI.
*
@@ -7131,14 +7140,12 @@
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
if (VDBG || DDBG) {
String notification = ConnectivityManager.getCallbackName(notifyType);
- log("notifyType " + notification + " for " + networkAgent.name());
+ log("notifyType " + notification + " for " + networkAgent.toShortString());
}
for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr);
- // TODO: if we're in the middle of a rematch, can we send a CAP_CHANGED callback for
- // a network that no longer satisfies the listen?
if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, networkAgent, notifyType, arg1);
} else {
@@ -7528,6 +7535,8 @@
*/
@VisibleForTesting
class ConnectivityDiagnosticsHandler extends Handler {
+ private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName();
+
/**
* Used to handle ConnectivityDiagnosticsCallback registration events from {@link
* android.net.ConnectivityDiagnosticsManager}.
@@ -7555,6 +7564,26 @@
*/
private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
+ /**
+ * Event for NetworkMonitor to inform ConnectivityService that a potential data stall has
+ * been detected on the network.
+ * obj = Long the timestamp (in millis) for when the suspected data stall was detected.
+ * arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method.
+ * arg2 = NetID.
+ * data = PersistableBundle of extras passed from NetworkMonitor.
+ */
+ private static final int EVENT_DATA_STALL_SUSPECTED = 4;
+
+ /**
+ * Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to
+ * the platform. This event will invoke {@link
+ * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned
+ * callbacks.
+ * obj = Network that was reported on
+ * arg1 = boolint for the quality reported
+ */
+ private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5;
+
private ConnectivityDiagnosticsHandler(Looper looper) {
super(looper);
}
@@ -7585,6 +7614,24 @@
handleNetworkTestedWithExtras(reportEvent, extras);
break;
}
+ case EVENT_DATA_STALL_SUSPECTED: {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+ if (nai == null) break;
+
+ // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected
+ // receives a PersistableBundle and converts it to the Bundle in the incoming
+ // Message.
+ final PersistableBundle extras = new PersistableBundle(msg.getData());
+ handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras);
+ break;
+ }
+ case EVENT_NETWORK_CONNECTIVITY_REPORTED: {
+ handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1));
+ break;
+ }
+ default: {
+ Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what);
+ }
}
}
}
@@ -7714,6 +7761,41 @@
}
}
+ private void handleDataStallSuspected(
+ @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod,
+ @NonNull PersistableBundle extras) {
+ final DataStallReport report =
+ new DataStallReport(
+ nai.network,
+ timestampMillis,
+ detectionMethod,
+ nai.linkProperties,
+ nai.networkCapabilities,
+ extras);
+ final List<IConnectivityDiagnosticsCallback> results =
+ getMatchingPermissionedCallbacks(nai);
+ for (final IConnectivityDiagnosticsCallback cb : results) {
+ try {
+ cb.onDataStallSuspected(report);
+ } catch (RemoteException ex) {
+ loge("Error invoking onDataStallSuspected", ex);
+ }
+ }
+ }
+
+ private void handleNetworkConnectivityReported(
+ @NonNull NetworkAgentInfo nai, boolean connectivity) {
+ final List<IConnectivityDiagnosticsCallback> results =
+ getMatchingPermissionedCallbacks(nai);
+ for (final IConnectivityDiagnosticsCallback cb : results) {
+ try {
+ cb.onNetworkConnectivityReported(nai.network, connectivity);
+ } catch (RemoteException ex) {
+ loge("Error invoking onNetworkConnectivityReported", ex);
+ }
+ }
+ }
+
private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
@NonNull NetworkAgentInfo nai) {
final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index af8a366..5059a48 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -325,7 +325,7 @@
mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
- Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
+ Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.toShortString());
switch (mType) {
case TYPE_NATT:
mNai.asyncChannel.sendMessage(
@@ -365,7 +365,8 @@
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
}
}
- Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name() + ": " + reason);
+ Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.toShortString()
+ + ": " + reason);
switch (mStartedState) {
case NOT_STARTED:
// Remove the reference of the keepalive that meet error before starting,
@@ -476,7 +477,7 @@
}
public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
- String networkName = (nai == null) ? "(null)" : nai.name();
+ final String networkName = NetworkAgentInfo.toShortString(nai);
HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
if (networkKeepalives == null) {
Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName);
@@ -493,7 +494,7 @@
}
private void cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot) {
- String networkName = (nai == null) ? "(null)" : nai.name();
+ final String networkName = NetworkAgentInfo.toShortString(nai);
HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
if (networkKeepalives == null) {
Log.e(TAG, "Attempt to remove keepalive on nonexistent network " + networkName);
@@ -540,7 +541,7 @@
} catch(NullPointerException e) {}
if (ki == null) {
Log.e(TAG, "Event " + message.what + "," + slot + "," + reason
- + " for unknown keepalive " + slot + " on " + nai.name());
+ + " for unknown keepalive " + slot + " on " + nai.toShortString());
return;
}
@@ -562,7 +563,7 @@
if (KeepaliveInfo.STARTING == ki.mStartedState) {
if (SUCCESS == reason) {
// Keepalive successfully started.
- Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
+ Log.d(TAG, "Started keepalive " + slot + " on " + nai.toShortString());
ki.mStartedState = KeepaliveInfo.STARTED;
try {
ki.mCallback.onStarted(slot);
@@ -570,14 +571,14 @@
Log.w(TAG, "Discarded onStarted(" + slot + ") callback");
}
} else {
- Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.name()
+ Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.toShortString()
+ ": " + reason);
// The message indicated some error trying to start: do call handleStopKeepalive.
handleStopKeepalive(nai, slot, reason);
}
} else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
// The message indicated result of stopping : clean up keepalive slots.
- Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.name()
+ Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.toShortString()
+ " stopped: " + reason);
ki.mStartedState = KeepaliveInfo.NOT_STARTED;
cleanupStoppedKeepalive(nai, slot);
@@ -733,7 +734,7 @@
pw.println("Socket keepalives:");
pw.increaseIndent();
for (NetworkAgentInfo nai : mKeepalives.keySet()) {
- pw.println(nai.name());
+ pw.println(nai.toShortString());
pw.increaseIndent();
for (int slot : mKeepalives.get(nai).keySet()) {
KeepaliveInfo ki = mKeepalives.get(nai).get(slot);
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index 7071510..04c000f 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -200,8 +200,9 @@
}
if (DBG) {
- Log.d(TAG, "Notifying switch from=" + fromNai.name() + " to=" + toNai.name() +
- " type=" + sNotifyTypeNames.get(notifyType, "unknown(" + notifyType + ")"));
+ Log.d(TAG, "Notifying switch from=" + fromNai.toShortString()
+ + " to=" + toNai.toShortString()
+ + " type=" + sNotifyTypeNames.get(notifyType, "unknown(" + notifyType + ")"));
}
mNotifications.put(fromNai.network.netId, toNai.network.netId);
@@ -222,10 +223,10 @@
public void noteLingerDefaultNetwork(@NonNull final NetworkAgentInfo fromNai,
@Nullable final NetworkAgentInfo toNai) {
if (VDBG) {
- Log.d(TAG, "noteLingerDefaultNetwork from=" + fromNai.name() +
- " everValidated=" + fromNai.everValidated +
- " lastValidated=" + fromNai.lastValidated +
- " to=" + toNai.name());
+ Log.d(TAG, "noteLingerDefaultNetwork from=" + fromNai.toShortString()
+ + " everValidated=" + fromNai.everValidated
+ + " lastValidated=" + fromNai.lastValidated
+ + " to=" + toNai.toShortString());
}
// If we are currently notifying the user because the device switched to fromNai, now that
@@ -270,7 +271,8 @@
// TODO: should we do this?
if (everNotified(fromNai)) {
if (VDBG) {
- Log.d(TAG, "Not notifying handover from " + fromNai.name() + ", already notified");
+ Log.d(TAG, "Not notifying handover from " + fromNai.toShortString()
+ + ", already notified");
}
return;
}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index f636d67..82465f8 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -174,7 +174,7 @@
try {
mNMService.registerObserver(this);
} catch (RemoteException e) {
- Slog.e(TAG, "Can't register interface observer for clat on " + mNetwork.name());
+ Slog.e(TAG, "Can't register iface observer for clat on " + mNetwork.toShortString());
return;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index d66aec5..d30a320 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,7 +16,10 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.transportNamesOf;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.IDnsResolver;
import android.net.INetd;
@@ -372,7 +375,7 @@
// Should only happen if the requestId wraps. If that happens lots of other things will
// be broken as well.
Log.wtf(TAG, String.format("Duplicate requestId for %s and %s on %s",
- networkRequest, existing, name()));
+ networkRequest, existing, toShortString()));
updateRequestCounts(REMOVE, existing);
}
mNetworkRequests.put(networkRequest.requestId, networkRequest);
@@ -542,11 +545,11 @@
// Cannot happen. Once a request is lingering on a particular network, we cannot
// re-linger it unless that network becomes the best for that request again, in which
// case we should have unlingered it.
- Log.wtf(TAG, this.name() + ": request " + request.requestId + " already lingered");
+ Log.wtf(TAG, toShortString() + ": request " + request.requestId + " already lingered");
}
final long expiryMs = now + duration;
LingerTimer timer = new LingerTimer(request, expiryMs);
- if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + this.name());
+ if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString());
mLingerTimers.add(timer);
mLingerTimerForRequest.put(request.requestId, timer);
}
@@ -558,7 +561,7 @@
public boolean unlingerRequest(NetworkRequest request) {
LingerTimer timer = mLingerTimerForRequest.get(request.requestId);
if (timer != null) {
- if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + this.name());
+ if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString());
mLingerTimers.remove(timer);
mLingerTimerForRequest.remove(request.requestId);
return true;
@@ -645,9 +648,16 @@
+ "}";
}
- public String name() {
- return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" +
- networkInfo.getSubtypeName() + ") - " + Objects.toString(network) + "]";
+ /**
+ * Show a short string representing a Network.
+ *
+ * This is often not enough for debugging purposes for anything complex, but the full form
+ * is very long and hard to read, so this is useful when there isn't a lot of ambiguity.
+ * This represents the network with something like "[100 WIFI|VPN]" or "[108 MOBILE]".
+ */
+ public String toShortString() {
+ return "[" + network.netId + " "
+ + transportNamesOf(networkCapabilities.getTransportTypes()) + "]";
}
// Enables sorting in descending order of score.
@@ -655,4 +665,12 @@
public int compareTo(NetworkAgentInfo other) {
return other.getCurrentScore() - getCurrentScore();
}
+
+ /**
+ * Null-guarding version of NetworkAgentInfo#toShortString()
+ */
+ @NonNull
+ public static String toShortString(@Nullable final NetworkAgentInfo nai) {
+ return null != nai ? nai.toShortString() : "[null]";
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java
new file mode 100644
index 0000000..d0aabf9
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkRequest;
+
+import java.util.Collection;
+
+/**
+ * A class that knows how to find the best network matching a request out of a list of networks.
+ */
+public class NetworkRanker {
+ public NetworkRanker() { }
+
+ /**
+ * Find the best network satisfying this request among the list of passed networks.
+ */
+ // Almost equivalent to Collections.max(nais), but allows returning null if no network
+ // satisfies the request.
+ @Nullable
+ public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request,
+ @NonNull final Collection<NetworkAgentInfo> nais) {
+ NetworkAgentInfo bestNetwork = null;
+ int bestScore = Integer.MIN_VALUE;
+ for (final NetworkAgentInfo nai : nais) {
+ if (!nai.satisfies(request)) continue;
+ if (nai.getCurrentScore() > bestScore) {
+ bestNetwork = nai;
+ bestScore = nai.getCurrentScore();
+ }
+ }
+ return bestNetwork;
+ }
+}
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index b2f384a..124b660 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -8,7 +8,6 @@
"libbacktrace",
"libbase",
"libbinder",
- "libbinderthreadstate",
"libbpf",
"libbpf_android",
"libc++",
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 0628691..8eb5cfa 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -146,17 +146,14 @@
@Test
public void testConnectivityReportEquals() {
- assertEquals(createSampleConnectivityReport(), createSampleConnectivityReport());
- assertEquals(createDefaultConnectivityReport(), createDefaultConnectivityReport());
+ final ConnectivityReport defaultReport = createDefaultConnectivityReport();
+ final ConnectivityReport sampleReport = createSampleConnectivityReport();
+ assertEquals(sampleReport, createSampleConnectivityReport());
+ assertEquals(defaultReport, createDefaultConnectivityReport());
- final LinkProperties linkProperties = new LinkProperties();
- linkProperties.setInterfaceName(INTERFACE_NAME);
-
- final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
- networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
-
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+ final LinkProperties linkProperties = sampleReport.getLinkProperties();
+ final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities();
+ final PersistableBundle bundle = sampleReport.getAdditionalInfo();
assertNotEquals(
createDefaultConnectivityReport(),
@@ -206,39 +203,104 @@
}
private DataStallReport createSampleDataStallReport() {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(INTERFACE_NAME);
+
final PersistableBundle bundle = new PersistableBundle();
bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
- return new DataStallReport(new Network(NET_ID), TIMESTAMP, DETECTION_METHOD, bundle);
+
+ final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+ networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+ return new DataStallReport(
+ new Network(NET_ID),
+ TIMESTAMP,
+ DETECTION_METHOD,
+ linkProperties,
+ networkCapabilities,
+ bundle);
}
private DataStallReport createDefaultDataStallReport() {
- return new DataStallReport(new Network(0), 0L, 0, PersistableBundle.EMPTY);
+ return new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY);
}
@Test
public void testDataStallReportEquals() {
- assertEquals(createSampleDataStallReport(), createSampleDataStallReport());
- assertEquals(createDefaultDataStallReport(), createDefaultDataStallReport());
+ final DataStallReport defaultReport = createDefaultDataStallReport();
+ final DataStallReport sampleReport = createSampleDataStallReport();
+ assertEquals(sampleReport, createSampleDataStallReport());
+ assertEquals(defaultReport, createDefaultDataStallReport());
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putString(BUNDLE_KEY, BUNDLE_VALUE);
+ final LinkProperties linkProperties = sampleReport.getLinkProperties();
+ final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities();
+ final PersistableBundle bundle = sampleReport.getStallDetails();
assertNotEquals(
- createDefaultDataStallReport(),
- new DataStallReport(new Network(NET_ID), 0L, 0, PersistableBundle.EMPTY));
+ defaultReport,
+ new DataStallReport(
+ new Network(NET_ID),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
assertNotEquals(
- createDefaultDataStallReport(),
- new DataStallReport(new Network(0), TIMESTAMP, 0, PersistableBundle.EMPTY));
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ TIMESTAMP,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
assertNotEquals(
- createDefaultDataStallReport(),
- new DataStallReport(new Network(0), 0L, DETECTION_METHOD, PersistableBundle.EMPTY));
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ DETECTION_METHOD,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
assertNotEquals(
- createDefaultDataStallReport(), new DataStallReport(new Network(0), 0L, 0, bundle));
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ linkProperties,
+ new NetworkCapabilities(),
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ networkCapabilities,
+ PersistableBundle.EMPTY));
+ assertNotEquals(
+ defaultReport,
+ new DataStallReport(
+ new Network(0),
+ 0L,
+ 0,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ bundle));
}
@Test
public void testDataStallReportParcelUnparcel() {
- assertParcelSane(createSampleDataStallReport(), 4);
+ assertParcelSane(createSampleDataStallReport(), 6);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8162843..8da1a5b 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -24,6 +24,7 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -570,6 +571,9 @@
| NETWORK_VALIDATION_RESULT_PARTIAL;
private static final int VALIDATION_RESULT_INVALID = 0;
+ private static final long DATA_STALL_TIMESTAMP = 10L;
+ private static final int DATA_STALL_DETECTION_METHOD = 1;
+
private INetworkMonitor mNetworkMonitor;
private INetworkMonitorCallbacks mNmCallbacks;
private int mNmValidationResult = VALIDATION_RESULT_BASE;
@@ -577,6 +581,7 @@
private int mProbesSucceeded;
private String mNmValidationRedirectUrl = null;
private PersistableBundle mValidationExtras = PersistableBundle.EMPTY;
+ private PersistableBundle mDataStallExtras = PersistableBundle.EMPTY;
private boolean mNmProvNotificationRequested = false;
private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
@@ -804,6 +809,11 @@
public void expectPreventReconnectReceived() {
expectPreventReconnectReceived(TIMEOUT_MS);
}
+
+ void notifyDataStallSuspected() throws Exception {
+ mNmCallbacks.notifyDataStallSuspected(
+ DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras);
+ }
}
/**
@@ -6436,14 +6446,16 @@
public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception {
final NetworkRequest wifiRequest =
new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build();
-
when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
mService.registerConnectivityDiagnosticsCallback(
mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
- verify(mIBinder, timeout(TIMEOUT_MS))
- .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+ verify(mConnectivityDiagnosticsCallback).asBinder();
assertTrue(
mService.mConnectivityDiagnosticsCallbacks.containsKey(
mConnectivityDiagnosticsCallback));
@@ -6466,8 +6478,10 @@
mService.registerConnectivityDiagnosticsCallback(
mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
- verify(mIBinder, timeout(TIMEOUT_MS))
- .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
verify(mConnectivityDiagnosticsCallback).asBinder();
assertTrue(
mService.mConnectivityDiagnosticsCallbacks.containsKey(
@@ -6599,8 +6613,7 @@
mServiceContext.setPermission(perm, PERMISSION_GRANTED);
}
- @Test
- public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+ private void setUpConnectivityDiagnosticsCallback() throws Exception {
final NetworkRequest request = new NetworkRequest.Builder().build();
when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
@@ -6620,9 +6633,58 @@
mCellNetworkAgent.connect(true);
callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
callback.assertNoCallback();
+ }
- // Wait for onConnectivityReport to fire
- verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+ setUpConnectivityDiagnosticsCallback();
+
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ // Verify onConnectivityReport fired
+ verify(mConnectivityDiagnosticsCallback)
.onConnectivityReport(any(ConnectivityReport.class));
}
+
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception {
+ setUpConnectivityDiagnosticsCallback();
+
+ // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the
+ // cellular network agent
+ mCellNetworkAgent.notifyDataStallSuspected();
+
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ // Verify onDataStallSuspected fired
+ verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(any(DataStallReport.class));
+ }
+
+ @Test
+ public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception {
+ setUpConnectivityDiagnosticsCallback();
+
+ final Network n = mCellNetworkAgent.getNetwork();
+ final boolean hasConnectivity = true;
+ mService.reportNetworkConnectivity(n, hasConnectivity);
+
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ // Verify onNetworkConnectivityReported fired
+ verify(mConnectivityDiagnosticsCallback)
+ .onNetworkConnectivityReported(eq(n), eq(hasConnectivity));
+
+ final boolean noConnectivity = false;
+ mService.reportNetworkConnectivity(n, noConnectivity);
+
+ // Block until all other events are done processing.
+ HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+ // Wait for onNetworkConnectivityReported to fire
+ verify(mConnectivityDiagnosticsCallback)
+ .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
new file mode 100644
index 0000000..86c9116
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 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 com.android.server.connectivity
+
+import android.net.NetworkRequest
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkRankerTest {
+ private val ranker = NetworkRanker()
+
+ private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also {
+ doReturn(satisfy).`when`(it).satisfies(any())
+ doReturn(score).`when`(it).currentScore
+ }
+
+ @Test
+ fun testGetBestNetwork() {
+ val scores = listOf(20, 50, 90, 60, 23, 68)
+ val nais = scores.map { makeNai(true, it) }
+ val bestNetwork = nais[2] // The one with the top score
+ val someRequest = mock(NetworkRequest::class.java)
+ assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+ }
+
+ @Test
+ fun testIgnoreNonSatisfying() {
+ val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90),
+ makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
+ val bestNetwork = nais[1] // Top score that's satisfying
+ val someRequest = mock(NetworkRequest::class.java)
+ assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+ }
+
+ @Test
+ fun testNoMatch() {
+ val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
+ val someRequest = mock(NetworkRequest::class.java)
+ assertNull(ranker.getBestNetwork(someRequest, nais))
+ }
+
+ @Test
+ fun testEmpty() {
+ val someRequest = mock(NetworkRequest::class.java)
+ assertNull(ranker.getBestNetwork(someRequest, emptyList()))
+ }
+
+ // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
+ // network satisfying the request if multiple of them have the same score.
+ @Test
+ fun testStable() {
+ val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
+ makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
+ val someRequest = mock(NetworkRequest::class.java)
+ assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1))
+
+ val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
+ makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
+ assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2))
+ }
+}