Reapply "Add LocalNetworkConfig"

This reverts commit 6eea22b18cace5ab7515ad21c2c019605720907d.

Change-Id: I0a2cef4ee992577674c92ecd384dc6f4361afa02
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 2315521..915c20d 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -3811,11 +3811,28 @@
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
             android.Manifest.permission.NETWORK_FACTORY})
-    public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp,
-            NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config,
-            int providerId) {
+    public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+            @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+            @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) {
+        return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config,
+                providerId);
+    }
+
+    /**
+     * @hide
+     * Register a NetworkAgent with ConnectivityService.
+     * @return Network corresponding to NetworkAgent.
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_FACTORY})
+    public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni,
+            @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+            @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+            @NonNull NetworkAgentConfig config, int providerId) {
         try {
-            return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId);
+            return mService.registerNetworkAgent(na, ni, lp, nc, score, localNetworkConfig, config,
+                    providerId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index ebe8bca..fe27773 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -27,6 +27,7 @@
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
 import android.net.Network;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
@@ -146,7 +147,8 @@
     void declareNetworkRequestUnfulfillable(in NetworkRequest request);
 
     Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp,
-            in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config,
+            in NetworkCapabilities nc, in NetworkScore score,
+            in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config,
             in int factorySerialNumber);
 
     NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index b375b7b..61b27b5 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -17,6 +17,7 @@
 
 import android.net.DscpPolicy;
 import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -34,6 +35,7 @@
     void sendLinkProperties(in LinkProperties lp);
     // TODO: consider replacing this by "markConnected()" and removing
     void sendNetworkInfo(in NetworkInfo info);
+    void sendLocalNetworkConfig(in LocalNetworkConfig config);
     void sendScore(in NetworkScore score);
     void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
     void sendSocketKeepaliveEvent(int slot, int reason);
diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java
new file mode 100644
index 0000000..fca7fd1
--- /dev/null
+++ b/framework/src/android/net/LocalNetworkConfig.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class to communicate configuration info about a local network through {@link NetworkAgent}.
+ * @hide
+ */
+// TODO : @SystemApi
+public final class LocalNetworkConfig implements Parcelable {
+    @Nullable
+    private final NetworkRequest mUpstreamSelector;
+
+    @NonNull
+    private final MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
+
+    @NonNull
+    private final MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
+
+    private LocalNetworkConfig(@Nullable final NetworkRequest upstreamSelector,
+            @Nullable final MulticastRoutingConfig upstreamConfig,
+            @Nullable final MulticastRoutingConfig downstreamConfig) {
+        mUpstreamSelector = upstreamSelector;
+        if (null != upstreamConfig) {
+            mUpstreamMulticastRoutingConfig = upstreamConfig;
+        } else {
+            mUpstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+        }
+        if (null != downstreamConfig) {
+            mDownstreamMulticastRoutingConfig = downstreamConfig;
+        } else {
+            mDownstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE;
+        }
+    }
+
+    /**
+     * Get the request choosing which network traffic from this network is forwarded to and from.
+     *
+     * This may be null if the local network doesn't forward the traffic anywhere.
+     */
+    @Nullable
+    public NetworkRequest getUpstreamSelector() {
+        return mUpstreamSelector;
+    }
+
+    public @NonNull MulticastRoutingConfig getUpstreamMulticastRoutingConfig() {
+        return mUpstreamMulticastRoutingConfig;
+    }
+
+    public @NonNull MulticastRoutingConfig getDownstreamMulticastRoutingConfig() {
+        return mDownstreamMulticastRoutingConfig;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+        dest.writeParcelable(mUpstreamSelector, flags);
+        dest.writeParcelable(mUpstreamMulticastRoutingConfig, flags);
+        dest.writeParcelable(mDownstreamMulticastRoutingConfig, flags);
+    }
+
+    public static final @NonNull Creator<LocalNetworkConfig> CREATOR = new Creator<>() {
+        public LocalNetworkConfig createFromParcel(Parcel in) {
+            final NetworkRequest upstreamSelector = in.readParcelable(null);
+            final MulticastRoutingConfig upstreamConfig = in.readParcelable(null);
+            final MulticastRoutingConfig downstreamConfig = in.readParcelable(null);
+            return new LocalNetworkConfig(
+                    upstreamSelector, upstreamConfig, downstreamConfig);
+        }
+
+        @Override
+        public LocalNetworkConfig[] newArray(final int size) {
+            return new LocalNetworkConfig[size];
+        }
+    };
+
+
+    public static final class Builder {
+        @Nullable
+        NetworkRequest mUpstreamSelector;
+
+        @Nullable
+        MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
+
+        @Nullable
+        MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
+
+        /**
+         * Create a Builder
+         */
+        public Builder() {
+        }
+
+        /**
+         * Set to choose where this local network should forward its traffic to.
+         *
+         * The system will automatically choose the best network matching the request as an
+         * upstream, and set up forwarding between this local network and the chosen upstream.
+         * If no network matches the request, there is no upstream and the traffic is not forwarded.
+         * The caller can know when this changes by listening to link properties changes of
+         * this network with the {@link android.net.LinkProperties#getForwardedNetwork()} getter.
+         *
+         * Set this to null if the local network shouldn't be forwarded. Default is null.
+         */
+        @NonNull
+        public Builder setUpstreamSelector(@Nullable NetworkRequest upstreamSelector) {
+            mUpstreamSelector = upstreamSelector;
+            return this;
+        }
+
+        /**
+         * Set the upstream multicast routing config.
+         *
+         * If null, don't route multicast packets upstream. This is equivalent to a
+         * MulticastRoutingConfig in mode FORWARD_NONE. The default is null.
+         */
+        @NonNull
+        public Builder setUpstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) {
+            mUpstreamMulticastRoutingConfig = cfg;
+            return this;
+        }
+
+        /**
+         * Set the downstream multicast routing config.
+         *
+         * If null, don't route multicast packets downstream. This is equivalent to a
+         * MulticastRoutingConfig in mode FORWARD_NONE. The default is null.
+         */
+        @NonNull
+        public Builder setDownstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) {
+            mDownstreamMulticastRoutingConfig = cfg;
+            return this;
+        }
+
+        /**
+         * Build the LocalNetworkConfig object.
+         */
+        @NonNull
+        public LocalNetworkConfig build() {
+            return new LocalNetworkConfig(mUpstreamSelector,
+                    mUpstreamMulticastRoutingConfig,
+                    mDownstreamMulticastRoutingConfig);
+        }
+    }
+}
diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java
new file mode 100644
index 0000000..ebd9fc5
--- /dev/null
+++ b/framework/src/android/net/MulticastRoutingConfig.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A class representing a configuration for multicast routing.
+ *
+ * Internal usage to Connectivity
+ * @hide
+ */
+// TODO : @SystemApi
+public class MulticastRoutingConfig implements Parcelable {
+    private static final String TAG = MulticastRoutingConfig.class.getSimpleName();
+
+    /** Do not forward any multicast packets. */
+    public static final int FORWARD_NONE = 0;
+    /**
+     * Forward only multicast packets with destination in the list of listening addresses.
+     * Ignore the min scope.
+     */
+    public static final int FORWARD_SELECTED = 1;
+    /**
+     * Forward all multicast packets with scope greater or equal than the min scope.
+     * Ignore the list of listening addresses.
+     */
+    public static final int FORWARD_WITH_MIN_SCOPE = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "FORWARD_" }, value = {
+            FORWARD_NONE,
+            FORWARD_SELECTED,
+            FORWARD_WITH_MIN_SCOPE
+    })
+    public @interface MulticastForwardingMode {}
+
+    /**
+     * Not a multicast scope, for configurations that do not use the min scope.
+     */
+    public static final int MULTICAST_SCOPE_NONE = -1;
+
+    public static final MulticastRoutingConfig CONFIG_FORWARD_NONE =
+            new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null);
+
+    @MulticastForwardingMode
+    private final int mForwardingMode;
+
+    private final int mMinScope;
+
+    @NonNull
+    private final Set<Inet6Address> mListeningAddresses;
+
+    private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope,
+            @Nullable final Set<Inet6Address> addresses) {
+        mForwardingMode = mode;
+        mMinScope = scope;
+        if (null != addresses) {
+            mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses));
+        } else {
+            mListeningAddresses = Collections.emptySet();
+        }
+    }
+
+    /**
+     * Returns the forwarding mode.
+     */
+    @MulticastForwardingMode
+    public int getForwardingMode() {
+        return mForwardingMode;
+    }
+
+    /**
+     * Returns the minimal group address scope that is allowed for forwarding.
+     * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE.
+     */
+    public int getMinScope() {
+        return mMinScope;
+    }
+
+    /**
+     * Returns the list of group addresses listened by the outgoing interface.
+     * The list will be empty if the forwarding mode is not FORWARD_SELECTED.
+     */
+    @NonNull
+    public Set<Inet6Address> getMulticastListeningAddresses() {
+        return mListeningAddresses;
+    }
+
+    private MulticastRoutingConfig(Parcel in) {
+        mForwardingMode = in.readInt();
+        mMinScope = in.readInt();
+        final int count = in.readInt();
+        final ArraySet<Inet6Address> listeningAddresses = new ArraySet<>(count);
+        final byte[] buffer = new byte[16]; // Size of an Inet6Address
+        for (int i = 0; i < count; ++i) {
+            in.readByteArray(buffer);
+            try {
+                listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer));
+            } catch (UnknownHostException e) {
+                Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer));
+            }
+        }
+        mListeningAddresses = Collections.unmodifiableSet(listeningAddresses);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mForwardingMode);
+        dest.writeInt(mMinScope);
+        dest.writeInt(mListeningAddresses.size());
+        for (final Inet6Address addr : mListeningAddresses) {
+            dest.writeByteArray(addr.getAddress());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<MulticastRoutingConfig> CREATOR = new Creator<>() {
+        @Override
+        public MulticastRoutingConfig createFromParcel(Parcel in) {
+            return new MulticastRoutingConfig(in);
+        }
+
+        @Override
+        public MulticastRoutingConfig[] newArray(int size) {
+            return new MulticastRoutingConfig[size];
+        }
+    };
+
+    public static class Builder {
+        @MulticastForwardingMode
+        private final int mForwardingMode;
+        private int mMinScope;
+        private final ArraySet<Inet6Address> mListeningAddresses;
+
+        private Builder(@MulticastForwardingMode final int mode, int scope) {
+            mForwardingMode = mode;
+            mMinScope = scope;
+            mListeningAddresses = new ArraySet<>();
+        }
+
+        /**
+         * Create a builder that forwards nothing.
+         * No properties can be set on such a builder.
+         */
+        public static Builder newBuilderForwardingNone() {
+            return new Builder(FORWARD_NONE, MULTICAST_SCOPE_NONE);
+        }
+
+        /**
+         * Create a builder that forwards packets above a certain scope
+         *
+         * The scope can be changed on this builder, but not the listening addresses.
+         * @param scope the initial scope
+         */
+        public static Builder newBuilderWithMinScope(final int scope) {
+            return new Builder(FORWARD_WITH_MIN_SCOPE, scope);
+        }
+
+        /**
+         * Create a builder that forwards a specified list of listening addresses.
+         *
+         * Addresses can be added and removed from this builder, but the scope can't be set.
+         */
+        public static Builder newBuilderWithListeningAddresses() {
+            return new Builder(FORWARD_SELECTED, MULTICAST_SCOPE_NONE);
+        }
+
+        /**
+         * Sets the minimum scope for this multicast routing config.
+         * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode.
+         * @return this builder
+         */
+        public Builder setMinimumScope(final int scope) {
+            if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) {
+                throw new IllegalArgumentException("Can't set the scope on a builder in mode "
+                        + modeToString(mForwardingMode));
+            }
+            mMinScope = scope;
+            return this;
+        }
+
+        /**
+         * Add an address to the set of listening addresses.
+         *
+         * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
+         * If this address was already added, this is a no-op.
+         * @return this builder
+         */
+        public Builder addListeningAddress(@NonNull final Inet6Address address) {
+            if (FORWARD_SELECTED != mForwardingMode) {
+                throw new IllegalArgumentException("Can't add an address on a builder in mode "
+                        + modeToString(mForwardingMode));
+            }
+            // TODO : should we check that this is a multicast address ?
+            mListeningAddresses.add(address);
+            return this;
+        }
+
+        /**
+         * Remove an address from the set of listening addresses.
+         *
+         * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
+         * If this address was not added, or was already removed, this is a no-op.
+         * @return this builder
+         */
+        public Builder removeListeningAddress(@NonNull final Inet6Address address) {
+            if (FORWARD_SELECTED != mForwardingMode) {
+                throw new IllegalArgumentException("Can't remove an address on a builder in mode "
+                        + modeToString(mForwardingMode));
+            }
+            mListeningAddresses.remove(address);
+            return this;
+        }
+
+        /**
+         * Build the config.
+         */
+        public MulticastRoutingConfig build() {
+            return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses);
+        }
+    }
+
+    private static String modeToString(@MulticastForwardingMode final int mode) {
+        switch (mode) {
+            case FORWARD_NONE: return "FORWARD_NONE";
+            case FORWARD_SELECTED: return "FORWARD_SELECTED";
+            case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE";
+            default: return "unknown multicast routing mode " + mode;
+        }
+    }
+}
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 177f7e3..4e9087c 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -151,7 +151,7 @@
 
     /**
      * Sent by the NetworkAgent to ConnectivityService to pass the current
-     * NetworkCapabilties.
+     * NetworkCapabilities.
      * obj = NetworkCapabilities
      * @hide
      */
@@ -443,6 +443,14 @@
     public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
 
     /**
+     * Sent by the NetworkAgent to ConnectivityService to pass the new value of the local
+     * network agent config.
+     * obj = {@code Pair<NetworkAgentInfo, LocalNetworkConfig>}
+     * @hide
+     */
+    public static final int EVENT_LOCAL_NETWORK_CONFIG_CHANGED = BASE + 30;
+
+    /**
      * DSCP policy was successfully added.
      */
     public static final int DSCP_POLICY_STATUS_SUCCESS = 0;
@@ -517,20 +525,47 @@
             @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
             @NonNull NetworkScore score, @NonNull NetworkAgentConfig config,
             @Nullable NetworkProvider provider) {
-        this(looper, context, logTag, nc, lp, score, config,
+        this(context, looper, logTag, nc, lp, null /* localNetworkConfig */, score, config,
+                provider);
+    }
+
+    /**
+     * Create a new network agent.
+     * @param context a {@link Context} to get system services from.
+     * @param looper the {@link Looper} on which to invoke the callbacks.
+     * @param logTag the tag for logs
+     * @param nc the initial {@link NetworkCapabilities} of this network. Update with
+     *           sendNetworkCapabilities.
+     * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties.
+     * @param localNetworkConfig the initial {@link LocalNetworkConfig} of this
+     *                                  network. Update with sendLocalNetworkConfig. Must be
+     *                                  non-null iff the nc have NET_CAPABILITY_LOCAL_NETWORK.
+     * @param score the initial score of this network. Update with sendNetworkScore.
+     * @param config an immutable {@link NetworkAgentConfig} for this agent.
+     * @param provider the {@link NetworkProvider} managing this agent.
+     * @hide
+     */
+    // TODO : expose
+    public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
+            @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+            @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+        this(looper, context, logTag, nc, lp, localNetworkConfig, score, config,
                 provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
                 getLegacyNetworkInfo(config));
     }
 
     private static class InitialConfiguration {
-        public final Context context;
-        public final NetworkCapabilities capabilities;
-        public final LinkProperties properties;
-        public final NetworkScore score;
-        public final NetworkAgentConfig config;
-        public final NetworkInfo info;
+        @NonNull public final Context context;
+        @NonNull public final NetworkCapabilities capabilities;
+        @NonNull public final LinkProperties properties;
+        @NonNull public final NetworkScore score;
+        @NonNull public final NetworkAgentConfig config;
+        @NonNull public final NetworkInfo info;
+        @Nullable public final LocalNetworkConfig localNetworkConfig;
         InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities,
-                @NonNull LinkProperties properties, @NonNull NetworkScore score,
+                @NonNull LinkProperties properties,
+                @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
                 @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) {
             this.context = context;
             this.capabilities = capabilities;
@@ -538,14 +573,15 @@
             this.score = score;
             this.config = config;
             this.info = info;
+            this.localNetworkConfig = localNetworkConfig;
         }
     }
     private volatile InitialConfiguration mInitialConfiguration;
 
     private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag,
             @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
-            @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId,
-            @NonNull NetworkInfo ni) {
+            @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score,
+            @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) {
         mHandler = new NetworkAgentHandler(looper);
         LOG_TAG = logTag;
         mNetworkInfo = new NetworkInfo(ni);
@@ -556,7 +592,7 @@
 
         mInitialConfiguration = new InitialConfiguration(context,
                 new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE),
-                new LinkProperties(lp), score, config, ni);
+                new LinkProperties(lp), localNetworkConfig, score, config, ni);
     }
 
     private class NetworkAgentHandler extends Handler {
@@ -723,7 +759,8 @@
             mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
                     new NetworkInfo(mInitialConfiguration.info),
                     mInitialConfiguration.properties, mInitialConfiguration.capabilities,
-                    mInitialConfiguration.score, mInitialConfiguration.config, providerId);
+                    mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score,
+                    mInitialConfiguration.config, providerId);
             mInitialConfiguration = null; // All this memory can now be GC'd
         }
         return mNetwork;
@@ -1099,6 +1136,18 @@
     }
 
     /**
+     * Must be called by the agent when the network's {@link LocalNetworkConfig} changes.
+     * @param config the new LocalNetworkConfig
+     * @hide
+     */
+    public void sendLocalNetworkConfig(@NonNull LocalNetworkConfig config) {
+        Objects.requireNonNull(config);
+        // If the agent doesn't have NET_CAPABILITY_LOCAL_NETWORK, this will be ignored by
+        // ConnectivityService with a Log.wtf.
+        queueOrSendMessage(reg -> reg.sendLocalNetworkConfig(config));
+    }
+
+    /**
      * Must be called by the agent to update the score of this network.
      *
      * @param score the new score.