Merge "Extend the time between WiFi energy samples" into mnc-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index b9f7365..80476ea 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2110,6 +2110,8 @@
          * can automatically log in to a captive portal without user intervention.
          *
          * @param network The {@link Network} of the network that is being evaluated.
+         *
+         * @hide
          */
         public void onPreCheck(Network network) {}
 
@@ -2379,6 +2381,14 @@
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
+     * <p>It is presently unsupported to request a network with mutable
+     * {@link NetworkCapabilities} such as
+     * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+     * as these {@code NetworkCapabilities} represent states that a particular
+     * network may never attain, and whether a network will attain these states
+     * is unknown prior to bringing up the network so the framework does not
+     * know how to go about satisfing a request with these capabilities.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
      *
@@ -2386,6 +2396,8 @@
      * @param networkCallback The {@link NetworkCallback} to be utilized for this
      *                        request.  Note the callback must not be shared - they
      *                        uniquely specify this request.
+     * @throws IllegalArgumentException if {@code request} specifies any mutable
+     *         {@code NetworkCapabilities}.
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
         sendRequestForNetwork(request.networkCapabilities, networkCallback, 0,
@@ -2467,12 +2479,22 @@
      * <p>
      * The request may be released normally by calling
      * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
+     * <p>It is presently unsupported to request a network with either
+     * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+     * as these {@code NetworkCapabilities} represent states that a particular
+     * network may never attain, and whether a network will attain these states
+     * is unknown prior to bringing up the network so the framework does not
+     * know how to go about satisfing a request with these capabilities.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
      * @param request {@link NetworkRequest} describing this request.
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
      *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+     * @throws IllegalArgumentException if {@code request} contains either
+     *         {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     *         {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
      */
     public void requestNetwork(NetworkRequest request, PendingIntent operation) {
         checkPendingIntent(operation);
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index cf747cf..658051c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -173,12 +173,17 @@
      * Indicates that connectivity on this network was successfully validated. For example, for a
      * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully
      * detected.
-     * @hide
      */
     public static final int NET_CAPABILITY_VALIDATED      = 16;
 
+    /**
+     * Indicates that this network was found to have a captive portal in place last time it was
+     * probed.
+     */
+    public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VALIDATED;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_CAPTIVE_PORTAL;
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1b2a2ed..e4f3f49 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -23,6 +23,7 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -1869,7 +1870,14 @@
                     if (nai == null) {
                         loge("EVENT_NETWORK_CAPABILITIES_CHANGED from unknown NetworkAgent");
                     } else {
-                        updateCapabilities(nai, (NetworkCapabilities)msg.obj);
+                        final NetworkCapabilities networkCapabilities =
+                                (NetworkCapabilities)msg.obj;
+                        if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
+                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+                            Slog.wtf(TAG, "BUG: " + nai + " has stateful capability.");
+                        }
+                        updateCapabilities(nai, networkCapabilities,
+                                NascentState.NOT_JUST_VALIDATED);
                     }
                     break;
                 }
@@ -1956,20 +1964,16 @@
                     if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) {
                         final boolean valid =
                                 (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
-                        final boolean validationChanged = (valid != nai.lastValidated);
-                        nai.lastValidated = valid;
-                        if (valid) {
-                            if (DBG) log("Validated " + nai.name());
-                            nai.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
-                            if (!nai.everValidated) {
-                                nai.everValidated = true;
-                                rematchNetworkAndRequests(nai, NascentState.JUST_VALIDATED,
-                                    ReapUnvalidatedNetworks.REAP);
-                                // If score has changed, rebroadcast to NetworkFactories. b/17726566
-                                sendUpdatedScoreToFactories(nai);
-                            }
-                        } else {
-                            nai.networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+                        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);
+                            // If score has changed, rebroadcast to NetworkFactories. b/17726566
+                            if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         }
                         updateInetCondition(nai);
                         // Let the NetworkAgent know the state of its network
@@ -1977,10 +1981,6 @@
                                 android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                                 (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                                 0, null);
-
-                        if (validationChanged) {
-                            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
-                        }
                     }
                     break;
                 }
@@ -1993,18 +1993,25 @@
                 }
                 case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
                     final int netId = msg.arg2;
-                    if (msg.arg1 == 0) {
+                    final boolean visible = (msg.arg1 != 0);
+                    final NetworkAgentInfo nai;
+                    synchronized (mNetworkForNetId) {
+                        nai = mNetworkForNetId.get(netId);
+                    }
+                    // If captive portal status has changed, update capabilities.
+                    if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
+                        nai.lastCaptivePortalDetected = visible;
+                        nai.everCaptivePortalDetected |= visible;
+                        updateCapabilities(nai, nai.networkCapabilities,
+                                NascentState.NOT_JUST_VALIDATED);
+                    }
+                    if (!visible) {
                         setProvNotificationVisibleIntent(false, netId, null, 0, null, null);
                     } else {
-                        final NetworkAgentInfo nai;
-                        synchronized (mNetworkForNetId) {
-                            nai = mNetworkForNetId.get(netId);
-                        }
                         if (nai == null) {
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                             break;
                         }
-                        nai.captivePortalDetected = true;
                         setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
                                 nai.networkInfo.getType(),nai.networkInfo.getExtraInfo(),
                                 (PendingIntent)msg.obj);
@@ -2390,7 +2397,7 @@
         if (accept != nai.networkMisc.acceptUnvalidated) {
             int oldScore = nai.getCurrentScore();
             nai.networkMisc.acceptUnvalidated = accept;
-            rematchAllNetworksAndRequests(nai, oldScore);
+            rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
             sendUpdatedScoreToFactories(nai);
         }
 
@@ -2426,7 +2433,7 @@
         // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
         // we haven't already been told to switch to it regardless of whether it validated or not.
         // Also don't prompt on captive portals because we're already prompting the user to sign in.
-        if (nai == null || nai.everValidated || nai.captivePortalDetected ||
+        if (nai == null || nai.everValidated || nai.everCaptivePortalDetected ||
                 !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
             return;
         }
@@ -3571,12 +3578,24 @@
         }
     }
 
+    private void ensureImmutableCapabilities(NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+            throw new IllegalArgumentException(
+                    "Cannot request network with NET_CAPABILITY_VALIDATED");
+        }
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
+            throw new IllegalArgumentException(
+                    "Cannot request network with NET_CAPABILITY_CAPTIVE_PORTAL");
+        }
+    }
+
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
         enforceNetworkRequestPermissions(networkCapabilities);
         enforceMeteredApnPolicy(networkCapabilities);
+        ensureImmutableCapabilities(networkCapabilities);
 
         if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
             throw new IllegalArgumentException("Bad timeout specified");
@@ -3645,6 +3664,7 @@
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
         enforceNetworkRequestPermissions(networkCapabilities);
         enforceMeteredApnPolicy(networkCapabilities);
+        ensureImmutableCapabilities(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId());
@@ -4046,18 +4066,35 @@
         mNumDnsEntries = last;
     }
 
+    /**
+     * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities}
+     * 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 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) {
+            NetworkCapabilities networkCapabilities, NascentState nascent) {
+        // Don't modify caller's NetworkCapabilities.
+        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+        if (networkAgent.lastValidated) {
+            networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+        } else {
+            networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+        }
+        if (networkAgent.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 (networkAgent.lastValidated) {
-                networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
-                // There's no need to remove the capability if we think the network is unvalidated,
-                // because NetworkAgents don't set the validated capability.
-            }
-            rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore());
+            rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore(), nascent);
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
     }
@@ -4401,15 +4438,21 @@
         }
     }
 
-    // Attempt to rematch all Networks with NetworkRequests.  This may result in Networks
-    // being disconnected.
-    // If only one Network's score or capabilities have been modified since the last time
-    // this function was called, pass this Network in via the "changed" arugment, otherwise
-    // pass null.
-    // If only one Network has been changed but its NetworkCapabilities have not changed,
-    // pass in the Network's score (from getCurrentScore()) prior to the change via
-    // "oldScore", otherwise pass changed.getCurrentScore() or 0 if "changed" is null.
-    private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {
+    /**
+     * Attempt to rematch all Networks with NetworkRequests.  This may result in Networks
+     * being disconnected.
+     * @param changed If only one Network's score or capabilities have been modified since the last
+     *         time this function was called, pass this Network in this argument, otherwise pass
+     *         null.
+     * @param oldScore If only one Network has been changed but its NetworkCapabilities have not
+     *         changed, pass in the Network's score (from getCurrentScore()) prior to the change via
+     *         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) {
         // 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
@@ -4418,9 +4461,9 @@
         // 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()) {
-            rematchNetworkAndRequests(changed, NascentState.NOT_JUST_VALIDATED,
-                    ReapUnvalidatedNetworks.REAP);
+        if (changed != null &&
+                (oldScore < changed.getCurrentScore() || nascent == NascentState.JUST_VALIDATED)) {
+            rematchNetworkAndRequests(changed, nascent, ReapUnvalidatedNetworks.REAP);
         } else {
             for (Iterator i = mNetworkAgentInfos.values().iterator(); i.hasNext(); ) {
                 rematchNetworkAndRequests((NetworkAgentInfo)i.next(),
@@ -4547,7 +4590,7 @@
         final int oldScore = nai.getCurrentScore();
         nai.setCurrentScore(score);
 
-        rematchAllNetworksAndRequests(nai, oldScore);
+        rematchAllNetworksAndRequests(nai, oldScore, NascentState.NOT_JUST_VALIDATED);
 
         sendUpdatedScoreToFactories(nai);
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 3bf1183..51c6628 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -45,6 +45,7 @@
     // This Network object is always valid.
     public final Network network;
     public LinkProperties linkProperties;
+    // This should only be modified via ConnectivityService.updateCapabilities().
     public NetworkCapabilities networkCapabilities;
     public final NetworkMonitor networkMonitor;
     public final NetworkMisc networkMisc;
@@ -66,7 +67,10 @@
 
     // Whether a captive portal was ever detected on this network.
     // This is a sticky bit; once set it is never cleared.
-    public boolean captivePortalDetected;
+    public boolean everCaptivePortalDetected;
+
+    // Whether a captive portal was found during the last network validation attempt.
+    public boolean lastCaptivePortalDetected;
 
     // This represents the last score received from the NetworkAgent.
     private int currentScore;
@@ -174,7 +178,8 @@
                 "created{" + created + "}  " +
                 "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
                 "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
-                "captivePortalDetected{" + captivePortalDetected + "} " +
+                "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
+                "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
                 "}";
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 712db09..fb8a5bb 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -792,6 +793,30 @@
         handlerThread.quit();
     }
 
+    @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);
+        try {
+            mCm.requestNetwork(builder.build(), new NetworkCallback());
+            fail();
+        } catch (IllegalArgumentException expected) {}
+        try {
+            mCm.requestNetwork(builder.build(), pendingIntent);
+            fail();
+        } catch (IllegalArgumentException expected) {}
+        builder = new NetworkRequest.Builder();
+        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        try {
+            mCm.requestNetwork(builder.build(), new NetworkCallback());
+            fail();
+        } catch (IllegalArgumentException expected) {}
+        try {
+            mCm.requestNetwork(builder.build(), pendingIntent);
+            fail();
+        } catch (IllegalArgumentException expected) {}
+    }
 
 //    @Override
 //    public void tearDown() throws Exception {