Merge \"Rewrite lingering.\" into nyc-mr1-dev

am: 673bdd122b

Change-Id: I62d69f20101318b42d4d333b104dcf4f64dc1725
This commit is contained in:
Lorenzo Colitti
2016-07-07 11:01:19 +00:00
committed by android-build-merger
3 changed files with 382 additions and 117 deletions

View File

@@ -94,6 +94,7 @@ import android.os.PowerManager;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
@@ -125,6 +126,7 @@ import com.android.internal.net.VpnProfile;
import com.android.internal.util.AsyncChannel; import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils; import com.android.internal.util.MessageUtils;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.XmlUtils; import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService; import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DataConnectionStats;
@@ -171,7 +173,7 @@ import java.util.TreeSet;
*/ */
public class ConnectivityService extends IConnectivityManager.Stub public class ConnectivityService extends IConnectivityManager.Stub
implements PendingIntent.OnFinished { implements PendingIntent.OnFinished {
private static final String TAG = "ConnectivityService"; private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean DBG = true; private static final boolean DBG = true;
private static final boolean VDBG = false; private static final boolean VDBG = false;
@@ -191,6 +193,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
// connect anyway?" dialog after the user selects a network that doesn't validate. // connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@VisibleForTesting
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
// How long to delay to removal of a pending intent based request. // How long to delay to removal of a pending intent based request.
// See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs; private final int mReleasePendingIntentDelayMs;
@@ -239,7 +247,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
private static final int DISABLED = 0; private static final int DISABLED = 0;
private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames( private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class }); new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class,
NetworkAgentInfo.class });
private enum ReapUnvalidatedNetworks { private enum ReapUnvalidatedNetworks {
// Tear down networks that have no chance (e.g. even if validated) of becoming // Tear down networks that have no chance (e.g. even if validated) of becoming
@@ -681,6 +690,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(), mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
mContext = checkNotNull(context, "missing Context"); mContext = checkNotNull(context, "missing Context");
mNetd = checkNotNull(netManager, "missing INetworkManagementService"); mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mStatsService = checkNotNull(statsService, "missing INetworkStatsService"); mStatsService = checkNotNull(statsService, "missing INetworkStatsService");
@@ -1905,7 +1916,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
pw.println(nai.toString()); pw.println(nai.toString());
pw.increaseIndent(); pw.increaseIndent();
pw.println("Requests:"); pw.println(String.format("Requests: %d request/%d total",
nai.numRequestNetworkRequests(), nai.numNetworkRequests()));
pw.increaseIndent(); pw.increaseIndent();
for (int i = 0; i < nai.numNetworkRequests(); i++) { for (int i = 0; i < nai.numNetworkRequests(); i++) {
pw.println(nai.requestAt(i).toString()); pw.println(nai.requestAt(i).toString());
@@ -1913,7 +1925,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
pw.decreaseIndent(); pw.decreaseIndent();
pw.println("Lingered:"); pw.println("Lingered:");
pw.increaseIndent(); pw.increaseIndent();
for (NetworkRequest nr : nai.networkLingered) pw.println(nr.toString()); nai.dumpLingerTimers(pw);
pw.decreaseIndent(); pw.decreaseIndent();
pw.decreaseIndent(); pw.decreaseIndent();
} }
@@ -2158,13 +2170,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
break; break;
} }
case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
if (isLiveNetworkAgent(nai, msg.what)) {
handleLingerComplete(nai);
}
break;
}
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2; final int netId = msg.arg2;
final boolean visible = (msg.arg1 != 0); final boolean visible = (msg.arg1 != 0);
@@ -2197,33 +2202,50 @@ public class ConnectivityService extends IConnectivityManager.Stub
return true; return true;
} }
private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
switch (msg.what) {
default:
return false;
case NetworkAgentInfo.EVENT_NETWORK_LINGER_COMPLETE: {
NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj;
if (nai != null && isLiveNetworkAgent(nai, msg.what)) {
handleLingerComplete(nai);
}
break;
}
}
return true;
}
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg)) { if (!maybeHandleAsyncChannelMessage(msg) &&
!maybeHandleNetworkMonitorMessage(msg) &&
!maybeHandleNetworkAgentInfoMessage(msg)) {
maybeHandleNetworkAgentMessage(msg); maybeHandleNetworkAgentMessage(msg);
} }
} }
} }
private void linger(NetworkAgentInfo nai) { private void updateLingerState(NetworkAgentInfo nai, long now) {
nai.lingering = true; // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); // 2. If the network was lingering and there are now requests, unlinger it.
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER); // 3. If this network is unneeded (which implies it is not lingering), and there is at least
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING); // one lingered request, start lingering.
} nai.updateLingerTimer();
if (nai.isLingering() && nai.numRequestNetworkRequests() > 0) {
// Cancel any lingering so the linger timeout doesn't teardown a network. if (DBG) log("Unlingering " + nai.name());
// This should be called when a network begins satisfying a NetworkRequest. nai.unlinger();
// Note: depending on what state the NetworkMonitor is in (e.g., logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
// if it's awaiting captive portal login, or if validation failed), this } else if (unneeded(nai) && nai.getLingerExpiry() > 0) { // unneeded() calls isLingering()
// may trigger a re-evaluation of the network. int lingerTime = (int) (nai.getLingerExpiry() - now);
private void unlinger(NetworkAgentInfo nai) { if (DBG) {
nai.networkLingered.clear(); Log.d(TAG, "Lingering " + nai.name() + " for " + lingerTime + "ms");
if (!nai.lingering) return; }
nai.lingering = false; nai.linger();
logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
if (VDBG) log("Canceling linger of " + nai.name()); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); }
} }
private void handleAsyncChannelHalfConnect(Message msg) { private void handleAsyncChannelHalfConnect(Message msg) {
@@ -2313,6 +2335,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
sendUpdatedScoreToFactories(request, 0); sendUpdatedScoreToFactories(request, 0);
} }
} }
nai.clearLingerState();
if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
removeDataActivityTracking(nai); removeDataActivityTracking(nai);
notifyLockdownVpn(nai); notifyLockdownVpn(nai);
@@ -2400,7 +2423,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
// This is whether it is satisfying any NetworkRequests or were it to become validated, // This is whether it is satisfying any NetworkRequests or were it to become validated,
// would it have a chance of satisfying any NetworkRequests. // would it have a chance of satisfying any NetworkRequests.
private boolean unneeded(NetworkAgentInfo nai) { private boolean unneeded(NetworkAgentInfo nai) {
if (!nai.everConnected || nai.isVPN() || nai.lingering) return false; if (!nai.everConnected || nai.isVPN() ||
nai.isLingering() || nai.numRequestNetworkRequests() > 0) {
return false;
}
for (NetworkRequestInfo nri : mNetworkRequests.values()) { for (NetworkRequestInfo nri : mNetworkRequests.values()) {
// If this Network is already the highest scoring Network for a request, or if // If this Network is already the highest scoring Network for a request, or if
// there is hope for it to become one if it validated, then it is needed. // there is hope for it to become one if it validated, then it is needed.
@@ -2453,6 +2479,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
log(" Removing from current network " + nai.name() + log(" Removing from current network " + nai.name() +
", leaving " + nai.numNetworkRequests() + " requests."); ", 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());
if (unneeded(nai)) { if (unneeded(nai)) {
if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
teardownUnneededNetwork(nai); teardownUnneededNetwork(nai);
@@ -2516,7 +2545,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
} }
} }
callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED); callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0);
} }
} }
@@ -4503,7 +4532,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
private void callCallbackForRequest(NetworkRequestInfo nri, private void callCallbackForRequest(NetworkRequestInfo nri,
NetworkAgentInfo networkAgent, int notificationType) { NetworkAgentInfo networkAgent, int notificationType, int arg1) {
if (nri.messenger == null) return; // Default request has no msgr if (nri.messenger == null) return; // Default request has no msgr
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(NetworkRequest.class.getSimpleName(), bundle.putParcelable(NetworkRequest.class.getSimpleName(),
@@ -4515,7 +4544,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
switch (notificationType) { switch (notificationType) {
case ConnectivityManager.CALLBACK_LOSING: { case ConnectivityManager.CALLBACK_LOSING: {
msg.arg1 = 30 * 1000; // TODO - read this from NetworkMonitor msg.arg1 = arg1;
break; break;
} }
case ConnectivityManager.CALLBACK_CAP_CHANGED: { case ConnectivityManager.CALLBACK_CAP_CHANGED: {
@@ -4562,7 +4591,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
return; return;
} }
if (DBG) log("handleLingerComplete for " + oldNetwork.name()); if (DBG) log("handleLingerComplete for " + oldNetwork.name());
teardownUnneededNetwork(oldNetwork);
// 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.
oldNetwork.clearLingerState();
if (unneeded(oldNetwork)) {
teardownUnneededNetwork(oldNetwork);
}
} }
private void makeDefault(NetworkAgentInfo newNetwork) { private void makeDefault(NetworkAgentInfo newNetwork) {
@@ -4607,7 +4643,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// performed to tear down unvalidated networks that have no chance (i.e. even if // performed to tear down unvalidated networks that have no chance (i.e. even if
// validated) of becoming the highest scoring network. // validated) of becoming the highest scoring network.
private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,
ReapUnvalidatedNetworks reapUnvalidatedNetworks) { ReapUnvalidatedNetworks reapUnvalidatedNetworks, long now) {
if (!newNetwork.everConnected) return; if (!newNetwork.everConnected) return;
boolean keep = newNetwork.isVPN(); boolean keep = newNetwork.isVPN();
boolean isNewDefault = false; boolean isNewDefault = false;
@@ -4653,12 +4689,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (currentNetwork != null) { if (currentNetwork != null) {
if (VDBG) log(" accepting network in place of " + currentNetwork.name()); if (VDBG) log(" accepting network in place of " + currentNetwork.name());
currentNetwork.removeRequest(nri.request.requestId); currentNetwork.removeRequest(nri.request.requestId);
currentNetwork.networkLingered.add(nri.request); currentNetwork.lingerRequest(nri.request, now, mLingerDelayMs);
affectedNetworks.add(currentNetwork); affectedNetworks.add(currentNetwork);
} else { } else {
if (VDBG) log(" accepting network in place of null"); if (VDBG) log(" accepting network in place of null");
} }
unlinger(newNetwork); newNetwork.unlingerRequest(nri.request);
mNetworkForRequestId.put(nri.request.requestId, newNetwork); mNetworkForRequestId.put(nri.request.requestId, newNetwork);
if (!newNetwork.addRequest(nri.request)) { if (!newNetwork.addRequest(nri.request)) {
Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request); Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
@@ -4706,23 +4742,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// a) be requested and b) change is NET_CAPABILITY_TRUSTED, // a) be requested and b) change is NET_CAPABILITY_TRUSTED,
// so this code is only incorrect for a network that loses // so this code is only incorrect for a network that loses
// the TRUSTED capability, which is a rare case. // the TRUSTED capability, which is a rare case.
callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST); callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST, 0);
}
}
// Linger any networks that are no longer needed.
for (NetworkAgentInfo nai : affectedNetworks) {
if (nai.lingering) {
// Already lingered. Nothing to do. This can only happen if "nai" is in
// "affectedNetworks" twice. The reasoning being that to get added to
// "affectedNetworks", "nai" must have been satisfying a NetworkRequest
// (i.e. not lingered) so it could have only been lingered by this loop.
// unneeded(nai) will be false and we'll call unlinger() below which would
// be bad, so handle it here.
} else if (unneeded(nai)) {
linger(nai);
} else {
// Clear nai.networkLingered we might have added above.
unlinger(nai);
} }
} }
if (isNewDefault) { if (isNewDefault) {
@@ -4747,6 +4767,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
// before LegacyTypeTracker sends legacy broadcasts // before LegacyTypeTracker sends legacy broadcasts
for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri); for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
// Linger any networks that are no longer needed. This should be done after sending the
// available callback for newNetwork.
for (NetworkAgentInfo nai : affectedNetworks) {
updateLingerState(nai, now);
}
// Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it
// does not need to be done in any particular order.
updateLingerState(newNetwork, now);
if (isNewDefault) { if (isNewDefault) {
// Maintain the illusion: since the legacy API only // Maintain the illusion: since the legacy API only
// understands one network at a time, we must pretend // understands one network at a time, we must pretend
@@ -4812,8 +4841,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) { if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
if (unneeded(nai)) { if (unneeded(nai)) {
if (DBG) log("Reaping " + nai.name()); if (nai.getLingerExpiry() > 0) {
teardownUnneededNetwork(nai); // This network has active linger timers and no requests, but is not
// lingering. Linger it.
//
// One way (the only way?) this can happen if this network is unvalidated
// 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);
} else {
if (DBG) log("Reaping " + nai.name());
teardownUnneededNetwork(nai);
}
} }
} }
} }
@@ -4840,8 +4880,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Optimization: Only reprocess "changed" if its score improved. This is safe because it // Optimization: Only reprocess "changed" if its score improved. This is safe because it
// can only add more NetworkRequests satisfied by "changed", and this is exactly what // can only add more NetworkRequests satisfied by "changed", and this is exactly what
// rematchNetworkAndRequests() handles. // rematchNetworkAndRequests() handles.
final long now = SystemClock.elapsedRealtime();
if (changed != null && oldScore < changed.getCurrentScore()) { if (changed != null && oldScore < changed.getCurrentScore()) {
rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP); rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP, now);
} else { } else {
final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray( final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
new NetworkAgentInfo[mNetworkAgentInfos.size()]); new NetworkAgentInfo[mNetworkAgentInfos.size()]);
@@ -4855,7 +4896,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// is complete could incorrectly teardown a network that hasn't yet been // is complete could incorrectly teardown a network that hasn't yet been
// rematched. // rematched.
(nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP (nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP
: ReapUnvalidatedNetworks.REAP); : ReapUnvalidatedNetworks.REAP,
now);
} }
} }
} }
@@ -4965,7 +5007,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateSignalStrengthThresholds(networkAgent, "CONNECT", null); updateSignalStrengthThresholds(networkAgent, "CONNECT", null);
// Consider network even though it is not yet validated. // Consider network even though it is not yet validated.
rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP); final long now = SystemClock.elapsedRealtime();
rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP, now);
// This has to happen after matching the requests, because callbacks are just requests. // This has to happen after matching the requests, because callbacks are just requests.
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
@@ -5013,14 +5056,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// notify only this one new request of the current state // notify only this one new request of the current state
protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) { protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
int notifyType = ConnectivityManager.CALLBACK_AVAILABLE; int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
// TODO - read state from monitor to decide what to send.
// if (nai.networkMonitor.isLingering()) {
// notifyType = NetworkCallbacks.LOSING;
// } else if (nai.networkMonitor.isEvaluating()) {
// notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
// }
if (nri.mPendingIntent == null) { if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, nai, notifyType); callCallbackForRequest(nri, nai, notifyType, 0);
} else { } else {
sendPendingIntentForRequest(nri, nai, notifyType); sendPendingIntentForRequest(nri, nai, notifyType);
} }
@@ -5072,20 +5109,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
} }
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) { protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name()); if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
for (int i = 0; i < networkAgent.numNetworkRequests(); i++) { for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
NetworkRequest nr = networkAgent.requestAt(i); NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr); NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr); if (VDBG) log(" sending notification for " + nr);
if (nri.mPendingIntent == null) { if (nri.mPendingIntent == null) {
callCallbackForRequest(nri, networkAgent, notifyType); callCallbackForRequest(nri, networkAgent, notifyType, arg1);
} else { } else {
sendPendingIntentForRequest(nri, networkAgent, notifyType); sendPendingIntentForRequest(nri, networkAgent, notifyType);
} }
} }
} }
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
notifyNetworkCallbacks(networkAgent, notifyType, 0);
}
private String notifyTypeToName(int notifyType) { private String notifyTypeToName(int notifyType) {
switch (notifyType) { switch (notifyType) {
case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK"; case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK";
@@ -5216,6 +5257,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
return new NetworkMonitor(context, handler, nai, defaultRequest); return new NetworkMonitor(context, handler, nai, defaultRequest);
} }
@VisibleForTesting
public WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int cmd, Object obj) {
return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
}
private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
int newNetid = NETID_UNSET; int newNetid = NETID_UNSET;
int prevNetid = NETID_UNSET; int prevNetid = NETID_UNSET;

View File

@@ -28,14 +28,21 @@ import android.net.NetworkRequest;
import android.net.NetworkState; import android.net.NetworkState;
import android.os.Handler; import android.os.Handler;
import android.os.Messenger; import android.os.Messenger;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.util.AsyncChannel; import com.android.internal.util.AsyncChannel;
import com.android.internal.util.WakeupMessage;
import com.android.server.ConnectivityService; import com.android.server.ConnectivityService;
import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkMonitor;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
/** /**
* A bag class used by ConnectivityService for holding a collection of most recent * A bag class used by ConnectivityService for holding a collection of most recent
@@ -143,12 +150,69 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Whether a captive portal was found during the last network validation attempt. // Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected; public boolean lastCaptivePortalDetected;
// Indicates whether the network is lingering. Networks are lingered when they become unneeded // Networks are lingered when they become unneeded as a result of their NetworkRequests being
// as a result of their NetworkRequests being satisfied by a different network, so as to allow // satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// communication to wrap up before the network is taken down. This usually only happens to the // network is taken down. This usually only happens to the default network. Lingering ends with
// default network. Lingering ends with either the linger timeout expiring and the network // either the linger timeout expiring and the network being taken down, or the network
// being taken down, or the network satisfying a request again. // satisfying a request again.
public boolean lingering; public static class LingerTimer implements Comparable<LingerTimer> {
public final NetworkRequest request;
public final long expiryMs;
public LingerTimer(NetworkRequest request, long expiryMs) {
this.request = request;
this.expiryMs = expiryMs;
}
public boolean equals(Object o) {
if (!(o instanceof LingerTimer)) return false;
LingerTimer other = (LingerTimer) o;
return (request.requestId == other.request.requestId) && (expiryMs == other.expiryMs);
}
public int hashCode() {
return Objects.hash(request.requestId, expiryMs);
}
public int compareTo(LingerTimer other) {
return (expiryMs != other.expiryMs) ?
Long.compare(expiryMs, other.expiryMs) :
Integer.compare(request.requestId, other.request.requestId);
}
public String toString() {
return String.format("%s, expires %dms", request.toString(),
expiryMs - SystemClock.elapsedRealtime());
}
}
/**
* Inform ConnectivityService that the network LINGER period has
* expired.
* obj = this NetworkAgentInfo
*/
public static final int EVENT_NETWORK_LINGER_COMPLETE = 1001;
// All linger timers for this network, sorted by expiry time. A linger timer is added whenever
// a request is moved to a network with a better score, regardless of whether the network is or
// was lingering or not.
// TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g.,
// SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire.
private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>();
// For fast lookups. Indexes into mLingerTimers by request ID.
private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>();
// Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the
// network is lingering or not. Always set to the expiry of the LingerTimer that expires last.
// When the timer fires, all linger state is cleared, and if the network has no requests, it is
// torn down.
private WakeupMessage mLingerMessage;
// Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed.
private long mLingerExpiryMs;
// Whether the network is lingering or not. Must be maintained separately from the above because
// it depends on the state of other networks and requests, which only ConnectivityService knows.
// (Example: we don't linger a network if it would become the best for a NetworkRequest if it
// validated).
private boolean mLingering;
// This represents the last score received from the NetworkAgent. // This represents the last score received from the NetworkAgent.
private int currentScore; private int currentScore;
@@ -165,8 +229,6 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
// The list of NetworkRequests that this Network previously satisfied with the highest // The list of NetworkRequests that this Network previously satisfied with the highest
// score. A non-empty list indicates that if this Network was validated it is lingered. // score. A non-empty list indicates that if this Network was validated it is lingered.
// NOTE: This list is only used for debugging.
public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
// How many of the satisfied requests are actual requests and not listens. // How many of the satisfied requests are actual requests and not listens.
private int mNumRequestNetworkRequests = 0; private int mNumRequestNetworkRequests = 0;
@@ -176,6 +238,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Used by ConnectivityService to keep track of 464xlat. // Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd; public Nat464Xlat clatd;
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
private final Context mContext;
private final Handler mHandler;
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) { NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
@@ -186,7 +254,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
linkProperties = lp; linkProperties = lp;
networkCapabilities = nc; networkCapabilities = nc;
currentScore = score; currentScore = score;
networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest); mConnService = connService;
mContext = context;
mHandler = handler;
networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc; networkMisc = misc;
} }
@@ -213,8 +284,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
*/ */
public void removeRequest(int requestId) { public void removeRequest(int requestId) {
NetworkRequest existing = mNetworkRequests.get(requestId); NetworkRequest existing = mNetworkRequests.get(requestId);
if (existing != null && existing.isRequest()) mNumRequestNetworkRequests--; if (existing == null) return;
mNetworkRequests.remove(requestId); mNetworkRequests.remove(requestId);
if (existing.isRequest()) {
mNumRequestNetworkRequests--;
unlingerRequest(existing);
}
} }
/** /**
@@ -316,13 +391,100 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
} }
} }
/**
* Sets the specified request to linger on this network for the specified time. Called by
* ConnectivityService when the request is moved to another network with a higher score.
*/
public void lingerRequest(NetworkRequest request, long now, long duration) {
if (mLingerTimerForRequest.get(request.requestId) != null) {
// 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");
}
final long expiryMs = now + duration;
LingerTimer timer = new LingerTimer(request, expiryMs);
if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + this.name());
mLingerTimers.add(timer);
mLingerTimerForRequest.put(request.requestId, timer);
}
/**
* Cancel lingering. Called by ConnectivityService when a request is added to this network.
*/
public void unlingerRequest(NetworkRequest request) {
LingerTimer timer = mLingerTimerForRequest.get(request.requestId);
if (timer != null) {
if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + this.name());
mLingerTimers.remove(timer);
mLingerTimerForRequest.remove(request.requestId);
}
}
public long getLingerExpiry() {
return mLingerExpiryMs;
}
public void updateLingerTimer() {
long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs;
if (newExpiry == mLingerExpiryMs) return;
// Even if we're going to reschedule the timer, cancel it first. This is because the
// semantics of WakeupMessage guarantee that if cancel is called then the alarm will
// never call its callback (handleLingerComplete), even if it has already fired.
// WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage
// has already been dispatched, rescheduling to some time in the future it won't stop it
// from calling its callback immediately.
if (mLingerMessage != null) {
mLingerMessage.cancel();
mLingerMessage = null;
}
if (newExpiry > 0) {
mLingerMessage = mConnService.makeWakeupMessage(
mContext, mHandler,
"NETWORK_LINGER_COMPLETE." + network.netId,
EVENT_NETWORK_LINGER_COMPLETE, this);
mLingerMessage.schedule(newExpiry);
}
mLingerExpiryMs = newExpiry;
}
public void linger() {
mLingering = true;
}
public void unlinger() {
mLingering = false;
}
public boolean isLingering() {
return mLingering;
}
public void clearLingerState() {
if (mLingerMessage != null) {
mLingerMessage.cancel();
mLingerMessage = null;
}
mLingerTimers.clear();
mLingerTimerForRequest.clear();
updateLingerTimer(); // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage.
mLingering = false;
}
public void dumpLingerTimers(PrintWriter pw) {
for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
}
public String toString() { public String toString() {
return "NetworkAgentInfo{ ni{" + networkInfo + "} " + return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
"network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
"lp{" + linkProperties + "} " + "lp{" + linkProperties + "} " +
"nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " +
"everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
"created{" + created + "} lingering{" + lingering + "} " + "created{" + created + "} lingering{" + isLingering() + "} " +
"explicitlySelected{" + networkMisc.explicitlySelected + "} " + "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
"acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
"everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +

View File

@@ -96,6 +96,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
private static final String TAG = "ConnectivityServiceTest"; private static final String TAG = "ConnectivityServiceTest";
private static final int TIMEOUT_MS = 500; private static final int TIMEOUT_MS = 500;
private static final int TEST_LINGER_DELAY_MS = 120;
private BroadcastInterceptingContext mServiceContext; private BroadcastInterceptingContext mServiceContext;
private WrappedConnectivityService mService; private WrappedConnectivityService mService;
@@ -330,7 +331,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
* @param validated Indicate if network should pretend to be validated. * @param validated Indicate if network should pretend to be validated.
*/ */
public void connect(boolean validated) { public void connect(boolean validated) {
assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE); assertEquals("MockNetworkAgents can only be connected once",
mNetworkInfo.getDetailedState(), DetailedState.IDLE);
assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
NetworkCallback callback = null; NetworkCallback callback = null;
@@ -548,6 +550,11 @@ public class ConnectivityServiceTest extends AndroidTestCase {
super(context, handler, cmdName, cmd); super(context, handler, cmdName, cmd);
} }
public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd,
int arg1, int arg2, Object obj) {
super(context, handler, cmdName, cmd, arg1, arg2, obj);
}
@Override @Override
public void schedule(long when) { public void schedule(long when) {
long delayMs = when - SystemClock.elapsedRealtime(); long delayMs = when - SystemClock.elapsedRealtime();
@@ -556,12 +563,13 @@ public class ConnectivityServiceTest extends AndroidTestCase {
fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT + fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT +
"ms into the future: " + delayMs); "ms into the future: " + delayMs);
} }
mHandler.sendEmptyMessageDelayed(mCmd, delayMs); Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
mHandler.sendMessageDelayed(msg, delayMs);
} }
@Override @Override
public void cancel() { public void cancel() {
mHandler.removeMessages(mCmd); mHandler.removeMessages(mCmd, mObj);
} }
@Override @Override
@@ -585,12 +593,6 @@ public class ConnectivityServiceTest extends AndroidTestCase {
protected CaptivePortalProbeResult isCaptivePortal() { protected CaptivePortalProbeResult isCaptivePortal() {
return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl); return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl);
} }
@Override
protected WakeupMessage makeWakeupMessage(
Context context, Handler handler, String cmdName, int cmd) {
return new FakeWakeupMessage(context, handler, cmdName, cmd);
}
} }
private class WrappedConnectivityService extends ConnectivityService { private class WrappedConnectivityService extends ConnectivityService {
@@ -599,6 +601,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
public WrappedConnectivityService(Context context, INetworkManagementService netManager, public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) { INetworkStatsService statsService, INetworkPolicyManager policyManager) {
super(context, netManager, statsService, policyManager); super(context, netManager, statsService, policyManager);
mLingerDelayMs = TEST_LINGER_DELAY_MS;
} }
@Override @Override
@@ -642,6 +645,12 @@ public class ConnectivityServiceTest extends AndroidTestCase {
return monitor; return monitor;
} }
@Override
public WakeupMessage makeWakeupMessage(
Context context, Handler handler, String cmdName, int cmd, Object obj) {
return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
}
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor; return mLastCreatedNetworkMonitor;
} }
@@ -686,8 +695,6 @@ public class ConnectivityServiceTest extends AndroidTestCase {
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
NetworkMonitor.SetDefaultLingerTime(120);
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 . // http://b/25897652 .
if (Looper.myLooper() == null) { if (Looper.myLooper() == null) {
@@ -1051,42 +1058,58 @@ public class ConnectivityServiceTest extends AndroidTestCase {
private class CallbackInfo { private class CallbackInfo {
public final CallbackState state; public final CallbackState state;
public final Network network; public final Network network;
public CallbackInfo(CallbackState s, Network n) { state = s; network = n; } public Object arg;
public CallbackInfo(CallbackState s, Network n, Object o) {
state = s; network = n; arg = o;
}
public String toString() { return String.format("%s (%s)", state, network); } public String toString() { return String.format("%s (%s)", state, network); }
public boolean equals(Object o) { public boolean equals(Object o) {
if (!(o instanceof CallbackInfo)) return false; if (!(o instanceof CallbackInfo)) return false;
// Ignore timeMs, since it's unpredictable.
CallbackInfo other = (CallbackInfo) o; CallbackInfo other = (CallbackInfo) o;
return state == other.state && Objects.equals(network, other.network); return state == other.state && Objects.equals(network, other.network);
} }
} }
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
private void setLastCallback(CallbackState state, Network network) { private void setLastCallback(CallbackState state, Network network, Object o) {
mCallbacks.offer(new CallbackInfo(state, network)); mCallbacks.offer(new CallbackInfo(state, network, o));
} }
public void onAvailable(Network network) { public void onAvailable(Network network) {
setLastCallback(CallbackState.AVAILABLE, network); setLastCallback(CallbackState.AVAILABLE, network, null);
} }
public void onLosing(Network network, int maxMsToLive) { public void onLosing(Network network, int maxMsToLive) {
setLastCallback(CallbackState.LOSING, network); setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
} }
public void onLost(Network network) { public void onLost(Network network) {
setLastCallback(CallbackState.LOST, network); setLastCallback(CallbackState.LOST, network, null);
}
void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
CallbackInfo expected = new CallbackInfo(
state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
CallbackInfo actual;
try {
actual = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
assertEquals("Unexpected callback:", expected, actual);
} catch (InterruptedException e) {
fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
actual = null; // Or the compiler can't tell it's never used uninitialized.
}
if (state == CallbackState.LOSING) {
String msg = String.format(
"Invalid linger time value %d, must be between %d and %d",
actual.arg, 0, TEST_LINGER_DELAY_MS);
int maxMsToLive = (Integer) actual.arg;
assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS);
}
} }
void expectCallback(CallbackState state, MockNetworkAgent mockAgent) { void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
CallbackInfo expected = new CallbackInfo( expectCallback(state, mockAgent, TIMEOUT_MS);
state,
(mockAgent != null) ? mockAgent.getNetwork() : null);
try {
assertEquals("Unexpected callback:",
expected, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
}
} }
void assertNoCallback() { void assertNoCallback() {
@@ -1249,6 +1272,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
} }
callback.expectCallback(CallbackState.LOSING, oldNetwork); callback.expectCallback(CallbackState.LOSING, oldNetwork);
// TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
// longer lingering?
defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork); defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork);
assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
} }
@@ -1306,8 +1331,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.adjustScore(50);
mWiFiNetworkAgent.connect(false); // Score: 70 mWiFiNetworkAgent.connect(false); // Score: 70
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1318,24 +1343,24 @@ public class ConnectivityServiceTest extends AndroidTestCase {
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi, then validate it. In this case we do not linger cell. What happens is that // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
// when wifi connects, we don't linger because cell could potentially become the default // it's arguably correct to linger it, since it was the default network before it validated.
// network if it validated. Then, when wifi validates, we re-evaluate cell, see it has no
// requests, and tear it down because it's unneeded.
// TODO: can we linger in this case?
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true); mWiFiNetworkAgent.connect(true);
callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
// The current code has a bug: if a network is lingering, and we add and then remove a // If a network is lingering, and we add and remove a request from it, resume lingering.
// request from it, we forget that the network was lingering and tear it down immediately.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true); mCellNetworkAgent.connect(true);
callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -1350,11 +1375,43 @@ public class ConnectivityServiceTest extends AndroidTestCase {
.addTransportType(TRANSPORT_CELLULAR).build(); .addTransportType(TRANSPORT_CELLULAR).build();
NetworkCallback noopCallback = new NetworkCallback(); NetworkCallback noopCallback = new NetworkCallback();
mCm.requestNetwork(cellRequest, noopCallback); mCm.requestNetwork(cellRequest, noopCallback);
// TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer
// lingering?
mCm.unregisterNetworkCallback(noopCallback); mCm.unregisterNetworkCallback(noopCallback);
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
// Similar to the above: lingering can start even after the lingered request is removed.
// Disconnect wifi and switch to cell.
mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
// Cell is now the default network. Pin it with a cell-specific request.
noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525
mCm.requestNetwork(cellRequest, noopCallback);
// Now connect wifi, and expect it to become the default network.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
// The default request is lingering on cell, but nothing happens to cell, and we send no
// callbacks for it, because it's kept up by cellRequest.
callback.assertNoCallback();
// Now unregister cellRequest and expect cell to start lingering.
mCm.unregisterNetworkCallback(noopCallback);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
// Let linger run its course.
callback.assertNoCallback();
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
TEST_LINGER_DELAY_MS /* timeoutMs */);
// Clean up.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback); mCm.unregisterNetworkCallback(defaultCallback);