Merge "Re-hide onPreCheck and unhide NET_CAPABILITY_VALIDATED." into mnc-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 96bf503..04a29aa 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -801,28 +801,6 @@
     }
 
     /**
-     * Returns details about the Provisioning or currently active default data network. When
-     * connected, this network is the default route for outgoing connections.
-     * You should always check {@link NetworkInfo#isConnected()} before initiating
-     * network traffic. This may return {@code null} when there is no default
-     * network.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
-     *
-     * @return a {@link NetworkInfo} object for the current default network
-     *        or {@code null} if no default network is currently active
-     *
-     * {@hide}
-     */
-    public NetworkInfo getProvisioningOrActiveNetworkInfo() {
-        try {
-            return mService.getProvisioningOrActiveNetworkInfo();
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
      * Returns the IP information for the current default network.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
@@ -2007,24 +1985,6 @@
     }
 
     /**
-     * Signal that the captive portal check on the indicated network
-     * is complete and whether its a captive portal or not.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
-     *
-     * @param info the {@link NetworkInfo} object for the networkType
-     *        in question.
-     * @param isCaptivePortal true/false.
-     * {@hide}
-     */
-    public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
-        try {
-            mService.captivePortalCheckCompleted(info, isCaptivePortal);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
      * Check mobile provisioning.
      *
      * @param suggestedTimeOutMs, timeout in milliseconds
@@ -2056,18 +2016,6 @@
     }
 
     /**
-     * Get the mobile redirected provisioning url.
-     * {@hide}
-     */
-    public String getMobileRedirectedProvisioningUrl() {
-        try {
-            return mService.getMobileRedirectedProvisioningUrl();
-        } catch (RemoteException e) {
-        }
-        return null;
-    }
-
-    /**
      * Set sign in error notification to visible or in visible
      *
      * @param visible
@@ -2515,7 +2463,7 @@
      * Intent to reserve the network or it will be released shortly after the Intent
      * is processed.
      * <p>
-     * If there is already an request for this Intent registered (with the equality of
+     * If there is already a request for this Intent registered (with the equality of
      * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
      * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
      * <p>
@@ -2575,6 +2523,44 @@
     }
 
     /**
+     * Registers a PendingIntent to be sent when a network is available which satisfies the given
+     * {@link NetworkRequest}.
+     *
+     * This function behaves identically to the version that takes a NetworkCallback, but instead
+     * of {@link NetworkCallback} a {@link PendingIntent} is used.  This means
+     * the request may outlive the calling application and get called back when a suitable
+     * network is found.
+     * <p>
+     * The operation is an Intent broadcast that goes to a broadcast receiver that
+     * you registered with {@link Context#registerReceiver} or through the
+     * &lt;receiver&gt; tag in an AndroidManifest.xml file
+     * <p>
+     * The operation Intent is delivered with two extras, a {@link Network} typed
+     * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+     * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
+     * the original requests parameters.
+     * <p>
+     * If there is already a request for this Intent registered (with the equality of
+     * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+     * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+     * <p>
+     * The request may be released normally by calling
+     * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     * @param request {@link NetworkRequest} describing this request.
+     * @param operation Action to perform when the network is available (corresponds
+     *                  to the {@link NetworkCallback#onAvailable} call.  Typically
+     *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+     */
+    public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
+        checkPendingIntent(operation);
+        try {
+            mService.pendingListenForNetwork(request.networkCapabilities, operation);
+        } catch (RemoteException e) {}
+    }
+
+    /**
      * Requests bandwidth update for a given {@link Network} and returns whether the update request
      * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying
      * network connection for updated bandwidth information. The caller will be notified via
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 89d23a2..29557bb 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -53,8 +53,6 @@
     Network[] getAllNetworks();
     NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
 
-    NetworkInfo getProvisioningOrActiveNetworkInfo();
-
     boolean isNetworkSupported(int networkType);
 
     LinkProperties getActiveLinkProperties();
@@ -122,14 +120,10 @@
 
     boolean updateLockdownVpn();
 
-    void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
-
     int checkMobileProvisioning(int suggestedTimeOutMs);
 
     String getMobileProvisioningUrl();
 
-    String getMobileRedirectedProvisioningUrl();
-
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
     void setAirplaneMode(boolean enable);
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 9628bae..fe69320 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -378,6 +378,9 @@
         //
         // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
         // value in the native/android/net.c NDK implementation.
+        if (netId == 0) {
+            return 0L;  // make this zero condition obvious for debugging
+        }
         final long HANDLE_MAGIC = 0xfacade;
         return (((long) netId) << 32) | HANDLE_MAGIC;
     }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 393637e..af7a465 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -120,7 +120,6 @@
     private String mExtraInfo;
     private boolean mIsFailover;
     private boolean mIsRoaming;
-    private boolean mIsConnectedToProvisioningNetwork;
 
     /**
      * Indicates whether network connectivity is possible:
@@ -142,7 +141,6 @@
         mState = State.UNKNOWN;
         mIsAvailable = false; // until we're told otherwise, assume unavailable
         mIsRoaming = false;
-        mIsConnectedToProvisioningNetwork = false;
     }
 
     /** {@hide} */
@@ -160,7 +158,6 @@
                 mIsFailover = source.mIsFailover;
                 mIsRoaming = source.mIsRoaming;
                 mIsAvailable = source.mIsAvailable;
-                mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
             }
         }
     }
@@ -332,22 +329,6 @@
         }
     }
 
-    /** {@hide} */
-    @VisibleForTesting
-    public boolean isConnectedToProvisioningNetwork() {
-        synchronized (this) {
-            return mIsConnectedToProvisioningNetwork;
-        }
-    }
-
-    /** {@hide} */
-    @VisibleForTesting
-    public void setIsConnectedToProvisioningNetwork(boolean val) {
-        synchronized (this) {
-            mIsConnectedToProvisioningNetwork = val;
-        }
-    }
-
     /**
      * Reports the current coarse-grained state of the network.
      * @return the coarse-grained state
@@ -431,8 +412,6 @@
             append(", roaming: ").append(mIsRoaming).
             append(", failover: ").append(mIsFailover).
             append(", isAvailable: ").append(mIsAvailable).
-            append(", isConnectedToProvisioningNetwork: ").
-            append(mIsConnectedToProvisioningNetwork).
             append("]");
             return builder.toString();
         }
@@ -461,7 +440,6 @@
             dest.writeInt(mIsFailover ? 1 : 0);
             dest.writeInt(mIsAvailable ? 1 : 0);
             dest.writeInt(mIsRoaming ? 1 : 0);
-            dest.writeInt(mIsConnectedToProvisioningNetwork ? 1 : 0);
             dest.writeString(mReason);
             dest.writeString(mExtraInfo);
         }
@@ -484,7 +462,6 @@
                 netInfo.mIsFailover = in.readInt() != 0;
                 netInfo.mIsAvailable = in.readInt() != 0;
                 netInfo.mIsRoaming = in.readInt() != 0;
-                netInfo.mIsConnectedToProvisioningNetwork = in.readInt() != 0;
                 netInfo.mReason = in.readString();
                 netInfo.mExtraInfo = in.readString();
                 return netInfo;
diff --git a/core/tests/coretests/src/android/net/NetworkTest.java b/core/tests/coretests/src/android/net/NetworkTest.java
index b0ecb04..74b6d98 100644
--- a/core/tests/coretests/src/android/net/NetworkTest.java
+++ b/core/tests/coretests/src/android/net/NetworkTest.java
@@ -16,11 +16,14 @@
 
 package android.net;
 
+import static android.test.MoreAsserts.assertNotEqual;
+
 import android.net.LocalServerSocket;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.net.Network;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -29,6 +32,7 @@
 import java.net.InetAddress;
 import java.net.Inet6Address;
 import java.net.SocketException;
+
 import junit.framework.TestCase;
 
 public class NetworkTest extends TestCase {
@@ -93,4 +97,50 @@
             fail("SocketException not thrown");
         } catch (SocketException expected) {}
     }
+
+    @SmallTest
+    public void testZeroIsObviousForDebugging() {
+        Network zero = new Network(0);
+        assertEquals(0, zero.hashCode());
+        assertEquals(0, zero.getNetworkHandle());
+        assertEquals("0", zero.toString());
+    }
+
+    @SmallTest
+    public void testGetNetworkHandle() {
+        Network one = new Network(1);
+        Network two = new Network(2);
+        Network three = new Network(3);
+
+        // None of the hashcodes are zero.
+        assertNotEqual(0, one.hashCode());
+        assertNotEqual(0, two.hashCode());
+        assertNotEqual(0, three.hashCode());
+
+        // All the hashcodes are distinct.
+        assertNotEqual(one.hashCode(), two.hashCode());
+        assertNotEqual(one.hashCode(), three.hashCode());
+        assertNotEqual(two.hashCode(), three.hashCode());
+
+        // None of the handles are zero.
+        assertNotEqual(0, one.getNetworkHandle());
+        assertNotEqual(0, two.getNetworkHandle());
+        assertNotEqual(0, three.getNetworkHandle());
+
+        // All the handles are distinct.
+        assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
+        assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
+        assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
+
+        // The handles are not equal to the hashcodes.
+        assertNotEqual(one.hashCode(), one.getNetworkHandle());
+        assertNotEqual(two.hashCode(), two.getNetworkHandle());
+        assertNotEqual(three.hashCode(), three.getNetworkHandle());
+
+        // Adjust as necessary to test an implementation's specific constants.
+        // When running with runtest, "adb logcat -s TestRunner" can be useful.
+        assertEquals(4311403230L, one.getNetworkHandle());
+        assertEquals(8606370526L, two.getNetworkHandle());
+        assertEquals(12901337822L, three.getNetworkHandle());
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9c6e16f..25d4d5e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -136,11 +136,13 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -324,7 +326,7 @@
 
     /**
      * used to add a network request with a pending intent
-     * includes a NetworkRequestInfo
+     * obj = NetworkRequestInfo
      */
     private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
 
@@ -354,6 +356,12 @@
      */
     private static final int EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON = 30;
 
+    /**
+     * used to add a network listener with a pending intent
+     * obj = NetworkRequestInfo
+     */
+    private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
+
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -960,44 +968,6 @@
         return nai != null ? nai.network : null;
     }
 
-    /**
-     * Find the first Provisioning network.
-     *
-     * @return NetworkInfo or null if none.
-     */
-    private NetworkInfo getProvisioningNetworkInfo() {
-        enforceAccessPermission();
-
-        // Find the first Provisioning Network
-        NetworkInfo provNi = null;
-        for (NetworkInfo ni : getAllNetworkInfo()) {
-            if (ni.isConnectedToProvisioningNetwork()) {
-                provNi = ni;
-                break;
-            }
-        }
-        if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
-        return provNi;
-    }
-
-    /**
-     * Find the first Provisioning network or the ActiveDefaultNetwork
-     * if there is no Provisioning network
-     *
-     * @return NetworkInfo or null if none.
-     */
-    @Override
-    public NetworkInfo getProvisioningOrActiveNetworkInfo() {
-        enforceAccessPermission();
-
-        NetworkInfo provNi = getProvisioningNetworkInfo();
-        if (provNi == null) {
-            provNi = getActiveNetworkInfo();
-        }
-        if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
-        return provNi;
-    }
-
     public NetworkInfo getActiveNetworkInfoUnfiltered() {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
@@ -1567,14 +1537,6 @@
         }
     };
 
-    /** @hide */
-    @Override
-    public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
-        enforceConnectivityInternalPermission();
-        if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
-//        mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
-    }
-
     /**
      * Setup data activity tracking for the given network.
      *
@@ -2112,15 +2074,6 @@
                 log(nai.name() + " got DISCONNECTED, was satisfying " + nai.networkRequests.size());
             }
             // A network agent has disconnected.
-            if (nai.created) {
-                // Tell netd to clean up the configuration for this network
-                // (routing rules, DNS, etc).
-                try {
-                    mNetd.removeNetwork(nai.network.netId);
-                } catch (Exception e) {
-                    loge("Exception removing network: " + e);
-                }
-            }
             // TODO - if we move the logic to the network agent (have them disconnect
             // because they lost all their requests or because their score isn't good)
             // then they would disconnect organically, report their new state and then
@@ -2139,8 +2092,9 @@
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
             synchronized (mNetworkForNetId) {
+                // Remove the NetworkAgent, but don't mark the netId as
+                // available until we've told netd to delete it below.
                 mNetworkForNetId.remove(nai.network.netId);
-                mNetIdInUse.delete(nai.network.netId);
             }
             // Since we've lost the network, go through all the requests that
             // it was satisfying and see if any other factory can satisfy them.
@@ -2182,9 +2136,28 @@
                 rematchNetworkAndRequests(networkToActivate, NascentState.NOT_JUST_VALIDATED,
                         ReapUnvalidatedNetworks.DONT_REAP);
             }
+            if (nai.created) {
+                // Tell netd to clean up the configuration for this network
+                // (routing rules, DNS, etc).
+                // This may be slow as it requires a lot of netd shelling out to ip and
+                // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
+                // after we've rematched networks with requests which should make a potential
+                // fallback network the default or requested a new network from the
+                // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+                // long time.
+                try {
+                    mNetd.removeNetwork(nai.network.netId);
+                } catch (Exception e) {
+                    loge("Exception removing network: " + e);
+                }
+            }
+            synchronized (mNetworkForNetId) {
+                mNetIdInUse.delete(nai.network.netId);
+            }
+        } else {
+            NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
+            if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
         }
-        NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
-        if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
     }
 
     // If this method proves to be too slow then we can maintain a separate
@@ -2229,7 +2202,9 @@
                     // Not setting bestNetwork here as a listening NetworkRequest may be
                     // satisfied by multiple Networks.  Instead the request is added to
                     // each satisfying Network and notified about each.
-                    network.addRequest(nri.request);
+                    if (!network.addRequest(nri.request)) {
+                        Slog.wtf(TAG, "BUG: " + network.name() + " already has " + nri.request);
+                    }
                     notifyNetworkCallback(network, nri);
                 } else if (bestNetwork == null ||
                         bestNetwork.getCurrentScore() < network.getCurrentScore()) {
@@ -2240,7 +2215,9 @@
         if (bestNetwork != null) {
             if (DBG) log("using " + bestNetwork.name());
             unlinger(bestNetwork);
-            bestNetwork.addRequest(nri.request);
+            if (!bestNetwork.addRequest(nri.request)) {
+                Slog.wtf(TAG, "BUG: " + bestNetwork.name() + " already has " + nri.request);
+            }
             mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
             notifyNetworkCallback(bestNetwork, nri);
             if (nri.request.legacyType != TYPE_NONE) {
@@ -2524,7 +2501,8 @@
                     handleRegisterNetworkRequest((NetworkRequestInfo) msg.obj);
                     break;
                 }
-                case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
+                case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT:
+                case EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT: {
                     handleRegisterNetworkRequestWithIntent(msg);
                     break;
                 }
@@ -3289,7 +3267,6 @@
             CharSequence title;
             CharSequence details;
             int icon;
-            Notification notification = new Notification();
             if (notifyType == NotificationType.NO_INTERNET &&
                     networkType == ConnectivityManager.TYPE_WIFI) {
                 title = r.getString(R.string.wifi_no_internet, 0);
@@ -3324,14 +3301,17 @@
                 return;
             }
 
-            notification.when = 0;
-            notification.icon = icon;
-            notification.flags = Notification.FLAG_AUTO_CANCEL;
-            notification.tickerText = title;
-            notification.color = mContext.getColor(
-                    com.android.internal.R.color.system_notification_accent_color);
-            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
-            notification.contentIntent = intent;
+            Notification notification = new Notification.Builder(mContext)
+                    .setWhen(0)
+                    .setSmallIcon(icon)
+                    .setAutoCancel(true)
+                    .setTicker(title)
+                    .setColor(mContext.getColor(
+                            com.android.internal.R.color.system_notification_accent_color))
+                    .setContentTitle(title)
+                    .setContentText(details)
+                    .setContentIntent(intent)
+                    .build();
 
             try {
                 notificationManager.notify(NOTIFICATION_ID, id, notification);
@@ -3355,7 +3335,6 @@
      * <?xml version="1.0" encoding="utf-8"?>
      *  <provisioningUrls>
      *   <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
-     *   <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
      *  </provisioningUrls>
      */
     private static final String PROVISIONING_URL_PATH =
@@ -3366,33 +3345,15 @@
     private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
     /** XML tag for individual url */
     private static final String TAG_PROVISIONING_URL = "provisioningUrl";
-    /** XML tag for redirected url */
-    private static final String TAG_REDIRECTED_URL = "redirectedUrl";
     /** XML attribute for mcc */
     private static final String ATTR_MCC = "mcc";
     /** XML attribute for mnc */
     private static final String ATTR_MNC = "mnc";
 
-    private static final int REDIRECTED_PROVISIONING = 1;
-    private static final int PROVISIONING = 2;
-
-    private String getProvisioningUrlBaseFromFile(int type) {
+    private String getProvisioningUrlBaseFromFile() {
         FileReader fileReader = null;
         XmlPullParser parser = null;
         Configuration config = mContext.getResources().getConfiguration();
-        String tagType;
-
-        switch (type) {
-            case PROVISIONING:
-                tagType = TAG_PROVISIONING_URL;
-                break;
-            case REDIRECTED_PROVISIONING:
-                tagType = TAG_REDIRECTED_URL;
-                break;
-            default:
-                throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
-                        type);
-        }
 
         try {
             fileReader = new FileReader(mProvisioningUrlFile);
@@ -3406,7 +3367,7 @@
                 String element = parser.getName();
                 if (element == null) break;
 
-                if (element.equals(tagType)) {
+                if (element.equals(TAG_PROVISIONING_URL)) {
                     String mcc = parser.getAttributeValue(null, ATTR_MCC);
                     try {
                         if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
@@ -3441,19 +3402,9 @@
     }
 
     @Override
-    public String getMobileRedirectedProvisioningUrl() {
-        enforceConnectivityInternalPermission();
-        String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
-        if (TextUtils.isEmpty(url)) {
-            url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
-        }
-        return url;
-    }
-
-    @Override
     public String getMobileProvisioningUrl() {
         enforceConnectivityInternalPermission();
-        String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+        String url = getProvisioningUrlBaseFromFile();
         if (TextUtils.isEmpty(url)) {
             url = mContext.getResources().getString(R.string.mobile_provisioning_url);
             log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
@@ -3760,6 +3711,18 @@
     @Override
     public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
             PendingIntent operation) {
+        checkNotNull(operation, "PendingIntent cannot be null.");
+        if (!hasWifiNetworkListenPermission(networkCapabilities)) {
+            enforceAccessPermission();
+        }
+
+        NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
+                networkCapabilities), TYPE_NONE, nextNetworkRequestId());
+        if (DBG) log("pendingListenForNetwork for " + networkRequest + " to trigger " + operation);
+        NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
+                NetworkRequestInfo.LISTEN);
+
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
     }
 
     @Override
@@ -3993,10 +3956,52 @@
         }
         return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
     }
+
+    // TODO: investigate moving this into LinkProperties, if only to make more accurate
+    // the isProvisioned() checks.
+    private static Collection<InetAddress> getLikelyReachableDnsServers(LinkProperties lp) {
+        final ArrayList<InetAddress> dnsServers = new ArrayList<InetAddress>();
+        final List<RouteInfo> allRoutes = lp.getAllRoutes();
+        for (InetAddress nameserver : lp.getDnsServers()) {
+            // If the LinkProperties doesn't include a route to the nameserver, ignore it.
+            final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, nameserver);
+            if (bestRoute == null) {
+                continue;
+            }
+
+            // TODO: better source address evaluation for destination addresses.
+            if (nameserver instanceof Inet4Address) {
+                if (!lp.hasIPv4Address()) {
+                    continue;
+                }
+            } else if (nameserver instanceof Inet6Address) {
+                if (nameserver.isLinkLocalAddress()) {
+                    if (((Inet6Address)nameserver).getScopeId() == 0) {
+                        // For now, just make sure link-local DNS servers have
+                        // scopedIds set, since DNS lookups will fail otherwise.
+                        // TODO: verify the scopeId matches that of lp's interface.
+                        continue;
+                    }
+                }  else {
+                    if (bestRoute.isIPv6Default() && !lp.hasGlobalIPv6Address()) {
+                        // TODO: reconsider all corner cases (disconnected ULA networks, ...).
+                        continue;
+                    }
+                }
+            }
+
+            dnsServers.add(nameserver);
+        }
+        return Collections.unmodifiableList(dnsServers);
+    }
+
     private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId,
                              boolean flush, boolean useDefaultDns) {
+        // TODO: consider comparing the getLikelyReachableDnsServers() lists, in case the
+        // route to a DNS server has been removed (only really applicable in special cases
+        // where there is no default route).
         if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
-            Collection<InetAddress> dnses = newLp.getDnsServers();
+            Collection<InetAddress> dnses = getLikelyReachableDnsServers(newLp);
             if (dnses.size() == 0 && mDefaultDns != null && useDefaultDns) {
                 dnses = new ArrayList();
                 dnses.add(mDefaultDns);
@@ -4226,6 +4231,7 @@
         // Find and migrate to this Network any NetworkRequests for
         // which this network is now the best.
         ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
+        ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();
         if (VDBG) log(" network has: " + newNetwork.networkCapabilities);
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
@@ -4244,7 +4250,7 @@
                 if (!nri.isRequest) {
                     // This is not a request, it's a callback listener.
                     // Add it to newNetwork regardless of score.
-                    newNetwork.addRequest(nri.request);
+                    if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
                     continue;
                 }
 
@@ -4267,7 +4273,10 @@
                     }
                     unlinger(newNetwork);
                     mNetworkForRequestId.put(nri.request.requestId, newNetwork);
-                    newNetwork.addRequest(nri.request);
+                    if (!newNetwork.addRequest(nri.request)) {
+                        Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
+                    }
+                    addedRequests.add(nri);
                     keep = true;
                     // Tell NetworkFactories about the new score, so they can stop
                     // trying to connect if they know they cannot match it.
@@ -4310,7 +4319,7 @@
 
             // do this after the default net is switched, but
             // before LegacyTypeTracker sends legacy broadcasts
-            notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
+            for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
 
             if (isNewDefault) {
                 // Maintain the illusion: since the legacy API only
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index eac748f..3bf1183 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -106,8 +106,16 @@
         networkMisc = misc;
     }
 
-    public void addRequest(NetworkRequest networkRequest) {
+    /**
+     * Add {@code networkRequest} to this network as it's satisfied by this network.
+     * NOTE: This function must only be called on ConnectivityService's main thread.
+     * @return true if {@code networkRequest} was added or false if {@code networkRequest} was
+     *         already present.
+     */
+    public boolean addRequest(NetworkRequest networkRequest) {
+        if (networkRequests.get(networkRequest.requestId) == networkRequest) return false;
         networkRequests.put(networkRequest.requestId, networkRequest);
+        return true;
     }
 
     // Does this network satisfy request?
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index bb0a36f..712db09 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -134,6 +134,7 @@
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
         private final Thread mThread;
+        private int mScore;
         private NetworkAgent mNetworkAgent;
 
         MockNetworkAgent(int transport) {
@@ -142,13 +143,12 @@
             mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
             mNetworkCapabilities = new NetworkCapabilities();
             mNetworkCapabilities.addTransportType(transport);
-            final int score;
             switch (transport) {
                 case TRANSPORT_WIFI:
-                    score = 60;
+                    mScore = 60;
                     break;
                 case TRANSPORT_CELLULAR:
-                    score = 50;
+                    mScore = 50;
                     break;
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
@@ -159,7 +159,7 @@
                     Looper.prepare();
                     mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext,
                             "Mock" + typeName, mNetworkInfo, mNetworkCapabilities,
-                            new LinkProperties(), score, new NetworkMisc()) {
+                            new LinkProperties(), mScore, new NetworkMisc()) {
                         public void unwanted() {}
                     };
                     initComplete.open();
@@ -167,7 +167,12 @@
                 }
             };
             mThread.start();
-            initComplete.block();
+            waitFor(initComplete);
+        }
+
+        public void adjustScore(int change) {
+            mScore += change;
+            mNetworkAgent.sendNetworkScore(mScore);
         }
 
         /**
@@ -209,7 +214,7 @@
 
             if (validated) {
                 // Wait for network to validate.
-                validatedCv.block();
+                waitFor(validatedCv);
                 mNetworkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
                 mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
             }
@@ -228,6 +233,10 @@
     }
 
     private static class MockNetworkFactory extends NetworkFactory {
+        final ConditionVariable mNetworkStartedCV = new ConditionVariable();
+        final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
+        final ConditionVariable mNetworkRequestedCV = new ConditionVariable();
+        final ConditionVariable mNetworkReleasedCV = new ConditionVariable();
         final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
 
         public MockNetworkFactory(Looper looper, Context context, String logTag,
@@ -241,15 +250,51 @@
 
         protected void startNetwork() {
             mNetworkStarted.set(true);
+            mNetworkStartedCV.open();
         }
 
         protected void stopNetwork() {
             mNetworkStarted.set(false);
+            mNetworkStoppedCV.open();
         }
 
         public boolean getMyStartRequested() {
             return mNetworkStarted.get();
         }
+
+        public ConditionVariable getNetworkStartedCV() {
+            mNetworkStartedCV.close();
+            return mNetworkStartedCV;
+        }
+
+        public ConditionVariable getNetworkStoppedCV() {
+            mNetworkStoppedCV.close();
+            return mNetworkStoppedCV;
+        }
+
+        protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+            super.needNetworkFor(networkRequest, score);
+            mNetworkRequestedCV.open();
+        }
+
+        protected void releaseNetworkFor(NetworkRequest networkRequest) {
+            super.releaseNetworkFor(networkRequest);
+            mNetworkReleasedCV.open();
+        }
+
+        public ConditionVariable getNetworkRequestedCV() {
+            mNetworkRequestedCV.close();
+            return mNetworkRequestedCV;
+        }
+
+        public ConditionVariable getNetworkReleasedCV() {
+            mNetworkReleasedCV.close();
+            return mNetworkReleasedCV;
+        }
+
+        public void waitForNetworkRequests(final int count) {
+            waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } });
+        }
     }
 
     private class WrappedConnectivityService extends ConnectivityService {
@@ -286,6 +331,45 @@
         }
     }
 
+    private interface Criteria {
+        public boolean get();
+    }
+
+    /**
+     * Wait up to 500ms for {@code criteria.get()} to become true, polling.
+     * Fails if 500ms goes by before {@code criteria.get()} to become true.
+     */
+    static private void waitFor(Criteria criteria) {
+        int delays = 0;
+        while (!criteria.get()) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+            if (++delays == 5) fail();
+        }
+    }
+
+    /**
+     * Wait up to 500ms for {@code conditonVariable} to open.
+     * Fails if 500ms goes by before {@code conditionVariable} opens.
+     */
+    static private void waitFor(ConditionVariable conditionVariable) {
+        assertTrue(conditionVariable.block(500));
+    }
+
+    /**
+     * This should only be used to verify that nothing happens, in other words that no unexpected
+     * changes occur.  It should never be used to wait for a specific positive signal to occur.
+     */
+    private void shortSleep() {
+        // TODO: Instead of sleeping, instead wait for all message loops to idle.
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+        }
+    }
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
@@ -376,7 +460,7 @@
         // Test bringing up validated cellular.
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(true);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         assertEquals(2, mCm.getAllNetworks().length);
         assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
@@ -386,7 +470,7 @@
         // Test bringing up validated WiFi.
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.connect(true);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(2, mCm.getAllNetworks().length);
         assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
@@ -396,7 +480,7 @@
         // Test cellular linger timeout.
         try {
             Thread.sleep(6000);
-        } catch (Exception e) {
+        } catch (InterruptedException e) {
         }
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
@@ -404,7 +488,7 @@
         // Test WiFi disconnect.
         cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         verifyNoNetwork();
     }
 
@@ -414,38 +498,32 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(false);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up unvalidated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        try {
-            Thread.sleep(1000);
-        } catch (Exception e) {
-        }
+        shortSleep();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test cellular disconnect.
         mCellNetworkAgent.disconnect();
-        try {
-            Thread.sleep(1000);
-        } catch (Exception e) {
-        }
+        shortSleep();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         cv = waitForConnectivityBroadcasts(2);
         mCellNetworkAgent.connect(true);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test cellular disconnect.
         cv = waitForConnectivityBroadcasts(2);
         mCellNetworkAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test WiFi disconnect.
         cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         verifyNoNetwork();
     }
 
@@ -455,52 +533,234 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.connect(false);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.connect(false);
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test WiFi disconnect.
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test cellular disconnect.
         cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         verifyNoNetwork();
     }
 
     @LargeTest
+    public void testCellularOutscoresWeakWifi() throws Exception {
+        // Test bringing up validated cellular.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Test bringing up validated WiFi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        // Test WiFi getting really weak.
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.adjustScore(-11);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Test WiFi restoring signal strength.
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.adjustScore(11);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        mCellNetworkAgent.disconnect();
+        mWiFiNetworkAgent.disconnect();
+    }
+
+    enum CallbackState {
+        NONE,
+        AVAILABLE,
+        LOSING,
+        LOST
+    }
+
+    private class TestNetworkCallback extends NetworkCallback {
+        private final ConditionVariable mConditionVariable = new ConditionVariable();
+        private CallbackState mLastCallback = CallbackState.NONE;
+
+        public void onAvailable(Network network) {
+            assertEquals(CallbackState.NONE, mLastCallback);
+            mLastCallback = CallbackState.AVAILABLE;
+            mConditionVariable.open();
+        }
+
+        public void onLosing(Network network, int maxMsToLive) {
+            assertEquals(CallbackState.NONE, mLastCallback);
+            mLastCallback = CallbackState.LOSING;
+            mConditionVariable.open();
+        }
+
+        public void onLost(Network network) {
+            assertEquals(CallbackState.NONE, mLastCallback);
+            mLastCallback = CallbackState.LOST;
+            mConditionVariable.open();
+        }
+
+        ConditionVariable getConditionVariable() {
+            mLastCallback = CallbackState.NONE;
+            mConditionVariable.close();
+            return mConditionVariable;
+        }
+
+        CallbackState getLastCallback() {
+            return mLastCallback;
+        }
+    }
+
+    @LargeTest
+    public void testStateChangeNetworkCallbacks() throws Exception {
+        final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        final NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build();
+        mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+        mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+
+        // Test unvalidated networks
+        ConditionVariable cellCv = cellNetworkCallback.getConditionVariable();
+        ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable();
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(false);
+        waitFor(cellCv);
+        assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        waitFor(cv);
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        // This should not trigger spurious onAvailable() callbacks, b/21762680.
+        mCellNetworkAgent.adjustScore(-1);
+        shortSleep();
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        waitFor(wifiCv);
+        assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        waitFor(cv);
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.disconnect();
+        waitFor(wifiCv);
+        assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        waitFor(cv);
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent.disconnect();
+        waitFor(cellCv);
+        assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        waitFor(cv);
+
+        // Test validated networks
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        // Our method for faking successful validation generates an additional callback, so wait
+        // for broadcast instead.
+        cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        waitFor(cv);
+        waitFor(cellCv);
+        assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        // This should not trigger spurious onAvailable() callbacks, b/21762680.
+        mCellNetworkAgent.adjustScore(-1);
+        shortSleep();
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        // Our method for faking successful validation generates an additional callback, so wait
+        // for broadcast instead.
+        cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        waitFor(wifiCv);
+        assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
+        waitFor(cellCv);
+        assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback());
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        mWiFiNetworkAgent.disconnect();
+        waitFor(wifiCv);
+        assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+
+        cellCv = cellNetworkCallback.getConditionVariable();
+        wifiCv = wifiNetworkCallback.getConditionVariable();
+        mCellNetworkAgent.disconnect();
+        waitFor(cellCv);
+        assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
+        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+    }
+
+    @LargeTest
     public void testNetworkFactoryRequests() throws Exception {
         NetworkCapabilities filter = new NetworkCapabilities();
         filter.addCapability(NET_CAPABILITY_INTERNET);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
-        MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+        final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter);
         testFactory.setScoreFilter(40);
+        ConditionVariable cv = testFactory.getNetworkStartedCV();
         testFactory.register();
-        try {
-            Thread.sleep(500);
-        } catch (Exception e) {}
+        waitFor(cv);
         assertEquals(1, testFactory.getMyRequestCount());
         assertEquals(true, testFactory.getMyStartRequested());
 
         // now bring in a higher scored network
         MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        cv = waitForConnectivityBroadcasts(1);
+        ConditionVariable cvRelease = testFactory.getNetworkStoppedCV();
         testAgent.connect(true);
-        cv.block();
+        waitFor(cv);
         // part of the bringup makes another network request and then releases it
         // wait for the release
-        try { Thread.sleep(500); } catch (Exception e) {}
-        assertEquals(1, testFactory.getMyRequestCount());
+        waitFor(cvRelease);
         assertEquals(false, testFactory.getMyStartRequested());
+        testFactory.waitForNetworkRequests(1);
 
         // bring in a bunch of requests..
         ConnectivityManager.NetworkCallback[] networkCallbacks =
@@ -511,27 +771,20 @@
             builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
             mCm.requestNetwork(builder.build(), networkCallbacks[i]);
         }
-
-        try {
-            Thread.sleep(1000);
-        } catch (Exception e) {}
-        assertEquals(11, testFactory.getMyRequestCount());
+        testFactory.waitForNetworkRequests(11);
         assertEquals(false, testFactory.getMyStartRequested());
 
         // remove the requests
         for (int i = 0; i < networkCallbacks.length; i++) {
             mCm.unregisterNetworkCallback(networkCallbacks[i]);
         }
-        try {
-            Thread.sleep(500);
-        } catch (Exception e) {}
-        assertEquals(1, testFactory.getMyRequestCount());
+        testFactory.waitForNetworkRequests(1);
         assertEquals(false, testFactory.getMyStartRequested());
 
         // drop the higher scored network
         cv = waitForConnectivityBroadcasts(1);
         testAgent.disconnect();
-        cv.block();
+        waitFor(cv);
         assertEquals(1, testFactory.getMyRequestCount());
         assertEquals(true, testFactory.getMyStartRequested());
 
@@ -557,7 +810,7 @@
 //
 //        cv = waitForConnectivityBroadcasts(1);
 //        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        cv.block();
+//        waitFor(cv);
 //
 //        // verify that both routes were added
 //        int mobileNetId = mMobile.tracker.getNetwork().netId;
@@ -577,7 +830,7 @@
 //
 //        cv = waitForConnectivityBroadcasts(1);
 //        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        cv.block();
+//        waitFor(cv);
 //
 //        reset(mNetManager);
 //
@@ -593,7 +846,7 @@
 //
 //        cv = waitForConnectivityBroadcasts(1);
 //        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
-//        cv.block();
+//        waitFor(cv);
 //
 //        // verify that wifi routes added, and teardown requested
 //        int wifiNetId = mWifi.tracker.getNetwork().netId;
@@ -612,7 +865,7 @@
 //
 //        cv = waitForConnectivityBroadcasts(1);
 //        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        cv.block();
+//        waitFor(cv);
 //
 //        verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
 //        verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));