Support LocalNetworkAgent for Tethering

The feature will be gradually enabled via flag rollout.

Sample ip rule output:
0:	from all lookup local
10000:	from all fwmark 0xc0000/0xd0000 lookup legacy_system
11000:	from all iif lo oif dummy0 uidrange 0-0 lookup dummy0
11000:	from all iif lo oif rmnet1 uidrange 0-0 lookup rmnet1
11000:	from all iif lo oif wlan0 uidrange 0-0 lookup wlan0
16000:	from all fwmark 0x10063/0x1ffff iif lo lookup local_network
16000:	from all fwmark 0x10064/0x1ffff iif lo lookup rmnet1
16000:	from all fwmark 0x10066/0x1ffff iif lo lookup wlan0
16000:	from all fwmark 0x10063/0x1ffff iif lo lookup wlan0
17000:	from all iif lo oif dummy0 lookup dummy0
17000:	from all iif lo oif rmnet1 lookup rmnet1
17000:	from all iif lo oif wlan0 lookup wlan0
18000:	from all fwmark 0x0/0x10000 lookup legacy_system
19000:	from all fwmark 0x0/0x10000 lookup legacy_network
20000:	from all fwmark 0x0/0x10000 lookup local_network
20000:	from all fwmark 0x0/0x10000 lookup wlan0
21000:	from all iif wlan0 lookup rmnet1
23000:	from all fwmark 0x64/0x1ffff iif lo lookup rmnet1
23000:	from all fwmark 0x66/0x1ffff iif lo lookup wlan0
31000:	from all fwmark 0x0/0xffff iif lo lookup rmnet1
32000:	from all unreachable

Test: adb shell device_config put tethering tethering_local_network_agent 1
Test: atest TetheringTests:android.net.ip.IpServerTest TetheringManagerTest
Bug: 349487600
Change-Id: Iaa88dde645da917149fec0d44fff796c551bcc7e
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 70a3442..a651b1b 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -31,6 +31,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
 import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
 import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
@@ -40,19 +41,25 @@
 
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHERING_LOCAL_NETWORK_AGENT;
 import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
 import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
 import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
+import static com.android.networkstack.tethering.util.TetheringUtils.getTransportTypeForTetherableType;
 
+import android.annotation.SuppressLint;
+import android.content.Context;
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
+import android.net.NetworkAgent;
 import android.net.RouteInfo;
 import android.net.TetheredClient;
 import android.net.TetheringManager.TetheringRequest;
+import android.net.connectivity.ConnectivityInternalApiUtil;
 import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
@@ -60,7 +67,9 @@
 import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.os.Build;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -70,11 +79,13 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.IIpv4PrefixRequest;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetdUtils;
@@ -204,6 +215,23 @@
         /** Create a DhcpServer instance to be used by IpServer. */
         public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb);
+
+        /**
+         * @see DeviceConfigUtils#isTetheringFeatureEnabled
+         */
+        public boolean isFeatureEnabled(Context context, String name) {
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
+        }
+
+        /** Create a NetworkAgent instance to be used by IpServer. */
+        @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+        @SuppressLint("NewApi")
+        public NetworkAgent makeNetworkAgent(
+                @NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+                int interfaceType, @NonNull LinkProperties lp) {
+            return ConnectivityInternalApiUtil.buildTetheringNetworkAgent(
+                    context, looper, logTag, getTransportTypeForTetherableType(interfaceType), lp);
+        }
     }
 
     // request from the user that it wants to tether
@@ -304,16 +332,28 @@
 
     private final TetheringMetrics mTetheringMetrics;
     private final Handler mHandler;
+    private final Context mContext;
+
+    private final boolean mSupportLocalAgent;
+
+    // This will be null if the TetheredState is not entered or feature not supported.
+    // This will be only accessed from the IpServer handler thread.
+    private NetworkAgent mTetheringAgent;
+
+    private static boolean everRegistered(@NonNull NetworkAgent agent) {
+        return agent.getNetwork() != null;
+    }
 
     // TODO: Add a dependency object to pass the data members or variables from the tethering
     // object. It helps to reduce the arguments of the constructor.
     public IpServer(
-            String ifaceName, Handler handler, int interfaceType, SharedLog log,
-            INetd netd, @NonNull BpfCoordinator bpfCoordinator,
+            String ifaceName, @NonNull Context context, Handler handler, int interfaceType,
+            SharedLog log, INetd netd, @NonNull BpfCoordinator bpfCoordinator,
             RoutingCoordinatorManager routingCoordinatorManager, Callback callback,
             TetheringConfiguration config,
             TetheringMetrics tetheringMetrics, Dependencies deps) {
         super(ifaceName, USE_SYNC_SM ? null : handler.getLooper());
+        mContext = Objects.requireNonNull(context);
         mHandler = handler;
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
@@ -339,6 +379,10 @@
         mLastError = TETHER_ERROR_NO_ERROR;
         mServingMode = STATE_AVAILABLE;
 
+        // Tethering network agent is supported on V+, and will be rolled out gradually.
+        mSupportLocalAgent = SdkLevel.isAtLeastV()
+                && mDeps.isFeatureEnabled(mContext, TETHERING_LOCAL_NETWORK_AGENT);
+
         mInitialState = new InitialState();
         mLocalHotspotState = new LocalHotspotState();
         mTetheredState = new TetheredState();
@@ -787,6 +831,7 @@
     //
     // TODO: Evaluate using a data structure than is more directly suited to
     // communicating only the relevant information.
+    @SuppressLint("NewApi")
     private void updateUpstreamIPv6LinkProperties(LinkProperties v6only, int ttlAdjustment) {
         if (mRaDaemon == null) return;
 
@@ -847,8 +892,7 @@
     }
 
     private void removeRoutesFromNetwork(int netId, @NonNull final List<RouteInfo> toBeRemoved) {
-        final int removalFailures = NetdUtils.removeRoutesFromNetwork(
-                mNetd, netId, toBeRemoved);
+        final int removalFailures = NetdUtils.removeRoutesFromNetwork(mNetd, netId, toBeRemoved);
         if (removalFailures > 0) {
             mLog.e("Failed to remove " + removalFailures
                     + " IPv6 routes from network " + netId + ".");
@@ -900,7 +944,9 @@
         if (!deprecatedPrefixes.isEmpty()) {
             final List<RouteInfo> routesToBeRemoved =
                     getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
-            removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+            if (mTetheringAgent == null) {
+                removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+            }
             for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route);
         }
 
@@ -914,7 +960,9 @@
             if (!addedPrefixes.isEmpty()) {
                 final List<RouteInfo> routesToBeAdded =
                         getLocalRoutesFor(mIfaceName, addedPrefixes);
-                addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+                if (mTetheringAgent == null) {
+                    addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+                }
                 for (RouteInfo route : routesToBeAdded) mLinkProperties.addRoute(route);
             }
         }
@@ -1112,7 +1160,26 @@
             return CONNECTIVITY_SCOPE_LOCAL;
         }
 
+        @SuppressLint("NewApi")
         private void startServingInterface() {
+            // TODO: Enable Network Agent for Wifi P2P Group Owner mode when Network Agent
+            //  for Group Client mode is supported.
+            if (mSupportLocalAgent && getScope() == CONNECTIVITY_SCOPE_GLOBAL) {
+                try {
+                    mTetheringAgent = mDeps.makeNetworkAgent(mContext, Looper.myLooper(), TAG,
+                            mInterfaceType, mLinkProperties);
+                    // Entering CONNECTING state, the ConnectivityService will create the
+                    // native network.
+                    mTetheringAgent.register();
+                } catch (RuntimeException e) {
+                    mLog.e("Error Creating Local Network", e);
+                    // If an exception occurs during the creation or registration of the
+                    // NetworkAgent, it typically indicates a problem with the system services.
+                    mLastError = TETHER_ERROR_SERVICE_UNAVAIL;
+                    return;
+                }
+            }
+
             if (!startIPv4(getScope())) {
                 mLastError = TETHER_ERROR_IFACE_CFG_ERROR;
                 return;
@@ -1122,14 +1189,16 @@
                 // Enable IPv6, disable accepting RA, etc. See TetherController::tetherInterface()
                 // for more detail.
                 mNetd.tetherInterfaceAdd(mIfaceName);
-                NetdUtils.networkAddInterface(mNetd, LOCAL_NET_ID, mIfaceName,
-                        20 /* maxAttempts */, 50 /* pollingIntervalMs */);
-                // Activate a route to dest and IPv6 link local.
-                NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
-                        new RouteInfo(asIpPrefix(mIpv4Address), null, mIfaceName, RTN_UNICAST));
-                NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
-                        new RouteInfo(new IpPrefix("fe80::/64"), null, mIfaceName,
-                                RTN_UNICAST));
+                if (mTetheringAgent == null) {
+                    NetdUtils.networkAddInterface(mNetd, LOCAL_NET_ID, mIfaceName,
+                            20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+                    // Activate a route to dest and IPv6 link local.
+                    NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
+                            new RouteInfo(asIpPrefix(mIpv4Address), null, mIfaceName, RTN_UNICAST));
+                    NetdUtils.modifyRoute(mNetd, NetdUtils.ModifyOperation.ADD, LOCAL_NET_ID,
+                            new RouteInfo(new IpPrefix("fe80::/64"), null, mIfaceName,
+                                    RTN_UNICAST));
+                }
             } catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
                 mLog.e("Error Tethering", e);
                 mLastError = TETHER_ERROR_TETHER_IFACE_ERROR;
@@ -1141,9 +1210,17 @@
                 // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
                 return;
             }
+
+            if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+                mTetheringAgent.sendLinkProperties(mLinkProperties);
+                // Mark it connected to notify the applications for
+                // the network availability.
+                mTetheringAgent.markConnected();
+            }
         }
 
         @Override
+        @SuppressLint("NewApi")
         public void exit() {
             // Note that at this point, we're leaving the tethered state.  We can fail any
             // of these operations, but it doesn't really change that we have to try them
@@ -1155,7 +1232,9 @@
                 try {
                     mNetd.tetherInterfaceRemove(mIfaceName);
                 } finally {
-                    mNetd.networkRemoveInterface(LOCAL_NET_ID, mIfaceName);
+                    if (mTetheringAgent == null) {
+                        mNetd.networkRemoveInterface(LOCAL_NET_ID, mIfaceName);
+                    }
                 }
             } catch (RemoteException | ServiceSpecificException e) {
                 mLastError = TETHER_ERROR_UNTETHER_IFACE_ERROR;
@@ -1165,6 +1244,11 @@
             stopIPv4();
             mBpfCoordinator.removeIpServer(IpServer.this);
 
+            if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+                mTetheringAgent.unregister();
+                mTetheringAgent = null;
+            }
+
             resetLinkProperties();
 
             mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
@@ -1184,6 +1268,12 @@
                     break;
                 case CMD_IPV6_TETHER_UPDATE:
                     updateUpstreamIPv6LinkProperties((LinkProperties) message.obj, message.arg1);
+                    // Sends update to the NetworkAgent.
+                    // TODO: Refactor the callers of sendLinkProperties()
+                    //  and move these code into sendLinkProperties().
+                    if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+                        mTetheringAgent.sendLinkProperties(mLinkProperties);
+                    }
                     sendLinkProperties();
                     break;
                 case CMD_IP_FORWARDING_ENABLE_ERROR:
@@ -1236,13 +1326,17 @@
             // Remove deprecated routes from downstream network.
             final List<RouteInfo> routesToBeRemoved =
                     List.of(getDirectConnectedRoute(deprecatedLinkAddress));
-            removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+            if (mTetheringAgent == null) {
+                removeRoutesFromNetwork(LOCAL_NET_ID, routesToBeRemoved);
+            }
             for (RouteInfo route : routesToBeRemoved) mLinkProperties.removeRoute(route);
             mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
 
             // Add new routes to downstream network.
             final List<RouteInfo> routesToBeAdded = List.of(getDirectConnectedRoute(mIpv4Address));
-            addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+            if (mTetheringAgent == null) {
+                addRoutesToNetwork(LOCAL_NET_ID, routesToBeAdded);
+            }
             for (RouteInfo route : routesToBeAdded) mLinkProperties.addRoute(route);
             mLinkProperties.addLinkAddress(mIpv4Address);
 
@@ -1255,6 +1349,10 @@
                 mLog.e("Failed to update local DNS caching server");
                 return;
             }
+            // Sends update to the NetworkAgent.
+            if (mTetheringAgent != null && everRegistered(mTetheringAgent)) {
+                mTetheringAgent.sendLinkProperties(mLinkProperties);
+            }
             sendLinkProperties();
 
             // Notify DHCP server that new prefix/route has been applied on IpServer.
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0cf008b..0730639 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -3105,7 +3105,7 @@
 
         mLog.i("adding IpServer for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
+                new IpServer(iface, mContext, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
                         mRoutingCoordinator, new ControlCallback(), mConfig, mTetheringMetrics,
                         mDeps.makeIpServerDependencies()), isNcm);
         mTetherStates.put(iface, tetherState);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 5f0e5d0..e2609e7 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -148,6 +148,12 @@
      */
     public static final String TETHER_ACTIVE_SESSIONS_METRICS = "tether_active_sessions_metrics";
 
+    /**
+     * A feature flag to control whether the tethering local network agent should be enabled.
+     * Disabled by default.
+     */
+    public static final String TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent";
+
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableWigigRegexs;
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index 79e6e16..9392ae8 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -15,8 +15,20 @@
  */
 package com.android.networkstack.tethering.util;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_USB;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHERING_WIGIG;
 
 import android.net.TetherStatsParcel;
 import android.net.TetheringManager.TetheringRequest;
@@ -207,4 +219,30 @@
         request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
         return request;
     }
+
+    /**
+     * Returns the transport type for the given interface type.
+     *
+     * @param interfaceType The interface type.
+     * @return The transport type.
+     * @throws IllegalArgumentException if the interface type is invalid.
+     */
+    public static int getTransportTypeForTetherableType(int interfaceType) {
+        switch (interfaceType) {
+            case TETHERING_WIFI:
+            case TETHERING_WIGIG:
+            case TETHERING_WIFI_P2P:
+                return TRANSPORT_WIFI;
+            case TETHERING_USB:
+            case TETHERING_NCM:
+                return TRANSPORT_USB;
+            case TETHERING_BLUETOOTH:
+                return TRANSPORT_BLUETOOTH;
+            case TETHERING_ETHERNET:
+            case TETHERING_VIRTUAL: // For virtual machines.
+                return TRANSPORT_ETHERNET;
+            default:
+                throw new IllegalArgumentException("Invalid interface type: " + interfaceType);
+        }
+    }
 }
diff --git a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
index ba39ca0..9478e91 100644
--- a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
+++ b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
@@ -16,11 +16,25 @@
 
 package android.net.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.LocalNetworkConfig;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScore;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.Looper;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 /**
@@ -57,4 +71,36 @@
         final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
         return cm.getRoutingCoordinatorService();
     }
+
+    /**
+     * Create a NetworkAgent instance to be used by Tethering.
+     * @param ctx the context
+     * @return an instance of the {@code NetworkAgent}
+     */
+    // TODO: Expose LocalNetworkConfig related APIs and delete this method. This method is
+    //  only here because on R Tethering is installed and not Connectivity, requiring all
+    //  shared classes to be public API. LocalNetworkConfig is not public yet, but it will
+    //  only be used by Tethering on V+ so it's fine.
+    @SuppressLint("WrongConstant")
+    @NonNull
+    public static NetworkAgent buildTetheringNetworkAgent(@NonNull Context ctx,
+            @NonNull Looper looper, @NonNull String logTag, int transportType,
+            @NonNull LinkProperties lp) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                .addCapability(NET_CAPABILITY_NOT_METERED)
+                .addCapability(NET_CAPABILITY_NOT_ROAMING)
+                .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addTransportType(transportType);
+        // TODO: Change to use the constant definition. Flags.netCapabilityLocalNetwork() was not
+        //  fully rolled out but the service will still process this capability, set it anyway.
+        builder.addCapability(36 /* NET_CAPABILITY_LOCAL_NETWORK */);
+        final NetworkCapabilities caps = builder.build();
+        final NetworkAgentConfig nac = new NetworkAgentConfig.Builder().build();
+        return new NetworkAgent(ctx, looper, logTag, caps, lp,
+                new LocalNetworkConfig.Builder().build(), new NetworkScore.Builder()
+                .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
+                .build(), nac, null /* provider */) {
+        };
+    }
 }