Merge "Add android.net.CaptivePortal class for captive-portal-handling-app callbacks" into mnc-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a1bc417..de7ca0c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2004,23 +2004,6 @@
      * changes.  Should be extended by applications wanting notifications.
      */
     public static class NetworkCallback {
-        /** @hide */
-        public static final int PRECHECK     = 1;
-        /** @hide */
-        public static final int AVAILABLE    = 2;
-        /** @hide */
-        public static final int LOSING       = 3;
-        /** @hide */
-        public static final int LOST         = 4;
-        /** @hide */
-        public static final int UNAVAIL      = 5;
-        /** @hide */
-        public static final int CAP_CHANGED  = 6;
-        /** @hide */
-        public static final int PROP_CHANGED = 7;
-        /** @hide */
-        public static final int CANCELED     = 8;
-
         /**
          * Called when the framework connects to a new network to evaluate whether it satisfies this
          * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable}
@@ -2097,30 +2080,54 @@
          */
         public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {}
 
+        /**
+         * Called when the network the framework connected to for this request
+         * goes into {@link NetworkInfo.DetailedState.SUSPENDED}.
+         * This generally means that while the TCP connections are still live,
+         * temporarily network data fails to transfer.  Specifically this is used
+         * on cellular networks to mask temporary outages when driving through
+         * a tunnel, etc.
+         * @hide
+         */
+        public void onNetworkSuspended(Network network) {}
+
+        /**
+         * Called when the network the framework connected to for this request
+         * returns from a {@link NetworkInfo.DetailedState.SUSPENDED} state.
+         * This should always be preceeded by a matching {@code onNetworkSuspended}
+         * call.
+         * @hide
+         */
+        public void onNetworkResumed(Network network) {}
+
         private NetworkRequest networkRequest;
     }
 
     private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
-    /** @hide obj = pair(NetworkRequest, Network) */
-    public static final int CALLBACK_PRECHECK           = BASE + 1;
-    /** @hide obj = pair(NetworkRequest, Network) */
-    public static final int CALLBACK_AVAILABLE          = BASE + 2;
-    /** @hide obj = pair(NetworkRequest, Network), arg1 = ttl */
-    public static final int CALLBACK_LOSING             = BASE + 3;
-    /** @hide obj = pair(NetworkRequest, Network) */
-    public static final int CALLBACK_LOST               = BASE + 4;
-    /** @hide obj = NetworkRequest */
-    public static final int CALLBACK_UNAVAIL            = BASE + 5;
-    /** @hide obj = pair(NetworkRequest, Network) */
-    public static final int CALLBACK_CAP_CHANGED        = BASE + 6;
-    /** @hide obj = pair(NetworkRequest, Network) */
-    public static final int CALLBACK_IP_CHANGED         = BASE + 7;
-    /** @hide obj = NetworkRequest */
-    public static final int CALLBACK_RELEASED           = BASE + 8;
     /** @hide */
-    public static final int CALLBACK_EXIT               = BASE + 9;
+    public static final int CALLBACK_PRECHECK            = BASE + 1;
+    /** @hide */
+    public static final int CALLBACK_AVAILABLE           = BASE + 2;
+    /** @hide arg1 = TTL */
+    public static final int CALLBACK_LOSING              = BASE + 3;
+    /** @hide */
+    public static final int CALLBACK_LOST                = BASE + 4;
+    /** @hide */
+    public static final int CALLBACK_UNAVAIL             = BASE + 5;
+    /** @hide */
+    public static final int CALLBACK_CAP_CHANGED         = BASE + 6;
+    /** @hide */
+    public static final int CALLBACK_IP_CHANGED          = BASE + 7;
+    /** @hide */
+    public static final int CALLBACK_RELEASED            = BASE + 8;
+    /** @hide */
+    public static final int CALLBACK_EXIT                = BASE + 9;
     /** @hide obj = NetworkCapabilities, arg1 = seq number */
-    private static final int EXPIRE_LEGACY_REQUEST      = BASE + 10;
+    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 10;
+    /** @hide */
+    public static final int CALLBACK_SUSPENDED           = BASE + 11;
+    /** @hide */
+    public static final int CALLBACK_RESUMED             = BASE + 12;
 
     private class CallbackHandler extends Handler {
         private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
@@ -2197,6 +2204,20 @@
                     }
                     break;
                 }
+                case CALLBACK_SUSPENDED: {
+                    NetworkCallback callback = getCallback(request, "SUSPENDED");
+                    if (callback != null) {
+                        callback.onNetworkSuspended(network);
+                    }
+                    break;
+                }
+                case CALLBACK_RESUMED: {
+                    NetworkCallback callback = getCallback(request, "RESUMED");
+                    if (callback != null) {
+                        callback.onNetworkResumed(network);
+                    }
+                    break;
+                }
                 case CALLBACK_RELEASED: {
                     NetworkCallback callback = null;
                     synchronized(mCallbackMap) {
@@ -2488,7 +2509,7 @@
      * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
      * <p>
      * The request may be released normally by calling
-     * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
+     * {@link #unregisterNetworkCallback(android.app.PendingIntent)}.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      * @param request {@link NetworkRequest} describing this request.
@@ -2542,6 +2563,19 @@
     }
 
     /**
+     * Unregisters a callback previously registered via
+     * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+     *
+     * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+     *                  PendingIntent passed to
+     *                  {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+     *                  Cannot be null.
+     */
+    public void unregisterNetworkCallback(PendingIntent operation) {
+        releaseNetworkRequest(operation);
+    }
+
+    /**
      * Informs the system whether it should switch to {@code network} regardless of whether it is
      * validated or not. If {@code accept} is true, and the network was explicitly selected by the
      * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 54d864b..672af5b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -493,10 +493,10 @@
             }
         }
 
-        private void maybeLogBroadcast(NetworkAgentInfo nai, boolean connected, int type,
+        private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
                 boolean isDefaultNetwork) {
             if (DBG) {
-                log("Sending " + (connected ? "connected" : "disconnected") +
+                log("Sending " + state +
                         " broadcast for type " + type + " " + nai.name() +
                         " isDefaultNetwork=" + isDefaultNetwork);
             }
@@ -520,8 +520,8 @@
             // Send a broadcast if this is the first network of its type or if it's the default.
             final boolean isDefaultNetwork = isDefaultNetwork(nai);
             if (list.size() == 1 || isDefaultNetwork) {
-                maybeLogBroadcast(nai, true, type, isDefaultNetwork);
-                sendLegacyNetworkBroadcast(nai, true, type);
+                maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
+                sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
             }
         }
 
@@ -538,17 +538,19 @@
                 return;
             }
 
+            final DetailedState state = DetailedState.DISCONNECTED;
+
             if (wasFirstNetwork || wasDefault) {
-                maybeLogBroadcast(nai, false, type, wasDefault);
-                sendLegacyNetworkBroadcast(nai, false, type);
+                maybeLogBroadcast(nai, state, type, wasDefault);
+                sendLegacyNetworkBroadcast(nai, state, type);
             }
 
             if (!list.isEmpty() && wasFirstNetwork) {
                 if (DBG) log("Other network available for type " + type +
                               ", sending connected broadcast");
                 final NetworkAgentInfo replacement = list.get(0);
-                maybeLogBroadcast(replacement, false, type, isDefaultNetwork(replacement));
-                sendLegacyNetworkBroadcast(replacement, false, type);
+                maybeLogBroadcast(replacement, state, type, isDefaultNetwork(replacement));
+                sendLegacyNetworkBroadcast(replacement, state, type);
             }
         }
 
@@ -560,6 +562,21 @@
             }
         }
 
+        // send out another legacy broadcast - currently only used for suspend/unsuspend
+        // toggle
+        public void update(NetworkAgentInfo nai) {
+            final boolean isDefault = isDefaultNetwork(nai);
+            final DetailedState state = nai.networkInfo.getDetailedState();
+            for (int type = 0; type < mTypeLists.length; type++) {
+                final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
+                final boolean isFirst = (list != null && list.size() > 0 && nai == list.get(0));
+                if (isFirst || isDefault) {
+                    maybeLogBroadcast(nai, state, type, isDefault);
+                    sendLegacyNetworkBroadcast(nai, state, type);
+                }
+            }
+        }
+
         private String naiToString(NetworkAgentInfo nai) {
             String name = (nai != null) ? nai.name() : "null";
             String state = (nai.networkInfo != null) ?
@@ -4492,6 +4509,7 @@
     private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
         NetworkInfo.State state = newInfo.getState();
         NetworkInfo oldInfo = null;
+        final int oldScore = networkAgent.getCurrentScore();
         synchronized (networkAgent) {
             oldInfo = networkAgent.networkInfo;
             networkAgent.networkInfo = newInfo;
@@ -4550,8 +4568,7 @@
 
             // This has to happen after matching the requests, because callbacks are just requests.
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
-        } else if (state == NetworkInfo.State.DISCONNECTED ||
-                state == NetworkInfo.State.SUSPENDED) {
+        } else if (state == NetworkInfo.State.DISCONNECTED) {
             networkAgent.asyncChannel.disconnect();
             if (networkAgent.isVPN()) {
                 synchronized (mProxyLock) {
@@ -4563,6 +4580,17 @@
                     }
                 }
             }
+        } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
+                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);
+            }
+            notifyNetworkCallbacks(networkAgent, (state == NetworkInfo.State.SUSPENDED ?
+                    ConnectivityManager.CALLBACK_SUSPENDED :
+                    ConnectivityManager.CALLBACK_RESUMED));
+            mLegacyTypeTracker.update(networkAgent);
         }
     }
 
@@ -4598,7 +4626,7 @@
         }
     }
 
-    private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+    private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
         // The NetworkInfo we actually send out has no bearing on the real
         // state of affairs. For example, if the default connection is mobile,
         // and a request for HIPRI has just gone away, we need to pretend that
@@ -4607,11 +4635,11 @@
         // and is still connected.
         NetworkInfo info = new NetworkInfo(nai.networkInfo);
         info.setType(type);
-        if (connected) {
-            info.setDetailedState(DetailedState.CONNECTED, null, info.getExtraInfo());
+        if (state != DetailedState.DISCONNECTED) {
+            info.setDetailedState(state, null, info.getExtraInfo());
             sendConnectedBroadcast(info);
         } else {
-            info.setDetailedState(DetailedState.DISCONNECTED, info.getReason(), info.getExtraInfo());
+            info.setDetailedState(state, info.getReason(), info.getExtraInfo());
             Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
             intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
             intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());