Merge "Use int array for included and excluded uids"
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
index 3a02682..ce0d69c 100644
--- a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
@@ -77,6 +77,47 @@
 Landroid/net/DataUsageRequest;->REQUEST_ID_UNSET:I
 Landroid/net/DataUsageRequest;->template:Landroid/net/NetworkTemplate;
 Landroid/net/DataUsageRequest;->thresholdInBytes:J
+Landroid/net/EthernetManager;-><init>(Landroid/content/Context;Landroid/net/IEthernetManager;)V
+Landroid/net/EthernetManager;->mContext:Landroid/content/Context;
+Landroid/net/EthernetManager;->mHandler:Landroid/os/Handler;
+Landroid/net/EthernetManager;->mListeners:Ljava/util/ArrayList;
+Landroid/net/EthernetManager;->mService:Landroid/net/IEthernetManager;
+Landroid/net/EthernetManager;->mServiceListener:Landroid/net/IEthernetServiceListener$Stub;
+Landroid/net/EthernetManager;->MSG_AVAILABILITY_CHANGED:I
+Landroid/net/EthernetManager;->TAG:Ljava/lang/String;
+Landroid/net/IEthernetManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/IEthernetManager$Stub$Proxy;->addListener(Landroid/net/IEthernetServiceListener;)V
+Landroid/net/IEthernetManager$Stub$Proxy;->getAvailableInterfaces()[Ljava/lang/String;
+Landroid/net/IEthernetManager$Stub$Proxy;->getConfiguration(Ljava/lang/String;)Landroid/net/IpConfiguration;
+Landroid/net/IEthernetManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/IEthernetManager$Stub$Proxy;->isAvailable(Ljava/lang/String;)Z
+Landroid/net/IEthernetManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/IEthernetManager$Stub$Proxy;->removeListener(Landroid/net/IEthernetServiceListener;)V
+Landroid/net/IEthernetManager$Stub$Proxy;->setConfiguration(Ljava/lang/String;Landroid/net/IpConfiguration;)V
+Landroid/net/IEthernetManager$Stub;-><init>()V
+Landroid/net/IEthernetManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IEthernetManager;
+Landroid/net/IEthernetManager$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_addListener:I
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_getAvailableInterfaces:I
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_getConfiguration:I
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_isAvailable:I
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_removeListener:I
+Landroid/net/IEthernetManager$Stub;->TRANSACTION_setConfiguration:I
+Landroid/net/IEthernetManager;->addListener(Landroid/net/IEthernetServiceListener;)V
+Landroid/net/IEthernetManager;->getAvailableInterfaces()[Ljava/lang/String;
+Landroid/net/IEthernetManager;->getConfiguration(Ljava/lang/String;)Landroid/net/IpConfiguration;
+Landroid/net/IEthernetManager;->isAvailable(Ljava/lang/String;)Z
+Landroid/net/IEthernetManager;->removeListener(Landroid/net/IEthernetServiceListener;)V
+Landroid/net/IEthernetManager;->setConfiguration(Ljava/lang/String;Landroid/net/IpConfiguration;)V
+Landroid/net/IEthernetServiceListener$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/IEthernetServiceListener$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/IEthernetServiceListener$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/IEthernetServiceListener$Stub$Proxy;->onAvailabilityChanged(Ljava/lang/String;Z)V
+Landroid/net/IEthernetServiceListener$Stub;-><init>()V
+Landroid/net/IEthernetServiceListener$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IEthernetServiceListener;
+Landroid/net/IEthernetServiceListener$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/IEthernetServiceListener$Stub;->TRANSACTION_onAvailabilityChanged:I
+Landroid/net/IEthernetServiceListener;->onAvailabilityChanged(Ljava/lang/String;Z)V
 Landroid/net/IIpSecService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/net/IIpSecService$Stub$Proxy;->addAddressToTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
 Landroid/net/IIpSecService$Stub$Proxy;->allocateSecurityParameterIndex(Ljava/lang/String;ILandroid/os/IBinder;)Landroid/net/IpSecSpiResponse;
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 4ca36df..844efde 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -16,6 +16,7 @@
 
 package com.android.networkstack.tethering;
 
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
 import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
@@ -32,6 +33,9 @@
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
 
+import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -39,6 +43,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.util.SharedLog;
 import android.os.Bundle;
 import android.os.Handler;
@@ -52,6 +57,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.io.PrintWriter;
 import java.util.BitSet;
@@ -74,6 +80,13 @@
     protected static final String ACTION_PROVISIONING_ALARM =
             "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM";
 
+    // Indicate tether provisioning is not required by carrier.
+    private static final int TETHERING_PROVISIONING_REQUIRED = 1000;
+    // Indicate tether provisioning is required by carrier.
+    private static final int TETHERING_PROVISIONING_NOT_REQUIRED = 1001;
+    // Indicate tethering is not supported by carrier.
+    private static final int TETHERING_PROVISIONING_CARRIER_UNSUPPORT = 1002;
+
     private final ComponentName mSilentProvisioningService;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private static final int DUMP_TIMEOUT = 10_000;
@@ -96,7 +109,7 @@
     private boolean mLastCellularUpstreamPermitted = true;
     private boolean mUsingCellularAsUpstream = false;
     private boolean mNeedReRunProvisioningUi = false;
-    private OnUiEntitlementFailedListener mListener;
+    private OnTetherProvisioningFailedListener mListener;
     private TetheringConfigurationFetcher mFetcher;
 
     public EntitlementManager(Context ctx, Handler h, SharedLog log,
@@ -115,18 +128,20 @@
                 mContext.getResources().getString(R.string.config_wifi_tether_enable));
     }
 
-    public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
+    public void setOnTetherProvisioningFailedListener(
+            final OnTetherProvisioningFailedListener listener) {
         mListener = listener;
     }
 
     /** Callback fired when UI entitlement failed. */
-    public interface OnUiEntitlementFailedListener {
+    public interface OnTetherProvisioningFailedListener {
         /**
          * Ui entitlement check fails in |downstream|.
          *
          * @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
+         * @param reason Failed reason.
          */
-        void onUiEntitlementFailed(int downstream);
+        void onTetherProvisioningFailed(int downstream, String reason);
     }
 
     public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) {
@@ -153,6 +168,9 @@
     }
 
     private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) {
+        // If #getTetherProvisioningCondition return TETHERING_PROVISIONING_CARRIER_UNSUPPORT,
+        // that means cellular upstream is not supported and entitlement check result is empty
+        // because entitlement check should not be run.
         if (!isTetherProvisioningRequired(config)) return true;
 
         // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular
@@ -199,11 +217,7 @@
         // If upstream is not cellular, provisioning app would not be launched
         // till upstream change to cellular.
         if (mUsingCellularAsUpstream) {
-            if (showProvisioningUi) {
-                runUiTetherProvisioning(downstreamType, config);
-            } else {
-                runSilentTetherProvisioning(downstreamType, config);
-            }
+            runTetheringProvisioning(showProvisioningUi, downstreamType, config);
             mNeedReRunProvisioningUi = false;
         } else {
             mNeedReRunProvisioningUi |= showProvisioningUi;
@@ -262,18 +276,51 @@
         // the change and get the new correct value.
         for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0;
                 downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) {
+            // If tethering provisioning is required but entitlement check result is empty,
+            // this means tethering may need to run entitlement check or carrier network
+            // is not supported.
             if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) {
-                if (mNeedReRunProvisioningUi) {
-                    mNeedReRunProvisioningUi = false;
-                    runUiTetherProvisioning(downstream, config);
-                } else {
-                    runSilentTetherProvisioning(downstream, config);
-                }
+                runTetheringProvisioning(mNeedReRunProvisioningUi, downstream, config);
+                mNeedReRunProvisioningUi = false;
             }
         }
     }
 
     /**
+     * Tether provisioning has these conditions to control provisioning behavior.
+     *  1st priority : Uses system property to disable any provisioning behavior.
+     *  2nd priority : Uses {@code CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL} to
+     *                 decide current carrier support cellular upstream tethering or not.
+     *                 If value is true, it means check follow up condition to know whether
+     *                 provisioning is required.
+     *                 If value is false, it means tethering could not use cellular as upstream.
+     *  3rd priority : Uses {@code CarrierConfigManager#KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL} to
+     *                 decide current carrier require the provisioning.
+     *  4th priority : Checks whether provisioning is required from RRO configuration.
+     *
+     * @param config
+     * @return integer {@see #TETHERING_PROVISIONING_NOT_REQUIRED,
+     *                 #TETHERING_PROVISIONING_REQUIRED,
+     *                 #TETHERING_PROVISIONING_CARRIER_UNSUPPORT}
+     */
+    private int getTetherProvisioningCondition(final TetheringConfiguration config) {
+        if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)) {
+            return TETHERING_PROVISIONING_NOT_REQUIRED;
+        }
+        // TODO: Find a way to avoid get carrier config twice.
+        if (carrierConfigAffirmsCarrierNotSupport(config)) {
+            // To block tethering, behave as if running provisioning check and failed.
+            return TETHERING_PROVISIONING_CARRIER_UNSUPPORT;
+        }
+
+        if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
+            return TETHERING_PROVISIONING_NOT_REQUIRED;
+        }
+        return (config.provisioningApp.length == 2)
+                ? TETHERING_PROVISIONING_REQUIRED : TETHERING_PROVISIONING_NOT_REQUIRED;
+    }
+
+    /**
      * Check if the device requires a provisioning check in order to enable tethering.
      *
      * @param config an object that encapsulates the various tethering configuration elements.
@@ -281,14 +328,26 @@
      */
     @VisibleForTesting
     protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) {
-        if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
-                || config.provisioningApp.length == 0) {
+        return getTetherProvisioningCondition(config) != TETHERING_PROVISIONING_NOT_REQUIRED;
+    }
+
+    /**
+     * Confirms the need of tethering provisioning but no entitlement package exists.
+     */
+    public boolean isProvisioningNeededButUnavailable() {
+        final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
+        return getTetherProvisioningCondition(config) == TETHERING_PROVISIONING_REQUIRED
+                && !doesEntitlementPackageExist(config);
+    }
+
+    private boolean doesEntitlementPackageExist(final TetheringConfiguration config) {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            pm.getPackageInfo(config.provisioningApp[0], GET_ACTIVITIES);
+        } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
-        if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
-            return false;
-        }
-        return (config.provisioningApp.length == 2);
+        return true;
     }
 
     /**
@@ -310,9 +369,7 @@
         mEntitlementCacheValue.clear();
         mCurrentEntitlementResults.clear();
 
-        // TODO: refine provisioning check to isTetherProvisioningRequired() ??
-        if (!config.hasMobileHotspotProvisionApp()
-                || carrierConfigAffirmsEntitlementCheckNotRequired(config)) {
+        if (!isTetherProvisioningRequired(config)) {
             evaluateCellularPermission(config);
             return;
         }
@@ -327,8 +384,8 @@
      * @param config an object that encapsulates the various tethering configuration elements.
      * */
     public PersistableBundle getCarrierConfig(final TetheringConfiguration config) {
-        final CarrierConfigManager configManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        final CarrierConfigManager configManager = mContext
+                .getSystemService(CarrierConfigManager.class);
         if (configManager == null) return null;
 
         final PersistableBundle carrierConfig = configManager.getConfigForSubId(
@@ -346,6 +403,7 @@
     //
     // TODO: find a better way to express this, or alter the checking process
     // entirely so that this is more intuitive.
+    // TODO: Find a way to avoid using getCarrierConfig everytime.
     private boolean carrierConfigAffirmsEntitlementCheckNotRequired(
             final TetheringConfiguration config) {
         // Check carrier config for entitlement checks
@@ -358,16 +416,29 @@
         return !isEntitlementCheckRequired;
     }
 
+    private boolean carrierConfigAffirmsCarrierNotSupport(final TetheringConfiguration config) {
+        if (!SdkLevel.isAtLeastT()) {
+            return false;
+        }
+        // Check carrier config for entitlement checks
+        final PersistableBundle carrierConfig = getCarrierConfig(config);
+        if (carrierConfig == null) return false;
+
+        // A CarrierConfigManager was found and it has a config.
+        final boolean mIsCarrierSupport = carrierConfig.getBoolean(
+                KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true);
+        return !mIsCarrierSupport;
+    }
+
     /**
      * Run no UI tethering provisioning check.
      * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param subId default data subscription ID.
      */
     @VisibleForTesting
-    protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) {
+    protected Intent runSilentTetherProvisioning(
+            int type, final TetheringConfiguration config, ResultReceiver receiver) {
         if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
-        // For silent provisioning, settings would stop tethering when entitlement fail.
-        ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null);
 
         Intent intent = new Intent();
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
@@ -383,11 +454,6 @@
         return intent;
     }
 
-    private void runUiTetherProvisioning(int type, final TetheringConfiguration config) {
-        ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null);
-        runUiTetherProvisioning(type, config, receiver);
-    }
-
     /**
      * Run the UI-enabled tethering provisioning check.
      * @param type tethering type from TetheringManager.TETHERING_{@code *}
@@ -411,6 +477,35 @@
         return intent;
     }
 
+    private void runTetheringProvisioning(
+            boolean showProvisioningUi, int downstreamType, final TetheringConfiguration config) {
+        if (carrierConfigAffirmsCarrierNotSupport(config)) {
+            mListener.onTetherProvisioningFailed(downstreamType, "Carrier does not support.");
+            if (showProvisioningUi) {
+                showCarrierUnsupportedDialog();
+            }
+            return;
+        }
+
+        ResultReceiver receiver =
+                buildProxyReceiver(downstreamType, showProvisioningUi/* notifyFail */, null);
+        if (showProvisioningUi) {
+            runUiTetherProvisioning(downstreamType, config, receiver);
+        } else {
+            runSilentTetherProvisioning(downstreamType, config, receiver);
+        }
+    }
+
+    private void showCarrierUnsupportedDialog() {
+        // This is only used when carrierConfigAffirmsCarrierNotSupport() is true.
+        if (!SdkLevel.isAtLeastT()) {
+            return;
+        }
+        Intent intent = new Intent(ACTION_TETHER_UNSUPPORTED_CARRIER_UI);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
     @VisibleForTesting
     PendingIntent createRecheckAlarmIntent() {
         final Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
@@ -576,7 +671,8 @@
                 int updatedCacheValue = updateEntitlementCacheValue(type, resultCode);
                 addDownstreamMapping(type, updatedCacheValue);
                 if (updatedCacheValue == TETHER_ERROR_PROVISIONING_FAILED && notifyFail) {
-                    mListener.onUiEntitlementFailed(type);
+                    mListener.onTetherProvisioningFailed(
+                            type, "Tethering provisioning failed.");
                 }
                 if (receiver != null) receiver.send(updatedCacheValue, null);
             }
@@ -632,9 +728,14 @@
         }
 
         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
-        if (!isTetherProvisioningRequired(config)) {
-            receiver.send(TETHER_ERROR_NO_ERROR, null);
-            return;
+
+        switch (getTetherProvisioningCondition(config)) {
+            case TETHERING_PROVISIONING_NOT_REQUIRED:
+                receiver.send(TETHER_ERROR_NO_ERROR, null);
+                return;
+            case TETHERING_PROVISIONING_CARRIER_UNSUPPORT:
+                receiver.send(TETHER_ERROR_PROVISIONING_FAILED, null);
+                return;
         }
 
         final int cacheValue = mEntitlementCacheValue.get(
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index bb9b6fb..0b607bd 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -319,8 +318,8 @@
         mEntitlementMgr = mDeps.getEntitlementManager(mContext, mHandler, mLog,
                 () -> mTetherMainSM.sendMessage(
                 TetherMainSM.EVENT_UPSTREAM_PERMISSION_CHANGED));
-        mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> {
-            mLog.log("OBSERVED UiEnitlementFailed");
+        mEntitlementMgr.setOnTetherProvisioningFailedListener((downstream, reason) -> {
+            mLog.log("OBSERVED OnTetherProvisioningFailed : " + reason);
             stopTethering(downstream);
         });
         mEntitlementMgr.setTetheringConfigurationFetcher(() -> {
@@ -995,30 +994,11 @@
         return tetherState.lastError;
     }
 
-    private boolean isProvisioningNeededButUnavailable() {
-        return isTetherProvisioningRequired() && !doesEntitlementPackageExist();
-    }
-
     boolean isTetherProvisioningRequired() {
         final TetheringConfiguration cfg = mConfig;
         return mEntitlementMgr.isTetherProvisioningRequired(cfg);
     }
 
-    private boolean doesEntitlementPackageExist() {
-        // provisioningApp must contain package and class name.
-        if (mConfig.provisioningApp.length != 2) {
-            return false;
-        }
-
-        final PackageManager pm = mContext.getPackageManager();
-        try {
-            pm.getPackageInfo(mConfig.provisioningApp[0], GET_ACTIVITIES);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-        return true;
-    }
-
     private int getRequestedState(int type) {
         final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
 
@@ -2476,7 +2456,7 @@
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
 
         return tetherEnabledInSettings && hasAnySupportedDownstream()
-                && !isProvisioningNeededButUnavailable();
+                && !mEntitlementMgr.isProvisioningNeededButUnavailable();
     }
 
     private void dumpBpf(IndentingPrintWriter pw) {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 46ce82c..690ff71 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -37,6 +37,9 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -47,6 +50,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -77,9 +81,12 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
@@ -96,6 +103,7 @@
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
     private static final String PROVISIONING_APP_RESPONSE = "app_response";
     private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
+    private static final String FAILED_TETHERING_REASON = "Tethering provisioning failed.";
     private static final int RECHECK_TIMER_HOURS = 24;
 
     @Mock private CarrierConfigManager mCarrierConfigManager;
@@ -103,10 +111,14 @@
     @Mock private Resources mResources;
     @Mock private SharedLog mLog;
     @Mock private PackageManager mPm;
-    @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener;
+    @Mock private EntitlementManager
+            .OnTetherProvisioningFailedListener mTetherProvisioningFailedListener;
     @Mock private AlarmManager mAlarmManager;
     @Mock private PendingIntent mAlarmIntent;
 
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
     // Like so many Android system APIs, these cannot be mocked because it is marked final.
     // We have to use the real versions.
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
@@ -179,8 +191,8 @@
 
         @Override
         protected Intent runSilentTetherProvisioning(int type,
-                final TetheringConfiguration config) {
-            Intent intent = super.runSilentTetherProvisioning(type, config);
+                final TetheringConfiguration config, final ResultReceiver receiver) {
+            Intent intent = super.runSilentTetherProvisioning(type, config, receiver);
             assertSilentTetherProvisioning(type, config, intent);
             silentProvisionCount++;
             addDownstreamMapping(type, fakeEntitlementResult);
@@ -245,7 +257,7 @@
         mPermissionChangeCallback = spy(() -> { });
         mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
                 mPermissionChangeCallback);
-        mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
+        mEnMgr.setOnTetherProvisioningFailedListener(mTetherProvisioningFailedListener);
         mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
         mEnMgr.setTetheringConfigurationFetcher(() -> {
             return mConfig;
@@ -268,14 +280,23 @@
         when(mResources.getInteger(R.integer.config_mobile_hotspot_provision_check_period))
                 .thenReturn(RECHECK_TIMER_HOURS);
         // Act like the CarrierConfigManager is present and ready unless told otherwise.
-        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
-                .thenReturn(mCarrierConfigManager);
+        mockService(Context.CARRIER_CONFIG_SERVICE,
+                CarrierConfigManager.class, mCarrierConfigManager);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
         mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
         mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
     }
 
+    private void setupCarrierConfig(boolean carrierSupported) {
+        mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, carrierSupported);
+    }
+
+    private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
+        when(mMockContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
+        when(mMockContext.getSystemService(serviceName)).thenReturn(service);
+    }
+
     @Test
     public void canRequireProvisioning() {
         setupForRequiredProvisioning();
@@ -285,8 +306,7 @@
     @Test
     public void toleratesCarrierConfigManagerMissing() {
         setupForRequiredProvisioning();
-        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
-            .thenReturn(null);
+        mockService(Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, null);
         mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
         // Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
         // Therefore provisioning still be required.
@@ -613,14 +633,16 @@
     @Test
     public void testCallStopTetheringWhenUiProvisioningFail() {
         setupForRequiredProvisioning();
-        verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI);
+        verify(mTetherProvisioningFailedListener, times(0))
+                .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
         mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
         mEnMgr.notifyUpstream(true);
         mLooper.dispatchAll();
         mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
         mLooper.dispatchAll();
         assertEquals(1, mEnMgr.uiProvisionCount);
-        verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI);
+        verify(mTetherProvisioningFailedListener, times(1))
+                .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON);
     }
 
     @Test
@@ -644,7 +666,8 @@
 
         // When second downstream is down, exempted downstream can use cellular upstream.
         assertEquals(1, mEnMgr.uiProvisionCount);
-        verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB);
+        verify(mTetherProvisioningFailedListener).onTetherProvisioningFailed(TETHERING_USB,
+                FAILED_TETHERING_REASON);
         mEnMgr.stopProvisioningIfNeeded(TETHERING_USB);
         assertTrue(mEnMgr.isCellularUpstreamPermitted());
 
@@ -678,4 +701,85 @@
         verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(),
                 eq(mAlarmIntent));
     }
+
+    @Test
+    @IgnoreUpTo(SC_V2)
+    public void requestLatestTetheringEntitlementResult_carrierDoesNotSupport_noProvisionCount()
+            throws Exception {
+        setupForRequiredProvisioning();
+        setupCarrierConfig(false);
+        mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
+        ResultReceiver receiver = new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
+            }
+        };
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+        mLooper.dispatchAll();
+        assertEquals(0, mEnMgr.uiProvisionCount);
+        mEnMgr.reset();
+    }
+
+    @Test
+    @IgnoreUpTo(SC_V2)
+    public void reevaluateSimCardProvisioning_carrierUnsupportAndSimswitch() {
+        setupForRequiredProvisioning();
+
+        // Start a tethering with cellular data without provisioning.
+        mEnMgr.notifyUpstream(true);
+        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false);
+        mLooper.dispatchAll();
+
+        // Tear down mobile, then switch SIM.
+        mEnMgr.notifyUpstream(false);
+        mLooper.dispatchAll();
+        setupCarrierConfig(false);
+        mEnMgr.reevaluateSimCardProvisioning(mConfig);
+
+        // Turn on upstream.
+        mEnMgr.notifyUpstream(true);
+        mLooper.dispatchAll();
+
+        verify(mTetherProvisioningFailedListener)
+                .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+    }
+
+    @Test
+    @IgnoreUpTo(SC_V2)
+    public void startProvisioningIfNeeded_carrierUnsupport()
+            throws Exception {
+        setupForRequiredProvisioning();
+        setupCarrierConfig(false);
+        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+        verify(mTetherProvisioningFailedListener, never())
+                .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+
+        mEnMgr.notifyUpstream(true);
+        mLooper.dispatchAll();
+        verify(mTetherProvisioningFailedListener)
+                .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+        mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
+        reset(mTetherProvisioningFailedListener);
+
+        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+        mLooper.dispatchAll();
+        verify(mTetherProvisioningFailedListener)
+                .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support.");
+    }
+
+    @Test
+    public void isTetherProvisioningRequired_carrierUnSupport() {
+        setupForRequiredProvisioning();
+        setupCarrierConfig(false);
+        when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
+                .thenReturn(new String[0]);
+        mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
+        if (SdkLevel.isAtLeastT()) {
+            assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig));
+        } else {
+            assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig));
+        }
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index fc34585..e9716b3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,7 +26,7 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
-import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
@@ -666,10 +666,12 @@
         callback.onStoppedLimitReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
 
-        if (isAtLeastS()) {
+        if (isAtLeastT()) {
+            mTetherStatsProviderCb.expectNotifyLimitReached();
+        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
             mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
         } else {
-            mTetherStatsProviderCb.expectLegacyNotifyLimitReached();
+            mTetherStatsProviderCb.expectNotifyLimitReached();
         }
     }
 
@@ -680,7 +682,12 @@
         final OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
         callback.onWarningReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
-        mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
+
+        if (isAtLeastT()) {
+            mTetherStatsProviderCb.expectNotifyWarningReached();
+        } else {
+            mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
+        }
     }
 
     @Test
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 2ab69b8..f46d887 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -127,6 +127,7 @@
         "//frameworks/base/tests/vcn",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 3d6fb3e..4fefa0a 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -61,6 +61,14 @@
 
 package android.net {
 
+  public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public EthernetNetworkSpecifier(@NonNull String);
+    method public int describeContents();
+    method @Nullable public String getInterfaceName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
+  }
+
   public final class IpSecAlgorithm implements android.os.Parcelable {
     ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]);
     ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int);
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 16308ac..1bdd388 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -31,6 +31,22 @@
     method public static void registerServiceWrappers();
   }
 
+  public class EthernetManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+    method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+    method public void setIncludeTestInterfaces(boolean);
+    field public static final int ROLE_CLIENT = 1; // 0x1
+    field public static final int ROLE_NONE = 0; // 0x0
+    field public static final int ROLE_SERVER = 2; // 0x2
+    field public static final int STATE_ABSENT = 0; // 0x0
+    field public static final int STATE_LINK_DOWN = 1; // 0x1
+    field public static final int STATE_LINK_UP = 2; // 0x2
+  }
+
+  public static interface EthernetManager.InterfaceStateListener {
+    method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration);
+  }
+
   public class IpSecManager {
     field public static final int DIRECTION_FWD = 2; // 0x2
   }
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 4035e9b..c502f72 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -12,6 +12,45 @@
 
 package android.net {
 
+  public class EthernetManager {
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void connectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disconnectNetwork(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.BiConsumer<android.net.Network,android.net.EthernetNetworkManagementException>);
+  }
+
+  public static interface EthernetManager.TetheredInterfaceCallback {
+    method public void onAvailable(@NonNull String);
+    method public void onUnavailable();
+  }
+
+  public static class EthernetManager.TetheredInterfaceRequest {
+    method public void release();
+  }
+
+  public final class EthernetNetworkManagementException extends java.lang.RuntimeException implements android.os.Parcelable {
+    ctor public EthernetNetworkManagementException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkManagementException> CREATOR;
+  }
+
+  public final class EthernetNetworkUpdateRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.IpConfiguration getIpConfiguration();
+    method @Nullable public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkUpdateRequest> CREATOR;
+  }
+
+  public static final class EthernetNetworkUpdateRequest.Builder {
+    ctor public EthernetNetworkUpdateRequest.Builder();
+    ctor public EthernetNetworkUpdateRequest.Builder(@NonNull android.net.EthernetNetworkUpdateRequest);
+    method @NonNull public android.net.EthernetNetworkUpdateRequest build();
+    method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setIpConfiguration(@NonNull android.net.IpConfiguration);
+    method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+  }
+
   public class IpSecManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
diff --git a/framework/Android.bp b/framework/Android.bp
index 66b662b..53f9217 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -86,6 +86,7 @@
     ],
     static_libs: [
         "framework-connectivity-protos",
+        "modules-utils-backgroundthread",
         "modules-utils-build",
         "modules-utils-preconditions",
     ],
@@ -139,6 +140,7 @@
         "//frameworks/base/tests/vcn",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 3146a93..8851554 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -30,6 +30,7 @@
     min_sdk_version: "30",
     srcs: [
         "src/**/*.java",
+        ":ethernet-service-updatable-sources",
         ":services.connectivity-tiramisu-updatable-sources",
     ],
     libs: [
@@ -39,6 +40,7 @@
         "framework-tethering.stubs.module_lib",
         "service-connectivity-pre-jarjar",
         "service-nearby-pre-jarjar",
+        "ServiceConnectivityResources",
         "unsupportedappusage",
     ],
     static_libs: [
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 25fe5e9..fa86f39 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -21,6 +21,8 @@
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.server.ethernet.EthernetService;
+import com.android.server.ethernet.EthernetServiceImpl;
 import com.android.server.nearby.NearbyService;
 
 /**
@@ -33,11 +35,13 @@
     private final IpSecService mIpSecService;
     private final NsdService mNsdService;
     private final NearbyService mNearbyService;
+    private final EthernetServiceImpl mEthernetServiceImpl;
 
     public ConnectivityServiceInitializer(Context context) {
         super(context);
         // Load JNI libraries used by ConnectivityService and its dependencies
         System.loadLibrary("service-connectivity");
+        mEthernetServiceImpl = createEthernetService(context);
         mConnectivity = new ConnectivityService(context);
         mIpSecService = createIpSecService(context);
         mNsdService = createNsdService(context);
@@ -46,6 +50,12 @@
 
     @Override
     public void onStart() {
+        if (mEthernetServiceImpl != null) {
+            Log.i(TAG, "Registering " + Context.ETHERNET_SERVICE);
+            publishBinderService(Context.ETHERNET_SERVICE, mEthernetServiceImpl,
+                    /* allowIsolated= */ false);
+        }
+
         Log.i(TAG, "Registering " + Context.CONNECTIVITY_SERVICE);
         publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity,
                 /* allowIsolated= */ false);
@@ -65,6 +75,7 @@
             publishBinderService(ConstantsShim.NEARBY_SERVICE, mNearbyService,
                     /* allowIsolated= */ false);
         }
+
     }
 
     @Override
@@ -72,6 +83,10 @@
         if (mNearbyService != null) {
             mNearbyService.onBootPhase(phase);
         }
+
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && mEthernetServiceImpl != null) {
+            mEthernetServiceImpl.start();
+        }
     }
 
     /**
@@ -106,4 +121,15 @@
             return null;
         }
     }
+
+    /**
+     * Return EthernetServiceImpl instance or null if current SDK is lower than T or Ethernet
+     * service isn't necessary.
+     */
+    private EthernetServiceImpl createEthernetService(final Context context) {
+        if (!SdkLevel.isAtLeastT() || !mConnectivity.deviceSupportsEthernet(context)) {
+            return null;
+        }
+        return EthernetService.create(context);
+    }
 }
diff --git a/service/Android.bp b/service/Android.bp
index aed4b1e..0e6fe92 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -114,6 +114,7 @@
         "net-utils-device-common",
         "net-utils-device-common-bpf",
         "net-utils-device-common-netlink",
+        "net-utils-services-common",
         "netd-client",
         "networkstack-client",
         "PlatformProperties",
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index f8f86a2..faa9998 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -136,4 +136,36 @@
          extreme adverse effects on performance of the new network.
     -->
     <bool translatable="false" name="config_cellular_radio_timesharing_capable">true</bool>
+
+    <!-- Configure ethernet tcp buffersizes in the form:
+         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
+    <string translatable="false" name="config_ethernet_tcp_buffers">524288,1048576,3145728,524288,1048576,2097152</string>
+
+    <!-- Configuration of Ethernet interfaces in the following format:
+         <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]
+         Where
+               [Network Capabilities] Optional. A comma separated list of network capabilities.
+                   Values must be from NetworkCapabilities#NET_CAPABILITY_* constants.
+                   The NOT_ROAMING, NOT_CONGESTED and NOT_SUSPENDED capabilities are always
+                   added automatically because this configuration provides no way to update
+                   them dynamically.
+               [IP config] Optional. If empty or not specified - DHCP will be used, otherwise
+                   use the following format to specify static IP configuration:
+                       ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
+                       domains=<comma-sep-domains>
+               [Override Transport] Optional. An override network transport type to allow
+                    the propagation of an interface type on the other end of a local Ethernet
+                    interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If
+                    left out, this will default to TRANSPORT_ETHERNET.
+         -->
+    <string-array translatable="false" name="config_ethernet_interfaces">
+        <!--
+        <item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
+        <item>eth2;;ip=192.168.0.11/24</item>
+        <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item>
+        -->
+    </string-array>
+
+    <!-- Regex of wired ethernet ifaces -->
+    <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
 </resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index e5010d7..9fa6a30 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -37,6 +37,9 @@
             <item type="drawable" name="stat_notify_wifi_in_range"/>
             <item type="drawable" name="stat_notify_rssi_in_range"/>
             <item type="bool" name="config_cellular_radio_timesharing_capable" />
+            <item type="string" name="config_ethernet_tcp_buffers"/>
+            <item type="array" name="config_ethernet_interfaces"/>
+            <item type="string" name="config_ethernet_iface_regex"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 62454cd..d607886 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -936,7 +936,7 @@
             }
             // Ethernet is often not specified in the configs, although many devices can use it via
             // USB host adapters. Add it as long as the ethernet service is here.
-            if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) {
+            if (deviceSupportsEthernet(ctx)) {
                 addSupportedType(TYPE_ETHERNET);
             }
 
@@ -1621,6 +1621,15 @@
                 mContext);
     }
 
+    /**
+     * Check whether or not the device supports Ethernet transport.
+     */
+    public static boolean deviceSupportsEthernet(final Context context) {
+        final PackageManager pm = context.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET)
+                || pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST);
+    }
+
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         return createDefaultNetworkCapabilitiesForUidRangeSet(Collections.singleton(
                 new UidRange(uid, uid)));
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 7bce3d2..8234ec1 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -52,6 +52,7 @@
 
 import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
 import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -129,12 +130,11 @@
             assertTrue("Failed to allocate specified SPI, " + DROID_SPI,
                     droidSpi.getSpi() == DROID_SPI);
 
-            try {
-                mISM.allocateSecurityParameterIndex(addr, DROID_SPI);
-                fail("Duplicate SPI was allowed to be created");
-            } catch (IpSecManager.SpiUnavailableException expected) {
-                // This is a success case because we expect a dupe SPI to throw
-            }
+            IpSecManager.SpiUnavailableException expectedException =
+                    assertThrows("Duplicate SPI was allowed to be created",
+                            IpSecManager.SpiUnavailableException.class,
+                            () -> mISM.allocateSecurityParameterIndex(addr, DROID_SPI));
+            assertEquals(expectedException.getSpi(), droidSpi.getSpi());
 
             randomSpi.close();
             droidSpi.close();
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 20a0e20..0ee9e0b 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -36,6 +36,7 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FEATURE_ETHERNET;
 import static android.content.pm.PackageManager.FEATURE_WIFI;
 import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -1739,6 +1740,7 @@
         mockDefaultPackages();
         mockHasSystemFeature(FEATURE_WIFI, true);
         mockHasSystemFeature(FEATURE_WIFI_DIRECT, true);
+        mockHasSystemFeature(FEATURE_ETHERNET, true);
         doReturn(true).when(mTelephonyManager).isDataCapable();
 
         FakeSettingsProvider.clearSettingsProvider();
diff --git a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
index 64736f2..7ed55e5 100644
--- a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -23,6 +23,8 @@
 
 import android.content.Context
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_ETHERNET
+import android.content.pm.PackageManager.FEATURE_USB_HOST
 import android.content.pm.PackageManager.FEATURE_WIFI
 import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
 import android.net.ConnectivityManager.TYPE_ETHERNET
@@ -40,7 +42,6 @@
 import android.net.ConnectivityManager.TYPE_WIFI
 import android.net.ConnectivityManager.TYPE_WIFI_P2P
 import android.net.ConnectivityManager.TYPE_WIMAX
-import android.net.EthernetManager
 import android.net.NetworkInfo.DetailedState.CONNECTED
 import android.net.NetworkInfo.DetailedState.DISCONNECTED
 import android.os.Build
@@ -82,9 +83,8 @@
     private val mContext = mock(Context::class.java).apply {
         doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI)
         doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
+        doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_ETHERNET)
         doReturn(mPm).`when`(this).packageManager
-        doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService(
-                Context.ETHERNET_SERVICE)
     }
     private val mTm = mock(TelephonyManager::class.java).apply {
         doReturn(true).`when`(this).isDataCapable
@@ -105,7 +105,8 @@
 
     @Test
     fun testSupportedTypes_NoEthernet() {
-        doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE)
+        doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_ETHERNET)
+        doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_USB_HOST)
         assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET))
     }