Revive NetworkInfo's SUSPENDED state.

This got lost in the multinetwork work for L.  It means
that if telephony stops having the ability to pass packets for a while
the rest of the platform doesn't know.

Telephony enters the suspended state if it enters a telephony call
while using certain radio access technologies, or if it switches to
one of those RATs while in a call.  It also can enter this state if
it temporarily loses contact with the network - the modem will
not report the loss of the data call for an indeterminant time in
the hope that regaining the network will restore the connection
without harm to any ongoing ip layer interactions.  For example
passing through a tunnel or taking an elevator trip may use this
mechanism.

bug: 19637156
Change-Id: If9fde68175e8561c19323c81fbfcb02a6e5a00fb
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 12cd5f1..01334c3 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2081,23 +2081,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}
@@ -2174,30 +2157,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;
@@ -2274,6 +2281,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) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7f124dc..8ca075f 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) ?
@@ -4502,6 +4519,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;
@@ -4560,8 +4578,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) {
@@ -4573,6 +4590,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);
         }
     }
 
@@ -4608,7 +4636,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
@@ -4617,11 +4645,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());