Merge "Improve ImageView drawable re-use" into mnc-dev
diff --git a/Android.mk b/Android.mk
index e96a932..1de3625 100644
--- a/Android.mk
+++ b/Android.mk
@@ -181,6 +181,7 @@
 	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
 	core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
+	core/java/android/net/ICaptivePortal.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/IEthernetManager.aidl \
 	core/java/android/net/IEthernetServiceListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index 3ad815c..b173638 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18101,6 +18101,14 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(android.net.Network);
@@ -18117,7 +18125,6 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
-    method public void ignoreNetworkWithCaptivePortal(android.net.Network, java.lang.String);
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -18126,7 +18133,6 @@
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public deprecated void reportBadNetwork(android.net.Network);
-    method public void reportCaptivePortalDismissed(android.net.Network, java.lang.String);
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
@@ -18139,7 +18145,7 @@
     field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
     field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
     field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
-    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
+    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
     field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover";
     field public static final java.lang.String EXTRA_NETWORK = "android.net.extra.NETWORK";
diff --git a/api/system-current.txt b/api/system-current.txt
index 86f615a..14fff8a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19611,6 +19611,14 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(android.net.Network);
@@ -19627,7 +19635,6 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
-    method public void ignoreNetworkWithCaptivePortal(android.net.Network, java.lang.String);
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -19636,7 +19643,6 @@
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public deprecated void reportBadNetwork(android.net.Network);
-    method public void reportCaptivePortalDismissed(android.net.Network, java.lang.String);
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
@@ -19649,7 +19655,7 @@
     field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
     field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
     field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
-    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
+    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
     field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover";
     field public static final java.lang.String EXTRA_NETWORK = "android.net.extra.NETWORK";
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
new file mode 100644
index 0000000..ee05f28
--- /dev/null
+++ b/core/java/android/net/CaptivePortal.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed urnder the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
+ * activity to indicate to the system different outcomes of captive portal sign in.  This class is
+ * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the
+ * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
+ */
+public class CaptivePortal implements Parcelable {
+    /** @hide */
+    public static final int APP_RETURN_DISMISSED    = 0;
+    /** @hide */
+    public static final int APP_RETURN_UNWANTED     = 1;
+    /** @hide */
+    public static final int APP_RETURN_WANTED_AS_IS = 2;
+
+    private final IBinder mBinder;
+
+    /** @hide */
+    public CaptivePortal(IBinder binder) {
+        mBinder = binder;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mBinder);
+    }
+
+    public static final Parcelable.Creator<CaptivePortal> CREATOR
+            = new Parcelable.Creator<CaptivePortal>() {
+        @Override
+        public CaptivePortal createFromParcel(Parcel in) {
+            return new CaptivePortal(in.readStrongBinder());
+        }
+
+        @Override
+        public CaptivePortal[] newArray(int size) {
+            return new CaptivePortal[size];
+        }
+    };
+
+    /**
+     * Indicate to the system that the captive portal has been
+     * dismissed.  In response the framework will re-evaluate the network's
+     * connectivity and might take further action thereafter.
+     */
+    public void reportCaptivePortalDismissed() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Indicate to the system that the user does not want to pursue signing in to the
+     * captive portal and the system should continue to prefer other networks
+     * without captive portals for use as the default active data network.  The
+     * system will not retest the network for a captive portal so as to avoid
+     * disturbing the user with further sign in to network notifications.
+     */
+    public void ignoreNetwork() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Indicate to the system the user wants to use this network as is, even though
+     * the captive portal is still in place.  The system will treat the network
+     * as if it did not have a captive portal when selecting the network to use
+     * as the default active data network. This may result in this network
+     * becoming the default active data network, which could disrupt network
+     * connectivity for apps because the captive portal is still in place.
+     * @hide
+     */
+    public void useNetwork() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 01334c3..de7ca0c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -102,26 +102,26 @@
      * portal, which is blocking Internet connectivity. The user was presented
      * with a notification that network sign in is required,
      * and the user invoked the notification's action indicating they
-     * desire to sign in to the network. Apps handling this action should
+     * desire to sign in to the network. Apps handling this activity should
      * facilitate signing in to the network. This action includes a
      * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents
      * the network presenting the captive portal; all communication with the
      * captive portal must be done using this {@code Network} object.
      * <p/>
-     * When the app handling this action believes the user has signed in to
-     * the network and the captive portal has been dismissed, the app should call
-     * {@link #reportCaptivePortalDismissed} so the system can reevaluate the network.
-     * If reevaluation finds the network no longer subject to a captive portal,
-     * the network may become the default active data network.
-     * <p/>
-     * When the app handling this action believes the user explicitly wants
+     * This activity includes a {@link CaptivePortal} extra named
+     * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different
+     * outcomes of the captive portal sign in to the system:
+     * <ul>
+     * <li> When the app handling this action believes the user has signed in to
+     * the network and the captive portal has been dismissed, the app should
+     * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
+     * reevaluate the network. If reevaluation finds the network no longer
+     * subject to a captive portal, the network may become the default active
+     * data network. </li>
+     * <li> When the app handling this action believes the user explicitly wants
      * to ignore the captive portal and the network, the app should call
-     * {@link #ignoreNetworkWithCaptivePortal}.
-     * <p/>
-     * Note that this action includes a {@code String} extra named
-     * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} that must
-     * be passed in to {@link #reportCaptivePortalDismissed} and
-     * {@link #ignoreNetworkWithCaptivePortal}.
+     * {@link CaptivePortal#ignoreNetwork}. </li>
+     * </ul>
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
@@ -187,16 +187,15 @@
      * {@hide}
      */
     public static final String EXTRA_INET_CONDITION = "inetCondition";
-
     /**
-     * The lookup key for a string that is sent out with
-     * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}. This string must be
-     * passed in to {@link #reportCaptivePortalDismissed} and
-     * {@link #ignoreNetworkWithCaptivePortal}. Retrieve it with
-     * {@link android.content.Intent#getStringExtra(String)}.
+     * The lookup key for a {@link CaptivePortal} object included with the
+     * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent.  The {@code CaptivePortal}
+     * object can be used to either indicate to the system that the captive
+     * portal has been dismissed or that the user does not want to pursue
+     * signing in to captive portal.  Retrieve it with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
      */
-    public static final String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
-
+    public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     /**
      * Broadcast action to indicate the change of data activity status
      * (idle or active) on a network in a recent period.
@@ -1779,82 +1778,6 @@
         }
     }
 
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_DISMISSED    = 0;
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED     = 1;
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2;
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate to the system that the captive portal has been
-     * dismissed.  In response the framework will re-evaluate the network's
-     * connectivity and might take further action thereafter.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     */
-    public void reportCaptivePortalDismissed(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_DISMISSED,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate that the user does not want to pursue signing in to
-     * captive portal and the system should continue to prefer other networks
-     * without captive portals for use as the default active data network.  The
-     * system will not retest the network for a captive portal so as to avoid
-     * disturbing the user with further sign in to network notifications.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     */
-    public void ignoreNetworkWithCaptivePortal(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate the user wants to use this network as is, even though
-     * the captive portal is still in place.  The system will treat the network
-     * as if it did not have a captive portal when selecting the network to use
-     * as the default active data network. This may result in this network
-     * becoming the default active data network, which could disrupt network
-     * connectivity for apps because the captive portal is still in place.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @hide
-     */
-    public void useNetworkWithCaptivePortal(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
     /**
      * Set a network-independent global http proxy.  This is not normally what you want
      * for typical HTTP proxies - they are general network dependent.  However if you're
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
new file mode 100644
index 0000000..a013e79
--- /dev/null
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Interface to inform NetworkMonitor of decisions of app handling captive portal.
+ * @hide
+ */
+interface ICaptivePortal
+{
+    oneway void appResponse(int response);
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 29557bb..78f8b95 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -96,8 +96,6 @@
 
     void reportNetworkConnectivity(in Network network, boolean hasConnectivity);
 
-    void captivePortalAppResponse(in Network network, int response, String actionToken);
-
     ProxyInfo getGlobalProxy();
 
     void setGlobalProxy(in ProxyInfo p);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index b9fd65f..bff1885 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -74,6 +74,13 @@
     paint->setTextEncoding(Paint::kGlyphID_TextEncoding);
 }
 
+struct LocaleCacheEntry {
+    std::string javaLocale;
+    std::string languageTag;
+};
+
+static thread_local LocaleCacheEntry sSingleEntryLocaleCache;
+
 class PaintGlue {
 public:
     enum MoveOpt {
@@ -399,10 +406,14 @@
     static void setTextLocale(JNIEnv* env, jobject clazz, jlong objHandle, jstring locale) {
         Paint* obj = reinterpret_cast<Paint*>(objHandle);
         ScopedUtfChars localeChars(env, locale);
-        char langTag[ULOC_FULLNAME_CAPACITY];
-        toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
+        if (sSingleEntryLocaleCache.javaLocale != localeChars.c_str()) {
+            sSingleEntryLocaleCache.javaLocale = localeChars.c_str();
+            char langTag[ULOC_FULLNAME_CAPACITY];
+            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
+            sSingleEntryLocaleCache.languageTag = langTag;
+        }
 
-        obj->setTextLocale(langTag);
+        obj->setTextLocale(sSingleEntryLocaleCache.languageTag);
     }
 
     static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
diff --git a/docs/html-intl/intl/es/index.jd b/docs/html-intl/intl/es/index.jd
index f69c442..e23c99f 100644
--- a/docs/html-intl/intl/es/index.jd
+++ b/docs/html-intl/intl/es/index.jd
@@ -26,9 +26,12 @@
         <p class="dac-hero-description">Prepárese para la próxima versión de
 Android. Pruebe sus aplicaciones en Nexus 5, 6, 9 y Player. </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
-          ¡Empiece hoy mismo!
+          ¡Empiece hoy mismo!</a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
         </a>
       </div>
     </div>
diff --git a/docs/html-intl/intl/es/preview/index.jd b/docs/html-intl/intl/es/preview/index.jd
index 35b578d..aae8e4c 100644
--- a/docs/html-intl/intl/es/preview/index.jd
+++ b/docs/html-intl/intl/es/preview/index.jd
@@ -27,6 +27,11 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           ¡Empiece hoy mismo!</a>
 <br>
+
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
+
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +48,7 @@
   <div class="dac-section-subtitle">
     Información esencial para ayudarlo a preparar sus aplicaciones para Android M.
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +60,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           Informe los problemas
         </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         Únase a la comunidad en G+        </a>
diff --git a/docs/html-intl/intl/ja/index.jd b/docs/html-intl/intl/ja/index.jd
index 4b1fa0e..3084b24 100644
--- a/docs/html-intl/intl/ja/index.jd
+++ b/docs/html-intl/intl/ja/index.jd
@@ -26,10 +26,14 @@
         <p class="dac-hero-description">次期バージョンの Android に向けて準備しましょう。
         Nexus 5、6、9、Nexus Player でアプリをテストします。 </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
           スタートガイド
-        </a>
+        </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
+
       </div>
     </div>
   </div>
diff --git a/docs/html-intl/intl/ja/preview/index.jd b/docs/html-intl/intl/ja/preview/index.jd
index 180f5ea..e44a42a 100644
--- a/docs/html-intl/intl/ja/preview/index.jd
+++ b/docs/html-intl/intl/ja/preview/index.jd
@@ -27,6 +27,10 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           スタートガイド
         </a><br>
+
+         <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +47,7 @@
   <div class="dac-section-subtitle">
     Android M 向けにアプリを用意する際に役立つ必須情報をご提供します。
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +59,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           問題の報告
         </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         Join G+ コミュニティ
diff --git a/docs/html-intl/intl/ko/index.jd b/docs/html-intl/intl/ko/index.jd
index dd756a4..34c14ec 100644
--- a/docs/html-intl/intl/ko/index.jd
+++ b/docs/html-intl/intl/ko/index.jd
@@ -23,13 +23,16 @@
         <div class="dac-hero-tag"></div>
 
         <h1 class="dac-hero-title">Android M Developer Preview</h1>
-        <p class="dac-hero-description">Android의 다음 버전을 만나볼 준비가 
+        <p class="dac-hero-description">Android의 다음 버전을 만나볼 준비가
         되셨습니까? 여러분의 앱을 Nexus 5, 6, 9 및 Player에서 테스트해보십시오. </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
           지금 시작하세요!
-        </a>
+        </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
   </div>
diff --git a/docs/html-intl/intl/ko/preview/index.jd b/docs/html-intl/intl/ko/preview/index.jd
index 15b14f9..9d0e040 100644
--- a/docs/html-intl/intl/ko/preview/index.jd
+++ b/docs/html-intl/intl/ko/preview/index.jd
@@ -27,6 +27,9 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           지금 시작하세요!
 </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +46,7 @@
   <div class="dac-section-subtitle">
     앱을 Android M에 대비시키는 데 유용한 중요 정보입니다.
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +58,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           문제 보고
 </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         G+ 커뮤니티 가입
diff --git a/docs/html-intl/intl/pt-br/index.jd b/docs/html-intl/intl/pt-br/index.jd
index dd48fe2..24dd2fe 100644
--- a/docs/html-intl/intl/pt-br/index.jd
+++ b/docs/html-intl/intl/pt-br/index.jd
@@ -26,10 +26,12 @@
         <p class="dac-hero-description">Prepare-se para a próxima versão do
         Android. Teste os aplicativos no Nexus 5, 6, 9 e Player.  </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
-          Comece!
-        </a>
+          Comece!</a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
   </div>
diff --git a/docs/html-intl/intl/pt-br/preview/index.jd b/docs/html-intl/intl/pt-br/preview/index.jd
index 1e0252f..172b211 100644
--- a/docs/html-intl/intl/pt-br/preview/index.jd
+++ b/docs/html-intl/intl/pt-br/preview/index.jd
@@ -27,6 +27,9 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           Comece!
 </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +46,7 @@
   <div class="dac-section-subtitle">
     Informações essenciais para ajudar você a preparar os aplicativos para Android M.
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +58,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           Relate problemas
 </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         Junte-se à comunidade do G+
diff --git a/docs/html-intl/intl/ru/index.jd b/docs/html-intl/intl/ru/index.jd
index f59a136..3fe65ea 100644
--- a/docs/html-intl/intl/ru/index.jd
+++ b/docs/html-intl/intl/ru/index.jd
@@ -27,10 +27,13 @@
         платформы Android. Протестируйте ваши приложения на устройствах Nexus
         5, 6, 9 и Player. </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
           Итак, приступим!
-        </a>
+        </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
   </div>
diff --git a/docs/html-intl/intl/ru/preview/index.jd b/docs/html-intl/intl/ru/preview/index.jd
index 27adbf9..138a5a6 100644
--- a/docs/html-intl/intl/ru/preview/index.jd
+++ b/docs/html-intl/intl/ru/preview/index.jd
@@ -27,6 +27,9 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           Итак, приступим!
 </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +46,7 @@
   <div class="dac-section-subtitle">
     Важная информация, которая поможет вам подготовить ваши приложения для работы в Android M.
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +58,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           Сообщить о проблеме
 </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         Присоединиться к сообществу в G+
diff --git a/docs/html-intl/intl/zh-cn/index.jd b/docs/html-intl/intl/zh-cn/index.jd
index 67035ea..bbd7fbe 100644
--- a/docs/html-intl/intl/zh-cn/index.jd
+++ b/docs/html-intl/intl/zh-cn/index.jd
@@ -26,9 +26,12 @@
         <p class="dac-hero-description">准备迎接 Android 的下一版本。在 Nexus 5、
         6、9 和 Player 中测试应用。 </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
-          开始!
+          开始!</a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
         </a>
       </div>
     </div>
diff --git a/docs/html-intl/intl/zh-cn/preview/index.jd b/docs/html-intl/intl/zh-cn/preview/index.jd
index 19b4b78..36ad718 100644
--- a/docs/html-intl/intl/zh-cn/preview/index.jd
+++ b/docs/html-intl/intl/zh-cn/preview/index.jd
@@ -27,6 +27,9 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           开始!
 </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +46,7 @@
   <div class="dac-section-subtitle">
     帮助您的应用准备使用 Android M 的必备信息。
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +58,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           报告问题
 </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         加入 G+ 社区
diff --git a/docs/html-intl/intl/zh-tw/index.jd b/docs/html-intl/intl/zh-tw/index.jd
index e788062..53bbd8d 100644
--- a/docs/html-intl/intl/zh-tw/index.jd
+++ b/docs/html-intl/intl/zh-tw/index.jd
@@ -23,13 +23,16 @@
         <div class="dac-hero-tag"></div>
 
         <h1 class="dac-hero-title">Android M Developer Preview</h1>
-        <p class="dac-hero-description">準備使用新版 Android。在 Nexus 5、6、9 和 
+        <p class="dac-hero-description">準備使用新版 Android。在 Nexus 5、6、9 和
         Player 上測試您的應用程式。 </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
           開始使用!
-        </a>
+        </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
   </div>
diff --git a/docs/html-intl/intl/zh-tw/preview/index.jd b/docs/html-intl/intl/zh-tw/preview/index.jd
index f57728c..a9cf1ae 100644
--- a/docs/html-intl/intl/zh-tw/preview/index.jd
+++ b/docs/html-intl/intl/zh-tw/preview/index.jd
@@ -27,6 +27,9 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           開始使用!
 </a><br>
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Developer Preview 2</a>
       </div>
     </div>
     <div class="dac-section dac-small">
@@ -43,7 +46,7 @@
   <div class="dac-section-subtitle">
     以下重要資訊可幫助您的應用程式準備好使用 Android M。
   </div>
- 
+
   <div class="resource-widget resource-flow-layout col-16"
        data-query="collection:preview/landing/more"
        data-cardSizes="6x6"
@@ -55,7 +58,7 @@
           <span class="dac-sprite dac-auto-chevron"></span>
           回報問題
 </a>
-      </li> 
+      </li>
       <li class="dac-section-link"><a href="http://g.co/dev/AndroidMDevPreview">
         <span class="dac-sprite dac-auto-chevron"></span>
         參加 G+ 社群
diff --git a/docs/html/index.jd b/docs/html/index.jd
index 794494e..c6dbbd5 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -26,10 +26,16 @@
         <p class="dac-hero-description">Get your apps ready for the next version
         of Android. Test on Nexus 5, 6, 9, and Player. </p>
 
-        <a class="dac-hero-cta" href="{@docRoot}preview/overview.html">
+        <a class="dac-hero-cta" href="{@docRoot}preview/index.html">
           <span class="dac-sprite dac-auto-chevron"></span>
           Get started
+        </a><br>
+
+        <a class="dac-hero-cta" href="{@docRoot}preview/support.html">
+          <span class="dac-sprite dac-auto-chevron"></span>
+          Update to Developer Preview 2
         </a>
+
       </div>
     </div>
   </div>
diff --git a/docs/html/preview/overview.jd b/docs/html/preview/overview.jd
index 36b177d..a19c926 100644
--- a/docs/html/preview/overview.jd
+++ b/docs/html/preview/overview.jd
@@ -5,6 +5,33 @@
 
 @jd:body
 
+<div class="cols" style=
+"background-color:#ffebc3; padding: 5px 0;margin-bottom:1em; text-align:center;">
+<h3>
+    Developer Preview 2 is now available
+  </h3>
+
+ <ul class="dac-section-links">
+    <li class="dac-section-link">
+      <a href="#preview2-notes">
+      <span class="dac-sprite dac-auto-chevron"></span>
+      Read the Notes</a>
+    </li>
+
+    <li class="dac-section-link">
+      <a href="#preview2-get">
+      <span class="dac-sprite dac-auto-chevron"></span>
+      Get the Update</a>
+    </li>
+
+    <li class="dac-section-link">
+      <a href="https://code.google.com/p/android-developer-preview/">
+      <span class="dac-sprite dac-auto-chevron"></span>
+      Report Issues</a>
+    </li>
+  </ul>
+</div>
+
 <p>
   Welcome to the <strong>Android M Developer Preview</strong>, a program that gives you
   everything you need to test and optimize your apps for the next version of
@@ -128,15 +155,15 @@
 
 <ul>
   <li>
-    <strong>Preview&nbsp;1</strong> (initial Preview release, late May),
+    <strong><a href="{@docRoot}preview/download_mp1.html">Preview&nbsp;1</a></strong> (available).
   </li>
 
   <li>
-    <strong>Preview&nbsp;2</strong> (late June/early July), and
+    <strong><a href="{@docRoot}preview/download.html">Preview&nbsp;2</a></strong> (available).
   </li>
 
   <li>
-    <strong>Preview&nbsp;3</strong> (near final, late July)
+    <strong>Preview&nbsp;3</strong> (near final, late July).
   </li>
 </ul>
 
diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs
index 5cc2159..83f69a4 100644
--- a/docs/html/preview/preview_toc.cs
+++ b/docs/html/preview/preview_toc.cs
@@ -126,7 +126,7 @@
 
   <li class="nav-section">
     <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/support.html">
-      Support</a></div>
+      Support & Release Notes</a></div>
   </li>
 
   <li class="nav-section">
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
index 8a73481..8792f778 100644
--- a/docs/html/preview/support.jd
+++ b/docs/html/preview/support.jd
@@ -1,4 +1,4 @@
-page.title=Support
+page.title=Support and Release Notes
 page.tags="preview", "developer preview"
 page.image=images/cards/card-support_16-9_2x.png
 
@@ -14,7 +14,7 @@
     <li class="dac-section-link">
       <a href="#preview2-notes">
       <span class="dac-sprite dac-auto-chevron"></span>
-      Release notes</a>
+      Read the Notes</a>
     </li>
 
     <li class="dac-section-link">
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index ddbcd78..a489f94 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
@@ -62,7 +63,7 @@
 
     private URL mURL;
     private Network mNetwork;
-    private String mResponseToken;
+    private CaptivePortal mCaptivePortal;
     private NetworkCallback mNetworkCallback;
     private ConnectivityManager mCm;
     private boolean mLaunchBrowser = false;
@@ -83,7 +84,7 @@
             done(Result.WANTED_AS_IS);
         }
         mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
-        mResponseToken = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_TOKEN);
+        mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
 
         // Also initializes proxy system properties.
         mCm.bindProcessToNetwork(mNetwork);
@@ -155,13 +156,13 @@
         }
         switch (result) {
             case DISMISSED:
-                mCm.reportCaptivePortalDismissed(mNetwork, mResponseToken);
+                mCaptivePortal.reportCaptivePortalDismissed();
                 break;
             case UNWANTED:
-                mCm.ignoreNetworkWithCaptivePortal(mNetwork, mResponseToken);
+                mCaptivePortal.ignoreNetwork();
                 break;
             case WANTED_AS_IS:
-                mCm.useNetworkWithCaptivePortal(mNetwork, mResponseToken);
+                mCaptivePortal.useNetwork();
                 break;
         }
         finish();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8ca075f..2075c07e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -220,23 +220,13 @@
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
 
-    // Arguments to rematchNetworkAndRequests()
-    private enum NascentState {
-        // Indicates a network was just validated for the first time.  If the network is found to
-        // be unwanted (i.e. not satisfy any NetworkRequests) it is torn down.
-        JUST_VALIDATED,
-        // Indicates a network was not validated for the first time immediately prior to this call.
-        NOT_JUST_VALIDATED
-    };
     private enum ReapUnvalidatedNetworks {
-        // Tear down unvalidated networks that have no chance (i.e. even if validated) of becoming
-        // the highest scoring network satisfying a NetworkRequest.  This should be passed when it's
-        // known that there may be unvalidated networks that could potentially be reaped, and when
+        // Tear down networks that have no chance (e.g. even if validated) of becoming
+        // the highest scoring network satisfying a NetworkRequest.  This should be passed when
         // all networks have been rematched against all NetworkRequests.
         REAP,
-        // Don't reap unvalidated networks.  This should be passed when it's known that there are
-        // no unvalidated networks that could potentially be reaped, and when some networks have
-        // not yet been rematched against all NetworkRequests.
+        // Don't reap networks.  This should be passed when some networks have not yet been
+        // rematched against all NetworkRequests.
         DONT_REAP
     };
 
@@ -890,7 +880,7 @@
         Network network = null;
         String subscriberId = null;
 
-        NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+        NetworkAgentInfo nai = getDefaultNetwork();
 
         final Network[] networks = getVpnUnderlyingNetworks(uid);
         if (networks != null) {
@@ -1795,7 +1785,7 @@
         pw.println();
         pw.println();
 
-        NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+        final NetworkAgentInfo defaultNai = getDefaultNetwork();
         pw.print("Active default network: ");
         if (defaultNai == null) {
             pw.println("none");
@@ -1920,8 +1910,7 @@
                                 networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
                             Slog.wtf(TAG, "BUG: " + nai + " has stateful capability.");
                         }
-                        updateCapabilities(nai, networkCapabilities,
-                                NascentState.NOT_JUST_VALIDATED);
+                        updateCapabilities(nai, networkCapabilities);
                     }
                     break;
                 }
@@ -2011,11 +2000,9 @@
                         if (DBG) log(nai.name() + " validation " + (valid ? " passed" : "failed"));
                         if (valid != nai.lastValidated) {
                             final int oldScore = nai.getCurrentScore();
-                            final NascentState nascent = (valid && !nai.everValidated) ?
-                                    NascentState.JUST_VALIDATED : NascentState.NOT_JUST_VALIDATED;
                             nai.lastValidated = valid;
                             nai.everValidated |= valid;
-                            updateCapabilities(nai, nai.networkCapabilities, nascent);
+                            updateCapabilities(nai, nai.networkCapabilities);
                             // If score has changed, rebroadcast to NetworkFactories. b/17726566
                             if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         }
@@ -2046,8 +2033,7 @@
                     if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
-                        updateCapabilities(nai, nai.networkCapabilities,
-                                NascentState.NOT_JUST_VALIDATED);
+                        updateCapabilities(nai, nai.networkCapabilities);
                     }
                     if (!visible) {
                         setProvNotificationVisibleIntent(false, netId, null, 0, null, null, false);
@@ -2066,17 +2052,22 @@
         }
     }
 
+    private void linger(NetworkAgentInfo nai) {
+        nai.lingering = true;
+        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
+        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+    }
+
     // Cancel any lingering so the linger timeout doesn't teardown a network.
     // This should be called when a network begins satisfying a NetworkRequest.
     // Note: depending on what state the NetworkMonitor is in (e.g.,
     // if it's awaiting captive portal login, or if validation failed), this
     // may trigger a re-evaluation of the network.
     private void unlinger(NetworkAgentInfo nai) {
-        if (VDBG) log("Canceling linger of " + nai.name());
-        // If network has never been validated, it cannot have been lingered, so don't bother
-        // needlessly triggering a re-evaluation.
-        if (!nai.everValidated) return;
         nai.networkLingered.clear();
+        if (!nai.lingering) return;
+        nai.lingering = false;
+        if (VDBG) log("Canceling linger of " + nai.name());
         nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
     }
 
@@ -2147,33 +2138,13 @@
                 // available until we've told netd to delete it below.
                 mNetworkForNetId.remove(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.
-            // TODO: This logic may be better replaced with a call to rematchAllNetworksAndRequests
-            final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
+            // Remove all previously satisfied requests.
             for (int i = 0; i < nai.networkRequests.size(); i++) {
                 NetworkRequest request = nai.networkRequests.valueAt(i);
                 NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
                 if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
-                    if (DBG) {
-                        log("Checking for replacement network to handle request " + request );
-                    }
                     mNetworkForRequestId.remove(request.requestId);
                     sendUpdatedScoreToFactories(request, 0);
-                    NetworkAgentInfo alternative = null;
-                    for (NetworkAgentInfo existing : mNetworkAgentInfos.values()) {
-                        if (existing.satisfies(request) &&
-                                (alternative == null ||
-                                 alternative.getCurrentScore() < existing.getCurrentScore())) {
-                            alternative = existing;
-                        }
-                    }
-                    if (alternative != null) {
-                        if (DBG) log(" found replacement in " + alternative.name());
-                        if (!toActivate.contains(alternative)) {
-                            toActivate.add(alternative);
-                        }
-                    }
                 }
             }
             if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
@@ -2182,11 +2153,7 @@
                 requestNetworkTransitionWakelock(nai.name());
             }
             mLegacyTypeTracker.remove(nai, wasDefault);
-            for (NetworkAgentInfo networkToActivate : toActivate) {
-                unlinger(networkToActivate);
-                rematchNetworkAndRequests(networkToActivate, NascentState.NOT_JUST_VALIDATED,
-                        ReapUnvalidatedNetworks.DONT_REAP);
-            }
+            rematchAllNetworksAndRequests(null, 0);
             if (nai.created) {
                 // Tell netd to clean up the configuration for this network
                 // (routing rules, DNS, etc).
@@ -2240,49 +2207,9 @@
 
     private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
         mNetworkRequests.put(nri.request, nri);
-
-        // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
-
-        // Check for the best currently alive network that satisfies this request
-        NetworkAgentInfo bestNetwork = null;
-        for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
-            if (DBG) log("handleRegisterNetworkRequest checking " + network.name());
-            if (network.satisfies(nri.request)) {
-                if (DBG) log("apparently satisfied.  currentScore=" + network.getCurrentScore());
-                if (!nri.isRequest) {
-                    // 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.
-                    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()) {
-                    bestNetwork = network;
-                }
-            }
-        }
-        if (bestNetwork != null) {
-            if (DBG) log("using " + bestNetwork.name());
-            unlinger(bestNetwork);
-            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) {
-                mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
-            }
-        }
-
-        if (nri.isRequest) {
-            if (DBG) log("sending new NetworkRequest to factories");
-            final int score = bestNetwork == null ? 0 : bestNetwork.getCurrentScore();
-            for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
-                        0, nri.request);
-            }
+        rematchAllNetworksAndRequests(null, 0);
+        if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
+            sendUpdatedScoreToFactories(nri.request, 0);
         }
     }
 
@@ -2295,43 +2222,28 @@
     }
 
     // Is nai unneeded by all NetworkRequests (and should be disconnected)?
-    // For validated Networks this is simply whether it is satsifying any NetworkRequests.
-    // For unvalidated Networks this is whether it is satsifying any NetworkRequests or
-    // were it to become validated, would it have a chance of satisfying any NetworkRequests.
+    // This is whether it is satisfying any NetworkRequests or were it to become validated,
+    // would it have a chance of satisfying any NetworkRequests.
     private boolean unneeded(NetworkAgentInfo nai) {
-        if (!nai.created || nai.isVPN()) return false;
-        boolean unneeded = true;
-        if (nai.everValidated) {
-            for (int i = 0; i < nai.networkRequests.size() && unneeded; i++) {
-                final NetworkRequest nr = nai.networkRequests.valueAt(i);
-                try {
-                    if (isRequest(nr)) unneeded = false;
-                } catch (Exception e) {
-                    loge("Request " + nr + " not found in mNetworkRequests.");
-                    loge("  it came from request list  of " + nai.name());
-                }
-            }
-        } else {
-            for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                // 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.
-                if (nri.isRequest && nai.satisfies(nri.request) &&
-                        (nai.networkRequests.get(nri.request.requestId) != null ||
-                        // Note that this catches two important cases:
-                        // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
-                        //    is currently satisfying the request.  This is desirable when
-                        //    cellular ends up validating but WiFi does not.
-                        // 2. Unvalidated WiFi will not be reaped when validated cellular
-                        //    is currently satsifying the request.  This is desirable when
-                        //    WiFi ends up validating and out scoring cellular.
-                        mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
-                                nai.getCurrentScoreAsValidated())) {
-                    unneeded = false;
-                    break;
-                }
+        if (!nai.created || nai.isVPN() || nai.lingering) return false;
+        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+            // 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.
+            if (nri.isRequest && nai.satisfies(nri.request) &&
+                    (nai.networkRequests.get(nri.request.requestId) != null ||
+                    // Note that this catches two important cases:
+                    // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
+                    //    is currently satisfying the request.  This is desirable when
+                    //    cellular ends up validating but WiFi does not.
+                    // 2. Unvalidated WiFi will not be reaped when validated cellular
+                    //    is currently satsifying the request.  This is desirable when
+                    //    WiFi ends up validating and out scoring cellular.
+                    mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+                            nai.getCurrentScoreAsValidated())) {
+                return false;
             }
         }
-        return unneeded;
+        return true;
     }
 
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
@@ -2441,7 +2353,7 @@
         if (accept != nai.networkMisc.acceptUnvalidated) {
             int oldScore = nai.getCurrentScore();
             nai.networkMisc.acceptUnvalidated = accept;
-            rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
+            rematchAllNetworksAndRequests(nai, oldScore);
             sendUpdatedScoreToFactories(nai);
         }
 
@@ -2750,16 +2662,6 @@
         }
     }
 
-    public void captivePortalAppResponse(Network network, int response, String actionToken) {
-        if (response == ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS) {
-            enforceConnectivityInternalPermission();
-        }
-        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai == null) return;
-        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_CAPTIVE_PORTAL_APP_FINISHED, response, 0,
-                actionToken);
-    }
-
     private ProxyInfo getDefaultProxy() {
         // this information is already available as a world read/writable jvm property
         // so this API change wouldn't have a benifit.  It also breaks the passing
@@ -4050,7 +3952,7 @@
             } catch (Exception e) {
                 loge("Exception in setDnsServersForNetwork: " + e);
             }
-            NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+            final NetworkAgentInfo defaultNai = getDefaultNetwork();
             if (defaultNai != null && defaultNai.network.netId == netId) {
                 setDefaultDnsSystemProperties(dnses);
             }
@@ -4085,31 +3987,29 @@
      * augmented with any stateful capabilities implied from {@code networkAgent}
      * (e.g., validated status and captive portal status).
      *
-     * @param networkAgent the network having its capabilities updated.
+     * @param nai the network having its capabilities updated.
      * @param networkCapabilities the new network capabilities.
-     * @param nascent indicates whether {@code networkAgent} was validated
-     *         (i.e. had everValidated set for the first time) immediately prior to this call.
      */
-    private void updateCapabilities(NetworkAgentInfo networkAgent,
-            NetworkCapabilities networkCapabilities, NascentState nascent) {
+    private void updateCapabilities(NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
         // Don't modify caller's NetworkCapabilities.
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
-        if (networkAgent.lastValidated) {
+        if (nai.lastValidated) {
             networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
         } else {
             networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
         }
-        if (networkAgent.lastCaptivePortalDetected) {
+        if (nai.lastCaptivePortalDetected) {
             networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         } else {
             networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         }
-        if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) {
-            synchronized (networkAgent) {
-                networkAgent.networkCapabilities = networkCapabilities;
+        if (!Objects.equals(nai.networkCapabilities, networkCapabilities)) {
+            final int oldScore = nai.getCurrentScore();
+            synchronized (nai) {
+                nai.networkCapabilities = networkCapabilities;
             }
-            rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore(), nascent);
-            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
+            rematchAllNetworksAndRequests(nai, oldScore);
+            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
     }
 
@@ -4251,7 +4151,7 @@
     //   one or more NetworkRequests, or if it is a VPN.
     //
     // - Tears down newNetwork if it just became validated
-    //   (i.e. nascent==JUST_VALIDATED) but turns out to be unneeded.
+    //   but turns out to be unneeded.
     //
     // - If reapUnvalidatedNetworks==REAP, tears down unvalidated
     //   networks that have no chance (i.e. even if validated)
@@ -4264,17 +4164,12 @@
     // as it performs better by a factor of the number of Networks.
     //
     // @param newNetwork is the network to be matched against NetworkRequests.
-    // @param nascent indicates if newNetwork just became validated, in which case it should be
-    //               torn down if unneeded.
     // @param reapUnvalidatedNetworks indicates if an additional pass over all networks should be
     //               performed to tear down unvalidated networks that have no chance (i.e. even if
     //               validated) of becoming the highest scoring network.
-    private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, NascentState nascent,
+    private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,
             ReapUnvalidatedNetworks reapUnvalidatedNetworks) {
         if (!newNetwork.created) return;
-        if (nascent == NascentState.JUST_VALIDATED && !newNetwork.everValidated) {
-            loge("ERROR: nascent network not validated.");
-        }
         boolean keep = newNetwork.isVPN();
         boolean isNewDefault = false;
         NetworkAgentInfo oldDefaultNetwork = null;
@@ -4287,7 +4182,7 @@
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
             if (newNetwork == currentNetwork) {
-                if (DBG) {
+                if (VDBG) {
                     log("Network " + newNetwork.name() + " was already satisfying" +
                             " request " + nri.request.requestId + ". No change.");
                 }
@@ -4344,10 +4239,17 @@
         }
         // Linger any networks that are no longer needed.
         for (NetworkAgentInfo nai : affectedNetworks) {
-            if (nai.everValidated && unneeded(nai)) {
-                nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
-                notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+            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);
             }
         }
@@ -4381,7 +4283,7 @@
                     mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
                                               oldDefaultNetwork, true);
                 }
-                mDefaultInetConditionPublished = newNetwork.everValidated ? 100 : 0;
+                mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
                 mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
                 notifyLockdownVpn(newNetwork);
             }
@@ -4432,19 +4334,10 @@
             if (newNetwork.isVPN()) {
                 mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
             }
-        } else if (nascent == NascentState.JUST_VALIDATED) {
-            // Only tear down newly validated networks here.  Leave unvalidated to either become
-            // validated (and get evaluated against peers, one losing here), or get reaped (see
-            // reapUnvalidatedNetworks) if they have no chance of becoming the highest scoring
-            // network.  Networks that have been up for a while and are validated should be torn
-            // down via the lingering process so communication on that network is given time to
-            // wrap up.
-            if (DBG) log("Validated network turns out to be unwanted.  Tear it down.");
-            teardownUnneededNetwork(newNetwork);
         }
         if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
             for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                if (!nai.everValidated && unneeded(nai)) {
+                if (unneeded(nai)) {
                     if (DBG) log("Reaping " + nai.name());
                     teardownUnneededNetwork(nai);
                 }
@@ -4463,10 +4356,8 @@
      *         this argument, otherwise pass {@code changed.getCurrentScore()} or 0 if
      *         {@code changed} is {@code null}. This is because NetworkCapabilities influence a
      *         network's score.
-     * @param nascent indicates if {@code changed} has just been validated.
      */
-    private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore,
-            NascentState nascent) {
+    private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {
         // TODO: This may get slow.  The "changed" parameter is provided for future optimization
         // to avoid the slowness.  It is not simply enough to process just "changed", for
         // example in the case where "changed"'s score decreases and another network should begin
@@ -4475,17 +4366,21 @@
         // 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
         // rematchNetworkAndRequests() handles.
-        if (changed != null &&
-                (oldScore < changed.getCurrentScore() || nascent == NascentState.JUST_VALIDATED)) {
-            rematchNetworkAndRequests(changed, nascent, ReapUnvalidatedNetworks.REAP);
+        if (changed != null && oldScore < changed.getCurrentScore()) {
+            rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP);
         } else {
-            for (Iterator i = mNetworkAgentInfos.values().iterator(); i.hasNext(); ) {
-                rematchNetworkAndRequests((NetworkAgentInfo)i.next(),
-                        NascentState.NOT_JUST_VALIDATED,
+            final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
+                    new NetworkAgentInfo[mNetworkAgentInfos.size()]);
+            // Rematch higher scoring networks first to prevent requests first matching a lower
+            // scoring network and then a higher scoring network, which could produce multiple
+            // callbacks and inadvertently unlinger networks.
+            Arrays.sort(nais);
+            for (NetworkAgentInfo nai : nais) {
+                rematchNetworkAndRequests(nai,
                         // Only reap the last time through the loop.  Reaping before all rematching
                         // is complete could incorrectly teardown a network that hasn't yet been
                         // rematched.
-                        i.hasNext() ? ReapUnvalidatedNetworks.DONT_REAP
+                        (nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP
                                 : ReapUnvalidatedNetworks.REAP);
             }
         }
@@ -4573,8 +4468,7 @@
             }
 
             // Consider network even though it is not yet validated.
-            rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
-                    ReapUnvalidatedNetworks.REAP);
+            rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP);
 
             // This has to happen after matching the requests, because callbacks are just requests.
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
@@ -4594,8 +4488,7 @@
                 state == NetworkInfo.State.SUSPENDED) {
             // going into or coming out of SUSPEND: rescore and notify
             if (networkAgent.getCurrentScore() != oldScore) {
-                rematchAllNetworksAndRequests(networkAgent, oldScore,
-                        NascentState.NOT_JUST_VALIDATED);
+                rematchAllNetworksAndRequests(networkAgent, oldScore);
             }
             notifyNetworkCallbacks(networkAgent, (state == NetworkInfo.State.SUSPENDED ?
                     ConnectivityManager.CALLBACK_SUSPENDED :
@@ -4615,7 +4508,7 @@
         final int oldScore = nai.getCurrentScore();
         nai.setCurrentScore(score);
 
-        rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
+        rematchAllNetworksAndRequests(nai, oldScore);
 
         sendUpdatedScoreToFactories(nai);
     }
@@ -4665,7 +4558,7 @@
             }
             NetworkAgentInfo newDefaultAgent = null;
             if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
-                newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId);
+                newDefaultAgent = getDefaultNetwork();
                 if (newDefaultAgent != null) {
                     intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
                             newDefaultAgent.networkInfo);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 51c6628..8a79430 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
 import android.content.Context;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -31,14 +33,71 @@
 import com.android.server.connectivity.NetworkMonitor;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 
 /**
  * A bag class used by ConnectivityService for holding a collection of most recent
  * information published by a particular NetworkAgent as well as the
  * AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
- * interested in using it.
+ * interested in using it.  Default sort order is descending by score.
  */
-public class NetworkAgentInfo {
+// States of a network:
+// --------------------
+// 1. registered, uncreated, disconnected, unvalidated
+//    This state is entered when a NetworkFactory registers a NetworkAgent in any state except
+//    the CONNECTED state.
+// 2. registered, uncreated, connected, unvalidated
+//    This state is entered when a registered NetworkAgent transitions to the CONNECTED state
+//    ConnectivityService will tell netd to create the network and immediately transition to
+//    state #3.
+// 3. registered, created, connected, unvalidated
+//    If this network can satsify the default NetworkRequest, then NetworkMonitor will
+//    probe for Internet connectivity.
+//    If this network cannot satisfy the default NetworkRequest, it will immediately be
+//    transitioned to state #4.
+//    A network may remain in this state if NetworkMonitor fails to find Internet connectivity,
+//    for example:
+//    a. a captive portal is present, or
+//    b. a WiFi router whose Internet backhaul is down, or
+//    c. a wireless connection stops transfering packets temporarily (e.g. device is in elevator
+//       or tunnel) but does not disconnect from the AP/cell tower, or
+//    d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes.
+// 4. registered, created, connected, validated
+//
+// The device's default network connection:
+// ----------------------------------------
+// Networks in states #3 and #4 may be used as a device's default network connection if they
+// satisfy the default NetworkRequest.
+// A network, that satisfies the default NetworkRequest, in state #4 should always be chosen
+// in favor of a network, that satisfies the default NetworkRequest, in state #3.
+// When deciding between two networks, that both satisfy the default NetworkRequest, to select
+// for the default network connection, the one with the higher score should be chosen.
+//
+// When a network disconnects:
+// ---------------------------
+// If a network's transport disappears, for example:
+// a. WiFi turned off, or
+// b. cellular data turned off, or
+// c. airplane mode is turned on, or
+// d. a wireless connection disconnects from AP/cell tower entirely (e.g. device is out of range
+//    of AP for an extended period of time, or switches to another AP without roaming)
+// then that network can transition from any state (#1-#4) to unregistered.  This happens by
+// the transport disconnecting their NetworkAgent's AsyncChannel with ConnectivityManager.
+// ConnectivityService also tells netd to destroy the network.
+//
+// When ConnectivityService disconnects a network:
+// -----------------------------------------------
+// If a network has no chance of satisfying any requests (even if it were to become validated
+// and enter state #4), ConnectivityService will disconnect the NetworkAgent's AsyncChannel.
+// If the network ever for any period of time had satisfied a NetworkRequest (i.e. had been
+// the highest scoring that satisfied the NetworkRequest's constraints), but is no longer the
+// highest scoring network for any NetworkRequest, then there will be a 30s pause before
+// ConnectivityService disconnects the NetworkAgent's AsyncChannel.  During this pause the
+// network is considered "lingering".  This pause exists to allow network communication to be
+// wrapped up rather than abruptly terminated.  During this pause if the network begins satisfying
+// a NetworkRequest, ConnectivityService will cancel the future disconnection of the NetworkAgent's
+// AsyncChannel, and the network is no longer considered "lingering".
+public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
     public NetworkInfo networkInfo;
     // This Network object should always be used if possible, so as to encourage reuse of the
     // enclosed socket factory and connection pool.  Avoid creating other Network objects.
@@ -72,6 +131,13 @@
     // Whether a captive portal was found during the last network validation attempt.
     public boolean lastCaptivePortalDetected;
 
+    // Indicates whether the network is lingering.  Networks are lingered when they become unneeded
+    // as a result of their NetworkRequests being satisfied by a different network, so as to allow
+    // communication to wrap up before the network is taken down.  This usually only happens to the
+    // default network.  Lingering ends with either the linger timeout expiring and the network
+    // being taken down, or the network satisfying a request again.
+    public boolean lingering;
+
     // This represents the last score received from the NetworkAgent.
     private int currentScore;
     // Penalty applied to scores of Networks that have not been validated.
@@ -148,7 +214,12 @@
         }
 
         int score = currentScore;
-        if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;
+        // Use NET_CAPABILITY_VALIDATED here instead of lastValidated, this allows
+        // ConnectivityService.updateCapabilities() to compute the old score prior to updating
+        // networkCapabilities (with a potentially different validated state).
+        if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) {
+            score -= UNVALIDATED_SCORE_PENALTY;
+        }
         if (score < 0) score = 0;
         return score;
     }
@@ -175,7 +246,7 @@
                 linkProperties + "}  nc{" +
                 networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
                 "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
-                "created{" + created + "}  " +
+                "created{" + created + "} lingering{" + lingering + "} " +
                 "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
                 "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
                 "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
@@ -188,4 +259,10 @@
                 networkInfo.getSubtypeName() + ") - " +
                 (network == null ? "null" : network.toString()) + "]";
     }
+
+    // Enables sorting in descending order of score.
+    @Override
+    public int compareTo(NetworkAgentInfo other) {
+        return other.getCurrentScore() - getCurrentScore();
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index e4729286..2633939 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -16,6 +16,10 @@
 
 package com.android.server.connectivity;
 
+import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
+import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
+import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -23,7 +27,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
+import android.net.ICaptivePortal;
 import android.net.NetworkRequest;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
@@ -160,12 +166,12 @@
 
     /**
      * Message to self indicating captive portal app finished.
-     * arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_DISMISSED,
-     *                CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
-     *                CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS
+     * arg1 = one of: APP_RETURN_DISMISSED,
+     *                APP_RETURN_UNWANTED,
+     *                APP_RETURN_WANTED_AS_IS
      * obj = mCaptivePortalLoggedInResponseToken as String
      */
-    public static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
+    private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
 
     /**
      * Request ConnectivityService display provisioning notification.
@@ -234,7 +240,6 @@
     private final State mLingeringState = new LingeringState();
 
     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
-    private String mCaptivePortalLoggedInResponseToken = null;
 
     private final LocalLog validationLogs = new LocalLog(20); // 20 lines
 
@@ -268,8 +273,6 @@
         mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
 
-        mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
-
         start();
     }
 
@@ -314,22 +317,18 @@
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
-                    if (!mCaptivePortalLoggedInResponseToken.equals((String)message.obj))
-                        return HANDLED;
                     log("CaptivePortal App responded with " + message.arg1);
-                    // Previous token was sent out, come up with a new one.
-                    mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
                     switch (message.arg1) {
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_DISMISSED:
+                        case APP_RETURN_DISMISSED:
                             sendMessage(CMD_FORCE_REEVALUATION, 0 /* no UID */, 0);
                             break;
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS:
+                        case APP_RETURN_WANTED_AS_IS:
                             mDontDisplaySigninNotification = true;
                             // TODO: Distinguish this from a network that actually validates.
                             // Displaying the "!" on the system UI icon may still be a good idea.
                             transitionTo(mValidatedState);
                             break;
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_UNWANTED:
+                        case APP_RETURN_UNWANTED:
                             mDontDisplaySigninNotification = true;
                             mUserDoesNotWant = true;
                             mConnectivityServiceHandler.sendMessage(obtainMessage(
@@ -380,8 +379,18 @@
                     final Intent intent = new Intent(
                             ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
                     intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
-                    intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_TOKEN,
-                            mCaptivePortalLoggedInResponseToken);
+                    intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
+                            new CaptivePortal(new ICaptivePortal.Stub() {
+                                @Override
+                                public void appResponse(int response) {
+                                    if (response == APP_RETURN_WANTED_AS_IS) {
+                                        mContext.enforceCallingPermission(
+                                                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                                                "CaptivePortal");
+                                    }
+                                    sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
+                                }
+                            }));
                     intent.setFlags(
                             Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
                     mContext.startActivityAsUser(intent, UserHandle.CURRENT);
@@ -576,9 +585,12 @@
             switch (message.what) {
                 case CMD_NETWORK_CONNECTED:
                     log("Unlingered");
-                    // Go straight to active as we've already evaluated.
-                    transitionTo(mValidatedState);
-                    return HANDLED;
+                    // If already validated, go straight to validated state.
+                    if (mNetworkAgentInfo.lastValidated) {
+                        transitionTo(mValidatedState);
+                        return HANDLED;
+                    }
+                    return NOT_HANDLED;
                 case CMD_LINGER_EXPIRED:
                     if (message.arg1 != mLingerToken)
                         return HANDLED;
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index fb8a5bb..cb9c6a76 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -20,8 +20,24 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static org.mockito.Matchers.anyInt;
@@ -135,6 +151,7 @@
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
         private final Thread mThread;
+        private final ConditionVariable mDisconnected = new ConditionVariable();
         private int mScore;
         private NetworkAgent mNetworkAgent;
 
@@ -161,7 +178,7 @@
                     mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext,
                             "Mock" + typeName, mNetworkInfo, mNetworkCapabilities,
                             new LinkProperties(), mScore, new NetworkMisc()) {
-                        public void unwanted() {}
+                        public void unwanted() { mDisconnected.open(); }
                     };
                     initComplete.open();
                     Looper.loop();
@@ -176,8 +193,18 @@
             mNetworkAgent.sendNetworkScore(mScore);
         }
 
+        public void addCapability(int capability) {
+            mNetworkCapabilities.addCapability(capability);
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+
+        public void connectWithoutInternet() {
+            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        }
+
         /**
-         * Transition this NetworkAgent to CONNECTED state.
+         * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET.
          * @param validated Indicate if network should pretend to be validated.
          */
         public void connect(boolean validated) {
@@ -210,8 +237,7 @@
                 mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
             }
 
-            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+            connectWithoutInternet();
 
             if (validated) {
                 // Wait for network to validate.
@@ -231,6 +257,10 @@
         public Network getNetwork() {
             return new Network(mNetworkAgent.netId);
         }
+
+        public ConditionVariable getDisconnectedCV() {
+            return mDisconnected;
+        }
     }
 
     private static class MockNetworkFactory extends NetworkFactory {
@@ -555,6 +585,34 @@
     }
 
     @LargeTest
+    public void testUnlingeringDoesNotValidate() throws Exception {
+        // Test bringing up unvalidated cellular.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent.connect(false);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        // Test bringing up validated WiFi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        // Test WiFi disconnect.
+        cv = waitForConnectivityBroadcasts(2);
+        mWiFiNetworkAgent.disconnect();
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Unlingering a network should not cause it to be marked as validated.
+        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+    }
+
+    @LargeTest
     public void testCellularOutscoresWeakWifi() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -582,6 +640,107 @@
         mWiFiNetworkAgent.disconnect();
     }
 
+    @LargeTest
+    public void testReapingNetwork() throws Exception {
+        // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
+        // Expect it to be torn down immediately because it satisfies no requests.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV();
+        mWiFiNetworkAgent.connectWithoutInternet();
+        waitFor(cv);
+        // Test bringing up cellular without NET_CAPABILITY_INTERNET.
+        // Expect it to be torn down immediately because it satisfies no requests.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        cv = mCellNetworkAgent.getDisconnectedCV();
+        mCellNetworkAgent.connectWithoutInternet();
+        waitFor(cv);
+        // Test bringing up validated WiFi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        // Test bringing up unvalidated cellular.
+        // Expect it to be torn down because it could never be the highest scoring network
+        // satisfying the default request even if it validated.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        cv = mCellNetworkAgent.getDisconnectedCV();
+        mCellNetworkAgent.connect(false);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        cv = mWiFiNetworkAgent.getDisconnectedCV();
+        mWiFiNetworkAgent.disconnect();
+        waitFor(cv);
+    }
+
+    @LargeTest
+    public void testCellularFallback() 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);
+        // Reevaluate WiFi (it'll instantly fail DNS).
+        cv = waitForConnectivityBroadcasts(2);
+        assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork());
+        // Should quickly fall back to Cellular.
+        waitFor(cv);
+        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Reevaluate cellular (it'll instantly fail DNS).
+        cv = waitForConnectivityBroadcasts(2);
+        assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+        // Should quickly fall back to WiFi.
+        waitFor(cv);
+        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        mCellNetworkAgent.disconnect();
+        mWiFiNetworkAgent.disconnect();
+    }
+
+    @LargeTest
+    public void testWiFiFallback() throws Exception {
+        // Test bringing up unvalidated WiFi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent.connect(false);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        // Test bringing up validated cellular.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        cv = waitForConnectivityBroadcasts(2);
+        mCellNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Reevaluate cellular (it'll instantly fail DNS).
+        cv = waitForConnectivityBroadcasts(2);
+        assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+        // Should quickly fall back to WiFi.
+        waitFor(cv);
+        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        mCellNetworkAgent.disconnect();
+        mWiFiNetworkAgent.disconnect();
+    }
+
     enum CallbackState {
         NONE,
         AVAILABLE,
@@ -736,10 +895,9 @@
         assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
     }
 
-    @LargeTest
-    public void testNetworkFactoryRequests() throws Exception {
+    private void tryNetworkFactoryRequests(int capability) throws Exception {
         NetworkCapabilities filter = new NetworkCapabilities();
-        filter.addCapability(NET_CAPABILITY_INTERNET);
+        filter.addCapability(capability);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -747,57 +905,91 @@
         testFactory.setScoreFilter(40);
         ConditionVariable cv = testFactory.getNetworkStartedCV();
         testFactory.register();
+        int expectedRequestCount = 1;
+        NetworkCallback networkCallback = null;
+        // For non-INTERNET capabilities we cannot rely on the default request being present, so
+        // add one.
+        if (capability != NET_CAPABILITY_INTERNET) {
+            testFactory.waitForNetworkRequests(1);
+            assertFalse(testFactory.getMyStartRequested());
+            NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
+            networkCallback = new NetworkCallback();
+            mCm.requestNetwork(request, networkCallback);
+            expectedRequestCount++;
+        }
         waitFor(cv);
-        assertEquals(1, testFactory.getMyRequestCount());
-        assertEquals(true, testFactory.getMyStartRequested());
+        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        assertTrue(testFactory.getMyStartRequested());
 
-        // now bring in a higher scored network
+        // Now bring in a higher scored network.
         MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
-        cv = waitForConnectivityBroadcasts(1);
-        ConditionVariable cvRelease = testFactory.getNetworkStoppedCV();
-        testAgent.connect(true);
+        // Rather than create a validated network which complicates things by registering it's
+        // own NetworkRequest during startup, just bump up the score to cancel out the
+        // unvalidated penalty.
+        testAgent.adjustScore(40);
+        cv = testFactory.getNetworkStoppedCV();
+        testAgent.connect(false);
+        testAgent.addCapability(capability);
         waitFor(cv);
-        // part of the bringup makes another network request and then releases it
-        // wait for the release
-        waitFor(cvRelease);
-        assertEquals(false, testFactory.getMyStartRequested());
-        testFactory.waitForNetworkRequests(1);
+        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        assertFalse(testFactory.getMyStartRequested());
 
-        // bring in a bunch of requests..
+        // Bring in a bunch of requests.
         ConnectivityManager.NetworkCallback[] networkCallbacks =
                 new ConnectivityManager.NetworkCallback[10];
         for (int i = 0; i< networkCallbacks.length; i++) {
             networkCallbacks[i] = new ConnectivityManager.NetworkCallback();
             NetworkRequest.Builder builder = new NetworkRequest.Builder();
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+            builder.addCapability(capability);
             mCm.requestNetwork(builder.build(), networkCallbacks[i]);
         }
-        testFactory.waitForNetworkRequests(11);
-        assertEquals(false, testFactory.getMyStartRequested());
+        testFactory.waitForNetworkRequests(10 + expectedRequestCount);
+        assertFalse(testFactory.getMyStartRequested());
 
-        // remove the requests
+        // Remove the requests.
         for (int i = 0; i < networkCallbacks.length; i++) {
             mCm.unregisterNetworkCallback(networkCallbacks[i]);
         }
-        testFactory.waitForNetworkRequests(1);
-        assertEquals(false, testFactory.getMyStartRequested());
+        testFactory.waitForNetworkRequests(expectedRequestCount);
+        assertFalse(testFactory.getMyStartRequested());
 
-        // drop the higher scored network
-        cv = waitForConnectivityBroadcasts(1);
+        // Drop the higher scored network.
+        cv = testFactory.getNetworkStartedCV();
         testAgent.disconnect();
         waitFor(cv);
-        assertEquals(1, testFactory.getMyRequestCount());
-        assertEquals(true, testFactory.getMyStartRequested());
+        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        assertTrue(testFactory.getMyStartRequested());
 
         testFactory.unregister();
+        if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
         handlerThread.quit();
     }
 
     @LargeTest
+    public void testNetworkFactoryRequests() throws Exception {
+        tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
+        tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
+        tryNetworkFactoryRequests(NET_CAPABILITY_DUN);
+        tryNetworkFactoryRequests(NET_CAPABILITY_FOTA);
+        tryNetworkFactoryRequests(NET_CAPABILITY_IMS);
+        tryNetworkFactoryRequests(NET_CAPABILITY_CBS);
+        tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P);
+        tryNetworkFactoryRequests(NET_CAPABILITY_IA);
+        tryNetworkFactoryRequests(NET_CAPABILITY_RCS);
+        tryNetworkFactoryRequests(NET_CAPABILITY_XCAP);
+        tryNetworkFactoryRequests(NET_CAPABILITY_EIMS);
+        tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED);
+        tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET);
+        tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED);
+        tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN);
+        // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
+    }
+
+    @LargeTest
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
-        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        builder.addCapability(NET_CAPABILITY_VALIDATED);
         try {
             mCm.requestNetwork(builder.build(), new NetworkCallback());
             fail();
@@ -807,7 +999,7 @@
             fail();
         } catch (IllegalArgumentException expected) {}
         builder = new NetworkRequest.Builder();
-        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        builder.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
         try {
             mCm.requestNetwork(builder.build(), new NetworkCallback());
             fail();
@@ -818,6 +1010,71 @@
         } catch (IllegalArgumentException expected) {}
     }
 
+    @LargeTest
+    public void testMMSonWiFi() throws Exception {
+        // Test bringing up cellular without MMS NetworkRequest gets reaped
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+        ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
+        mCellNetworkAgent.connectWithoutInternet();
+        waitFor(cv);
+        waitFor(new Criteria() {
+                public boolean get() { return mCm.getAllNetworks().length == 0; } });
+        verifyNoNetwork();
+        // Test bringing up validated WiFi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        // Register MMS NetworkRequest
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(builder.build(), networkCallback);
+        // Test bringing up unvalidated cellular with MMS
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+        cv = networkCallback.getConditionVariable();
+        mCellNetworkAgent.connectWithoutInternet();
+        waitFor(cv);
+        assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        // Test releasing NetworkRequest disconnects cellular with MMS
+        cv = mCellNetworkAgent.getDisconnectedCV();
+        mCm.unregisterNetworkCallback(networkCallback);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+    }
+
+    @LargeTest
+    public void testMMSonCell() throws Exception {
+        // Test bringing up cellular without MMS
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent.connect(false);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Register MMS NetworkRequest
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(builder.build(), networkCallback);
+        // Test bringing up MMS cellular network
+        cv = networkCallback.getConditionVariable();
+        MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+        mmsNetworkAgent.connectWithoutInternet();
+        waitFor(cv);
+        assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+        // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
+        cv = mmsNetworkAgent.getDisconnectedCV();
+        mCm.unregisterNetworkCallback(networkCallback);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_CELLULAR);
+    }
+
 //    @Override
 //    public void tearDown() throws Exception {
 //        super.tearDown();
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 4ab9ee5..4562514 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -48,6 +48,12 @@
     }
 
     void addAdapter(IConnectionServiceAdapter adapter) {
+        for (IConnectionServiceAdapter it : mAdapters) {
+            if (it.asBinder() == adapter.asBinder()) {
+                Log.w(this, "Ignoring duplicate adapter addition.");
+                return;
+            }
+        }
         if (mAdapters.add(adapter)) {
             try {
                 adapter.asBinder().linkToDeath(this, 0);
@@ -58,8 +64,13 @@
     }
 
     void removeAdapter(IConnectionServiceAdapter adapter) {
-        if (adapter != null && mAdapters.remove(adapter)) {
-            adapter.asBinder().unlinkToDeath(this, 0);
+        if (adapter != null) {
+            for (IConnectionServiceAdapter it : mAdapters) {
+                if (it.asBinder() == adapter.asBinder() && mAdapters.remove(it)) {
+                    adapter.asBinder().unlinkToDeath(this, 0);
+                    break;
+                }
+            }
         }
     }