Add Tetheroffload AIDL interface support

Add the AIDL HAL service interface support and abstract the HIDL/AIDL
interface implementations to allow OffloadController to talk to both
interfaces.

Bug: b/205762647
Test: atest com.android.networkstack.tethering
      atest ConnectivityCoverageTests
      atest ConntrackSocketTest
Change-Id: If7634a298d03668ed70b328f1ad6ca0ac0fe170b
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index d2f6d15..8810a8c 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -61,9 +61,13 @@
         "modules-utils-build",
         "modules-utils-statemachine",
         "networkstack-client",
+        // AIDL tetheroffload implementation
+        "android.hardware.tetheroffload-V1-java",
+        // HIDL tetheroffload implementation
         "android.hardware.tetheroffload.config-V1.0-java",
         "android.hardware.tetheroffload.control-V1.0-java",
         "android.hardware.tetheroffload.control-V1.1-java",
+        "android.hidl.manager-V1.2-java",
         "net-utils-framework-common",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
diff --git a/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java b/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java
new file mode 100644
index 0000000..e66e7ae
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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 com.android.networkstack.tethering;
+
+import android.annotation.NonNull;
+import android.os.NativeHandle;
+
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+
+/** Abstraction of Tetheroffload HAL interface */
+interface IOffloadHal {
+    /*
+     * Initialize the Tetheroffload HAL. Offload management process need to know conntrack rules to
+     * support NAT, but it may not have permission to create netlink netfilter sockets. Create two
+     * netlink netfilter sockets and share them with offload management process.
+     */
+    boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback);
+
+    /** Stop the Tetheroffload HAL. */
+    boolean stopOffload();
+
+    /** Get HAL interface version number. */
+    int getVersion();
+
+    /** Get Tx/Rx usage from last query. */
+    ForwardedStats getForwardedStats(@NonNull String upstream);
+
+    /** Set local prefixes to offload management process. */
+    boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes);
+
+    /** Set data limit value to offload management process. */
+    boolean setDataLimit(@NonNull String iface, long limit);
+
+    /** Set data warning and limit value to offload management process. */
+    boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit);
+
+    /** Set upstream parameters to offload management process. */
+    boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws);
+
+    /** Add downstream prefix to offload management process. */
+    boolean addDownstream(@NonNull String ifname, @NonNull String prefix);
+
+    /** Remove downstream prefix from offload management process. */
+    boolean removeDownstream(@NonNull String ifname, @NonNull String prefix);
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index d2f177d..5fa6b2d 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -26,8 +26,8 @@
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 
@@ -98,9 +98,8 @@
     private final OffloadTetheringStatsProvider mStatsProvider;
     private final SharedLog mLog;
     private final HashMap<String, LinkProperties> mDownstreams;
-    private boolean mConfigInitialized;
     @OffloadHardwareInterface.OffloadHalVersion
-    private int mControlHalVersion;
+    private int mOffloadHalVersion;
     private LinkProperties mUpstreamLinkProperties;
     // The complete set of offload-exempt prefixes passed in via Tethering from
     // all upstream and downstream sources.
@@ -205,20 +204,11 @@
             return false;
         }
 
-        if (!mConfigInitialized) {
-            mConfigInitialized = mHwInterface.initOffloadConfig();
-            if (!mConfigInitialized) {
-                mLog.i("tethering offload config not supported");
-                stop();
-                return false;
-            }
-        }
-
-        mControlHalVersion = mHwInterface.initOffloadControl(
+        mOffloadHalVersion = mHwInterface.initOffload(
                 // OffloadHardwareInterface guarantees that these callback
                 // methods are called on the handler passed to it, which is the
                 // same as mHandler, as coordinated by the setup in Tethering.
-                new OffloadHardwareInterface.ControlCallback() {
+                new OffloadHardwareInterface.OffloadHalCallback() {
                     @Override
                     public void onStarted() {
                         if (!started()) return;
@@ -305,11 +295,11 @@
 
         final boolean isStarted = started();
         if (!isStarted) {
-            mLog.i("tethering offload control not supported");
+            mLog.i("tethering offload not supported");
             stop();
         } else {
             mLog.log("tethering offload started, version: "
-                    + OffloadHardwareInterface.halVerToString(mControlHalVersion));
+                    + OffloadHardwareInterface.halVerToString(mOffloadHalVersion));
             mNatUpdateCallbacksReceived = 0;
             mNatUpdateNetlinkErrors = 0;
             maybeSchedulePollingStats();
@@ -325,9 +315,8 @@
         final boolean wasStarted = started();
         updateStatsForCurrentUpstream();
         mUpstreamLinkProperties = null;
-        mHwInterface.stopOffloadControl();
-        mControlHalVersion = OFFLOAD_HAL_VERSION_NONE;
-        mConfigInitialized = false;
+        mHwInterface.stopOffload();
+        mOffloadHalVersion = OFFLOAD_HAL_VERSION_NONE;
         if (mHandler.hasCallbacks(mScheduledPollingTask)) {
             mHandler.removeCallbacks(mScheduledPollingTask);
         }
@@ -335,7 +324,7 @@
     }
 
     private boolean started() {
-        return mConfigInitialized && mControlHalVersion != OFFLOAD_HAL_VERSION_NONE;
+        return mOffloadHalVersion != OFFLOAD_HAL_VERSION_NONE;
     }
 
     @VisibleForTesting
@@ -528,7 +517,7 @@
     }
 
     private boolean useStatsPolling() {
-        return mControlHalVersion == OFFLOAD_HAL_VERSION_1_0;
+        return mOffloadHalVersion == OFFLOAD_HAL_VERSION_HIDL_1_0;
     }
 
     private boolean maybeUpdateDataWarningAndLimit(String iface) {
@@ -540,7 +529,7 @@
 
         final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE);
         final boolean ret;
-        if (mControlHalVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (mOffloadHalVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             ret = mHwInterface.setDataWarningAndLimit(iface, quota.warningBytes, quota.limitBytes);
         } else {
             ret = mHwInterface.setDataLimit(iface, quota.limitBytes);
@@ -611,7 +600,7 @@
         for (RouteInfo ri : oldRoutes) {
             if (shouldIgnoreDownstreamRoute(ri)) continue;
             if (!newRoutes.contains(ri)) {
-                mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
+                mHwInterface.removeDownstream(ifname, ri.getDestination().toString());
             }
         }
 
@@ -619,7 +608,7 @@
         for (RouteInfo ri : newRoutes) {
             if (shouldIgnoreDownstreamRoute(ri)) continue;
             if (!oldRoutes.contains(ri)) {
-                mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
+                mHwInterface.addDownstream(ifname, ri.getDestination().toString());
             }
         }
     }
@@ -639,7 +628,7 @@
 
         for (RouteInfo route : lp.getRoutes()) {
             if (shouldIgnoreDownstreamRoute(route)) continue;
-            mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
+            mHwInterface.removeDownstream(ifname, route.getDestination().toString());
         }
     }
 
@@ -768,7 +757,7 @@
         final boolean isStarted = started();
         pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
         pw.println("Offload Control HAL version: "
-                + OffloadHardwareInterface.halVerToString(mControlHalVersion));
+                + OffloadHardwareInterface.halVerToString(mOffloadHalVersion));
         LinkProperties lp = mUpstreamLinkProperties;
         String upstream = (lp != null) ? lp.getInterfaceName() : null;
         pw.println("Current upstream: " + upstream);
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java
new file mode 100644
index 0000000..e7dc757
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2022 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+
+import android.annotation.NonNull;
+import android.hardware.tetheroffload.ForwardedStats;
+import android.hardware.tetheroffload.IOffload;
+import android.hardware.tetheroffload.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.NatTimeoutUpdate;
+import android.hardware.tetheroffload.NetworkProtocol;
+import android.hardware.tetheroffload.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.system.OsConstants;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+
+/**
+ * The implementation of IOffloadHal which based on Stable AIDL interface
+ */
+public class OffloadHalAidlImpl implements IOffloadHal {
+    private static final String TAG = OffloadHalAidlImpl.class.getSimpleName();
+    private static final String HAL_INSTANCE_NAME = IOffload.DESCRIPTOR + "/default";
+
+    private final Handler mHandler;
+    private final SharedLog mLog;
+    private final IOffload mIOffload;
+    @OffloadHardwareInterface.OffloadHalVersion
+    private final int mOffloadVersion;
+
+    private TetheringOffloadCallback mTetheringOffloadCallback;
+
+    public OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler,
+            @NonNull SharedLog log) {
+        mOffloadVersion = version;
+        mIOffload = offload;
+        mHandler = handler;
+        mLog = log.forSubComponent(TAG);
+    }
+
+    /**
+     * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
+     * management process.
+     */
+    public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback) {
+        final String methodStr = String.format("initOffload(%d, %d, %s)",
+                handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(),
+                (callback == null) ? "null"
+                : "0x" + Integer.toHexString(System.identityHashCode(callback)));
+        mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, callback, mLog);
+        try {
+            mIOffload.initOffload(
+                    ParcelFileDescriptor.adoptFd(handle1.getFileDescriptor().getInt$()),
+                    ParcelFileDescriptor.adoptFd(handle2.getFileDescriptor().getInt$()),
+                    mTetheringOffloadCallback);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Stop the Tetheroffload HAL. */
+    public boolean stopOffload() {
+        final String methodStr = "stopOffload()";
+        try {
+            mIOffload.stopOffload();
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+
+        mTetheringOffloadCallback = null;
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Get HAL interface version number. */
+    public int getVersion() {
+        return mOffloadVersion;
+    }
+
+    /** Get Tx/Rx usage from last query. */
+    public OffloadHardwareInterface.ForwardedStats getForwardedStats(@NonNull String upstream) {
+        ForwardedStats stats = new ForwardedStats();
+        final String methodStr = String.format("getForwardedStats(%s)",  upstream);
+        try {
+            stats = mIOffload.getForwardedStats(upstream);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+        }
+        mLog.i(methodStr);
+        return new OffloadHardwareInterface.ForwardedStats(stats.rxBytes, stats.txBytes);
+    }
+
+    /** Set local prefixes to offload management process. */
+    public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
+        final String methodStr = String.format("setLocalPrefixes([%s])",
+                String.join(",", localPrefixes));
+        try {
+            mIOffload.setLocalPrefixes(localPrefixes.toArray(new String[localPrefixes.size()]));
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /**
+     * Set data limit value to offload management process.
+     * Method setDataLimit is deprecated in AIDL, so call setDataWarningAndLimit instead,
+     * with warningBytes set to its MAX_VALUE.
+     */
+    public boolean setDataLimit(@NonNull String iface, long limit) {
+        final long warning = Long.MAX_VALUE;
+        final String methodStr = String.format("setDataLimit(%s, %d)", iface, limit);
+        try {
+            mIOffload.setDataWarningAndLimit(iface, warning, limit);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Set data warning and limit value to offload management process. */
+    public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
+        final String methodStr =
+                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
+        try {
+            mIOffload.setDataWarningAndLimit(iface, warning, limit);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Set upstream parameters to offload management process. */
+    public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
+        final String methodStr = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
+                iface, v4addr, v4gateway, String.join(",", v6gws));
+        try {
+            mIOffload.setUpstreamParameters(iface, v4addr, v4gateway,
+                    v6gws.toArray(new String[v6gws.size()]));
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Add downstream prefix to offload management process. */
+    public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String methodStr = String.format("addDownstream(%s, %s)", ifname, prefix);
+        try {
+            mIOffload.addDownstream(ifname, prefix);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Remove downstream prefix from offload management process. */
+    public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String methodStr = String.format("removeDownstream(%s, %s)", ifname, prefix);
+        try {
+            mIOffload.removeDownstream(ifname, prefix);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /**
+     * Get {@link IOffloadHal} object from the AIDL service.
+     *
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param log Log to be used by the repository.
+     */
+    public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
+        // Tetheroffload AIDL interface is only supported after U.
+        if (!SdkLevel.isAtLeastU() || !ServiceManager.isDeclared(HAL_INSTANCE_NAME)) return null;
+
+        IOffload offload = IOffload.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
+        if (offload == null) return null;
+
+        return new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, offload, handler, log);
+    }
+
+    private void logAndIgnoreException(Exception e, final String methodStr) {
+        mLog.e(methodStr + " failed with " + e.getClass().getSimpleName() + ": ", e);
+    }
+
+    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
+        public final Handler handler;
+        public final OffloadHalCallback callback;
+        public final SharedLog log;
+
+        TetheringOffloadCallback(
+                Handler h, OffloadHalCallback cb, SharedLog sharedLog) {
+            handler = h;
+            callback = cb;
+            log = sharedLog;
+        }
+
+        private void handleOnEvent(int event) {
+            switch (event) {
+                case OffloadCallbackEvent.OFFLOAD_STARTED:
+                    callback.onStarted();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+                    callback.onStoppedError();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+                    callback.onStoppedUnsupported();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+                    callback.onSupportAvailable();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+                    callback.onStoppedLimitReached();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
+                    callback.onWarningReached();
+                    break;
+                default:
+                    log.e("Unsupported OffloadCallbackEvent: " + event);
+            }
+        }
+
+        @Override
+        public void onEvent(int event) {
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void updateTimeout(NatTimeoutUpdate params) {
+            handler.post(() -> {
+                callback.onNatTimeoutUpdate(
+                        networkProtocolToOsConstant(params.proto),
+                        params.src.addr, params.src.port,
+                        params.dst.addr, params.dst.port);
+            });
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return ITetheringOffloadCallback.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return ITetheringOffloadCallback.VERSION;
+        }
+    }
+
+    private static int networkProtocolToOsConstant(int proto) {
+        switch (proto) {
+            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
+            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
+            default:
+                // The caller checks this value and will log an error. Just make
+                // sure it won't collide with valid OsConstants.IPPROTO_* values.
+                return -Math.abs(proto);
+        }
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java
new file mode 100644
index 0000000..3e02543
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2022 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.halVerToString;
+import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+
+import android.annotation.NonNull;
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+/**
+ * The implementation of IOffloadHal which based on HIDL interfaces
+ */
+public class OffloadHalHidlImpl implements IOffloadHal {
+    private static final String TAG = OffloadHalHidlImpl.class.getSimpleName();
+    private static final String YIELDS = " -> ";
+
+    private final Handler mHandler;
+    private final SharedLog mLog;
+    private final IOffloadConfig mIOffloadConfig;
+    private final IOffloadControl mIOffloadControl;
+    @OffloadHardwareInterface.OffloadHalVersion
+    private final int mOffloadControlVersion;
+
+    private OffloadHalCallback mOffloadHalCallback;
+    private TetheringOffloadCallback mTetheringOffloadCallback;
+
+    public OffloadHalHidlImpl(int version, @NonNull IOffloadConfig config,
+            @NonNull IOffloadControl control, @NonNull Handler handler, @NonNull SharedLog log) {
+        mOffloadControlVersion = version;
+        mIOffloadConfig = config;
+        mIOffloadControl = control;
+        mHandler = handler;
+        mLog = log.forSubComponent(TAG);
+    }
+
+    /**
+     * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
+     * management process.
+     */
+    public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback) {
+        final String logmsg = String.format("initOffload(%d, %d, %s)",
+                handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(),
+                (callback == null) ? "null"
+                : "0x" + Integer.toHexString(System.identityHashCode(callback)));
+
+        mOffloadHalCallback = callback;
+        mTetheringOffloadCallback = new TetheringOffloadCallback(
+                mHandler, mOffloadHalCallback, mLog, mOffloadControlVersion);
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadConfig.setHandles(handle1, handle2,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+            mIOffloadControl.initOffload(
+                    mTetheringOffloadCallback,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Stop the Tetheroffload HAL. */
+    public boolean stopOffload() {
+        try {
+            mIOffloadControl.stopOffload(
+                    (boolean success, String errMsg) -> {
+                        if (!success) mLog.e("stopOffload failed: " + errMsg);
+                    });
+        } catch (RemoteException e) {
+            mLog.e("failed to stopOffload: " + e);
+        }
+        mOffloadHalCallback = null;
+        mTetheringOffloadCallback = null;
+        mLog.log("stopOffload()");
+        return true;
+    }
+
+    /** Get HAL interface version number. */
+    public int getVersion() {
+        return mOffloadControlVersion;
+    }
+
+    /** Get Tx/Rx usage from last query. */
+    public ForwardedStats getForwardedStats(@NonNull String upstream) {
+        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
+
+        final ForwardedStats stats = new ForwardedStats();
+        try {
+            mIOffloadControl.getForwardedStats(
+                    upstream,
+                    (long rxBytes, long txBytes) -> {
+                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
+                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return stats;
+        }
+
+        return stats;
+    }
+
+    /** Set local prefixes to offload management process. */
+    public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
+        final String logmsg = String.format("setLocalPrefixes([%s])",
+                String.join(",", localPrefixes));
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setLocalPrefixes(localPrefixes,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set data limit value to offload management process. */
+    public boolean setDataLimit(@NonNull String iface, long limit) {
+
+        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setDataLimit(
+                    iface, limit,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set data warning and limit value to offload management process. */
+    public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
+        if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+            throw new UnsupportedOperationException(
+                    "setDataWarningAndLimit is not supported below HAL V1.1");
+        }
+        final String logmsg =
+                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
+
+        final CbResults results = new CbResults();
+        try {
+            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
+                    .setDataWarningAndLimit(
+                            iface, warning, limit,
+                            (boolean success, String errMsg) -> {
+                                results.mSuccess = success;
+                                results.mErrMsg = errMsg;
+                            });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set upstream parameters to offload management process. */
+    public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
+        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
+                iface, v4addr, v4gateway, String.join(",", v6gws));
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setUpstreamParameters(
+                    iface, v4addr, v4gateway, v6gws,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Add downstream prefix to offload management process. */
+    public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String logmsg = String.format("addDownstream(%s, %s)", ifname, prefix);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.addDownstream(ifname, prefix,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Remove downstream prefix from offload management process. */
+    public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String logmsg = String.format("removeDownstream(%s, %s)", ifname, prefix);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.removeDownstream(ifname, prefix,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /**
+     * Get {@link IOffloadHal} object from the HIDL service.
+     *
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param log Log to be used by the repository.
+     */
+    public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
+        IOffloadConfig config = null;
+        try {
+            config = IOffloadConfig.getService(true /*retry*/);
+        } catch (RemoteException | NoSuchElementException e) {
+            log.e("getIOffloadConfig error " + e);
+            return null;
+        }
+
+        IOffloadControl control = null;
+        int version = OFFLOAD_HAL_VERSION_NONE;
+        try {
+            control = android.hardware.tetheroffload.control
+                    .V1_1.IOffloadControl.getService(true /*retry*/);
+            version = OFFLOAD_HAL_VERSION_HIDL_1_1;
+        } catch (NoSuchElementException e) {
+            // Unsupported by device.
+        } catch (RemoteException e) {
+            log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_1);
+        }
+        if (control == null) {
+            try {
+                control = IOffloadControl.getService(true /*retry*/);
+                version = OFFLOAD_HAL_VERSION_HIDL_1_0;
+            } catch (NoSuchElementException e) {
+                // Unsupported by device.
+            } catch (RemoteException e) {
+                log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_0);
+            }
+        }
+
+        if (config == null || control == null) return null;
+
+        return new OffloadHalHidlImpl(version, config, control, handler, log);
+    }
+
+    private void record(String msg, Throwable t) {
+        mLog.e(msg + YIELDS + "exception: " + t);
+    }
+
+    private void record(String msg, CbResults results) {
+        final String logmsg = msg + YIELDS + results;
+        if (!results.mSuccess) {
+            mLog.e(logmsg);
+        } else {
+            mLog.log(logmsg);
+        }
+    }
+
+    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
+        public final Handler handler;
+        public final OffloadHalCallback callback;
+        public final SharedLog log;
+        private final int mOffloadControlVersion;
+
+        TetheringOffloadCallback(
+                Handler h, OffloadHalCallback cb, SharedLog sharedLog, int offloadControlVersion) {
+            handler = h;
+            callback = cb;
+            log = sharedLog;
+            this.mOffloadControlVersion = offloadControlVersion;
+        }
+
+        private void handleOnEvent(int event) {
+            switch (event) {
+                case OffloadCallbackEvent.OFFLOAD_STARTED:
+                    callback.onStarted();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+                    callback.onStoppedError();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+                    callback.onStoppedUnsupported();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+                    callback.onSupportAvailable();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+                    callback.onStoppedLimitReached();
+                    break;
+                case android.hardware.tetheroffload.control
+                        .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
+                    callback.onWarningReached();
+                    break;
+                default:
+                    log.e("Unsupported OffloadCallbackEvent: " + event);
+            }
+        }
+
+        @Override
+        public void onEvent(int event) {
+            // The implementation should never call onEvent()) if the event is already reported
+            // through newer callback.
+            if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_HIDL_1_0) {
+                Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
+                        + halVerToString(mOffloadControlVersion));
+            }
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void onEvent_1_1(int event) {
+            if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+                Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
+                        + halVerToString(mOffloadControlVersion));
+                return;
+            }
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void updateTimeout(NatTimeoutUpdate params) {
+            handler.post(() -> {
+                callback.onNatTimeoutUpdate(
+                        networkProtocolToOsConstant(params.proto),
+                        params.src.addr, uint16(params.src.port),
+                        params.dst.addr, uint16(params.dst.port));
+            });
+        }
+    }
+
+    private static int networkProtocolToOsConstant(int proto) {
+        switch (proto) {
+            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
+            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
+            default:
+                // The caller checks this value and will log an error. Just make
+                // sure it won't collide with valid OsConstants.IPPROTO_* values.
+                return -Math.abs(proto);
+        }
+    }
+
+    private static class CbResults {
+        boolean mSuccess;
+        String mErrMsg;
+
+        @Override
+        public String toString() {
+            if (mSuccess) {
+                return "ok";
+            } else {
+                return "fail: " + mErrMsg;
+            }
+        }
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 76ddfe5..ea20063 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -18,25 +18,15 @@
 
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
-import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
-import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
-import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
-import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
 import android.net.util.SocketUtils;
 import android.os.Handler;
 import android.os.NativeHandle;
-import android.os.RemoteException;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.SharedLog;
@@ -54,8 +44,6 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.NoSuchElementException;
-
 
 /**
  * Capture tethering dependencies, for injection.
@@ -86,43 +74,43 @@
     private final Handler mHandler;
     private final SharedLog mLog;
     private final Dependencies mDeps;
-    private IOffloadControl mOffloadControl;
+    private IOffloadHal mIOffload;
 
     // TODO: Use major-minor version control to prevent from defining new constants.
     static final int OFFLOAD_HAL_VERSION_NONE = 0;
-    static final int OFFLOAD_HAL_VERSION_1_0 = 1;
-    static final int OFFLOAD_HAL_VERSION_1_1 = 2;
+    static final int OFFLOAD_HAL_VERSION_HIDL_1_0 = 1;
+    static final int OFFLOAD_HAL_VERSION_HIDL_1_1 = 2;
+    static final int OFFLOAD_HAL_VERSION_AIDL = 3;
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "OFFLOAD_HAL_VERSION_", value = {
             OFFLOAD_HAL_VERSION_NONE,
-            OFFLOAD_HAL_VERSION_1_0,
-            OFFLOAD_HAL_VERSION_1_1
+            OFFLOAD_HAL_VERSION_HIDL_1_0,
+            OFFLOAD_HAL_VERSION_HIDL_1_1,
+            OFFLOAD_HAL_VERSION_AIDL,
     })
     public @interface OffloadHalVersion {}
-    @OffloadHalVersion
-    private int mOffloadControlVersion = OFFLOAD_HAL_VERSION_NONE;
 
     @NonNull
     static String halVerToString(int version) {
         switch(version) {
-            case OFFLOAD_HAL_VERSION_1_0:
-                return "1.0";
-            case OFFLOAD_HAL_VERSION_1_1:
-                return "1.1";
+            case OFFLOAD_HAL_VERSION_HIDL_1_0:
+                return "HIDL 1.0";
+            case OFFLOAD_HAL_VERSION_HIDL_1_1:
+                return "HIDL 1.1";
+            case OFFLOAD_HAL_VERSION_AIDL:
+                return "AIDL";
             case OFFLOAD_HAL_VERSION_NONE:
                 return "None";
             default:
                 throw new IllegalArgumentException("Unsupported version int " + version);
         }
-
     }
 
-    private TetheringOffloadCallback mTetheringOffloadCallback;
-    private ControlCallback mControlCallback;
+    private OffloadHalCallback mOffloadHalCallback;
 
     /** The callback to notify status of offload management process. */
-    public static class ControlCallback {
+    public static class OffloadHalCallback {
         /** Offload started. */
         public void onStarted() {}
         /**
@@ -179,7 +167,7 @@
     }
 
     public OffloadHardwareInterface(Handler h, SharedLog log) {
-        this(h, log, new Dependencies(log));
+        this(h, log, new Dependencies(h, log));
     }
 
     OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) {
@@ -190,45 +178,21 @@
 
     /** Capture OffloadHardwareInterface dependencies, for injection. */
     static class Dependencies {
+        private final Handler mHandler;
         private final SharedLog mLog;
 
-        Dependencies(SharedLog log) {
+        Dependencies(Handler handler, SharedLog log) {
+            mHandler = handler;
             mLog = log;
         }
 
-        public IOffloadConfig getOffloadConfig() {
-            try {
-                return IOffloadConfig.getService(true /*retry*/);
-            } catch (RemoteException | NoSuchElementException e) {
-                mLog.e("getIOffloadConfig error " + e);
-                return null;
-            }
-        }
-
-        @NonNull
-        public Pair<IOffloadControl, Integer> getOffloadControl() {
-            IOffloadControl hal = null;
-            int version = OFFLOAD_HAL_VERSION_NONE;
-            try {
-                hal = android.hardware.tetheroffload.control
-                        .V1_1.IOffloadControl.getService(true /*retry*/);
-                version = OFFLOAD_HAL_VERSION_1_1;
-            } catch (NoSuchElementException e) {
-                // Unsupported by device.
-            } catch (RemoteException e) {
-                mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_1);
-            }
+        public IOffloadHal getOffload() {
+            // Prefer AIDL implementation if its service is declared.
+            IOffloadHal hal = OffloadHalAidlImpl.getIOffloadHal(mHandler, mLog);
             if (hal == null) {
-                try {
-                    hal = IOffloadControl.getService(true /*retry*/);
-                    version = OFFLOAD_HAL_VERSION_1_0;
-                } catch (NoSuchElementException e) {
-                    // Unsupported by device.
-                } catch (RemoteException e) {
-                    mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_0);
-                }
+                hal = OffloadHalHidlImpl.getIOffloadHal(mHandler, mLog);
             }
-            return new Pair<IOffloadControl, Integer>(hal, version);
+            return hal;
         }
 
         public NativeHandle createConntrackSocket(final int groups) {
@@ -273,56 +237,6 @@
         return DEFAULT_TETHER_OFFLOAD_DISABLED;
     }
 
-    /**
-     * Offload management process need to know conntrack rules to support NAT, but it may not have
-     * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
-     * share them with offload management process.
-     */
-    public boolean initOffloadConfig() {
-        final IOffloadConfig offloadConfig = mDeps.getOffloadConfig();
-        if (offloadConfig == null) {
-            mLog.e("Could not find IOffloadConfig service");
-            return false;
-        }
-        // Per the IConfigOffload definition:
-        //
-        // h1    provides a file descriptor bound to the following netlink groups
-        //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
-        //
-        // h2    provides a file descriptor bound to the following netlink groups
-        //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
-        final NativeHandle h1 = mDeps.createConntrackSocket(
-                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
-        if (h1 == null) return false;
-
-        requestSocketDump(h1);
-
-        final NativeHandle h2 = mDeps.createConntrackSocket(
-                NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
-        if (h2 == null) {
-            closeFdInNativeHandle(h1);
-            return false;
-        }
-
-        final CbResults results = new CbResults();
-        try {
-            offloadConfig.setHandles(h1, h2,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record("initOffloadConfig, setHandles fail", e);
-            return false;
-        }
-        // Explicitly close FDs.
-        closeFdInNativeHandle(h1);
-        closeFdInNativeHandle(h2);
-
-        record("initOffloadConfig, setHandles results:", results);
-        return results.mSuccess;
-    }
-
     @VisibleForTesting
     void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
         final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
@@ -355,165 +269,107 @@
                 (short) (NLM_F_REQUEST | NLM_F_DUMP));
     }
 
-    private void closeFdInNativeHandle(final NativeHandle h) {
-        try {
-            h.close();
-        } catch (IOException | IllegalStateException e) {
-            // IllegalStateException means fd is already closed, do nothing here.
-            // Also nothing we can do if IOException.
+    private void maybeCloseFdInNativeHandles(final NativeHandle... handles) {
+        for (NativeHandle h : handles) {
+            if (h == null) continue;
+            try {
+                h.close();
+            } catch (IOException | IllegalStateException e) {
+                // IllegalStateException means fd is already closed, do nothing here.
+                // Also nothing we can do if IOException.
+            }
         }
     }
 
+    private int initWithHandles(NativeHandle h1, NativeHandle h2) {
+        if (h1 == null || h2 == null) {
+            mLog.e("Failed to create socket.");
+            return OFFLOAD_HAL_VERSION_NONE;
+        }
+
+        requestSocketDump(h1);
+        if (!mIOffload.initOffload(h1, h2, mOffloadHalCallback)) {
+            mIOffload.stopOffload();
+            mLog.e("Failed to initialize offload.");
+            return OFFLOAD_HAL_VERSION_NONE;
+        }
+
+        return mIOffload.getVersion();
+    }
+
     /**
      * Initialize the tethering offload HAL.
      *
      * @return one of {@code OFFLOAD_HAL_VERSION_*} represents the HAL version, or
      *         {@link #OFFLOAD_HAL_VERSION_NONE} if failed.
      */
-    public int initOffloadControl(ControlCallback controlCb) {
-        mControlCallback = controlCb;
-
-        if (mOffloadControl == null) {
-            final Pair<IOffloadControl, Integer> halAndVersion = mDeps.getOffloadControl();
-            mOffloadControl = halAndVersion.first;
-            mOffloadControlVersion = halAndVersion.second;
-            if (mOffloadControl == null) {
-                mLog.e("tethering IOffloadControl.getService() returned null");
+    public int initOffload(OffloadHalCallback offloadCb) {
+        if (mIOffload == null) {
+            mIOffload = mDeps.getOffload();
+            if (mIOffload == null) {
+                mLog.e("No tethering offload HAL service found.");
                 return OFFLOAD_HAL_VERSION_NONE;
             }
-            mLog.i("tethering offload control version "
-                    + halVerToString(mOffloadControlVersion) + " is supported.");
+            mLog.i("Tethering offload version "
+                    + halVerToString(mIOffload.getVersion()) + " is supported.");
         }
 
-        final String logmsg = String.format("initOffloadControl(%s)",
-                (controlCb == null) ? "null"
-                        : "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
+        // Per the IOffload definition:
+        //
+        // h1    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+        //
+        // h2    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+        final NativeHandle h1 = mDeps.createConntrackSocket(
+                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+        final NativeHandle h2 = mDeps.createConntrackSocket(
+                NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
 
-        mTetheringOffloadCallback = new TetheringOffloadCallback(
-                mHandler, mControlCallback, mLog, mOffloadControlVersion);
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.initOffload(
-                    mTetheringOffloadCallback,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return OFFLOAD_HAL_VERSION_NONE;
+        mOffloadHalCallback = offloadCb;
+        final int version = initWithHandles(h1, h2);
+
+        // Explicitly close FDs for HIDL. AIDL will pass the original FDs to the service,
+        // they shouldn't be closed here.
+        if (version < OFFLOAD_HAL_VERSION_AIDL) {
+            maybeCloseFdInNativeHandles(h1, h2);
         }
-
-        record(logmsg, results);
-        return results.mSuccess ? mOffloadControlVersion : OFFLOAD_HAL_VERSION_NONE;
+        return version;
     }
 
-    /** Stop IOffloadControl. */
-    public void stopOffloadControl() {
-        if (mOffloadControl != null) {
-            try {
-                mOffloadControl.stopOffload(
-                        (boolean success, String errMsg) -> {
-                            if (!success) mLog.e("stopOffload failed: " + errMsg);
-                        });
-            } catch (RemoteException e) {
-                mLog.e("failed to stopOffload: " + e);
+    /** Stop the tethering offload HAL. */
+    public void stopOffload() {
+        if (mIOffload != null) {
+            if (!mIOffload.stopOffload()) {
+                mLog.e("Failed to stop offload.");
             }
         }
-        mOffloadControl = null;
-        mTetheringOffloadCallback = null;
-        mControlCallback = null;
-        mLog.log("stopOffloadControl()");
+        mIOffload = null;
+        mOffloadHalCallback = null;
     }
 
     /** Get Tx/Rx usage from last query. */
     public ForwardedStats getForwardedStats(String upstream) {
-        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
-
-        final ForwardedStats stats = new ForwardedStats();
-        try {
-            mOffloadControl.getForwardedStats(
-                    upstream,
-                    (long rxBytes, long txBytes) -> {
-                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
-                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return stats;
-        }
-
-        return stats;
+        return mIOffload.getForwardedStats(upstream);
     }
 
     /** Set local prefixes to offload management process. */
     public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
-        final String logmsg = String.format("setLocalPrefixes([%s])",
-                String.join(",", localPrefixes));
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setLocalPrefixes(localPrefixes,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setLocalPrefixes(localPrefixes);
     }
 
     /** Set data limit value to offload management process. */
     public boolean setDataLimit(String iface, long limit) {
-
-        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setDataLimit(
-                    iface, limit,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setDataLimit(iface, limit);
     }
 
     /** Set data warning and limit value to offload management process. */
     public boolean setDataWarningAndLimit(String iface, long warning, long limit) {
-        if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
-            throw new IllegalArgumentException(
+        if (mIOffload.getVersion() < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+            throw new UnsupportedOperationException(
                     "setDataWarningAndLimit is not supported below HAL V1.1");
         }
-        final String logmsg =
-                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
-
-        final CbResults results = new CbResults();
-        try {
-            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mOffloadControl)
-                    .setDataWarningAndLimit(
-                            iface, warning, limit,
-                            (boolean success, String errMsg) -> {
-                                results.mSuccess = success;
-                                results.mErrMsg = errMsg;
-                            });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setDataWarningAndLimit(iface, warning, limit);
     }
 
     /** Set upstream parameters to offload management process. */
@@ -523,178 +379,16 @@
         v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
         v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
         v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
-
-        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
-                iface, v4addr, v4gateway, String.join(",", v6gws));
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setUpstreamParameters(
-                    iface, v4addr, v4gateway, v6gws,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setUpstreamParameters(iface, v4addr, v4gateway, v6gws);
     }
 
     /** Add downstream prefix to offload management process. */
-    public boolean addDownstreamPrefix(String ifname, String prefix) {
-        final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.addDownstream(ifname, prefix,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+    public boolean addDownstream(String ifname, String prefix) {
+        return  mIOffload.addDownstream(ifname, prefix);
     }
 
     /** Remove downstream prefix from offload management process. */
-    public boolean removeDownstreamPrefix(String ifname, String prefix) {
-        final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.removeDownstream(ifname, prefix,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
-    }
-
-    private void record(String msg, Throwable t) {
-        mLog.e(msg + YIELDS + "exception: " + t);
-    }
-
-    private void record(String msg, CbResults results) {
-        final String logmsg = msg + YIELDS + results;
-        if (!results.mSuccess) {
-            mLog.e(logmsg);
-        } else {
-            mLog.log(logmsg);
-        }
-    }
-
-    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
-        public final Handler handler;
-        public final ControlCallback controlCb;
-        public final SharedLog log;
-        private final int mOffloadControlVersion;
-
-        TetheringOffloadCallback(
-                Handler h, ControlCallback cb, SharedLog sharedLog, int offloadControlVersion) {
-            handler = h;
-            controlCb = cb;
-            log = sharedLog;
-            this.mOffloadControlVersion = offloadControlVersion;
-        }
-
-        private void handleOnEvent(int event) {
-            switch (event) {
-                case OffloadCallbackEvent.OFFLOAD_STARTED:
-                    controlCb.onStarted();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
-                    controlCb.onStoppedError();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
-                    controlCb.onStoppedUnsupported();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
-                    controlCb.onSupportAvailable();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
-                    controlCb.onStoppedLimitReached();
-                    break;
-                case android.hardware.tetheroffload.control
-                        .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
-                    controlCb.onWarningReached();
-                    break;
-                default:
-                    log.e("Unsupported OffloadCallbackEvent: " + event);
-            }
-        }
-
-        @Override
-        public void onEvent(int event) {
-            // The implementation should never call onEvent()) if the event is already reported
-            // through newer callback.
-            if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_1_0) {
-                Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
-                        + halVerToString(mOffloadControlVersion));
-            }
-            handler.post(() -> {
-                handleOnEvent(event);
-            });
-        }
-
-        @Override
-        public void onEvent_1_1(int event) {
-            if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
-                Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
-                        + halVerToString(mOffloadControlVersion));
-                return;
-            }
-            handler.post(() -> {
-                handleOnEvent(event);
-            });
-        }
-
-        @Override
-        public void updateTimeout(NatTimeoutUpdate params) {
-            handler.post(() -> {
-                controlCb.onNatTimeoutUpdate(
-                        networkProtocolToOsConstant(params.proto),
-                        params.src.addr, uint16(params.src.port),
-                        params.dst.addr, uint16(params.dst.port));
-            });
-        }
-    }
-
-    private static int networkProtocolToOsConstant(int proto) {
-        switch (proto) {
-            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
-            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
-            default:
-                // The caller checks this value and will log an error. Just make
-                // sure it won't collide with valid OsContants.IPPROTO_* values.
-                return -Math.abs(proto);
-        }
-    }
-
-    private static class CbResults {
-        boolean mSuccess;
-        String mErrMsg;
-
-        @Override
-        public String toString() {
-            if (mSuccess) {
-                return "ok";
-            } else {
-                return "fail: " + mErrMsg;
-            }
-        }
+    public boolean removeDownstream(String ifname, String prefix) {
+        return  mIOffload.removeDownstream(ifname, prefix);
     }
 }
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index 706df4e..b3fb3e4 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -80,7 +80,7 @@
         // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
         if (Looper.myLooper() == null) Looper.prepare();
 
-        mDeps = new OffloadHardwareInterface.Dependencies(mLog);
+        mDeps = new OffloadHardwareInterface.Dependencies(mHandler, mLog);
         mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
     }
 
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 faca1c8..36c15a7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -31,8 +31,8 @@
 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;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 import static com.android.testutils.MiscAsserts.assertContainsAll;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -79,6 +79,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.TestableNetworkStatsProviderCbBinder;
@@ -125,8 +126,8 @@
     private OffloadController.OffloadTetheringStatsProvider mTetherStatsProvider;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
-    private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
-            ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
+    private final ArgumentCaptor<OffloadHalCallback> mOffloadHalCallbackCaptor =
+            ArgumentCaptor.forClass(OffloadHalCallback.class);
     private MockContentResolver mContentResolver;
     private final TestLooper mTestLooper = new TestLooper();
     private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() {
@@ -151,10 +152,9 @@
         FakeSettingsProvider.clearSettingsProvider();
     }
 
-    private void setupFunctioningHardwareInterface(int controlVersion) {
-        when(mHardware.initOffloadConfig()).thenReturn(true);
-        when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
-                .thenReturn(controlVersion);
+    private void setupFunctioningHardwareInterface(int offloadHalVersion) {
+        when(mHardware.initOffload(mOffloadHalCallbackCaptor.capture()))
+                .thenReturn(offloadHalVersion);
         when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true);
         when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
@@ -192,9 +192,9 @@
     @Test
     public void testStartStop() throws Exception {
         stopOffloadController(
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/));
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/));
         stopOffloadController(
-                startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/));
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_1, true /*expectStart*/));
     }
 
     @NonNull
@@ -206,9 +206,8 @@
 
         final InOrder inOrder = inOrder(mHardware);
         inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
-        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadConfig();
-        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadControl(
-                any(OffloadHardwareInterface.ControlCallback.class));
+        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffload(
+                any(OffloadHalCallback.class));
         inOrder.verifyNoMoreInteractions();
         // Clear counters only instead of whole mock to preserve the mocking setup.
         clearInvocations(mHardware);
@@ -218,7 +217,7 @@
     private void stopOffloadController(final OffloadController offload) throws Exception {
         final InOrder inOrder = inOrder(mHardware);
         offload.stop();
-        inOrder.verify(mHardware, times(1)).stopOffloadControl();
+        inOrder.verify(mHardware, times(1)).stopOffload();
         inOrder.verifyNoMoreInteractions();
         reset(mHardware);
     }
@@ -228,7 +227,7 @@
         when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
         assertThrows(SettingNotFoundException.class, () ->
                 Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, false /*expectStart*/);
     }
 
     @Test
@@ -236,26 +235,26 @@
         when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
         assertThrows(SettingNotFoundException.class, () ->
                 Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
     }
 
     @Test
     public void testSettingsAllowsStart() throws Exception {
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
     }
 
     @Test
     public void testSettingsDisablesStart() throws Exception {
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, false /*expectStart*/);
     }
 
     @Test
     public void testSetUpstreamLinkPropertiesWorking() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // In reality, the UpstreamNetworkMonitor would have passed down to us
         // a covering set of local prefixes representing a minimum essential
@@ -426,7 +425,7 @@
     public void testGetForwardedStats() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         final String ethernetIface = "eth1";
         final String mobileIface = "rmnet_data0";
@@ -521,11 +520,11 @@
         // Verify the OffloadController is called by R framework, where the framework doesn't send
         // warning.
         // R only uses HAL 1.0.
-        checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_0);
+        checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_HIDL_1_0);
         // Verify the OffloadController is called by S+ framework, where the framework sends
         // warning along with limit.
-        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_0);
-        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_1);
+        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_HIDL_1_0);
+        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 
     private void checkSetDataWarningAndLimit(boolean isProviderSetWarning, int controlVersion)
@@ -550,7 +549,7 @@
         when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
         offload.setUpstreamLinkProperties(lp);
         // Applying an interface sends the initial quota to the hardware.
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
                     Long.MAX_VALUE);
         } else {
@@ -576,7 +575,7 @@
             mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
                     ethernetLimit);
         } else {
@@ -591,7 +590,7 @@
             mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(),
                     anyLong());
         } else {
@@ -603,7 +602,7 @@
         lp.setInterfaceName(mobileIface);
         offload.setUpstreamLinkProperties(lp);
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface,
                     isProviderSetWarning ? mobileWarning : Long.MAX_VALUE,
                     mobileLimit);
@@ -620,7 +619,7 @@
             mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, Long.MAX_VALUE,
                     Long.MAX_VALUE);
         } else {
@@ -655,15 +654,15 @@
         }
         waitForIdle();
         inOrder.verify(mHardware).getForwardedStats(ethernetIface);
-        inOrder.verify(mHardware).stopOffloadControl();
+        inOrder.verify(mHardware).stopOffload();
     }
 
     @Test
     public void testDataWarningAndLimitCallback_LimitReached() throws Exception {
         enableOffload();
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
-        final OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        final OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onStoppedLimitReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
 
@@ -679,8 +678,8 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)  // HAL 1.1 is only supported from S
     public void testDataWarningAndLimitCallback_WarningReached() throws Exception {
-        startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/);
-        final OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_1, true /*expectStart*/);
+        final OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onWarningReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
 
@@ -695,7 +694,7 @@
     public void testAddRemoveDownstreams() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
         final InOrder inOrder = inOrder(mHardware);
 
         // Tethering makes several calls to setLocalPrefixes() before add/remove
@@ -710,14 +709,14 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, USB_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [2] Routes for IPv6 link-local prefixes should never be added.
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+        inOrder.verify(mHardware, never()).addDownstream(eq(RNDIS0), anyString());
         inOrder.verifyNoMoreInteractions();
 
         // [3] Add an IPv6 prefix for good measure. Only new offload-able
@@ -726,14 +725,14 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, IPV6_DOC_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [4] Adding addresses doesn't affect notifyDownstreamLinkProperties().
         // The address is passed in by a separate setLocalPrefixes() invocation.
         usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64"));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+        inOrder.verify(mHardware, never()).addDownstream(eq(RNDIS0), anyString());
 
         // [5] Differences in local routes are converted into addDownstream()
         // and removeDownstream() invocations accordingly.
@@ -742,8 +741,8 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, IPV6_DOC_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, IPV6_DISCARD_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [6] Removing a downstream interface which was never added causes no
@@ -753,8 +752,8 @@
 
         // [7] Removing an active downstream removes all remaining prefixes.
         offload.removeDownstreamInterface(RNDIS0);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, USB_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, IPV6_DISCARD_PREFIX);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -762,7 +761,7 @@
     public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Pretend to set a few different upstreams (only the interface name
         // matters for this test; we're ignoring IP and route information).
@@ -776,7 +775,7 @@
         // that happen with setUpstreamParameters().
         clearInvocations(mHardware);
 
-        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onStoppedUnsupported();
 
         // Verify forwarded stats behaviour.
@@ -793,7 +792,7 @@
             throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Pretend to set a few different upstreams (only the interface name
         // matters for this test; we're ignoring IP and route information).
@@ -840,7 +839,7 @@
         // that happen with setUpstreamParameters().
         clearInvocations(mHardware);
 
-        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onSupportAvailable();
 
         // Verify forwarded stats behaviour.
@@ -859,8 +858,8 @@
                 // into OffloadController proper. After this, also check for:
                 //     "192.168.43.1/32", "2001:2::1/128", "2001:2::2/128"
                 "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
-        verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "192.168.43.0/24");
-        verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "2001:2::/64");
+        verify(mHardware, times(1)).addDownstream(WLAN0, "192.168.43.0/24");
+        verify(mHardware, times(1)).addDownstream(WLAN0, "2001:2::/64");
         verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any());
         verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong());
         verifyNoMoreInteractions(mHardware);
@@ -871,7 +870,7 @@
         enableOffload();
         setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Initialize with fake eth upstream.
         final String ethernetIface = "eth1";
@@ -925,7 +924,7 @@
         offload.setUpstreamLinkProperties(makeEthernetLinkProperties());
         mTetherStatsProvider.onSetAlert(0);
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             mTetherStatsProviderCb.assertNoCallback();
         } else {
             mTetherStatsProviderCb.expectNotifyAlertReached();
@@ -935,7 +934,7 @@
 
     @Test
     public void testSoftwarePollingUsed() throws Exception {
-        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_0);
-        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_1);
+        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java
new file mode 100644
index 0000000..c9ce64f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2022 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.tetheroffload.ForwardedStats;
+import android.hardware.tetheroffload.IOffload;
+import android.hardware.tetheroffload.IPv4AddrPortPair;
+import android.hardware.tetheroffload.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.NatTimeoutUpdate;
+import android.hardware.tetheroffload.NetworkProtocol;
+import android.hardware.tetheroffload.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceSpecificException;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHalAidlImplTest {
+    private static final String RMNET0 = "test_rmnet_data0";
+
+    private final SharedLog mLog = new SharedLog("test");
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private IOffload mIOffloadMock;
+    private OffloadHalAidlImpl mIOffloadHal;
+    private ITetheringOffloadCallback mTetheringOffloadCallback;
+    private OffloadHalCallback mOffloadHalCallback;
+
+    private void initAndValidateOffloadHal(boolean initSuccess)
+            throws Exception {
+        final FileDescriptor fd1 = new FileDescriptor();
+        final FileDescriptor fd2 = new FileDescriptor();
+        final NativeHandle handle1 = new NativeHandle(fd1, true);
+        final NativeHandle handle2 = new NativeHandle(fd2, true);
+        final ArgumentCaptor<ParcelFileDescriptor> fdCaptor1 =
+                ArgumentCaptor.forClass(ParcelFileDescriptor.class);
+        final ArgumentCaptor<ParcelFileDescriptor> fdCaptor2 =
+                ArgumentCaptor.forClass(ParcelFileDescriptor.class);
+        final ArgumentCaptor<ITetheringOffloadCallback> offloadCallbackCaptor =
+                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+        if (initSuccess) {
+            doNothing().when(mIOffloadMock).initOffload(any(), any(), any());
+        } else {
+            doThrow(new IllegalStateException()).when(mIOffloadMock).initOffload(any(), any(),
+                    any());
+        }
+        assertEquals(mIOffloadHal.initOffload(handle1, handle2, mOffloadHalCallback),
+                     initSuccess);
+        verify(mIOffloadMock).initOffload(fdCaptor1.capture(), fdCaptor2.capture(),
+                                          offloadCallbackCaptor.capture());
+        assertEquals(fdCaptor1.getValue().getFd(), fd1.getInt$());
+        assertEquals(fdCaptor2.getValue().getFd(), fd2.getInt$());
+        mTetheringOffloadCallback = offloadCallbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mIOffloadMock = mock(IOffload.class);
+        mIOffloadHal = new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, mIOffloadMock,
+                new Handler(mTestLooper.getLooper()), mLog);
+        mOffloadHalCallback = spy(new OffloadHalCallback());
+    }
+
+    @Test
+    public void testInitOffloadSuccess() throws Exception {
+        initAndValidateOffloadHal(true /* initSuccess */);
+    }
+
+    @Test
+    public void testInitOffloadFailure() throws Exception {
+        initAndValidateOffloadHal(false /* initSuccess */);
+    }
+
+    @Test
+    public void testStopOffloadSuccess() throws Exception {
+        initAndValidateOffloadHal(true);
+        doNothing().when(mIOffloadMock).stopOffload();
+        assertTrue(mIOffloadHal.stopOffload());
+        verify(mIOffloadMock).stopOffload();
+    }
+
+    @Test
+    public void testStopOffloadFailure() throws Exception {
+        initAndValidateOffloadHal(true);
+        doThrow(new IllegalStateException()).when(mIOffloadMock).stopOffload();
+        assertFalse(mIOffloadHal.stopOffload());
+    }
+
+    private void doTestGetForwardedStats(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final ForwardedStats returnStats = new ForwardedStats();
+        if (expectSuccess) {
+            returnStats.rxBytes = 12345;
+            returnStats.txBytes = 67890;
+            when(mIOffloadMock.getForwardedStats(anyString())).thenReturn(returnStats);
+        } else {
+            when(mIOffloadMock.getForwardedStats(anyString()))
+                    .thenThrow(new ServiceSpecificException(IOffload.ERROR_CODE_UNUSED));
+        }
+        final OffloadHardwareInterface.ForwardedStats stats =
+                mIOffloadHal.getForwardedStats(RMNET0);
+        verify(mIOffloadMock).getForwardedStats(eq(RMNET0));
+        assertNotNull(stats);
+        assertEquals(stats.rxBytes, returnStats.rxBytes);
+        assertEquals(stats.txBytes, returnStats.txBytes);
+    }
+
+    @Test
+    public void testGetForwardedStatsSuccess() throws Exception {
+        doTestGetForwardedStats(true);
+    }
+
+    @Test
+    public void testGetForwardedStatsFailure() throws Exception {
+        doTestGetForwardedStats(false);
+    }
+
+    private void doTestSetLocalPrefixes(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final ArrayList<String> localPrefixes = new ArrayList<>();
+        localPrefixes.add("127.0.0.0/8");
+        localPrefixes.add("fe80::/64");
+        final String[] localPrefixesArray =
+                localPrefixes.toArray(new String[localPrefixes.size()]);
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setLocalPrefixes(any());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).setLocalPrefixes(any());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setLocalPrefixes(localPrefixes));
+        verify(mIOffloadMock).setLocalPrefixes(eq(localPrefixesArray));
+    }
+
+    @Test
+    public void testSetLocalPrefixesSuccess() throws Exception {
+        doTestSetLocalPrefixes(true);
+    }
+
+    @Test
+    public void testSetLocalPrefixesFailure() throws Exception {
+        doTestSetLocalPrefixes(false);
+    }
+
+    private void doTestSetDataLimit(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final long limit = 12345;
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(),
+                    anyLong());
+        } else {
+            doThrow(new IllegalArgumentException())
+                    .when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(), anyLong());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setDataLimit(RMNET0, limit));
+        verify(mIOffloadMock).setDataWarningAndLimit(eq(RMNET0), eq(Long.MAX_VALUE), eq(limit));
+    }
+
+    @Test
+    public void testSetDataLimitSuccess() throws Exception {
+        doTestSetDataLimit(true);
+    }
+
+    @Test
+    public void testSetDataLimitFailure() throws Exception {
+        doTestSetDataLimit(false);
+    }
+
+    private void doTestSetDataWarningAndLimit(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final long warning = 12345;
+        final long limit = 67890;
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(),
+                    anyLong());
+        } else {
+            doThrow(new IllegalArgumentException())
+                    .when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(), anyLong());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify(mIOffloadMock).setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit));
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitSuccess() throws Exception {
+        doTestSetDataWarningAndLimit(true);
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailure() throws Exception {
+        doTestSetDataWarningAndLimit(false);
+    }
+
+    private void doTestSetUpstreamParameters(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String v4addr = "192.168.10.1";
+        final String v4gateway = "192.168.10.255";
+        final ArrayList<String> v6gws = new ArrayList<>(0);
+        v6gws.add("2001:db8::1");
+        String[] v6gwsArray = v6gws.toArray(new String[v6gws.size()]);
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setUpstreamParameters(anyString(), anyString(),
+                    anyString(), any());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).setUpstreamParameters(
+                    anyString(), anyString(), anyString(), any());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setUpstreamParameters(RMNET0, v4addr, v4gateway,
+                  v6gws));
+        verify(mIOffloadMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                  eq(v6gwsArray));
+    }
+
+    @Test
+    public void testSetUpstreamParametersSuccess() throws Exception {
+        doTestSetUpstreamParameters(true);
+    }
+
+    @Test
+    public void testSetUpstreamParametersFailure() throws Exception {
+        doTestSetUpstreamParameters(false);
+    }
+
+    private void doTestAddDownstream(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).addDownstream(anyString(), anyString());
+        } else {
+            doThrow(new IllegalStateException()).when(mIOffloadMock).addDownstream(anyString(),
+                    anyString());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.addDownstream(ifName, prefix));
+        verify(mIOffloadMock).addDownstream(eq(ifName), eq(prefix));
+    }
+
+    @Test
+    public void testAddDownstreamSuccess() throws Exception {
+        doTestAddDownstream(true);
+    }
+
+    @Test
+    public void testAddDownstreamFailure() throws Exception {
+        doTestAddDownstream(false);
+    }
+
+    private void doTestRemoveDownstream(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).removeDownstream(anyString(), anyString());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).removeDownstream(
+                    anyString(), anyString());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.removeDownstream(ifName, prefix));
+        verify(mIOffloadMock).removeDownstream(eq(ifName), eq(prefix));
+    }
+
+    @Test
+    public void testRemoveDownstreamSuccess() throws Exception {
+        doTestRemoveDownstream(true);
+    }
+
+    @Test
+    public void testRemoveDownstreamFailure() throws Exception {
+        doTestRemoveDownstream(false);
+    }
+
+    @Test
+    public void testTetheringOffloadCallback() throws Exception {
+        initAndValidateOffloadHal(true);
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        final InOrder inOrder = inOrder(mOffloadHalCallback);
+        inOrder.verify(mOffloadHalCallback).onStarted();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedError();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedUnsupported();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onSupportAvailable();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedLimitReached();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onWarningReached();
+        inOrder.verifyNoMoreInteractions();
+
+        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+        mTetheringOffloadCallback.updateTimeout(tcpParams);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+                eq(tcpParams.src.addr),
+                eq(tcpParams.src.port),
+                eq(tcpParams.dst.addr),
+                eq(tcpParams.dst.port));
+        inOrder.verifyNoMoreInteractions();
+
+        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+        mTetheringOffloadCallback.updateTimeout(udpParams);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+                eq(udpParams.src.addr),
+                eq(udpParams.src.port),
+                eq(udpParams.dst.addr),
+                eq(udpParams.dst.port));
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+        final NatTimeoutUpdate params = new NatTimeoutUpdate();
+        params.proto = proto;
+        params.src = new IPv4AddrPortPair();
+        params.dst = new IPv4AddrPortPair();
+        params.src.addr = "192.168.43.200";
+        params.src.port = 100;
+        params.dst.addr = "172.50.46.169";
+        params.dst.port = 150;
+        return params;
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java
new file mode 100644
index 0000000..6fdab5a
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2022 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHalHidlImplTest {
+    private static final String RMNET0 = "test_rmnet_data0";
+
+    private final SharedLog mLog = new SharedLog("test");
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private OffloadHalHidlImpl mIOffloadHal;
+    private IOffloadConfig mIOffloadConfigMock;
+    private IOffloadControl mIOffloadControlMock;
+    private ITetheringOffloadCallback mTetheringOffloadCallback;
+    private OffloadHalCallback mOffloadHalCallback;
+
+    private void createAndInitOffloadHal(int version) throws Exception {
+        final FileDescriptor fd1 = new FileDescriptor();
+        final FileDescriptor fd2 = new FileDescriptor();
+        final NativeHandle handle1 = new NativeHandle(fd1, true);
+        final NativeHandle handle2 = new NativeHandle(fd2, true);
+        mIOffloadConfigMock = mock(IOffloadConfig.class);
+        switch (version) {
+            case OFFLOAD_HAL_VERSION_HIDL_1_0:
+                mIOffloadControlMock = mock(IOffloadControl.class);
+                break;
+            case OFFLOAD_HAL_VERSION_HIDL_1_1:
+                mIOffloadControlMock = mock(
+                        android.hardware.tetheroffload.control.V1_1.IOffloadControl.class);
+                break;
+            default:
+                fail("Nonexistent HAL version");
+                return;
+        }
+        mIOffloadHal = new OffloadHalHidlImpl(version, mIOffloadConfigMock,
+                mIOffloadControlMock, new Handler(mTestLooper.getLooper()), mLog);
+        mIOffloadHal.initOffload(handle1, handle2, mOffloadHalCallback);
+
+        final ArgumentCaptor<NativeHandle> nativeHandleCaptor1 =
+                ArgumentCaptor.forClass(NativeHandle.class);
+        final ArgumentCaptor<NativeHandle> nativeHandleCaptor2 =
+                ArgumentCaptor.forClass(NativeHandle.class);
+        final ArgumentCaptor<ITetheringOffloadCallback> offloadCallbackCaptor =
+                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+        verify(mIOffloadConfigMock).setHandles(nativeHandleCaptor1.capture(),
+                nativeHandleCaptor2.capture(), any());
+        verify(mIOffloadControlMock).initOffload(offloadCallbackCaptor.capture(), any());
+        assertEquals(nativeHandleCaptor1.getValue().getFileDescriptor().getInt$(),
+                handle1.getFileDescriptor().getInt$());
+        assertEquals(nativeHandleCaptor2.getValue().getFileDescriptor().getInt$(),
+                handle2.getFileDescriptor().getInt$());
+        mTetheringOffloadCallback = offloadCallbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mOffloadHalCallback = spy(new OffloadHalCallback());
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long rxBytes = 12345;
+        final long txBytes = 67890;
+        doAnswer(invocation -> {
+            ((IOffloadControl.getForwardedStatsCallback) invocation.getArgument(1))
+                    .onValues(rxBytes, txBytes);
+            return null;
+        }).when(mIOffloadControlMock).getForwardedStats(eq(RMNET0), any());
+        final ForwardedStats stats = mIOffloadHal.getForwardedStats(RMNET0);
+        verify(mIOffloadControlMock).getForwardedStats(eq(RMNET0), any());
+        assertNotNull(stats);
+        assertEquals(rxBytes, stats.rxBytes);
+        assertEquals(txBytes, stats.txBytes);
+    }
+
+    private void doTestSetLocalPrefixes(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final ArrayList<String> localPrefixes = new ArrayList<>();
+        localPrefixes.add("127.0.0.0/8");
+        localPrefixes.add("fe80::/64");
+        doAnswer(invocation -> {
+            ((IOffloadControl.setLocalPrefixesCallback) invocation.getArgument(1))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setLocalPrefixes(eq(localPrefixes), any());
+        assertEquals(expectSuccess, mIOffloadHal.setLocalPrefixes(localPrefixes));
+        verify(mIOffloadControlMock).setLocalPrefixes(eq(localPrefixes), any());
+    }
+
+    @Test
+    public void testSetLocalPrefixesSuccess() throws Exception {
+        doTestSetLocalPrefixes(true);
+    }
+
+    @Test
+    public void testSetLocalPrefixesFailure() throws Exception {
+        doTestSetLocalPrefixes(false);
+    }
+
+    private void doTestSetDataLimit(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long limit = 12345;
+        doAnswer(invocation -> {
+            ((IOffloadControl.setDataLimitCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setDataLimit(eq(RMNET0), eq(limit), any());
+        assertEquals(expectSuccess, mIOffloadHal.setDataLimit(RMNET0, limit));
+        verify(mIOffloadControlMock).setDataLimit(eq(RMNET0), eq(limit), any());
+    }
+
+    @Test
+    public void testSetDataLimitSuccess() throws Exception {
+        doTestSetDataLimit(true);
+    }
+
+    @Test
+    public void testSetDataLimitFailure() throws Exception {
+        doTestSetDataLimit(false);
+    }
+
+    private void doTestSetDataWarningAndLimit(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_1);
+        final long warning = 12345;
+        final long limit = 67890;
+        doAnswer(invocation -> {
+            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl
+                    .setDataWarningAndLimitCallback) invocation.getArgument(3))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControlMock)
+                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+        assertEquals(expectSuccess, mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControlMock)
+                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitSuccess() throws Exception {
+        doTestSetDataWarningAndLimit(true);
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailure() throws Exception {
+        // Verify that V1.0 control HAL would reject the function call with exception.
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long warning = 12345;
+        final long limit = 67890;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+
+        doTestSetDataWarningAndLimit(false);
+    }
+
+    private void doTestSetUpstreamParameters(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String v4addr = "192.168.10.1";
+        final String v4gateway = "192.168.10.255";
+        final ArrayList<String> v6gws = new ArrayList<>(0);
+        v6gws.add("2001:db8::1");
+        doAnswer(invocation -> {
+            ((IOffloadControl.setUpstreamParametersCallback) invocation.getArgument(4))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                eq(v6gws), any());
+        assertEquals(expectSuccess, mIOffloadHal.setUpstreamParameters(RMNET0, v4addr, v4gateway,
+                v6gws));
+        verify(mIOffloadControlMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                eq(v6gws), any());
+    }
+
+    @Test
+    public void testSetUpstreamParametersSuccess() throws Exception {
+        doTestSetUpstreamParameters(true);
+    }
+
+    @Test
+    public void testSetUpstreamParametersFailure() throws Exception {
+        doTestSetUpstreamParameters(false);
+    }
+
+    private void doTestAddDownstream(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        doAnswer(invocation -> {
+            ((IOffloadControl.addDownstreamCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).addDownstream(eq(ifName), eq(prefix), any());
+        assertEquals(expectSuccess, mIOffloadHal.addDownstream(ifName, prefix));
+        verify(mIOffloadControlMock).addDownstream(eq(ifName), eq(prefix), any());
+    }
+
+    @Test
+    public void testAddDownstreamSuccess() throws Exception {
+        doTestAddDownstream(true);
+    }
+
+    @Test
+    public void testAddDownstreamFailure() throws Exception {
+        doTestAddDownstream(false);
+    }
+
+    private void doTestRemoveDownstream(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        doAnswer(invocation -> {
+            ((IOffloadControl.removeDownstreamCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).removeDownstream(eq(ifName), eq(prefix), any());
+        assertEquals(expectSuccess, mIOffloadHal.removeDownstream(ifName, prefix));
+        verify(mIOffloadControlMock).removeDownstream(eq(ifName), eq(prefix), any());
+    }
+
+    @Test
+    public void testRemoveDownstreamSuccess() throws Exception {
+        doTestRemoveDownstream(true);
+    }
+
+    @Test
+    public void testRemoveDownstreamFailure() throws Exception {
+        doTestRemoveDownstream(false);
+    }
+
+    @Test
+    public void testTetheringOffloadCallback() throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStarted();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedError();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedUnsupported();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onSupportAvailable();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedLimitReached();
+
+        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+        mTetheringOffloadCallback.updateTimeout(tcpParams);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+                eq(tcpParams.src.addr),
+                eq(uint16(tcpParams.src.port)),
+                eq(tcpParams.dst.addr),
+                eq(uint16(tcpParams.dst.port)));
+
+        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+        mTetheringOffloadCallback.updateTimeout(udpParams);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+                eq(udpParams.src.addr),
+                eq(uint16(udpParams.src.port)),
+                eq(udpParams.dst.addr),
+                eq(uint16(udpParams.dst.port)));
+        reset(mOffloadHalCallback);
+
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_1);
+
+        // Verify the interface will process the events that comes from V1.1 HAL.
+        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        final InOrder inOrder = inOrder(mOffloadHalCallback);
+        inOrder.verify(mOffloadHalCallback).onStarted();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onWarningReached();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+        final NatTimeoutUpdate params = new NatTimeoutUpdate();
+        params.proto = proto;
+        params.src.addr = "192.168.43.200";
+        params.src.port = 100;
+        params.dst.addr = "172.50.46.169";
+        params.dst.port = 150;
+        return params;
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index 36b439b..b1f875b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -20,36 +20,29 @@
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.SOCK_STREAM;
 
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
-import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
-import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
-import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
-import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
-import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
 import android.os.Handler;
 import android.os.NativeHandle;
 import android.os.test.TestLooper;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
-import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,12 +50,13 @@
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.StructNfGenMsg;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -79,11 +73,9 @@
     private final TestLooper mTestLooper = new TestLooper();
 
     private OffloadHardwareInterface mOffloadHw;
-    private ITetheringOffloadCallback mTetheringOffloadCallback;
-    private OffloadHardwareInterface.ControlCallback mControlCallback;
+    private OffloadHalCallback mOffloadHalCallback;
 
-    @Mock private IOffloadConfig mIOffloadConfig;
-    private IOffloadControl mIOffloadControl;
+    @Mock private IOffloadHal mIOffload;
     @Mock private NativeHandle mNativeHandle;
 
     // Random values to test Netlink message.
@@ -91,32 +83,16 @@
     private static final short TEST_FLAGS = 263;
 
     class MyDependencies extends OffloadHardwareInterface.Dependencies {
-        private final int mMockControlVersion;
-        MyDependencies(SharedLog log, final int mockControlVersion) {
-            super(log);
-            mMockControlVersion = mockControlVersion;
+        private final int mMockOffloadHalVersion;
+        MyDependencies(Handler handler, SharedLog log, final int mockOffloadHalVersion) {
+            super(handler, log);
+            mMockOffloadHalVersion = mockOffloadHalVersion;
+            when(mIOffload.getVersion()).thenReturn(mMockOffloadHalVersion);
         }
 
         @Override
-        public IOffloadConfig getOffloadConfig() {
-            return mIOffloadConfig;
-        }
-
-        @Override
-        public Pair<IOffloadControl, Integer> getOffloadControl() {
-            switch (mMockControlVersion) {
-                case OFFLOAD_HAL_VERSION_1_0:
-                    mIOffloadControl = mock(IOffloadControl.class);
-                    break;
-                case OFFLOAD_HAL_VERSION_1_1:
-                    mIOffloadControl =
-                            mock(android.hardware.tetheroffload.control.V1_1.IOffloadControl.class);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Invalid offload control version "
-                            + mMockControlVersion);
-            }
-            return new Pair<IOffloadControl, Integer>(mIOffloadControl, mMockControlVersion);
+        public IOffloadHal getOffload() {
+            return mMockOffloadHalVersion == OFFLOAD_HAL_VERSION_NONE ? null : mIOffload;
         }
 
         @Override
@@ -128,156 +104,140 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
+        mOffloadHalCallback = new OffloadHalCallback();
+        when(mIOffload.initOffload(any(NativeHandle.class), any(NativeHandle.class),
+                any(OffloadHalCallback.class))).thenReturn(true);
     }
 
-    private void startOffloadHardwareInterface(int controlVersion) throws Exception {
+    private void startOffloadHardwareInterface(int offloadHalVersion)
+            throws Exception {
         final SharedLog log = new SharedLog("test");
-        mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
-                new MyDependencies(log, controlVersion));
-        mOffloadHw.initOffloadConfig();
-        mOffloadHw.initOffloadControl(mControlCallback);
-        final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
-                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
-        verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any());
-        mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue();
+        final Handler handler = new Handler(mTestLooper.getLooper());
+        final int num = offloadHalVersion != OFFLOAD_HAL_VERSION_NONE ? 1 : 0;
+        mOffloadHw = new OffloadHardwareInterface(handler, log,
+                new MyDependencies(handler, log, offloadHalVersion));
+        assertEquals(offloadHalVersion, mOffloadHw.initOffload(mOffloadHalCallback));
+        verify(mIOffload, times(num)).initOffload(any(NativeHandle.class), any(NativeHandle.class),
+                eq(mOffloadHalCallback));
+    }
+
+    @Test
+    public void testInitFailureWithNoHal() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_NONE);
+    }
+
+    @Test
+    public void testInitSuccessWithAidl() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL);
+    }
+
+    @Test
+    public void testInitSuccessWithHidl_1_0() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+    }
+
+    @Test
+    public void testInitSuccessWithHidl_1_1() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 
     @Test
     public void testGetForwardedStats() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
-        final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
-        verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
-        assertNotNull(stats);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        ForwardedStats stats = new ForwardedStats(12345, 56780);
+        when(mIOffload.getForwardedStats(anyString())).thenReturn(stats);
+        assertEquals(mOffloadHw.getForwardedStats(RMNET0), stats);
+        verify(mIOffload).getForwardedStats(eq(RMNET0));
     }
 
     @Test
     public void testSetLocalPrefixes() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final ArrayList<String> localPrefixes = new ArrayList<>();
         localPrefixes.add("127.0.0.0/8");
         localPrefixes.add("fe80::/64");
-        mOffloadHw.setLocalPrefixes(localPrefixes);
-        verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any());
+        when(mIOffload.setLocalPrefixes(any())).thenReturn(true);
+        assertTrue(mOffloadHw.setLocalPrefixes(localPrefixes));
+        verify(mIOffload).setLocalPrefixes(eq(localPrefixes));
+        when(mIOffload.setLocalPrefixes(any())).thenReturn(false);
+        assertFalse(mOffloadHw.setLocalPrefixes(localPrefixes));
     }
 
     @Test
     public void testSetDataLimit() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final long limit = 12345;
-        mOffloadHw.setDataLimit(RMNET0, limit);
-        verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
+        when(mIOffload.setDataLimit(anyString(), anyLong())).thenReturn(true);
+        assertTrue(mOffloadHw.setDataLimit(RMNET0, limit));
+        verify(mIOffload).setDataLimit(eq(RMNET0), eq(limit));
+        when(mIOffload.setDataLimit(anyString(), anyLong())).thenReturn(false);
+        assertFalse(mOffloadHw.setDataLimit(RMNET0, limit));
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailureWithHidl_1_0() throws Exception {
+        // Verify V1.0 control HAL would reject the function call with exception.
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long warning = 12345;
+        final long limit = 67890;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
     }
 
     @Test
     public void testSetDataWarningAndLimit() throws Exception {
-        // Verify V1.0 control HAL would reject the function call with exception.
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        // Verify V1.1 control HAL could receive this function call.
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1);
         final long warning = 12345;
         final long limit = 67890;
-        assertThrows(IllegalArgumentException.class,
-                () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
-        reset(mIOffloadControl);
-
-        // Verify V1.1 control HAL could receive this function call.
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
-        mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit);
-        verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
-                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+        when(mIOffload.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
+        assertTrue(mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify(mIOffload).setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit));
+        when(mIOffload.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(false);
+        assertFalse(mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
     }
 
     @Test
     public void testSetUpstreamParameters() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final String v4addr = "192.168.10.1";
         final String v4gateway = "192.168.10.255";
         final ArrayList<String> v6gws = new ArrayList<>(0);
         v6gws.add("2001:db8::1");
-        mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws);
-        verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
-                eq(v6gws), any());
+        when(mIOffload.setUpstreamParameters(anyString(), anyString(), anyString(), any()))
+                .thenReturn(true);
+        assertTrue(mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws));
+        verify(mIOffload).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway), eq(v6gws));
 
         final ArgumentCaptor<ArrayList<String>> mArrayListCaptor =
                 ArgumentCaptor.forClass(ArrayList.class);
-        mOffloadHw.setUpstreamParameters(null, null, null, null);
-        verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""),
-                mArrayListCaptor.capture(), any());
+        when(mIOffload.setUpstreamParameters(anyString(), anyString(), anyString(), any()))
+                .thenReturn(false);
+        assertFalse(mOffloadHw.setUpstreamParameters(null, null, null, null));
+        verify(mIOffload).setUpstreamParameters(eq(""), eq(""), eq(""), mArrayListCaptor.capture());
         assertEquals(mArrayListCaptor.getValue().size(), 0);
     }
 
     @Test
-    public void testUpdateDownstreamPrefix() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+    public void testUpdateDownstream() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final String ifName = "wlan1";
         final String prefix = "192.168.43.0/24";
-        mOffloadHw.addDownstreamPrefix(ifName, prefix);
-        verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any());
-
-        mOffloadHw.removeDownstreamPrefix(ifName, prefix);
-        verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any());
-    }
-
-    @Test
-    public void testTetheringOffloadCallback() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStarted();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedError();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedUnsupported();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onSupportAvailable();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedLimitReached();
-
-        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
-        mTetheringOffloadCallback.updateTimeout(tcpParams);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
-                eq(tcpParams.src.addr),
-                eq(uint16(tcpParams.src.port)),
-                eq(tcpParams.dst.addr),
-                eq(uint16(tcpParams.dst.port)));
-
-        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
-        mTetheringOffloadCallback.updateTimeout(udpParams);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
-                eq(udpParams.src.addr),
-                eq(uint16(udpParams.src.port)),
-                eq(udpParams.dst.addr),
-                eq(uint16(udpParams.dst.port)));
-        reset(mControlCallback);
-
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
-
-        // Verify the interface will process the events that comes from V1.1 HAL.
-        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED);
-        mTestLooper.dispatchAll();
-        final InOrder inOrder = inOrder(mControlCallback);
-        inOrder.verify(mControlCallback).onStarted();
-        inOrder.verifyNoMoreInteractions();
-
-        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
-        mTestLooper.dispatchAll();
-        inOrder.verify(mControlCallback).onWarningReached();
-        inOrder.verifyNoMoreInteractions();
+        when(mIOffload.addDownstream(anyString(), anyString())).thenReturn(true);
+        assertTrue(mOffloadHw.addDownstream(ifName, prefix));
+        verify(mIOffload).addDownstream(eq(ifName), eq(prefix));
+        when(mIOffload.addDownstream(anyString(), anyString())).thenReturn(false);
+        assertFalse(mOffloadHw.addDownstream(ifName, prefix));
+        when(mIOffload.removeDownstream(anyString(), anyString())).thenReturn(true);
+        assertTrue(mOffloadHw.removeDownstream(ifName, prefix));
+        verify(mIOffload).removeDownstream(eq(ifName), eq(prefix));
+        when(mIOffload.removeDownstream(anyString(), anyString())).thenReturn(false);
+        assertFalse(mOffloadHw.removeDownstream(ifName, prefix));
     }
 
     @Test
     public void testSendIpv4NfGenMsg() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         FileDescriptor writeSocket = new FileDescriptor();
         FileDescriptor readSocket = new FileDescriptor();
         try {
@@ -308,14 +268,4 @@
         assertEquals(0 /* error */, buffer.getShort());  // res_id
         assertEquals(expectedLen, buffer.position());
     }
-
-    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
-        final NatTimeoutUpdate params = new NatTimeoutUpdate();
-        params.proto = proto;
-        params.src.addr = "192.168.43.200";
-        params.src.port = 100;
-        params.dst.addr = "172.50.46.169";
-        params.dst.port = 150;
-        return params;
-    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 79590b7..adb1590 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -68,7 +68,7 @@
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
 import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
@@ -649,8 +649,7 @@
         mInterfaceConfiguration.flags = new String[0];
         when(mRouterAdvertisementDaemon.start())
                 .thenReturn(true);
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                0 /* defaultDisabled */);
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 0 /* defaultDisabled */);
         when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
 
         mServiceContext = new TestContext(mContext);
@@ -2345,25 +2344,15 @@
         mLooper.dispatchAll();
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
 
-        // 1. Offload fail if no OffloadConfig.
-        initOffloadConfiguration(false /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                0 /* defaultDisabled */);
+        // 1. Offload fail if no IOffloadHal.
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_NONE, 0 /* defaultDisabled */);
         runUsbTethering(upstreamState);
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
         runStopUSBTethering();
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
         reset(mUsbManager, mIPv6TetheringCoordinator);
-        // 2. Offload fail if no OffloadControl.
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_NONE,
-                0 /* defaultDisabled */);
-        runUsbTethering(upstreamState);
-        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
-        runStopUSBTethering();
-        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
-        reset(mUsbManager, mIPv6TetheringCoordinator);
-        // 3. Offload fail if disabled by settings.
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                1 /* defaultDisabled */);
+        // 2. Offload fail if disabled by settings.
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 1 /* defaultDisabled */);
         runUsbTethering(upstreamState);
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
         runStopUSBTethering();
@@ -2378,11 +2367,10 @@
         verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
     }
 
-    private void initOffloadConfiguration(final boolean offloadConfig,
-            @OffloadHardwareInterface.OffloadHalVersion final int offloadControlVersion,
+    private void initOffloadConfiguration(
+            @OffloadHardwareInterface.OffloadHalVersion final int offloadHalVersion,
             final int defaultDisabled) {
-        when(mOffloadHardwareInterface.initOffloadConfig()).thenReturn(offloadConfig);
-        when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControlVersion);
+        when(mOffloadHardwareInterface.initOffload(any())).thenReturn(offloadHalVersion);
         when(mOffloadHardwareInterface.getDefaultTetherOffloadDisabled()).thenReturn(
                 defaultDisabled);
     }