Add LocalNetworkInfo and send callbacks when it changes

Test: CSLocalAgentTest
Change-Id: I8caca97b891081f9212a01d428a34ed1a08d5126
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 50b4134..d8417c1 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -168,6 +168,7 @@
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.LocalNetworkConfig;
+import android.net.LocalNetworkInfo;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NativeNetworkConfig;
 import android.net.NativeNetworkType;
@@ -4183,7 +4184,7 @@
                 }
                 case NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED: {
                     final LocalNetworkConfig config = (LocalNetworkConfig) arg.second;
-                    updateLocalNetworkConfig(nai, nai.localNetworkConfig, config);
+                    handleUpdateLocalNetworkConfig(nai, nai.localNetworkConfig, config);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
@@ -4946,7 +4947,7 @@
         notifyIfacesChangedForNetworkStats();
         // If this was a local network forwarded to some upstream, or if some local network was
         // forwarded to this nai, then disable forwarding rules now.
-        maybeDisableForwardRulesForDisconnectingNai(nai);
+        maybeDisableForwardRulesForDisconnectingNai(nai, true /* sendCallbacks */);
         // If this is a local network with an upstream selector, remove the associated network
         // request.
         if (nai.isLocalNetwork()) {
@@ -5069,7 +5070,7 @@
     }
 
     private void maybeDisableForwardRulesForDisconnectingNai(
-            @NonNull final NetworkAgentInfo disconnecting) {
+            @NonNull final NetworkAgentInfo disconnecting, final boolean sendCallbacks) {
         // Step 1 : maybe this network was the upstream for one or more local networks.
         for (final NetworkAgentInfo local : mNetworkAgentInfos) {
             if (!local.isLocalNetwork()) continue;
@@ -5082,6 +5083,13 @@
             final NetworkAgentInfo satisfier = nri.getSatisfier();
             if (disconnecting != satisfier) continue;
             removeLocalNetworkUpstream(local, disconnecting);
+            // Set the satisfier to null immediately so that the LOCAL_NETWORK_CHANGED callback
+            // correctly contains null as an upstream.
+            if (sendCallbacks) {
+                nri.setSatisfier(null, null);
+                notifyNetworkCallbacks(local,
+                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+            }
         }
 
         // Step 2 : maybe this is a local network that had an upstream.
@@ -5148,8 +5156,10 @@
             mDscpPolicyTracker.removeAllDscpPolicies(nai, false);
         }
         // Remove any forwarding rules to and from the interface for this network, since
-        // the interface is going to go away.
-        maybeDisableForwardRulesForDisconnectingNai(nai);
+        // the interface is going to go away. Don't send the callbacks however ; if the network
+        // was is being disconnected the callbacks have already been sent, and if it is being
+        // destroyed pending replacement they will be sent when it is disconnected.
+        maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */);
         try {
             mNetd.networkDestroy(nai.network.getNetId());
         } catch (RemoteException | ServiceSpecificException e) {
@@ -8320,7 +8330,7 @@
         }
 
         if (nai.isLocalNetwork()) {
-            updateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig);
+            handleUpdateLocalNetworkConfig(nai, null /* oldConfig */, nai.localNetworkConfig);
         }
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
@@ -8988,7 +8998,7 @@
     }
 
     // oldConfig is null iff this is the original registration of the local network config
-    private void updateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai,
+    private void handleUpdateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai,
             @Nullable final LocalNetworkConfig oldConfig,
             @NonNull final LocalNetworkConfig newConfig) {
         if (!nai.isLocalNetwork()) {
@@ -9021,6 +9031,12 @@
                 // If there is an old satisfier, but no new request, then remove the old upstream.
                 removeLocalNetworkUpstream(nai, oldSatisfier);
                 nai.localNetworkConfig = configBuilder.build();
+                // When there is a new request, the rematch sees the new request and sends the
+                // LOCAL_NETWORK_INFO_CHANGED callbacks accordingly.
+                // But here there is no new request, so the rematch won't see anything. Send
+                // callbacks to apps now to tell them about the loss of upstream.
+                notifyNetworkCallbacks(nai,
+                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
                 return;
             }
         }
@@ -9042,12 +9058,14 @@
                 nri.setSatisfier(oldSatisfier, nr);
             }
             nai.localNetworkConfig = configBuilder.build();
+            // handleRegisterNetworkRequest causes a rematch. The rematch must happen after
+            // nai.localNetworkConfig is set, since it will base its callbacks on the old
+            // satisfier and the new request.
             handleRegisterNetworkRequest(nri);
         } else {
             configBuilder.setUpstreamSelector(oldRequest);
             nai.localNetworkConfig = configBuilder.build();
         }
-
     }
 
     /**
@@ -9378,6 +9396,21 @@
         releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
+    @Nullable
+    private LocalNetworkInfo localNetworkInfoForNai(@NonNull final NetworkAgentInfo nai) {
+        if (!nai.isLocalNetwork()) return null;
+        final Network upstream;
+        final NetworkRequest selector = nai.localNetworkConfig.getUpstreamSelector();
+        if (null == selector) {
+            upstream = null;
+        } else {
+            final NetworkRequestInfo upstreamNri = mNetworkRequests.get(selector);
+            final NetworkAgentInfo satisfier = upstreamNri.getSatisfier();
+            upstream = (null == satisfier) ? null : satisfier.network;
+        }
+        return new LocalNetworkInfo.Builder().setUpstreamNetwork(upstream).build();
+    }
+
     // networkAgent is only allowed to be null if notificationType is
     // CALLBACK_UNAVAIL. This is because UNAVAIL is about no network being
     // available, while all other cases are about some particular network.
@@ -9413,6 +9446,10 @@
                 putParcelable(bundle, nc);
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
+                // The local network info is often null, so can't use the static putParcelable
+                // method here.
+                bundle.putParcelable(LocalNetworkInfo.class.getSimpleName(),
+                        localNetworkInfoForNai(networkAgent));
                 // For this notification, arg1 contains the blocked status.
                 msg.arg1 = arg1;
                 break;
@@ -9444,6 +9481,14 @@
                 msg.arg1 = arg1;
                 break;
             }
+            case ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
+                if (!networkAgent.isLocalNetwork()) {
+                    Log.wtf(TAG, "Callback for local info for a non-local network");
+                    return;
+                }
+                putParcelable(bundle, localNetworkInfoForNai(networkAgent));
+                break;
+            }
         }
         msg.what = notificationType;
         msg.setData(bundle);
@@ -10077,6 +10122,7 @@
                     loge("Can't update forwarding rules", e);
                 }
             }
+            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
         }
 
         updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);