Merge "[Thread] initial Thread network service" into main
diff --git a/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
index 5372a4d..61e3ba4 100644
--- a/Cronet/tools/import/copy.bara.sky
+++ b/Cronet/tools/import/copy.bara.sky
@@ -71,6 +71,8 @@
         "base/third_party/nspr/**",
         "base/third_party/superfasthash/**",
         "base/third_party/valgrind/**",
+        # Those are temporarily needed until Chromium finish the migration
+        # of libc++[abi]
         "buildtools/third_party/libc++/**",
         "buildtools/third_party/libc++abi/**",
         # Note: Only used for tests.
@@ -84,9 +86,15 @@
         "third_party/brotli/**",
         # Note: Only used for tests.
         "third_party/ced/**",
+        "third_party/cpu_features/**",
+        # Note: Only used for tests.
+        "third_party/google_benchmark/**",
         # Note: Only used for tests.
         "third_party/googletest/**",
         "third_party/icu/**",
+        "third_party/jni_zero/**",
+        "third_party/libc++/**",
+        "third_party/libc++abi/**",
         "third_party/libevent/**",
         # Note: Only used for tests.
         "third_party/libxml/**",
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index a4db776..c26c32f 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -21,6 +21,7 @@
     name: "framework-tethering",
     defaults: [
         "framework-tethering-defaults",
+        "FlaggedApiDefaults",
     ],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering:__subpackages__",
@@ -37,10 +38,10 @@
         "//frameworks/base/core/tests/utillib",
         "//frameworks/base/packages/Connectivity/tests:__subpackages__",
         "//frameworks/base/tests/vcn",
-        "//frameworks/libs/net/common/testutils",
-        "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/staticlibs/testutils",
+        "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
diff --git a/Tethering/common/TetheringLib/udc-extended-api/current.txt b/Tethering/common/TetheringLib/udc-extended-api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/Tethering/common/TetheringLib/udc-extended-api/module-lib-current.txt b/Tethering/common/TetheringLib/udc-extended-api/module-lib-current.txt
new file mode 100644
index 0000000..460c216
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/module-lib-current.txt
@@ -0,0 +1,50 @@
+// Signature format: 2.0
+package android.net {
+
+  public final class TetheringConstants {
+    field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+    field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    field public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    field public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+  }
+
+  public class TetheringManager {
+    ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>);
+    method public int getLastTetherError(@NonNull String);
+    method @NonNull public String[] getTetherableBluetoothRegexs();
+    method @NonNull public String[] getTetherableIfaces();
+    method @NonNull public String[] getTetherableUsbRegexs();
+    method @NonNull public String[] getTetherableWifiRegexs();
+    method @NonNull public String[] getTetheredIfaces();
+    method @NonNull public String[] getTetheringErroredIfaces();
+    method public boolean isTetheringSupported();
+    method public boolean isTetheringSupported(@NonNull String);
+    method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
+    method @Deprecated public int setUsbTethering(boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @Deprecated public int tether(@NonNull String);
+    method @Deprecated public int untether(@NonNull String);
+  }
+
+  public static interface TetheringManager.TetheredInterfaceCallback {
+    method public void onAvailable(@NonNull String);
+    method public void onUnavailable();
+  }
+
+  public static interface TetheringManager.TetheredInterfaceRequest {
+    method public void release();
+  }
+
+  public static interface TetheringManager.TetheringEventCallback {
+    method @Deprecated public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+  }
+
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+  }
+
+}
+
diff --git a/Tethering/common/TetheringLib/udc-extended-api/module-lib-removed.txt b/Tethering/common/TetheringLib/udc-extended-api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/Tethering/common/TetheringLib/udc-extended-api/removed.txt b/Tethering/common/TetheringLib/udc-extended-api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/Tethering/common/TetheringLib/udc-extended-api/system-current.txt b/Tethering/common/TetheringLib/udc-extended-api/system-current.txt
new file mode 100644
index 0000000..844ff64
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/system-current.txt
@@ -0,0 +1,117 @@
+// Signature format: 2.0
+package android.net {
+
+  public final class TetheredClient implements android.os.Parcelable {
+    ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses();
+    method @NonNull public android.net.MacAddress getMacAddress();
+    method public int getTetheringType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR;
+  }
+
+  public static final class TetheredClient.AddressInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.LinkAddress getAddress();
+    method @Nullable public String getHostname();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
+  }
+
+  public final class TetheringInterface implements android.os.Parcelable {
+    ctor public TetheringInterface(int, @NonNull String);
+    method public int describeContents();
+    method @NonNull public String getInterface();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
+  }
+
+  public class TetheringManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    field @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
+    field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
+    field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+    field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_ETHERNET = 5; // 0x5
+    field public static final int TETHERING_INVALID = -1; // 0xffffffff
+    field public static final int TETHERING_NCM = 4; // 0x4
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+    field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+    field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9
+    field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8
+    field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+    field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+    field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5
+    field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+    field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb
+    field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+    field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+    field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+    field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+    field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10
+    field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+    field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+    field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2
+    field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1
+    field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0
+  }
+
+  public static interface TetheringManager.OnTetheringEntitlementResultListener {
+    method public void onTetheringEntitlementResult(int);
+  }
+
+  public static interface TetheringManager.StartTetheringCallback {
+    method public default void onTetheringFailed(int);
+    method public default void onTetheringStarted();
+  }
+
+  public static interface TetheringManager.TetheringEventCallback {
+    method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
+    method public default void onError(@NonNull String, int);
+    method public default void onError(@NonNull android.net.TetheringInterface, int);
+    method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
+    method public default void onOffloadStatusChanged(int);
+    method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onTetherableInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
+    method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public default void onTetheredInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
+    method public default void onTetheringSupported(boolean);
+    method public default void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  public static class TetheringManager.TetheringRequest {
+    method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+    method public int getConnectivityScope();
+    method @Nullable public android.net.LinkAddress getLocalIpv4Address();
+    method public boolean getShouldShowEntitlementUi();
+    method public int getTetheringType();
+    method public boolean isExemptFromEntitlementCheck();
+  }
+
+  public static class TetheringManager.TetheringRequest.Builder {
+    ctor public TetheringManager.TetheringRequest.Builder(int);
+    method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setConnectivityScope(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
+  }
+
+}
+
diff --git a/Tethering/common/TetheringLib/udc-extended-api/system-removed.txt b/Tethering/common/TetheringLib/udc-extended-api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/Tethering/common/TetheringLib/udc-extended-api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 109bbda..47e2848 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -15,6 +15,10 @@
     native <methods>;
 }
 
+-keep class com.android.networkstack.tethering.util.TetheringUtils {
+    native <methods>;
+}
+
 # Ensure runtime-visible field annotations are kept when using R8 full mode.
 -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
 -keep interface com.android.networkstack.tethering.util.Struct$Field {
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index eadba58..a851410 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -252,7 +252,6 @@
     private final int mInterfaceType;
     private final LinkProperties mLinkProperties;
     private final boolean mUsingLegacyDhcp;
-    private final boolean mUsingBpfOffload;
     private final int mP2pLeasesSubnetPrefixLength;
 
     private final Dependencies mDeps;
@@ -314,7 +313,6 @@
         mInterfaceType = interfaceType;
         mLinkProperties = new LinkProperties();
         mUsingLegacyDhcp = config.useLegacyDhcpServer();
-        mUsingBpfOffload = config.isBpfOffloadEnabled();
         mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
         mPrivateAddressCoordinator = addressCoordinator;
         mDeps = deps;
@@ -326,11 +324,9 @@
         mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
                 new MyNeighborEventConsumer());
 
-        // IP neighbor monitor monitors the neighbor events for adding/removing offload
-        // forwarding rules per client. If BPF offload is not supported, don't start listening
-        // for neighbor events. See updateIpv6ForwardingRules, addIpv6DownstreamRule,
-        // removeIpv6DownstreamRule.
-        if (mUsingBpfOffload && !mIpNeighborMonitor.start()) {
+        // IP neighbor monitor monitors the neighbor events for adding/removing IPv6 downstream rule
+        // per client. If BPF offload is not supported, don't start listening for neighbor events.
+        if (mBpfCoordinator.isUsingBpfOffload() && !mIpNeighborMonitor.start()) {
             mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
         }
 
@@ -891,37 +887,6 @@
         }
     }
 
-    private void addIpv6DownstreamRule(Ipv6DownstreamRule rule) {
-        // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
-        // offload is disabled. Add this check just in case.
-        // TODO: Perhaps remove this protection check.
-        if (!mUsingBpfOffload) return;
-
-        mBpfCoordinator.addIpv6DownstreamRule(this, rule);
-    }
-
-    private void removeIpv6DownstreamRule(Ipv6DownstreamRule rule) {
-        // TODO: Perhaps remove this protection check.
-        // See the related comment in #addIpv6DownstreamRule.
-        if (!mUsingBpfOffload) return;
-
-        mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
-    }
-
-    private void clearIpv6ForwardingRules() {
-        if (!mUsingBpfOffload) return;
-
-        mBpfCoordinator.tetherOffloadRuleClear(this);
-    }
-
-    private void updateIpv6ForwardingRule(int newIfindex) {
-        // TODO: Perhaps remove this protection check.
-        // See the related comment in #addIpv6DownstreamRule.
-        if (!mUsingBpfOffload) return;
-
-        mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex);
-    }
-
     // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
     // changes or if a neighbor event is received.
     private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
@@ -930,14 +895,14 @@
         // nothing else.
         // TODO: Rather than always clear rules, ensure whether ipv6 ever enable first.
         if (upstreamIfindex == 0 || !upstreamSupportsBpf) {
-            clearIpv6ForwardingRules();
+            mBpfCoordinator.tetherOffloadRuleClear(this);
             return;
         }
 
         // If the upstream interface has changed, remove all rules and re-add them with the new
         // upstream interface.
         if (prevUpstreamIfindex != upstreamIfindex) {
-            updateIpv6ForwardingRule(upstreamIfindex);
+            mBpfCoordinator.tetherOffloadRuleUpdate(this, upstreamIfindex);
         }
 
         // If we're here to process a NeighborEvent, do so now.
@@ -955,18 +920,14 @@
         Ipv6DownstreamRule rule = new Ipv6DownstreamRule(upstreamIfindex, mInterfaceParams.index,
                 (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
         if (e.isValid()) {
-            addIpv6DownstreamRule(rule);
+            mBpfCoordinator.addIpv6DownstreamRule(this, rule);
         } else {
-            removeIpv6DownstreamRule(rule);
+            mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
         }
     }
 
     // TODO: consider moving into BpfCoordinator.
     private void updateClientInfoIpv4(NeighborEvent e) {
-        // TODO: Perhaps remove this protection check.
-        // See the related comment in #addIpv6DownstreamRule.
-        if (!mUsingBpfOffload) return;
-
         if (e == null) return;
         if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
                 || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
@@ -1342,7 +1303,7 @@
 
             for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
             mUpstreamIfaceSet = null;
-            clearIpv6ForwardingRules();
+            mBpfCoordinator.tetherOffloadRuleClear(IpServer.this);
         }
 
         private void cleanupUpstreamInterface(String upstreamIface) {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 7311125..1b23a6c 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -523,6 +523,18 @@
         mLog.i("Polling stopped");
     }
 
+    /**
+     * Return whether BPF offload is supported
+     */
+    public boolean isUsingBpfOffload() {
+        return isUsingBpf();
+    }
+
+    // This is identical to isUsingBpfOffload above but is only used internally.
+    // The reason for having two separate methods is that the code calls isUsingBpf
+    // very often. But the tests call verifyNoMoreInteractions, which will check all
+    // calls to public methods. If isUsingBpf were public, the test would need to
+    // verify all calls to it, which would clutter the test.
     private boolean isUsingBpf() {
         return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized();
     }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index fe70820..747cc20 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -22,9 +22,7 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
-import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
-import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
 import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
 
 import android.content.ContentResolver;
@@ -179,16 +177,30 @@
      */
     @VisibleForTesting
     public static class Dependencies {
-        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
-                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
-            return DeviceConfigUtils.isTetheringFeatureEnabled(context, namespace, name,
-                    moduleName, defaultEnabled);
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
         }
 
         boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
                 boolean defaultValue) {
             return DeviceConfig.getBoolean(namespace, name, defaultValue);
         }
+
+        /**
+         * TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION is used to force enable the feature on specific
+         * R devices. Just checking the flag value is enough since the flag has been pushed to
+         * enable the feature on the old version and any new binary will always have a version
+         * number newer than the flag.
+         * This flag is wrongly configured in the connectivity namespace so this method reads the
+         * flag value from the connectivity namespace. But the tethering module should use the
+         * tethering namespace. This method can be removed after R EOL.
+         */
+        boolean isTetherForceUpstreamAutomaticFeatureEnabled() {
+            final int flagValue = DeviceConfigUtils.getDeviceConfigPropertyInt(
+                    NAMESPACE_CONNECTIVITY, TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION,
+                    0 /* defaultValue */);
+            return flagValue > 0;
+        }
     }
 
     public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id) {
@@ -237,7 +249,7 @@
         // - S, T: can be enabled/disabled by resource config_tether_upstream_automatic.
         // - U+  : automatic mode only.
         final boolean forceAutomaticUpstream = SdkLevel.isAtLeastU() || (!SdkLevel.isAtLeastS()
-                && isConnectivityFeatureEnabled(ctx, TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION));
+                && mDeps.isTetherForceUpstreamAutomaticFeatureEnabled());
         chooseUpstreamAutomatically = forceAutomaticUpstream || getResourceBoolean(
                 res, R.bool.config_tether_upstream_automatic, false /** defaultValue */);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
@@ -607,32 +619,13 @@
 
     private boolean shouldEnableWearTethering(Context context) {
         return SdkLevel.isAtLeastT()
-            && isTetheringFeatureEnabled(context, TETHER_ENABLE_WEAR_TETHERING);
+            && mDeps.isFeatureEnabled(context, TETHER_ENABLE_WEAR_TETHERING);
     }
 
     private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
         return mDeps.getDeviceConfigBoolean(NAMESPACE_CONNECTIVITY, name, defaultValue);
     }
 
-    /**
-     * This is deprecated because connectivity namespace already be used for NetworkStack mainline
-     * module. Tethering should use its own namespace to roll out the feature flag.
-     * @deprecated new caller should use isTetheringFeatureEnabled instead.
-     */
-    @Deprecated
-    private boolean isConnectivityFeatureEnabled(Context ctx, String featureVersionFlag) {
-        return isFeatureEnabled(ctx, NAMESPACE_CONNECTIVITY, featureVersionFlag);
-    }
-
-    private boolean isTetheringFeatureEnabled(Context ctx, String featureVersionFlag) {
-        return isFeatureEnabled(ctx, NAMESPACE_TETHERING, featureVersionFlag);
-    }
-
-    private boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
-        return mDeps.isFeatureEnabled(ctx, namespace, featureVersionFlag, TETHERING_MODULE_NAME,
-                false /* defaultEnabled */);
-    }
-
     private Resources getResources(Context ctx, int subId) {
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             return getResourcesForSubIdWrapper(ctx, subId);
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
new file mode 100644
index 0000000..cbbbdec
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
@@ -0,0 +1,252 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.State;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An implementation of a state machine, meant to be called synchronously.
+ *
+ * This class implements a finite state automaton based on the same State
+ * class as StateMachine.
+ * All methods of this class must be called on only one thread.
+ */
+public class SyncStateMachine {
+    @NonNull private final String mName;
+    @NonNull private final Thread mMyThread;
+    private final boolean mDbg;
+    private final ArrayMap<State, StateInfo> mStateInfo = new ArrayMap<>();
+
+    // mCurrentState is the current state. mDestState is the target state that mCurrentState will
+    // transition to. The value of mDestState can be changed when a state processes a message and
+    // calls #transitionTo, but it cannot be changed during the state transition. When the state
+    // transition is complete, mDestState will be set to mCurrentState. Both mCurrentState and
+    // mDestState only be null before state machine starts and must only be touched on mMyThread.
+    @Nullable private State mCurrentState;
+    @Nullable private State mDestState;
+
+    /**
+     * A information class about a state and its parent. Used to maintain the state hierarchy.
+     */
+    public static class StateInfo {
+        /** The state who owns this StateInfo. */
+        public final State state;
+        /** The parent state. */
+        public final State parent;
+        // True when the state has been entered and on the stack.
+        private boolean mActive;
+
+        public StateInfo(@NonNull final State child, @Nullable final State parent) {
+            this.state = child;
+            this.parent = parent;
+        }
+    }
+
+    /**
+     * The constructor.
+     *
+     * @param name of this machine.
+     * @param thread the running thread of this machine. It must either be the thread on which this
+     * constructor is called, or a thread that is not started yet.
+     */
+    public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread) {
+        this(name, thread, false /* debug */);
+    }
+
+    /**
+     * The constructor.
+     *
+     * @param name of this machine.
+     * @param thread the running thread of this machine. It must either be the thread on which this
+     * constructor is called, or a thread that is not started yet.
+     * @param dbg whether to print debug logs.
+     */
+    public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread,
+            final boolean dbg) {
+        mMyThread = thread;
+        // Machine can either be setup from machine thread or before machine thread started.
+        ensureCorrectOrNotStartedThread();
+
+        mName = name;
+        mDbg = dbg;
+    }
+
+    /**
+     * Add all of states to the state machine. Different StateInfos which have same state are not
+     * allowed. In other words, a state can not have multiple parent states. #addAllStates can
+     * only be called once either from mMyThread or before mMyThread started.
+     */
+    public final void addAllStates(@NonNull final List<StateInfo> stateInfos) {
+        ensureCorrectOrNotStartedThread();
+
+        if (mCurrentState != null) {
+            throw new IllegalStateException("State only can be added before started");
+        }
+
+        if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed");
+
+        if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured");
+
+        final Set<Class> usedClasses = new ArraySet<>();
+        for (final StateInfo info : stateInfos) {
+            Objects.requireNonNull(info.state);
+            if (!usedClasses.add(info.state.getClass())) {
+                throw new IllegalStateException("Adding the same state multiple times in a state "
+                        + "machine is forbidden because it tends to be confusing; it can be done "
+                        + "with anonymous subclasses but consider carefully whether you want to "
+                        + "use a single state or other alternatives instead.");
+            }
+
+            mStateInfo.put(info.state, info);
+        }
+
+        // Check whether all of parent states indicated from StateInfo are added.
+        for (final StateInfo info : stateInfos) {
+            if (info.parent != null) ensureExistingState(info.parent);
+        }
+    }
+
+    /**
+     * Start the state machine. The initial state can't be child state.
+     *
+     * @param initialState the first state of this machine. The state must be exact state object
+     * setting up by {@link #addAllStates}, not a copy of it.
+     */
+    public final void start(@NonNull final State initialState) {
+        ensureCorrectThread();
+        ensureExistingState(initialState);
+
+        mDestState = initialState;
+        performTransitions();
+    }
+
+    /**
+     * Process the message synchronously then perform state transition.
+     */
+    public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
+        ensureCorrectThread();
+
+        performTransitions();
+    }
+
+    /**
+     * Transition to destination state. Upon returning from processMessage the automaton will
+     * transition to the given destination state.
+     *
+     * This function can NOT be called inside the State enter and exit function. The transition
+     * target is always defined and can never be changed mid-way of state transition.
+     *
+     * @param destState will be the state to transition to. The state must be the same instance set
+     * up by {@link #addAllStates}, not a copy of it.
+     */
+    public final void transitionTo(@NonNull final State destState) {
+        if (mDbg) Log.d(mName, "transitionTo " + destState);
+        ensureCorrectThread();
+        ensureExistingState(destState);
+
+        if (mDestState == mCurrentState) {
+            mDestState = destState;
+        } else {
+            throw new IllegalStateException("Destination already specified");
+        }
+    }
+
+    private void performTransitions() {
+        // 1. Determine the common ancestor state of current/destination states
+        // 2. Invoke state exit list from current state to common ancestor state.
+        // 3. Invoke state enter list from common ancestor state to destState by going
+        // through mEnterStateStack.
+        if (mDestState == mCurrentState) return;
+
+        final StateInfo commonAncestor = getLastActiveAncestor(mStateInfo.get(mDestState));
+
+        executeExitMethods(commonAncestor, mStateInfo.get(mCurrentState));
+        executeEnterMethods(commonAncestor, mStateInfo.get(mDestState));
+        mCurrentState = mDestState;
+    }
+
+    // Null is the root of all states.
+    private StateInfo getLastActiveAncestor(@Nullable final StateInfo start) {
+        if (null == start || start.mActive) return start;
+
+        return getLastActiveAncestor(mStateInfo.get(start.parent));
+    }
+
+    // Call the exit method from current state to common ancestor state.
+    // Both the commonAncestor and exitingState StateInfo can be null because null is the ancestor
+    // of all states.
+    // For example: When transitioning from state1 to state2, the
+    // executeExitMethods(commonAncestor, exitingState) function will be called twice, once with
+    // null and state1 as the argument, and once with null and null as the argument.
+    //              root
+    //              |   \
+    // current <- state1 state2 -> destination
+    private void executeExitMethods(@Nullable StateInfo commonAncestor,
+            @Nullable StateInfo exitingState) {
+        if (commonAncestor == exitingState) return;
+
+        if (mDbg) Log.d(mName, exitingState.state + " exit()");
+        exitingState.state.exit();
+        exitingState.mActive = false;
+        executeExitMethods(commonAncestor, mStateInfo.get(exitingState.parent));
+    }
+
+    // Call the enter method from common ancestor state to destination state.
+    // Both the commonAncestor and enteringState StateInfo can be null because null is the ancestor
+    // of all states.
+    // For example: When transitioning from state1 to state2, the
+    // executeEnterMethods(commonAncestor, enteringState) function will be called twice, once with
+    // null and state2 as the argument, and once with null and null as the argument.
+    //              root
+    //              |   \
+    // current <- state1 state2 -> destination
+    private void executeEnterMethods(@Nullable StateInfo commonAncestor,
+            @Nullable StateInfo enteringState) {
+        if (enteringState == commonAncestor) return;
+
+        executeEnterMethods(commonAncestor, mStateInfo.get(enteringState.parent));
+        if (mDbg) Log.d(mName, enteringState.state + " enter()");
+        enteringState.state.enter();
+        enteringState.mActive = true;
+    }
+
+    private void ensureCorrectThread() {
+        if (!mMyThread.equals(Thread.currentThread())) {
+            throw new IllegalStateException("Called from wrong thread");
+        }
+    }
+
+    private void ensureCorrectOrNotStartedThread() {
+        if (!mMyThread.isAlive()) return;
+
+        ensureCorrectThread();
+    }
+
+    private void ensureExistingState(@NonNull final State state) {
+        if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state");
+    }
+}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 20f0bc6..2594a5e 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -28,7 +28,7 @@
         "DhcpPacketLib",
         "androidx.test.rules",
         "cts-net-utils",
-        "mockito-target-extended-minus-junit4",
+        "mockito-target-minus-junit4",
         "net-tests-utils",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
@@ -40,11 +40,6 @@
         "android.test.base",
         "android.test.mock",
     ],
-    jni_libs: [
-        // For mockito extended
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
 }
 
 android_library {
@@ -54,6 +49,7 @@
     defaults: ["TetheringIntegrationTestsDefaults"],
     visibility: [
         "//packages/modules/Connectivity/Tethering/tests/mts",
+        "//packages/modules/Connectivity/tests/cts/net",
     ]
 }
 
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 83fc3e4..0702aa7 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -31,14 +31,12 @@
 import static android.net.TetheringTester.isExpectedIcmpPacket;
 import static android.net.TetheringTester.isExpectedTcpPacket;
 import static android.net.TetheringTester.isExpectedUdpPacket;
-
 import static com.android.net.module.util.HexDump.dumpHexString;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -164,6 +162,10 @@
     private TapPacketReader mDownstreamReader;
     private MyTetheringEventCallback mTetheringEventCallback;
 
+    public Context getContext() {
+        return mContext;
+    }
+
     @BeforeClass
     public static void setUpOnce() throws Exception {
         // The first test case may experience tethering restart with IP conflict handling.
diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java
index 4f3c6e7..ae4ae55 100644
--- a/Tethering/tests/integration/base/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/base/android/net/TetheringTester.java
@@ -27,12 +27,9 @@
 import static android.system.OsConstants.IPPROTO_IPV6;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
-
 import static com.android.net.module.util.DnsPacket.ANSECTION;
-import static com.android.net.module.util.DnsPacket.ARSECTION;
 import static com.android.net.module.util.DnsPacket.DnsHeader;
 import static com.android.net.module.util.DnsPacket.DnsRecord;
-import static com.android.net.module.util.DnsPacket.NSSECTION;
 import static com.android.net.module.util.DnsPacket.QDSECTION;
 import static com.android.net.module.util.HexDump.dumpHexString;
 import static com.android.net.module.util.IpUtils.icmpChecksum;
@@ -56,7 +53,6 @@
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index eed308c..076fde3 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -326,6 +326,14 @@
 
             waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
             expectLocalOnlyAddresses(iface);
+
+            // After testing the IPv6 local address, the DHCP server may still be in the process
+            // of being created. If the downstream interface is killed by the test while the
+            // DHCP server is starting, a DHCP server error may occur. To ensure that the DHCP
+            // server has started completely before finishing the test, also test the dhcp server
+            // by calling runDhcp.
+            final TetheringTester tester = new TetheringTester(downstreamReader);
+            tester.runDhcp(MacAddress.fromString("1:2:3:4:5:6").toByteArray());
         } finally {
             maybeStopTapPacketReader(downstreamReader);
             maybeCloseTestInterface(downstreamIface);
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 19d70c6..c0718d1 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -244,6 +244,8 @@
         when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
         when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
         when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
+        // Recreate mBpfCoordinator again here because mTetherConfig has changed
+        mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
         mIpServer = new IpServer(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
                 mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies);
@@ -1244,13 +1246,11 @@
         resetNetdBpfMapAndCoordinator();
 
         recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
-        verify(mBpfCoordinator, never()).addIpv6DownstreamRule(any(), any());
         verifyNeverTetherOffloadRuleAdd();
         verifyNoUpstreamIpv6ForwardingChange(null);
         resetNetdBpfMapAndCoordinator();
 
         recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
-        verify(mBpfCoordinator, never()).removeIpv6DownstreamRule(any(), any());
         verifyNeverTetherOffloadRuleRemove();
         verifyNoUpstreamIpv6ForwardingChange(null);
         resetNetdBpfMapAndCoordinator();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index 9e287a0..087be26 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -28,9 +28,8 @@
     FakeTetheringConfiguration(Context ctx, SharedLog log, int id) {
         super(ctx, log, id, new Dependencies() {
             @Override
-            boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
-                    @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
-                return defaultEnabled;
+            boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
+                return false;
             }
 
             @Override
@@ -38,6 +37,11 @@
                     boolean defaultValue) {
                 return defaultValue;
             }
+
+            @Override
+            boolean isTetherForceUpstreamAutomaticFeatureEnabled() {
+                return false;
+            }
         });
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 3382af8..aa322dc 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -155,9 +155,8 @@
         private ArrayMap<String, Boolean> mMockFlags = new ArrayMap<>();
 
         @Override
-        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
-                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
-            return isMockFlagEnabled(name, defaultEnabled);
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) {
+            return isMockFlagEnabled(name, false /* defaultEnabled */);
         }
 
         @Override
@@ -172,6 +171,12 @@
             return isMockFlagEnabled(name, defaultValue);
         }
 
+        @Override
+        boolean isTetherForceUpstreamAutomaticFeatureEnabled() {
+            return isMockFlagEnabled(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION,
+                    false /* defaultEnabled */);
+        }
+
         private boolean isMockFlagEnabled(@NonNull String name, boolean defaultEnabled) {
             final Boolean flag = mMockFlags.getOrDefault(name, defaultEnabled);
             // Value in the map can also be null
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index a104084..8f0ff84 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -240,6 +240,8 @@
             return TC_ACT_SHOT;
     }
 
+    try_make_writable(skb, l2_header_size + sizeof(struct iphdr));
+
     // bpf_skb_change_proto() invalidates all pointers - reload them.
     data = (void*)(long)skb->data;
     data_end = (void*)(long)skb->data_end;
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index e2e6d02..c3258e9 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -56,6 +56,8 @@
 // see include/uapi/linux/tcp.h
 #define TCP_FLAG32_OFF 12
 
+#define TCP_FLAG8_OFF (TCP_FLAG32_OFF + 1)
+
 // For maps netd does not need to access
 #define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries)      \
     DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries,              \
@@ -270,17 +272,41 @@
         (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
         L4_off = sizeof(struct ipv6hdr);
         ipVersion = 6;
+        // skip over a *single* HOPOPTS or DSTOPTS extension header (if present)
+        if (proto == IPPROTO_HOPOPTS || proto == IPPROTO_DSTOPTS) {
+            struct {
+                uint8_t proto, len;
+            } ext_hdr;
+            if (!bpf_skb_load_bytes_net(skb, L4_off, &ext_hdr, sizeof(ext_hdr), kver)) {
+                proto = ext_hdr.proto;
+                L4_off += (ext_hdr.len + 1) * 8;
+            }
+        }
     }
 
     uint8_t flags = 0;
     __be16 sport = 0, dport = 0;
-    if (proto == IPPROTO_TCP && L4_off >= 20) {
-        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG32_OFF + 1, &flags, sizeof(flags), kver);
-        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(source), &sport, sizeof(sport), kver);
-        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(dest), &dport, sizeof(dport), kver);
-    } else if (proto == IPPROTO_UDP && L4_off >= 20) {
-        (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(source), &sport, sizeof(sport), kver);
-        (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(dest), &dport, sizeof(dport), kver);
+    if (L4_off >= 20) {
+      switch (proto) {
+        case IPPROTO_TCP:
+          (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG8_OFF, &flags, sizeof(flags), kver);
+          // fallthrough
+        case IPPROTO_DCCP:
+        case IPPROTO_UDP:
+        case IPPROTO_UDPLITE:
+        case IPPROTO_SCTP:
+          // all of these L4 protocols start with be16 src & dst port
+          (void)bpf_skb_load_bytes_net(skb, L4_off + 0, &sport, sizeof(sport), kver);
+          (void)bpf_skb_load_bytes_net(skb, L4_off + 2, &dport, sizeof(dport), kver);
+          break;
+        case IPPROTO_ICMP:
+        case IPPROTO_ICMPV6:
+          // Both IPv4 and IPv6 icmp start with u8 type & code, which we store in the bottom
+          // (ie. second) byte of sport/dport (which are be16s), the top byte is already zero.
+          (void)bpf_skb_load_bytes_net(skb, L4_off + 0, (char *)&sport + 1, 1, kver); //type
+          (void)bpf_skb_load_bytes_net(skb, L4_off + 1, (char *)&dport + 1, 1, kver); //code
+          break;
+      }
     }
 
     pkt->timestampNs = bpf_ktime_get_boot_ns();
@@ -293,6 +319,7 @@
     pkt->dport = dport;
 
     pkt->egress = egress;
+    pkt->wakeup = !egress && (skb->mark & 0x80000000);  // Fwmark.ingress_cpu_wakeup
     pkt->ipProto = proto;
     pkt->tcpFlags = flags;
     pkt->ipVersion = ipVersion;
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 836e998..6e9acaa 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -81,7 +81,8 @@
   __be16 sport;
   __be16 dport;
 
-  bool egress;
+  bool egress:1,
+       wakeup:1;
   uint8_t ipProto;
   uint8_t tcpFlags;
   uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown
diff --git a/clatd/.clang-format b/clatd/.clang-format
new file mode 100644
index 0000000..f1debbd
--- /dev/null
+++ b/clatd/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: Google
+AlignConsecutiveAssignments: true
+AlignEscapedNewlines: Right
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 2
+Cpp11BracedListStyle: false
+TabWidth: 2
diff --git a/clatd/Android.bp b/clatd/Android.bp
new file mode 100644
index 0000000..595c6b9
--- /dev/null
+++ b/clatd/Android.bp
@@ -0,0 +1,118 @@
+package {
+    default_applicable_licenses: ["external_android-clat_license"],
+}
+
+// Added automatically by a large-scale-change
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+    name: "external_android-clat_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "LICENSE",
+        "NOTICE",
+    ],
+}
+
+cc_defaults {
+    name: "clatd_defaults",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused-parameter",
+
+        // Bug: http://b/33566695
+        "-Wno-address-of-packed-member",
+    ],
+}
+
+// Code used both by the daemon and by unit tests.
+filegroup {
+    name: "clatd_common",
+    srcs: [
+        "clatd.c",
+        "dump.c",
+        "icmp.c",
+        "ipv4.c",
+        "ipv6.c",
+        "logging.c",
+        "translate.c",
+    ],
+}
+
+// The clat daemon.
+cc_binary {
+    name: "clatd",
+    defaults: ["clatd_defaults"],
+    srcs: [
+        ":clatd_common",
+        "main.c"
+    ],
+    static_libs: [
+        "libip_checksum",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    relative_install_path: "for-system",
+
+    // Static libc++ for smaller apex size while shipping clatd in the mainline module.
+    // See b/213123047
+    stl: "libc++_static",
+
+    // Only enable clang-tidy for the daemon, not the tests, because enabling it for the
+    // tests substantially increases build/compile cycle times and doesn't really provide a
+    // security benefit.
+    tidy: true,
+    tidy_checks: [
+        "-*",
+        "cert-*",
+        "clang-analyzer-security*",
+        // b/2043314, warnings on memcpy_s, memset_s, snprintf_s calls
+        // are blocking the migration from gnu99 to gnu11.
+        // Until those warnings are fixed, disable these checks.
+        "-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling",
+        "android-*",
+    ],
+    tidy_checks_as_errors: [
+        "clang-analyzer-security*",
+        "cert-*",
+        "android-*",
+    ],
+
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    min_sdk_version: "30",
+}
+
+// Unit tests.
+cc_test {
+    name: "clatd_test",
+    defaults: ["clatd_defaults"],
+    srcs: [
+        ":clatd_common",
+        "clatd_test.cpp"
+    ],
+    static_libs: [
+        "libbase",
+        "libip_checksum",
+        "libnetd_test_tun_interface",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libnetutils",
+    ],
+    test_suites: ["device-tests"],
+    require_root: true,
+}
diff --git a/clatd/BUGS b/clatd/BUGS
new file mode 100644
index 0000000..24e6639
--- /dev/null
+++ b/clatd/BUGS
@@ -0,0 +1,5 @@
+known problems/assumptions:
+ - does not handle protocols other than ICMP, UDP, TCP and GRE/ESP
+ - assumes the handset has its own (routed) /64 ipv6 subnet
+ - assumes the /128 ipv6 subnet it generates can use the nat64 gateway
+ - assumes the nat64 gateway has the ipv4 address in the last 32 bits of the ipv6 address (that it uses a /96 plat subnet)
diff --git a/clatd/LICENSE b/clatd/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/clatd/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/clatd/METADATA b/clatd/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/clatd/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/clatd/MODULE_LICENSE_APACHE2 b/clatd/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/clatd/MODULE_LICENSE_APACHE2
diff --git a/clatd/NOTICE b/clatd/NOTICE
new file mode 100644
index 0000000..5943b54
--- /dev/null
+++ b/clatd/NOTICE
@@ -0,0 +1,189 @@
+   Copyright (c) 2010-2012, Daniel Drown
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/clatd/PREUPLOAD.cfg b/clatd/PREUPLOAD.cfg
new file mode 100644
index 0000000..c8dbf77
--- /dev/null
+++ b/clatd/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/clatd/TEST_MAPPING b/clatd/TEST_MAPPING
new file mode 100644
index 0000000..d36908a
--- /dev/null
+++ b/clatd/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    { "name": "clatd_test" },
+    { "name": "netd_integration_test" },
+    { "name": "netd_unit_test" },
+    { "name": "netdutils_test" },
+    { "name": "resolv_integration_test" },
+    { "name": "resolv_unit_test" }
+  ]
+}
diff --git a/clatd/clatd.c b/clatd/clatd.c
new file mode 100644
index 0000000..bac8b1d
--- /dev/null
+++ b/clatd/clatd.c
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2012 Daniel Drown
+ *
+ * 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.
+ *
+ * clatd.c - tun interface setup and main event loop
+ */
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/filter.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include <linux/virtio_net.h>
+#include <net/if.h>
+#include <sys/uio.h>
+
+#include "clatd.h"
+#include "checksum.h"
+#include "config.h"
+#include "dump.h"
+#include "logging.h"
+#include "translate.h"
+
+struct clat_config Global_Clatd_Config;
+
+volatile sig_atomic_t running = 1;
+
+// reads IPv6 packet from AF_PACKET socket, translates to IPv4, writes to tun
+void process_packet_6_to_4(struct tun_data *tunnel) {
+  // ethernet header is 14 bytes, plus 4 for a normal VLAN tag or 8 for Q-in-Q
+  // we don't really support vlans (or especially Q-in-Q)...
+  // but a few bytes of extra buffer space doesn't hurt...
+  struct {
+    struct virtio_net_hdr vnet;
+    uint8_t payload[22 + MAXMTU];
+    char pad; // +1 to make packet truncation obvious
+  } buf;
+  struct iovec iov = {
+    .iov_base = &buf,
+    .iov_len = sizeof(buf),
+  };
+  char cmsg_buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))];
+  struct msghdr msgh = {
+    .msg_iov = &iov,
+    .msg_iovlen = 1,
+    .msg_control = cmsg_buf,
+    .msg_controllen = sizeof(cmsg_buf),
+  };
+  ssize_t readlen = recvmsg(tunnel->read_fd6, &msgh, /*flags*/ 0);
+
+  if (readlen < 0) {
+    if (errno != EAGAIN) {
+      logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno));
+    }
+    return;
+  } else if (readlen == 0) {
+    logmsg(ANDROID_LOG_WARN, "%s: packet socket removed?", __func__);
+    running = 0;
+    return;
+  } else if (readlen >= sizeof(buf)) {
+    logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__);
+    return;
+  }
+
+  bool ok = false;
+  __u32 tp_status = 0;
+  __u16 tp_net = 0;
+
+  for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
+    if (cmsg->cmsg_level == SOL_PACKET && cmsg->cmsg_type == PACKET_AUXDATA) {
+      struct tpacket_auxdata *aux = (struct tpacket_auxdata *)CMSG_DATA(cmsg);
+      ok = true;
+      tp_status = aux->tp_status;
+      tp_net = aux->tp_net;
+      break;
+    }
+  }
+
+  if (!ok) {
+    // theoretically this should not happen...
+    static bool logged = false;
+    if (!logged) {
+      logmsg(ANDROID_LOG_ERROR, "%s: failed to fetch tpacket_auxdata cmsg", __func__);
+      logged = true;
+    }
+  }
+
+  const int payload_offset = offsetof(typeof(buf), payload);
+  if (readlen < payload_offset + tp_net) {
+    logmsg(ANDROID_LOG_WARN, "%s: ignoring %zd byte pkt shorter than %d+%u L2 header",
+           __func__, readlen, payload_offset, tp_net);
+    return;
+  }
+
+  const int pkt_len = readlen - payload_offset;
+
+  // This will detect a skb->ip_summed == CHECKSUM_PARTIAL packet with non-final L4 checksum
+  if (tp_status & TP_STATUS_CSUMNOTREADY) {
+    static bool logged = false;
+    if (!logged) {
+      logmsg(ANDROID_LOG_WARN, "%s: L4 checksum calculation required", __func__);
+      logged = true;
+    }
+
+    // These are non-negative by virtue of csum_start/offset being u16
+    const int cs_start = buf.vnet.csum_start;
+    const int cs_offset = cs_start + buf.vnet.csum_offset;
+    if (cs_start > pkt_len) {
+      logmsg(ANDROID_LOG_ERROR, "%s: out of range - checksum start %d > %d",
+             __func__, cs_start, pkt_len);
+    } else if (cs_offset + 1 >= pkt_len) {
+      logmsg(ANDROID_LOG_ERROR, "%s: out of range - checksum offset %d + 1 >= %d",
+             __func__, cs_offset, pkt_len);
+    } else {
+      uint16_t csum = ip_checksum(buf.payload + cs_start, pkt_len - cs_start);
+      if (!csum) csum = 0xFFFF;  // required fixup for UDP, TCP must live with it
+      buf.payload[cs_offset] = csum & 0xFF;
+      buf.payload[cs_offset + 1] = csum >> 8;
+    }
+  }
+
+  translate_packet(tunnel->fd4, 0 /* to_ipv6 */, buf.payload + tp_net, pkt_len - tp_net);
+}
+
+// reads TUN_PI + L3 IPv4 packet from tun, translates to IPv6, writes to AF_INET6/RAW socket
+void process_packet_4_to_6(struct tun_data *tunnel) {
+  struct {
+    struct tun_pi pi;
+    uint8_t payload[MAXMTU];
+    char pad; // +1 byte to make packet truncation obvious
+  } buf;
+  ssize_t readlen = read(tunnel->fd4, &buf, sizeof(buf));
+
+  if (readlen < 0) {
+    if (errno != EAGAIN) {
+      logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno));
+    }
+    return;
+  } else if (readlen == 0) {
+    logmsg(ANDROID_LOG_WARN, "%s: tun interface removed", __func__);
+    running = 0;
+    return;
+  } else if (readlen >= sizeof(buf)) {
+    logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__);
+    return;
+  }
+
+  const int payload_offset = offsetof(typeof(buf), payload);
+
+  if (readlen < payload_offset) {
+    logmsg(ANDROID_LOG_WARN, "%s: short read: got %ld bytes", __func__, readlen);
+    return;
+  }
+
+  const int pkt_len = readlen - payload_offset;
+
+  uint16_t proto = ntohs(buf.pi.proto);
+  if (proto != ETH_P_IP) {
+    logmsg(ANDROID_LOG_WARN, "%s: unknown packet type = 0x%x", __func__, proto);
+    return;
+  }
+
+  if (buf.pi.flags != 0) {
+    logmsg(ANDROID_LOG_WARN, "%s: unexpected flags = %d", __func__, buf.pi.flags);
+  }
+
+  translate_packet(tunnel->write_fd6, 1 /* to_ipv6 */, buf.payload, pkt_len);
+}
+
+// IPv6 DAD packet format:
+//   Ethernet header (if needed) will be added by the kernel:
+//     u8[6] src_mac; u8[6] dst_mac '33:33:ff:XX:XX:XX'; be16 ethertype '0x86DD'
+//   IPv6 header:
+//     be32 0x60000000 - ipv6, tclass 0, flowlabel 0
+//     be16 payload_length '32'; u8 nxt_hdr ICMPv6 '58'; u8 hop limit '255'
+//     u128 src_ip6 '::'
+//     u128 dst_ip6 'ff02::1:ffXX:XXXX'
+//   ICMPv6 header:
+//     u8 type '135'; u8 code '0'; u16 icmp6 checksum; u32 reserved '0'
+//   ICMPv6 neighbour solicitation payload:
+//     u128 tgt_ip6
+//   ICMPv6 ND options:
+//     u8 opt nr '14'; u8 length '1'; u8[6] nonce '6 random bytes'
+void send_dad(int fd, const struct in6_addr* tgt) {
+  struct {
+    struct ip6_hdr ip6h;
+    struct nd_neighbor_solicit ns;
+    uint8_t ns_opt_nr;
+    uint8_t ns_opt_len;
+    uint8_t ns_opt_nonce[6];
+  } dad_pkt = {
+    .ip6h = {
+      .ip6_flow = htonl(6 << 28),  // v6, 0 tclass, 0 flowlabel
+      .ip6_plen = htons(sizeof(dad_pkt) - sizeof(struct ip6_hdr)),  // payload length, ie. 32
+      .ip6_nxt = IPPROTO_ICMPV6,  // 58
+      .ip6_hlim = 255,
+      .ip6_src = {},  // ::
+      .ip6_dst.s6_addr = {
+        0xFF, 0x02, 0, 0,
+        0, 0, 0, 0,
+        0, 0, 0, 1,
+        0xFF, tgt->s6_addr[13], tgt->s6_addr[14], tgt->s6_addr[15],
+      },  // ff02::1:ffXX:XXXX - multicast group address derived from bottom 24-bits of tgt
+    },
+    .ns = {
+      .nd_ns_type = ND_NEIGHBOR_SOLICIT,  // 135
+      .nd_ns_code = 0,
+      .nd_ns_cksum = 0,  // will be calculated later
+      .nd_ns_reserved = 0,
+      .nd_ns_target = *tgt,
+    },
+    .ns_opt_nr = 14,  // icmp6 option 'nonce' from RFC3971
+    .ns_opt_len = 1,  // in units of 8 bytes, including option nr and len
+    .ns_opt_nonce = {},  // opt_len *8 - sizeof u8(opt_nr) - sizeof u8(opt_len) = 6 ranodmized bytes
+  };
+  arc4random_buf(&dad_pkt.ns_opt_nonce, sizeof(dad_pkt.ns_opt_nonce));
+
+  // 40 byte IPv6 header + 8 byte ICMPv6 header + 16 byte ipv6 target address + 8 byte nonce option
+  _Static_assert(sizeof(dad_pkt) == 40 + 8 + 16 + 8, "sizeof dad packet != 72");
+
+  // IPv6 header checksum is standard negated 16-bit one's complement sum over the icmpv6 pseudo
+  // header (which includes payload length, nextheader, and src/dst ip) and the icmpv6 payload.
+  //
+  // Src/dst ip immediately prefix the icmpv6 header itself, so can be handled along
+  // with the payload.  We thus only need to manually account for payload len & next header.
+  //
+  // The magic '8' is simply the offset of the ip6_src field in the ipv6 header,
+  // ie. we're skipping over the ipv6 version, tclass, flowlabel, payload length, next header
+  // and hop limit fields, because they're not quite where we want them to be.
+  //
+  // ip6_plen is already in network order, while ip6_nxt is a single byte and thus needs htons().
+  uint32_t csum = dad_pkt.ip6h.ip6_plen + htons(dad_pkt.ip6h.ip6_nxt);
+  csum = ip_checksum_add(csum, &dad_pkt.ip6h.ip6_src, sizeof(dad_pkt) - 8);
+  dad_pkt.ns.nd_ns_cksum = ip_checksum_finish(csum);
+
+  const struct sockaddr_in6 dst = {
+    .sin6_family = AF_INET6,
+    .sin6_addr = dad_pkt.ip6h.ip6_dst,
+    .sin6_scope_id = if_nametoindex(Global_Clatd_Config.native_ipv6_interface),
+  };
+
+  sendto(fd, &dad_pkt, sizeof(dad_pkt), 0 /*flags*/, (const struct sockaddr *)&dst, sizeof(dst));
+}
+
+/* function: event_loop
+ * reads packets from the tun network interface and passes them down the stack
+ *   tunnel - tun device data
+ */
+void event_loop(struct tun_data *tunnel) {
+  // Apparently some network gear will refuse to perform NS for IPs that aren't DAD'ed,
+  // this would then result in an ipv6-only network with working native ipv6, working
+  // IPv4 via DNS64, but non-functioning IPv4 via CLAT (ie. IPv4 literals + IPv4 only apps).
+  // The kernel itself doesn't do DAD for anycast ips (but does handle IPV6 MLD and handle ND).
+  // So we'll spoof dad here, and yeah, we really should check for a response and in
+  // case of failure pick a different IP.  Seeing as 48-bits of the IP are utterly random
+  // (with the other 16 chosen to guarantee checksum neutrality) this seems like a remote
+  // concern...
+  // TODO: actually perform true DAD
+  send_dad(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
+
+  struct pollfd wait_fd[] = {
+    { tunnel->read_fd6, POLLIN, 0 },
+    { tunnel->fd4, POLLIN, 0 },
+  };
+
+  while (running) {
+    if (poll(wait_fd, ARRAY_SIZE(wait_fd), -1) == -1) {
+      if (errno != EINTR) {
+        logmsg(ANDROID_LOG_WARN, "event_loop/poll returned an error: %s", strerror(errno));
+      }
+    } else {
+      // Call process_packet if the socket has data to be read, but also if an
+      // error is waiting. If we don't call read() after getting POLLERR, a
+      // subsequent poll() will return immediately with POLLERR again,
+      // causing this code to spin in a loop. Calling read() will clear the
+      // socket error flag instead.
+      if (wait_fd[0].revents) process_packet_6_to_4(tunnel);
+      if (wait_fd[1].revents) process_packet_4_to_6(tunnel);
+    }
+  }
+}
diff --git a/clatd/clatd.h b/clatd/clatd.h
new file mode 100644
index 0000000..e170c58
--- /dev/null
+++ b/clatd/clatd.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * clatd.h - main routines used by clatd
+ */
+#ifndef __CLATD_H__
+#define __CLATD_H__
+
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+
+struct tun_data;
+
+// IPv4 header has a u16 total length field, for maximum L3 mtu of 0xFFFF.
+//
+// Translating IPv4 to IPv6 requires removing the IPv4 header (20) and adding
+// an IPv6 header (40), possibly with an extra ipv6 fragment extension header (8).
+//
+// As such the maximum IPv4 L3 mtu size is 0xFFFF (by u16 tot_len field)
+// and the maximum IPv6 L3 mtu size is 0xFFFF + 28 (which is larger)
+//
+// A received non-jumbogram IPv6 frame could potentially be u16 payload_len = 0xFFFF
+// + sizeof ipv6 header = 40, bytes in size.  But such a packet cannot be meaningfully
+// converted to IPv4 (it's too large).  As such the restriction is the same: 0xFFFF + 28
+//
+// (since there's no jumbogram support in IPv4, IPv6 jumbograms cannot be meaningfully
+// converted to IPv4 anyway, and are thus entirely unsupported)
+#define MAXMTU (0xFFFF + 28)
+
+// logcat_hexdump() maximum binary data length, this is the maximum packet size
+// plus some extra space for various headers:
+//   struct tun_pi (4 bytes)
+//   struct virtio_net_hdr (10 bytes)
+//   ethernet (14 bytes), potentially including vlan tag (4) or tags (8 or 12)
+// plus some extra just-in-case headroom, because it doesn't hurt.
+#define MAXDUMPLEN (64 + MAXMTU)
+
+#define CLATD_VERSION "1.7"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+extern volatile sig_atomic_t running;
+
+void event_loop(struct tun_data *tunnel);
+
+/* function: parse_int
+ * parses a string as a decimal/hex/octal signed integer
+ *   str - the string to parse
+ *   out - the signed integer to write to, gets clobbered on failure
+ */
+static inline int parse_int(const char *str, int *out) {
+  char *end_ptr;
+  *out = strtol(str, &end_ptr, 0);
+  return *str && !*end_ptr;
+}
+
+/* function: parse_unsigned
+ * parses a string as a decimal/hex/octal unsigned integer
+ *   str - the string to parse
+ *   out - the unsigned integer to write to, gets clobbered on failure
+ */
+static inline int parse_unsigned(const char *str, unsigned *out) {
+  char *end_ptr;
+  *out = strtoul(str, &end_ptr, 0);
+  return *str && !*end_ptr;
+}
+
+#endif /* __CLATD_H__ */
diff --git a/clatd/clatd_test.cpp b/clatd/clatd_test.cpp
new file mode 100644
index 0000000..0ed5f28
--- /dev/null
+++ b/clatd/clatd_test.cpp
@@ -0,0 +1,835 @@
+/*
+ * Copyright 2014 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.
+ *
+ * clatd_test.cpp - unit tests for clatd
+ */
+
+#include <iostream>
+
+#include <arpa/inet.h>
+#include <linux/if_packet.h>
+#include <netinet/in6.h>
+#include <stdio.h>
+#include <sys/uio.h>
+
+#include <gtest/gtest.h>
+
+#include "netutils/ifc.h"
+#include "tun_interface.h"
+
+extern "C" {
+#include "checksum.h"
+#include "clatd.h"
+#include "config.h"
+#include "translate.h"
+}
+
+// For convenience.
+#define ARRAYSIZE(x) sizeof((x)) / sizeof((x)[0])
+
+using android::net::TunInterface;
+
+// Default translation parameters.
+static const char kIPv4LocalAddr[]  = "192.0.0.4";
+static const char kIPv6LocalAddr[]  = "2001:db8:0:b11::464";
+static const char kIPv6PlatSubnet[] = "64:ff9b::";
+
+// clang-format off
+// Test packet portions. Defined as macros because it's easy to concatenate them to make packets.
+#define IPV4_HEADER(p, c1, c2) \
+    0x45, 0x00,    0,   41,  /* Version=4, IHL=5, ToS=0x80, len=41 */     \
+    0x00, 0x00, 0x40, 0x00,  /* ID=0x0000, flags=IP_DF, offset=0 */       \
+      55,  (p), (c1), (c2),  /* TTL=55, protocol=p, checksum=c1,c2 */     \
+     192,    0,    0,    4,  /* Src=192.0.0.4 */                          \
+       8,    8,    8,    8,  /* Dst=8.8.8.8 */
+#define IPV4_UDP_HEADER IPV4_HEADER(IPPROTO_UDP, 0x73, 0xb0)
+#define IPV4_ICMP_HEADER IPV4_HEADER(IPPROTO_ICMP, 0x73, 0xc0)
+
+#define IPV6_HEADER(p) \
+    0x60, 0x00,    0,    0,  /* Version=6, tclass=0x00, flowlabel=0 */    \
+       0,   21,  (p),   55,  /* plen=11, nxthdr=p, hlim=55 */             \
+    0x20, 0x01, 0x0d, 0xb8,  /* Src=2001:db8:0:b11::464 */                \
+    0x00, 0x00, 0x0b, 0x11,                                               \
+    0x00, 0x00, 0x00, 0x00,                                               \
+    0x00, 0x00, 0x04, 0x64,                                               \
+    0x00, 0x64, 0xff, 0x9b,  /* Dst=64:ff9b::8.8.8.8 */                   \
+    0x00, 0x00, 0x00, 0x00,                                               \
+    0x00, 0x00, 0x00, 0x00,                                               \
+    0x08, 0x08, 0x08, 0x08,
+#define IPV6_UDP_HEADER IPV6_HEADER(IPPROTO_UDP)
+#define IPV6_ICMPV6_HEADER IPV6_HEADER(IPPROTO_ICMPV6)
+
+#define UDP_LEN 21
+#define UDP_HEADER \
+    0xc8, 0x8b,    0,   53,  /* Port 51339->53 */                         \
+    0x00, UDP_LEN, 0,    0,  /* Length 21, checksum empty for now */
+
+#define PAYLOAD 'H', 'e', 'l', 'l', 'o', ' ', 0x4e, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x00
+
+#define IPV4_PING \
+    0x08, 0x00, 0x88, 0xd0,  /* Type 8, code 0, checksum 0x88d0 */        \
+    0xd0, 0x0d, 0x00, 0x03,  /* ID=0xd00d, seq=3 */
+
+#define IPV6_PING \
+    0x80, 0x00, 0xc3, 0x42,  /* Type 128, code 0, checksum 0xc342 */      \
+    0xd0, 0x0d, 0x00, 0x03,  /* ID=0xd00d, seq=3 */
+
+// Macros to return pseudo-headers from packets.
+#define IPV4_PSEUDOHEADER(ip, tlen)                                  \
+  ip[12], ip[13], ip[14], ip[15],        /* Source address      */   \
+  ip[16], ip[17], ip[18], ip[19],        /* Destination address */   \
+  0, ip[9],                              /* 0, protocol         */   \
+  ((tlen) >> 16) & 0xff, (tlen) & 0xff,  /* Transport length */
+
+#define IPV6_PSEUDOHEADER(ip6, protocol, tlen)                       \
+  ip6[8],  ip6[9],  ip6[10], ip6[11],  /* Source address */          \
+  ip6[12], ip6[13], ip6[14], ip6[15],                                \
+  ip6[16], ip6[17], ip6[18], ip6[19],                                \
+  ip6[20], ip6[21], ip6[22], ip6[23],                                \
+  ip6[24], ip6[25], ip6[26], ip6[27],  /* Destination address */     \
+  ip6[28], ip6[29], ip6[30], ip6[31],                                \
+  ip6[32], ip6[33], ip6[34], ip6[35],                                \
+  ip6[36], ip6[37], ip6[38], ip6[39],                                \
+  ((tlen) >> 24) & 0xff,               /* Transport length */        \
+  ((tlen) >> 16) & 0xff,                                             \
+  ((tlen) >> 8) & 0xff,                                              \
+  (tlen) & 0xff,                                                     \
+  0, 0, 0, (protocol),
+
+// A fragmented DNS request.
+static const uint8_t kIPv4Frag1[] = {
+    0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x00, 0x40, 0x11,
+    0x8c, 0x6d, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08,
+    0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47,
+    0x01, 0x00, 0x00, 0x01, 0x00, 0x00
+};
+static const uint8_t kIPv4Frag2[] = {
+    0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x02, 0x40, 0x11,
+    0x8c, 0x6b, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08,
+    0x00, 0x00, 0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06,
+    0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65
+};
+static const uint8_t kIPv4Frag3[] = {
+    0x45, 0x00, 0x00, 0x1d, 0xfe, 0x47, 0x00, 0x04, 0x40, 0x11,
+    0xac, 0x70, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08,
+    0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01
+};
+static const uint8_t *kIPv4Fragments[] = { kIPv4Frag1, kIPv4Frag2, kIPv4Frag3 };
+static const size_t kIPv4FragLengths[] = { sizeof(kIPv4Frag1), sizeof(kIPv4Frag2),
+                                           sizeof(kIPv4Frag3) };
+
+static const uint8_t kIPv6Frag1[] = {
+    0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01,
+    0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
+    0x11, 0x00, 0x00, 0x01, 0x00, 0x00, 0xfe, 0x47, 0x14, 0x5d,
+    0x00, 0x35, 0x00, 0x29, 0xeb, 0x91, 0x50, 0x47, 0x01, 0x00,
+    0x00, 0x01, 0x00, 0x00
+};
+
+static const uint8_t kIPv6Frag2[] = {
+    0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01,
+    0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
+    0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0xfe, 0x47, 0x00, 0x00,
+    0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f,
+    0x6f, 0x67, 0x6c, 0x65
+};
+
+static const uint8_t kIPv6Frag3[] = {
+    0x60, 0x00, 0x00, 0x00, 0x00, 0x11, 0x2c, 0x40, 0x20, 0x01,
+    0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
+    0x11, 0x00, 0x00, 0x20, 0x00, 0x00, 0xfe, 0x47, 0x03, 0x63,
+    0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01
+};
+static const uint8_t *kIPv6Fragments[] = { kIPv6Frag1, kIPv6Frag2, kIPv6Frag3 };
+static const size_t kIPv6FragLengths[] = { sizeof(kIPv6Frag1), sizeof(kIPv6Frag2),
+                                           sizeof(kIPv6Frag3) };
+
+static const uint8_t kReassembledIPv4[] = {
+    0x45, 0x00, 0x00, 0x3d, 0xfe, 0x47, 0x00, 0x00, 0x40, 0x11,
+    0xac, 0x54, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08,
+    0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47,
+    0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f, 0x6f, 0x67,
+    0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00,
+    0x01
+};
+// clang-format on
+
+// Expected checksums.
+static const uint32_t kUdpPartialChecksum     = 0xd5c8;
+static const uint32_t kPayloadPartialChecksum = 0x31e9c;
+static const uint16_t kUdpV4Checksum          = 0xd0c7;
+static const uint16_t kUdpV6Checksum          = 0xa74a;
+
+uint8_t ip_version(const uint8_t *packet) {
+  uint8_t version = packet[0] >> 4;
+  return version;
+}
+
+int is_ipv4_fragment(struct iphdr *ip) {
+  // A packet is a fragment if its fragment offset is nonzero or if the MF flag is set.
+  return ntohs(ip->frag_off) & (IP_OFFMASK | IP_MF);
+}
+
+int is_ipv6_fragment(struct ip6_hdr *ip6, size_t len) {
+  if (ip6->ip6_nxt != IPPROTO_FRAGMENT) {
+    return 0;
+  }
+  struct ip6_frag *frag = (struct ip6_frag *)(ip6 + 1);
+  return len >= sizeof(*ip6) + sizeof(*frag) &&
+         (frag->ip6f_offlg & (IP6F_OFF_MASK | IP6F_MORE_FRAG));
+}
+
+int ipv4_fragment_offset(struct iphdr *ip) {
+  return ntohs(ip->frag_off) & IP_OFFMASK;
+}
+
+int ipv6_fragment_offset(struct ip6_frag *frag) {
+  return ntohs((frag->ip6f_offlg & IP6F_OFF_MASK) >> 3);
+}
+
+void check_packet(const uint8_t *packet, size_t len, const char *msg) {
+  void *payload;
+  size_t payload_length    = 0;
+  uint32_t pseudo_checksum = 0;
+  uint8_t protocol         = 0;
+  int version              = ip_version(packet);
+  switch (version) {
+    case 4: {
+      struct iphdr *ip = (struct iphdr *)packet;
+      ASSERT_GE(len, sizeof(*ip)) << msg << ": IPv4 packet shorter than IPv4 header\n";
+      EXPECT_EQ(5, ip->ihl) << msg << ": Unsupported IP header length\n";
+      EXPECT_EQ(len, ntohs(ip->tot_len)) << msg << ": Incorrect IPv4 length\n";
+      EXPECT_EQ(0, ip_checksum(ip, sizeof(*ip))) << msg << ": Incorrect IP checksum\n";
+      protocol = ip->protocol;
+      payload  = ip + 1;
+      if (!is_ipv4_fragment(ip)) {
+        payload_length  = len - sizeof(*ip);
+        pseudo_checksum = ipv4_pseudo_header_checksum(ip, payload_length);
+      }
+      ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMP)
+        << msg << ": Unsupported IPv4 protocol " << protocol << "\n";
+      break;
+    }
+    case 6: {
+      struct ip6_hdr *ip6 = (struct ip6_hdr *)packet;
+      ASSERT_GE(len, sizeof(*ip6)) << msg << ": IPv6 packet shorter than IPv6 header\n";
+      EXPECT_EQ(len - sizeof(*ip6), htons(ip6->ip6_plen)) << msg << ": Incorrect IPv6 length\n";
+
+      if (ip6->ip6_nxt == IPPROTO_FRAGMENT) {
+        struct ip6_frag *frag = (struct ip6_frag *)(ip6 + 1);
+        ASSERT_GE(len, sizeof(*ip6) + sizeof(*frag))
+          << msg << ": IPv6 fragment: short fragment header\n";
+        protocol = frag->ip6f_nxt;
+        payload  = frag + 1;
+        // Even though the packet has a Fragment header, it might not be a fragment.
+        if (!is_ipv6_fragment(ip6, len)) {
+          payload_length = len - sizeof(*ip6) - sizeof(*frag);
+        }
+      } else {
+        // Since there are no extension headers except Fragment, this must be the payload.
+        protocol       = ip6->ip6_nxt;
+        payload        = ip6 + 1;
+        payload_length = len - sizeof(*ip6);
+      }
+      ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMPV6)
+        << msg << ": Unsupported IPv6 next header " << protocol;
+      if (payload_length) {
+        pseudo_checksum = ipv6_pseudo_header_checksum(ip6, payload_length, protocol);
+      }
+      break;
+    }
+    default:
+      FAIL() << msg << ": Unsupported IP version " << version << "\n";
+      return;
+  }
+
+  // If we understand the payload, verify the checksum.
+  if (payload_length) {
+    uint16_t checksum;
+    switch (protocol) {
+      case IPPROTO_UDP:
+      case IPPROTO_TCP:
+      case IPPROTO_ICMPV6:
+        checksum = ip_checksum_finish(ip_checksum_add(pseudo_checksum, payload, payload_length));
+        break;
+      case IPPROTO_ICMP:
+        checksum = ip_checksum(payload, payload_length);
+        break;
+      default:
+        checksum = 0;  // Don't check.
+        break;
+    }
+    EXPECT_EQ(0, checksum) << msg << ": Incorrect transport checksum\n";
+  }
+
+  if (protocol == IPPROTO_UDP) {
+    struct udphdr *udp = (struct udphdr *)payload;
+    EXPECT_NE(0, udp->check) << msg << ": UDP checksum 0 should be 0xffff";
+    // If this is not a fragment, check the UDP length field.
+    if (payload_length) {
+      EXPECT_EQ(payload_length, ntohs(udp->len)) << msg << ": Incorrect UDP length\n";
+    }
+  }
+}
+
+void reassemble_packet(const uint8_t **fragments, const size_t lengths[], int numpackets,
+                       uint8_t *reassembled, size_t *reassembled_len, const char *msg) {
+  struct iphdr *ip    = nullptr;
+  struct ip6_hdr *ip6 = nullptr;
+  size_t total_length, pos = 0;
+  uint8_t protocol = 0;
+  uint8_t version  = ip_version(fragments[0]);
+
+  for (int i = 0; i < numpackets; i++) {
+    const uint8_t *packet = fragments[i];
+    int len               = lengths[i];
+    int headersize, payload_offset;
+
+    ASSERT_EQ(ip_version(packet), version) << msg << ": Inconsistent fragment versions\n";
+    check_packet(packet, len, "Fragment sanity check");
+
+    switch (version) {
+      case 4: {
+        struct iphdr *ip_orig = (struct iphdr *)packet;
+        headersize            = sizeof(*ip_orig);
+        ASSERT_TRUE(is_ipv4_fragment(ip_orig))
+          << msg << ": IPv4 fragment #" << i + 1 << " not a fragment\n";
+        ASSERT_EQ(pos, ipv4_fragment_offset(ip_orig) * 8 + ((i != 0) ? sizeof(*ip) : 0))
+          << msg << ": IPv4 fragment #" << i + 1 << ": inconsistent offset\n";
+
+        headersize     = sizeof(*ip_orig);
+        payload_offset = headersize;
+        if (pos == 0) {
+          ip = (struct iphdr *)reassembled;
+        }
+        break;
+      }
+      case 6: {
+        struct ip6_hdr *ip6_orig = (struct ip6_hdr *)packet;
+        struct ip6_frag *frag    = (struct ip6_frag *)(ip6_orig + 1);
+        ASSERT_TRUE(is_ipv6_fragment(ip6_orig, len))
+          << msg << ": IPv6 fragment #" << i + 1 << " not a fragment\n";
+        ASSERT_EQ(pos, ipv6_fragment_offset(frag) * 8 + ((i != 0) ? sizeof(*ip6) : 0))
+          << msg << ": IPv6 fragment #" << i + 1 << ": inconsistent offset\n";
+
+        headersize     = sizeof(*ip6_orig);
+        payload_offset = sizeof(*ip6_orig) + sizeof(*frag);
+        if (pos == 0) {
+          ip6      = (struct ip6_hdr *)reassembled;
+          protocol = frag->ip6f_nxt;
+        }
+        break;
+      }
+      default:
+        FAIL() << msg << ": Invalid IP version << " << version;
+    }
+
+    // If this is the first fragment, copy the header.
+    if (pos == 0) {
+      ASSERT_LT(headersize, (int)*reassembled_len) << msg << ": Reassembly buffer too small\n";
+      memcpy(reassembled, packet, headersize);
+      total_length = headersize;
+      pos += headersize;
+    }
+
+    // Copy the payload.
+    int payload_length = len - payload_offset;
+    total_length += payload_length;
+    ASSERT_LT(total_length, *reassembled_len) << msg << ": Reassembly buffer too small\n";
+    memcpy(reassembled + pos, packet + payload_offset, payload_length);
+    pos += payload_length;
+  }
+
+  // Fix up the reassembled headers to reflect fragmentation and length (and IPv4 checksum).
+  ASSERT_EQ(total_length, pos) << msg << ": Reassembled packet length incorrect\n";
+  if (ip) {
+    ip->frag_off &= ~htons(IP_MF);
+    ip->tot_len = htons(total_length);
+    ip->check   = 0;
+    ip->check   = ip_checksum(ip, sizeof(*ip));
+    ASSERT_FALSE(is_ipv4_fragment(ip)) << msg << ": reassembled IPv4 packet is a fragment!\n";
+  }
+  if (ip6) {
+    ip6->ip6_nxt  = protocol;
+    ip6->ip6_plen = htons(total_length - sizeof(*ip6));
+    ASSERT_FALSE(is_ipv6_fragment(ip6, ip6->ip6_plen))
+      << msg << ": reassembled IPv6 packet is a fragment!\n";
+  }
+
+  *reassembled_len = total_length;
+}
+
+void check_data_matches(const void *expected, const void *actual, size_t len, const char *msg) {
+  if (memcmp(expected, actual, len)) {
+    // Hex dump, 20 bytes per line, one space between bytes (1 byte = 3 chars), indented by 4.
+    int hexdump_len = len * 3 + (len / 20 + 1) * 5;
+    char expected_hexdump[hexdump_len], actual_hexdump[hexdump_len];
+    unsigned pos = 0;
+    for (unsigned i = 0; i < len; i++) {
+      if (i % 20 == 0) {
+        snprintf(expected_hexdump + pos, hexdump_len - pos, "\n   ");
+        snprintf(actual_hexdump + pos, hexdump_len - pos, "\n   ");
+        pos += 4;
+      }
+      snprintf(expected_hexdump + pos, hexdump_len - pos, " %02x", ((uint8_t *)expected)[i]);
+      snprintf(actual_hexdump + pos, hexdump_len - pos, " %02x", ((uint8_t *)actual)[i]);
+      pos += 3;
+    }
+    FAIL() << msg << ": Data doesn't match"
+           << "\n  Expected:" << (char *) expected_hexdump
+           << "\n  Actual:" << (char *) actual_hexdump << "\n";
+  }
+}
+
+void fix_udp_checksum(uint8_t *packet) {
+  uint32_t pseudo_checksum;
+  uint8_t version = ip_version(packet);
+  struct udphdr *udp;
+  switch (version) {
+    case 4: {
+      struct iphdr *ip = (struct iphdr *)packet;
+      udp              = (struct udphdr *)(ip + 1);
+      pseudo_checksum  = ipv4_pseudo_header_checksum(ip, ntohs(udp->len));
+      break;
+    }
+    case 6: {
+      struct ip6_hdr *ip6 = (struct ip6_hdr *)packet;
+      udp                 = (struct udphdr *)(ip6 + 1);
+      pseudo_checksum     = ipv6_pseudo_header_checksum(ip6, ntohs(udp->len), IPPROTO_UDP);
+      break;
+    }
+    default:
+      FAIL() << "unsupported IP version" << version << "\n";
+      return;
+  }
+
+  udp->check = 0;
+  udp->check = ip_checksum_finish(ip_checksum_add(pseudo_checksum, udp, ntohs(udp->len)));
+}
+
+// Testing stub for send_rawv6. The real version uses sendmsg() with a
+// destination IPv6 address, and attempting to call that on our test socketpair
+// fd results in EINVAL.
+extern "C" void send_rawv6(int fd, clat_packet out, int iov_len) { writev(fd, out, iov_len); }
+
+void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *out, size_t *outlen,
+                         const char *msg) {
+  int fds[2];
+  if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, fds)) {
+    abort();
+  }
+
+  char foo[512];
+  snprintf(foo, sizeof(foo), "%s: Invalid original packet", msg);
+  check_packet(original, original_len, foo);
+
+  int read_fd, write_fd;
+  uint16_t expected_proto;
+  int version = ip_version(original);
+  switch (version) {
+    case 4:
+      expected_proto = htons(ETH_P_IPV6);
+      read_fd        = fds[1];
+      write_fd       = fds[0];
+      break;
+    case 6:
+      expected_proto = htons(ETH_P_IP);
+      read_fd        = fds[0];
+      write_fd       = fds[1];
+      break;
+    default:
+      FAIL() << msg << ": Unsupported IP version " << version << "\n";
+      break;
+  }
+
+  translate_packet(write_fd, (version == 4), original, original_len);
+
+  snprintf(foo, sizeof(foo), "%s: Invalid translated packet", msg);
+  if (version == 6) {
+    // Translating to IPv4. Expect a tun header.
+    struct tun_pi new_tun_header;
+    struct iovec iov[] = {
+      { &new_tun_header, sizeof(new_tun_header) },
+      { out, *outlen },
+    };
+
+    int len = readv(read_fd, iov, 2);
+    if (len > (int)sizeof(new_tun_header)) {
+      ASSERT_LT((size_t)len, *outlen) << msg << ": Translated packet buffer too small\n";
+      EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n";
+      *outlen = len - sizeof(new_tun_header);
+      check_packet(out, *outlen, msg);
+    } else {
+      FAIL() << msg << ": Packet was not translated: len=" << len;
+      *outlen = 0;
+    }
+  } else {
+    // Translating to IPv6. Expect raw packet.
+    *outlen = read(read_fd, out, *outlen);
+    check_packet(out, *outlen, msg);
+  }
+}
+
+void check_translated_packet(const uint8_t *original, size_t original_len, const uint8_t *expected,
+                             size_t expected_len, const char *msg) {
+  uint8_t translated[MAXMTU];
+  size_t translated_len = sizeof(translated);
+  do_translate_packet(original, original_len, translated, &translated_len, msg);
+  EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n";
+  check_data_matches(expected, translated, translated_len, msg);
+}
+
+void check_fragment_translation(const uint8_t *original[], const size_t original_lengths[],
+                                const uint8_t *expected[], const size_t expected_lengths[],
+                                int numfragments, const char *msg) {
+  for (int i = 0; i < numfragments; i++) {
+    // Check that each of the fragments translates as expected.
+    char frag_msg[512];
+    snprintf(frag_msg, sizeof(frag_msg), "%s: fragment #%d", msg, i + 1);
+    check_translated_packet(original[i], original_lengths[i], expected[i], expected_lengths[i],
+                            frag_msg);
+  }
+
+  // Sanity check that reassembling the original and translated fragments produces valid packets.
+  uint8_t reassembled[MAXMTU];
+  size_t reassembled_len = sizeof(reassembled);
+  reassemble_packet(original, original_lengths, numfragments, reassembled, &reassembled_len, msg);
+  check_packet(reassembled, reassembled_len, msg);
+
+  uint8_t translated[MAXMTU];
+  size_t translated_len = sizeof(translated);
+  do_translate_packet(reassembled, reassembled_len, translated, &translated_len, msg);
+  check_packet(translated, translated_len, msg);
+}
+
+int get_transport_checksum(const uint8_t *packet) {
+  struct iphdr *ip;
+  struct ip6_hdr *ip6;
+  uint8_t protocol;
+  const void *payload;
+
+  int version = ip_version(packet);
+  switch (version) {
+    case 4:
+      ip = (struct iphdr *)packet;
+      if (is_ipv4_fragment(ip)) {
+        return -1;
+      }
+      protocol = ip->protocol;
+      payload  = ip + 1;
+      break;
+    case 6:
+      ip6      = (struct ip6_hdr *)packet;
+      protocol = ip6->ip6_nxt;
+      payload  = ip6 + 1;
+      break;
+    default:
+      return -1;
+  }
+
+  switch (protocol) {
+    case IPPROTO_UDP:
+      return ((struct udphdr *)payload)->check;
+
+    case IPPROTO_TCP:
+      return ((struct tcphdr *)payload)->check;
+
+    case IPPROTO_FRAGMENT:
+    default:
+      return -1;
+  }
+}
+
+class ClatdTest : public ::testing::Test {
+ protected:
+  static TunInterface sTun;
+
+  virtual void SetUp() {
+    inet_pton(AF_INET, kIPv4LocalAddr, &Global_Clatd_Config.ipv4_local_subnet);
+    inet_pton(AF_INET6, kIPv6PlatSubnet, &Global_Clatd_Config.plat_subnet);
+    memset(&Global_Clatd_Config.ipv6_local_subnet, 0, sizeof(in6_addr));
+    Global_Clatd_Config.native_ipv6_interface = const_cast<char *>(sTun.name().c_str());
+  }
+
+  // Static because setting up the tun interface takes about 40ms.
+  static void SetUpTestCase() { ASSERT_EQ(0, sTun.init()); }
+
+  // Closing the socket removes the interface and IP addresses.
+  static void TearDownTestCase() { sTun.destroy(); }
+};
+
+TunInterface ClatdTest::sTun;
+
+void expect_ipv6_addr_equal(struct in6_addr *expected, struct in6_addr *actual) {
+  if (!IN6_ARE_ADDR_EQUAL(expected, actual)) {
+    char expected_str[INET6_ADDRSTRLEN], actual_str[INET6_ADDRSTRLEN];
+    inet_ntop(AF_INET6, expected, expected_str, sizeof(expected_str));
+    inet_ntop(AF_INET6, actual, actual_str, sizeof(actual_str));
+    FAIL()
+        << "Unexpected IPv6 address:: "
+        << "\n  Expected: " << expected_str
+        << "\n  Actual:   " << actual_str
+        << "\n";
+  }
+}
+
+TEST_F(ClatdTest, TestIPv6PrefixEqual) {
+  EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet,
+                                &Global_Clatd_Config.plat_subnet));
+  EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet,
+                                 &Global_Clatd_Config.ipv6_local_subnet));
+
+  struct in6_addr subnet2 = Global_Clatd_Config.ipv6_local_subnet;
+  EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2));
+  EXPECT_TRUE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet));
+
+  subnet2.s6_addr[6] = 0xff;
+  EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2));
+  EXPECT_FALSE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet));
+}
+
+TEST_F(ClatdTest, DataSanitycheck) {
+  // Sanity checks the data.
+  uint8_t v4_header[] = { IPV4_UDP_HEADER };
+  ASSERT_EQ(sizeof(struct iphdr), sizeof(v4_header)) << "Test IPv4 header: incorrect length\n";
+
+  uint8_t v6_header[] = { IPV6_UDP_HEADER };
+  ASSERT_EQ(sizeof(struct ip6_hdr), sizeof(v6_header)) << "Test IPv6 header: incorrect length\n";
+
+  uint8_t udp_header[] = { UDP_HEADER };
+  ASSERT_EQ(sizeof(struct udphdr), sizeof(udp_header)) << "Test UDP header: incorrect length\n";
+
+  // Sanity checks check_packet.
+  struct udphdr *udp;
+  uint8_t v4_udp_packet[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD };
+  udp                     = (struct udphdr *)(v4_udp_packet + sizeof(struct iphdr));
+  fix_udp_checksum(v4_udp_packet);
+  ASSERT_EQ(kUdpV4Checksum, udp->check) << "UDP/IPv4 packet checksum sanity check\n";
+  check_packet(v4_udp_packet, sizeof(v4_udp_packet), "UDP/IPv4 packet sanity check");
+
+  uint8_t v6_udp_packet[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD };
+  udp                     = (struct udphdr *)(v6_udp_packet + sizeof(struct ip6_hdr));
+  fix_udp_checksum(v6_udp_packet);
+  ASSERT_EQ(kUdpV6Checksum, udp->check) << "UDP/IPv6 packet checksum sanity check\n";
+  check_packet(v6_udp_packet, sizeof(v6_udp_packet), "UDP/IPv6 packet sanity check");
+
+  uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD };
+  check_packet(ipv4_ping, sizeof(ipv4_ping), "IPv4 ping sanity check");
+
+  uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD };
+  check_packet(ipv6_ping, sizeof(ipv6_ping), "IPv6 ping sanity check");
+
+  // Sanity checks reassemble_packet.
+  uint8_t reassembled[MAXMTU];
+  size_t total_length = sizeof(reassembled);
+  reassemble_packet(kIPv4Fragments, kIPv4FragLengths, ARRAYSIZE(kIPv4Fragments), reassembled,
+                    &total_length, "Reassembly sanity check");
+  check_packet(reassembled, total_length, "IPv4 Reassembled packet is valid");
+  ASSERT_EQ(sizeof(kReassembledIPv4), total_length) << "IPv4 reassembly sanity check: length\n";
+  ASSERT_TRUE(!is_ipv4_fragment((struct iphdr *)reassembled))
+    << "Sanity check: reassembled packet is a fragment!\n";
+  check_data_matches(kReassembledIPv4, reassembled, total_length, "IPv4 reassembly sanity check");
+
+  total_length = sizeof(reassembled);
+  reassemble_packet(kIPv6Fragments, kIPv6FragLengths, ARRAYSIZE(kIPv6Fragments), reassembled,
+                    &total_length, "IPv6 reassembly sanity check");
+  ASSERT_TRUE(!is_ipv6_fragment((struct ip6_hdr *)reassembled, total_length))
+    << "Sanity check: reassembled packet is a fragment!\n";
+  check_packet(reassembled, total_length, "IPv6 Reassembled packet is valid");
+}
+
+TEST_F(ClatdTest, PseudoChecksum) {
+  uint32_t pseudo_checksum;
+
+  uint8_t v4_header[]        = { IPV4_UDP_HEADER };
+  uint8_t v4_pseudo_header[] = { IPV4_PSEUDOHEADER(v4_header, UDP_LEN) };
+  pseudo_checksum            = ipv4_pseudo_header_checksum((struct iphdr *)v4_header, UDP_LEN);
+  EXPECT_EQ(ip_checksum_finish(pseudo_checksum),
+            ip_checksum(v4_pseudo_header, sizeof(v4_pseudo_header)))
+    << "ipv4_pseudo_header_checksum incorrect\n";
+
+  uint8_t v6_header[]        = { IPV6_UDP_HEADER };
+  uint8_t v6_pseudo_header[] = { IPV6_PSEUDOHEADER(v6_header, IPPROTO_UDP, UDP_LEN) };
+  pseudo_checksum = ipv6_pseudo_header_checksum((struct ip6_hdr *)v6_header, UDP_LEN, IPPROTO_UDP);
+  EXPECT_EQ(ip_checksum_finish(pseudo_checksum),
+            ip_checksum(v6_pseudo_header, sizeof(v6_pseudo_header)))
+    << "ipv6_pseudo_header_checksum incorrect\n";
+}
+
+TEST_F(ClatdTest, TransportChecksum) {
+  uint8_t udphdr[]  = { UDP_HEADER };
+  uint8_t payload[] = { PAYLOAD };
+  EXPECT_EQ(kUdpPartialChecksum, ip_checksum_add(0, udphdr, sizeof(udphdr)))
+    << "UDP partial checksum\n";
+  EXPECT_EQ(kPayloadPartialChecksum, ip_checksum_add(0, payload, sizeof(payload)))
+    << "Payload partial checksum\n";
+
+  uint8_t ip[]             = { IPV4_UDP_HEADER };
+  uint8_t ip6[]            = { IPV6_UDP_HEADER };
+  uint32_t ipv4_pseudo_sum = ipv4_pseudo_header_checksum((struct iphdr *)ip, UDP_LEN);
+  uint32_t ipv6_pseudo_sum =
+    ipv6_pseudo_header_checksum((struct ip6_hdr *)ip6, UDP_LEN, IPPROTO_UDP);
+
+  EXPECT_NE(0, ipv4_pseudo_sum);
+  EXPECT_NE(0, ipv6_pseudo_sum);
+  EXPECT_EQ(0x3ad0U, ipv4_pseudo_sum % 0xFFFF) << "IPv4 pseudo-checksum sanity check\n";
+  EXPECT_EQ(0x644dU, ipv6_pseudo_sum % 0xFFFF) << "IPv6 pseudo-checksum sanity check\n";
+  EXPECT_EQ(
+      kUdpV4Checksum,
+      ip_checksum_finish(ipv4_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum))
+      << "Unexpected UDP/IPv4 checksum\n";
+  EXPECT_EQ(
+      kUdpV6Checksum,
+      ip_checksum_finish(ipv6_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum))
+      << "Unexpected UDP/IPv6 checksum\n";
+
+  EXPECT_EQ(kUdpV6Checksum,
+      ip_checksum_adjust(kUdpV4Checksum, ipv4_pseudo_sum, ipv6_pseudo_sum))
+      << "Adjust IPv4/UDP checksum to IPv6\n";
+  EXPECT_EQ(kUdpV4Checksum,
+      ip_checksum_adjust(kUdpV6Checksum, ipv6_pseudo_sum, ipv4_pseudo_sum))
+      << "Adjust IPv6/UDP checksum to IPv4\n";
+}
+
+TEST_F(ClatdTest, AdjustChecksum) {
+  struct checksum_data {
+    uint16_t checksum;
+    uint32_t old_hdr_sum;
+    uint32_t new_hdr_sum;
+    uint16_t result;
+  } DATA[] = {
+    { 0x1423, 0xb8ec, 0x2d757, 0xf5b5 },
+    { 0xf5b5, 0x2d757, 0xb8ec, 0x1423 },
+    { 0xdd2f, 0x5555, 0x3285, 0x0000 },
+    { 0x1215, 0x5560, 0x15560 + 20, 0x1200 },
+    { 0xd0c7, 0x3ad0, 0x2644b, 0xa74a },
+  };
+  unsigned i = 0;
+
+  for (i = 0; i < ARRAYSIZE(DATA); i++) {
+    struct checksum_data *data = DATA + i;
+    uint16_t result = ip_checksum_adjust(data->checksum, data->old_hdr_sum, data->new_hdr_sum);
+    EXPECT_EQ(result, data->result)
+        << "Incorrect checksum" << std::showbase << std::hex
+        << "\n  Expected: " << data->result
+        << "\n  Actual:   " << result
+        << "\n    checksum=" << data->checksum
+        << " old_sum=" << data->old_hdr_sum << " new_sum=" << data->new_hdr_sum << "\n";
+  }
+}
+
+TEST_F(ClatdTest, Translate) {
+  // This test uses hardcoded packets so the clatd address must be fixed.
+  inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet);
+
+  uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD };
+  uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD };
+  fix_udp_checksum(udp_ipv4);
+  fix_udp_checksum(udp_ipv6);
+  check_translated_packet(udp_ipv4, sizeof(udp_ipv4), udp_ipv6, sizeof(udp_ipv6),
+                          "UDP/IPv4 -> UDP/IPv6 translation");
+  check_translated_packet(udp_ipv6, sizeof(udp_ipv6), udp_ipv4, sizeof(udp_ipv4),
+                          "UDP/IPv6 -> UDP/IPv4 translation");
+
+  uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD };
+  uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD };
+  check_translated_packet(ipv4_ping, sizeof(ipv4_ping), ipv6_ping, sizeof(ipv6_ping),
+                          "ICMP->ICMPv6 translation");
+  check_translated_packet(ipv6_ping, sizeof(ipv6_ping), ipv4_ping, sizeof(ipv4_ping),
+                          "ICMPv6->ICMP translation");
+}
+
+TEST_F(ClatdTest, Fragmentation) {
+  // This test uses hardcoded packets so the clatd address must be fixed.
+  inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet);
+
+  check_fragment_translation(kIPv4Fragments, kIPv4FragLengths, kIPv6Fragments, kIPv6FragLengths,
+                             ARRAYSIZE(kIPv4Fragments), "IPv4->IPv6 fragment translation");
+
+  check_fragment_translation(kIPv6Fragments, kIPv6FragLengths, kIPv4Fragments, kIPv4FragLengths,
+                             ARRAYSIZE(kIPv6Fragments), "IPv6->IPv4 fragment translation");
+}
+
+// picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix
+void gen_random_iid(struct in6_addr *myaddr, struct in_addr *ipv4_local_subnet,
+                    struct in6_addr *plat_subnet) {
+  // Fill last 8 bytes of IPv6 address with random bits.
+  arc4random_buf(&myaddr->s6_addr[8], 8);
+
+  // Make the IID checksum-neutral. That is, make it so that:
+  //   checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+  // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+  //   checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+  // Do this by adjusting the two bytes in the middle of the IID.
+
+  uint16_t middlebytes = (myaddr->s6_addr[11] << 8) + myaddr->s6_addr[12];
+
+  uint32_t c1 = ip_checksum_add(0, ipv4_local_subnet, sizeof(*ipv4_local_subnet));
+  uint32_t c2 = ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) +
+                ip_checksum_add(0, myaddr, sizeof(*myaddr));
+
+  uint16_t delta      = ip_checksum_adjust(middlebytes, c1, c2);
+  myaddr->s6_addr[11] = delta >> 8;
+  myaddr->s6_addr[12] = delta & 0xff;
+}
+
+void check_translate_checksum_neutral(const uint8_t *original, size_t original_len,
+                                      size_t expected_len, const char *msg) {
+  uint8_t translated[MAXMTU];
+  size_t translated_len = sizeof(translated);
+  do_translate_packet(original, original_len, translated, &translated_len, msg);
+  EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n";
+  // do_translate_packet already checks packets for validity and verifies the checksum.
+  int original_check   = get_transport_checksum(original);
+  int translated_check = get_transport_checksum(translated);
+  ASSERT_NE(-1, original_check);
+  ASSERT_NE(-1, translated_check);
+  ASSERT_EQ(original_check, translated_check)
+    << "Not checksum neutral: original and translated checksums differ\n";
+}
+
+TEST_F(ClatdTest, TranslateChecksumNeutral) {
+  // Generate a random clat IPv6 address and check that translation is checksum-neutral.
+  ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54",
+                        &Global_Clatd_Config.ipv6_local_subnet));
+
+  gen_random_iid(&Global_Clatd_Config.ipv6_local_subnet, &Global_Clatd_Config.ipv4_local_subnet,
+                 &Global_Clatd_Config.plat_subnet);
+
+  ASSERT_NE(htonl((uint32_t)0x00000464), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+  ASSERT_NE((uint32_t)0, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+
+  // Check that translating UDP packets is checksum-neutral. First, IPv4.
+  uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD };
+  fix_udp_checksum(udp_ipv4);
+  check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20,
+                                   "UDP/IPv4 -> UDP/IPv6 checksum neutral");
+
+  // Now try IPv6.
+  uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD };
+  // The test packet uses the static IID, not the random IID. Fix up the source address.
+  struct ip6_hdr *ip6 = (struct ip6_hdr *)udp_ipv6;
+  memcpy(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet, sizeof(ip6->ip6_src));
+  fix_udp_checksum(udp_ipv6);
+  check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20,
+                                   "UDP/IPv4 -> UDP/IPv6 checksum neutral");
+}
diff --git a/clatd/common.h b/clatd/common.h
new file mode 100644
index 0000000..e9551ee
--- /dev/null
+++ b/clatd/common.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 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.
+ *
+ * common.h - common definitions
+ */
+#ifndef __CLATD_COMMON_H__
+#define __CLATD_COMMON_H__
+
+#include <sys/uio.h>
+
+// A clat_packet is an array of iovec structures representing a packet that we are translating.
+// The CLAT_POS_XXX constants represent the array indices within the clat_packet that contain
+// specific parts of the packet. The packet_* functions operate on all the packet segments past a
+// given position.
+typedef enum {
+  CLAT_POS_TUNHDR,
+  CLAT_POS_IPHDR,
+  CLAT_POS_FRAGHDR,
+  CLAT_POS_TRANSPORTHDR,
+  CLAT_POS_ICMPERR_IPHDR,
+  CLAT_POS_ICMPERR_FRAGHDR,
+  CLAT_POS_ICMPERR_TRANSPORTHDR,
+  CLAT_POS_PAYLOAD,
+  CLAT_POS_MAX
+} clat_packet_index;
+typedef struct iovec clat_packet[CLAT_POS_MAX];
+
+#endif /* __CLATD_COMMON_H__ */
diff --git a/clatd/config.h b/clatd/config.h
new file mode 100644
index 0000000..9612192
--- /dev/null
+++ b/clatd/config.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * config.h - configuration settings
+ */
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+#include <linux/if.h>
+#include <netinet/in.h>
+
+struct tun_data {
+  char device4[IFNAMSIZ];
+  int read_fd6, write_fd6, fd4;
+};
+
+struct clat_config {
+  struct in6_addr ipv6_local_subnet;
+  struct in_addr ipv4_local_subnet;
+  struct in6_addr plat_subnet;
+  const char *native_ipv6_interface;
+};
+
+extern struct clat_config Global_Clatd_Config;
+
+/* function: ipv6_prefix_equal
+ * compares the /64 prefixes of two ipv6 addresses.
+ *   a1 - first address
+ *   a2 - second address
+ *   returns: 0 if the subnets are different, 1 if they are the same.
+ */
+static inline int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2) {
+  return !memcmp(a1, a2, 8);
+}
+
+#endif /* __CONFIG_H__ */
diff --git a/clatd/debug.h b/clatd/debug.h
new file mode 100644
index 0000000..8e09672
--- /dev/null
+++ b/clatd/debug.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * debug.h - debug settings
+ */
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+// set to 1 to enable debug logging and packet dumping.
+#define CLAT_DEBUG 0
+
+#endif /* __DEBUG_H__ */
diff --git a/clatd/dump.c b/clatd/dump.c
new file mode 100644
index 0000000..dff3d5e
--- /dev/null
+++ b/clatd/dump.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * dump.c - print various headers for debugging
+ */
+#include "dump.h"
+
+#include <arpa/inet.h>
+#include <linux/icmp.h>
+#include <linux/if_tun.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "checksum.h"
+#include "clatd.h"
+#include "debug.h"
+#include "logging.h"
+
+#if CLAT_DEBUG
+
+/* print ip header */
+void dump_ip(struct iphdr *header) {
+  u_int16_t frag_flags;
+  char addrstr[INET6_ADDRSTRLEN];
+
+  frag_flags = ntohs(header->frag_off);
+
+  printf("IP packet\n");
+  printf("header_len = %x\n", header->ihl);
+  printf("version = %x\n", header->version);
+  printf("tos = %x\n", header->tos);
+  printf("tot_len = %x\n", ntohs(header->tot_len));
+  printf("id = %x\n", ntohs(header->id));
+  printf("frag: ");
+  if (frag_flags & IP_RF) {
+    printf("(RF) ");
+  }
+  if (frag_flags & IP_DF) {
+    printf("DF ");
+  }
+  if (frag_flags & IP_MF) {
+    printf("MF ");
+  }
+  printf("offset = %x\n", frag_flags & IP_OFFMASK);
+  printf("ttl = %x\n", header->ttl);
+  printf("protocol = %x\n", header->protocol);
+  printf("checksum = %x\n", ntohs(header->check));
+  inet_ntop(AF_INET, &header->saddr, addrstr, sizeof(addrstr));
+  printf("saddr = %s\n", addrstr);
+  inet_ntop(AF_INET, &header->daddr, addrstr, sizeof(addrstr));
+  printf("daddr = %s\n", addrstr);
+}
+
+/* print ip6 header */
+void dump_ip6(struct ip6_hdr *header) {
+  char addrstr[INET6_ADDRSTRLEN];
+
+  printf("ipv6\n");
+  printf("version = %x\n", header->ip6_vfc >> 4);
+  printf("traffic class = %x\n", header->ip6_flow >> 20);
+  printf("flow label = %x\n", ntohl(header->ip6_flow & 0x000fffff));
+  printf("payload len = %x\n", ntohs(header->ip6_plen));
+  printf("next header = %x\n", header->ip6_nxt);
+  printf("hop limit = %x\n", header->ip6_hlim);
+
+  inet_ntop(AF_INET6, &header->ip6_src, addrstr, sizeof(addrstr));
+  printf("source = %s\n", addrstr);
+
+  inet_ntop(AF_INET6, &header->ip6_dst, addrstr, sizeof(addrstr));
+  printf("dest = %s\n", addrstr);
+}
+
+/* print icmp header */
+void dump_icmp(struct icmphdr *icmp) {
+  printf("ICMP\n");
+
+  printf("icmp.type = %x ", icmp->type);
+  if (icmp->type == ICMP_ECHOREPLY) {
+    printf("echo reply");
+  } else if (icmp->type == ICMP_ECHO) {
+    printf("echo request");
+  } else {
+    printf("other");
+  }
+  printf("\n");
+  printf("icmp.code = %x\n", icmp->code);
+  printf("icmp.checksum = %x\n", ntohs(icmp->checksum));
+  if (icmp->type == ICMP_ECHOREPLY || icmp->type == ICMP_ECHO) {
+    printf("icmp.un.echo.id = %x\n", ntohs(icmp->un.echo.id));
+    printf("icmp.un.echo.sequence = %x\n", ntohs(icmp->un.echo.sequence));
+  }
+}
+
+/* print icmp6 header */
+void dump_icmp6(struct icmp6_hdr *icmp6) {
+  printf("ICMP6\n");
+  printf("type = %x", icmp6->icmp6_type);
+  if (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) {
+    printf("(echo request)");
+  } else if (icmp6->icmp6_type == ICMP6_ECHO_REPLY) {
+    printf("(echo reply)");
+  }
+  printf("\n");
+  printf("code = %x\n", icmp6->icmp6_code);
+
+  printf("checksum = %x\n", icmp6->icmp6_cksum);
+
+  if ((icmp6->icmp6_type == ICMP6_ECHO_REQUEST) || (icmp6->icmp6_type == ICMP6_ECHO_REPLY)) {
+    printf("icmp6_id = %x\n", icmp6->icmp6_id);
+    printf("icmp6_seq = %x\n", icmp6->icmp6_seq);
+  }
+}
+
+/* print udp header */
+void dump_udp_generic(const struct udphdr *udp, uint32_t temp_checksum, const uint8_t *payload,
+                      size_t payload_size) {
+  uint16_t my_checksum;
+
+  temp_checksum = ip_checksum_add(temp_checksum, udp, sizeof(struct udphdr));
+  temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size);
+  my_checksum   = ip_checksum_finish(temp_checksum);
+
+  printf("UDP\n");
+  printf("source = %x\n", ntohs(udp->source));
+  printf("dest = %x\n", ntohs(udp->dest));
+  printf("len = %x\n", ntohs(udp->len));
+  printf("check = %x (mine %x)\n", udp->check, my_checksum);
+}
+
+/* print ipv4/udp header */
+void dump_udp(const struct udphdr *udp, const struct iphdr *ip, const uint8_t *payload,
+              size_t payload_size) {
+  uint32_t temp_checksum;
+  temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*udp) + payload_size);
+  dump_udp_generic(udp, temp_checksum, payload, payload_size);
+}
+
+/* print ipv6/udp header */
+void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, const uint8_t *payload,
+               size_t payload_size) {
+  uint32_t temp_checksum;
+  temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*udp) + payload_size, IPPROTO_UDP);
+  dump_udp_generic(udp, temp_checksum, payload, payload_size);
+}
+
+/* print tcp header */
+void dump_tcp_generic(const struct tcphdr *tcp, const uint8_t *options, size_t options_size,
+                      uint32_t temp_checksum, const uint8_t *payload, size_t payload_size) {
+  uint16_t my_checksum;
+
+  temp_checksum = ip_checksum_add(temp_checksum, tcp, sizeof(struct tcphdr));
+  if (options) {
+    temp_checksum = ip_checksum_add(temp_checksum, options, options_size);
+  }
+  temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size);
+  my_checksum   = ip_checksum_finish(temp_checksum);
+
+  printf("TCP\n");
+  printf("source = %x\n", ntohs(tcp->source));
+  printf("dest = %x\n", ntohs(tcp->dest));
+  printf("seq = %x\n", ntohl(tcp->seq));
+  printf("ack = %x\n", ntohl(tcp->ack_seq));
+  printf("d_off = %x\n", tcp->doff);
+  printf("res1 = %x\n", tcp->res1);
+#ifdef __BIONIC__
+  printf("CWR = %x\n", tcp->cwr);
+  printf("ECE = %x\n", tcp->ece);
+#else
+  printf("CWR/ECE = %x\n", tcp->res2);
+#endif
+  printf("urg = %x  ack = %x  psh = %x  rst = %x  syn = %x  fin = %x\n", tcp->urg, tcp->ack,
+         tcp->psh, tcp->rst, tcp->syn, tcp->fin);
+  printf("window = %x\n", ntohs(tcp->window));
+  printf("check = %x [mine %x]\n", tcp->check, my_checksum);
+  printf("urgent = %x\n", tcp->urg_ptr);
+
+  if (options) {
+    size_t i;
+
+    printf("options: ");
+    for (i = 0; i < options_size; i++) {
+      printf("%x ", *(options + i));
+    }
+    printf("\n");
+  }
+}
+
+/* print ipv4/tcp header */
+void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip, const uint8_t *payload,
+              size_t payload_size, const uint8_t *options, size_t options_size) {
+  uint32_t temp_checksum;
+
+  temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*tcp) + options_size + payload_size);
+  dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size);
+}
+
+/* print ipv6/tcp header */
+void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6, const uint8_t *payload,
+               size_t payload_size, const uint8_t *options, size_t options_size) {
+  uint32_t temp_checksum;
+
+  temp_checksum =
+    ipv6_pseudo_header_checksum(ip6, sizeof(*tcp) + options_size + payload_size, IPPROTO_TCP);
+  dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size);
+}
+
+/* generic hex dump */
+void logcat_hexdump(const char *info, const uint8_t *data, size_t len) {
+  char output[MAXDUMPLEN * 3 + 2];
+  size_t i;
+
+  output[0] = '\0';
+  for (i = 0; i < len && i < MAXDUMPLEN; i++) {
+    snprintf(output + i * 3, 4, " %02x", data[i]);
+  }
+  output[len * 3 + 3] = '\0';
+
+  logmsg(ANDROID_LOG_WARN, "info %s len %d data%s", info, len, output);
+}
+
+void dump_iovec(const struct iovec *iov, int iov_len) {
+  int i;
+  char *str;
+  for (i = 0; i < iov_len; i++) {
+    asprintf(&str, "iov[%d]: ", i);
+    logcat_hexdump(str, iov[i].iov_base, iov[i].iov_len);
+    free(str);
+  }
+}
+#endif  // CLAT_DEBUG
diff --git a/clatd/dump.h b/clatd/dump.h
new file mode 100644
index 0000000..6b96cd2
--- /dev/null
+++ b/clatd/dump.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * dump.h - debug functions
+ */
+#ifndef __DUMP_H__
+#define __DUMP_H__
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+void dump_ip(struct iphdr *header);
+void dump_icmp(struct icmphdr *icmp);
+void dump_udp(const struct udphdr *udp, const struct iphdr *ip, const uint8_t *payload,
+              size_t payload_size);
+void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip, const uint8_t *payload,
+              size_t payload_size, const uint8_t *options, size_t options_size);
+
+void dump_ip6(struct ip6_hdr *header);
+void dump_icmp6(struct icmp6_hdr *icmp6);
+void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, const uint8_t *payload,
+               size_t payload_size);
+void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6, const uint8_t *payload,
+               size_t payload_size, const uint8_t *options, size_t options_size);
+
+void logcat_hexdump(const char *info, const uint8_t *data, size_t len);
+void dump_iovec(const struct iovec *iov, int iov_len);
+
+#endif /* __DUMP_H__ */
diff --git a/clatd/icmp.c b/clatd/icmp.c
new file mode 100644
index 0000000..f9ba113
--- /dev/null
+++ b/clatd/icmp.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ * icmp.c - convenience functions for translating ICMP and ICMPv6 packets.
+ */
+
+#include <linux/icmp.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+
+#include "icmp.h"
+#include "logging.h"
+
+/* function: icmp_guess_ttl
+ * Guesses the number of hops a received packet has traversed based on its TTL.
+ * ttl - the ttl of the received packet.
+ */
+uint8_t icmp_guess_ttl(uint8_t ttl) {
+  if (ttl > 128) {
+    return 255 - ttl;
+  } else if (ttl > 64) {
+    return 128 - ttl;
+  } else if (ttl > 32) {
+    return 64 - ttl;
+  } else {
+    return 32 - ttl;
+  }
+}
+
+/* function: is_icmp_error
+ * Determines whether an ICMP type is an error message.
+ * type: the ICMP type
+ */
+int is_icmp_error(uint8_t type) { return type == 3 || type == 11 || type == 12; }
+
+/* function: is_icmp6_error
+ * Determines whether an ICMPv6 type is an error message.
+ * type: the ICMPv6 type
+ */
+int is_icmp6_error(uint8_t type) { return type < 128; }
+
+/* function: icmp_to_icmp6_type
+ * Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2.
+ * type - the ICMPv6 type
+ */
+uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code) {
+  switch (type) {
+    case ICMP_ECHO:
+      return ICMP6_ECHO_REQUEST;
+
+    case ICMP_ECHOREPLY:
+      return ICMP6_ECHO_REPLY;
+
+    case ICMP_TIME_EXCEEDED:
+      return ICMP6_TIME_EXCEEDED;
+
+    case ICMP_DEST_UNREACH:
+      // These two types need special translation which we don't support yet.
+      if (code != ICMP_UNREACH_PROTOCOL && code != ICMP_UNREACH_NEEDFRAG) {
+        return ICMP6_DST_UNREACH;
+      }
+  }
+
+  // We don't understand this ICMP type. Return parameter problem so the caller will bail out.
+  logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_type: unhandled ICMP type %d", type);
+  return ICMP6_PARAM_PROB;
+}
+
+/* function: icmp_to_icmp6_code
+ * Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2.
+ * type - the ICMP type
+ * code - the ICMP code
+ */
+uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code) {
+  switch (type) {
+    case ICMP_ECHO:
+    case ICMP_ECHOREPLY:
+      return 0;
+
+    case ICMP_TIME_EXCEEDED:
+      return code;
+
+    case ICMP_DEST_UNREACH:
+      switch (code) {
+        case ICMP_UNREACH_NET:
+        case ICMP_UNREACH_HOST:
+          return ICMP6_DST_UNREACH_NOROUTE;
+
+        case ICMP_UNREACH_PORT:
+          return ICMP6_DST_UNREACH_NOPORT;
+
+        case ICMP_UNREACH_NET_PROHIB:
+        case ICMP_UNREACH_HOST_PROHIB:
+        case ICMP_UNREACH_FILTER_PROHIB:
+        case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+          return ICMP6_DST_UNREACH_ADMIN;
+
+          // Otherwise, we don't understand this ICMP type/code combination. Fall through.
+      }
+  }
+  logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_code: unhandled ICMP type/code %d/%d", type, code);
+  return 0;
+}
+
+/* function: icmp6_to_icmp_type
+ * Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2.
+ * type - the ICMP type
+ */
+uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code) {
+  switch (type) {
+    case ICMP6_ECHO_REQUEST:
+      return ICMP_ECHO;
+
+    case ICMP6_ECHO_REPLY:
+      return ICMP_ECHOREPLY;
+
+    case ICMP6_DST_UNREACH:
+      return ICMP_DEST_UNREACH;
+
+    case ICMP6_TIME_EXCEEDED:
+      return ICMP_TIME_EXCEEDED;
+  }
+
+  // We don't understand this ICMP type. Return parameter problem so the caller will bail out.
+  logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_type: unhandled ICMP type/code %d/%d", type, code);
+  return ICMP_PARAMETERPROB;
+}
+
+/* function: icmp6_to_icmp_code
+ * Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2.
+ * type - the ICMPv6 type
+ * code - the ICMPv6 code
+ */
+uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code) {
+  switch (type) {
+    case ICMP6_ECHO_REQUEST:
+    case ICMP6_ECHO_REPLY:
+    case ICMP6_TIME_EXCEEDED:
+      return code;
+
+    case ICMP6_DST_UNREACH:
+      switch (code) {
+        case ICMP6_DST_UNREACH_NOROUTE:
+          return ICMP_UNREACH_HOST;
+
+        case ICMP6_DST_UNREACH_ADMIN:
+          return ICMP_UNREACH_HOST_PROHIB;
+
+        case ICMP6_DST_UNREACH_BEYONDSCOPE:
+          return ICMP_UNREACH_HOST;
+
+        case ICMP6_DST_UNREACH_ADDR:
+          return ICMP_HOST_UNREACH;
+
+        case ICMP6_DST_UNREACH_NOPORT:
+          return ICMP_UNREACH_PORT;
+
+          // Otherwise, we don't understand this ICMPv6 type/code combination. Fall through.
+      }
+  }
+
+  logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_code: unhandled ICMP type/code %d/%d", type, code);
+  return 0;
+}
diff --git a/clatd/icmp.h b/clatd/icmp.h
new file mode 100644
index 0000000..632e92d
--- /dev/null
+++ b/clatd/icmp.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ * icmp.c - convenience functions for translating ICMP and ICMPv6 packets.
+ */
+
+#ifndef __ICMP_H__
+#define __ICMP_H__
+
+#include <stdint.h>
+
+// Guesses the number of hops a received packet has traversed based on its TTL.
+uint8_t icmp_guess_ttl(uint8_t ttl);
+
+// Determines whether an ICMP type is an error message.
+int is_icmp_error(uint8_t type);
+
+// Determines whether an ICMPv6 type is an error message.
+int is_icmp6_error(uint8_t type);
+
+// Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2.
+uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code);
+
+// Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2.
+uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code);
+
+// Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2.
+uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code);
+
+// Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2.
+uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code);
+
+#endif /* __ICMP_H__ */
diff --git a/clatd/ipv4.c b/clatd/ipv4.c
new file mode 100644
index 0000000..2be02e3
--- /dev/null
+++ b/clatd/ipv4.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * ipv4.c - takes ipv4 packets, finds their headers, and then calls translation functions on them
+ */
+#include <string.h>
+
+#include "checksum.h"
+#include "debug.h"
+#include "dump.h"
+#include "logging.h"
+#include "translate.h"
+
+/* function: icmp_packet
+ * translates an icmp packet
+ * out      - output packet
+ * icmp     - pointer to icmp header in packet
+ * checksum - pseudo-header checksum
+ * len      - size of ip payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int icmp_packet(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp,
+                uint32_t checksum, size_t len) {
+  const uint8_t *payload;
+  size_t payload_size;
+
+  if (len < sizeof(struct icmphdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "icmp_packet/(too small)");
+    return 0;
+  }
+
+  payload      = (const uint8_t *)(icmp + 1);
+  payload_size = len - sizeof(struct icmphdr);
+
+  return icmp_to_icmp6(out, pos, icmp, checksum, payload, payload_size);
+}
+
+/* function: ipv4_packet
+ * translates an ipv4 packet
+ * out    - output packet
+ * packet - packet data
+ * len    - size of packet
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) {
+  const struct iphdr *header = (struct iphdr *)packet;
+  struct ip6_hdr *ip6_targ   = (struct ip6_hdr *)out[pos].iov_base;
+  struct ip6_frag *frag_hdr;
+  size_t frag_hdr_len;
+  uint8_t nxthdr;
+  const uint8_t *next_header;
+  size_t len_left;
+  uint32_t old_sum, new_sum;
+  int iov_len;
+
+  if (len < sizeof(struct iphdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/too short for an ip header");
+    return 0;
+  }
+
+  if (header->ihl < 5) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set to less than 5: %x", header->ihl);
+    return 0;
+  }
+
+  if ((size_t)header->ihl * 4 > len) {  // ip header length larger than entire packet
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set too large: %x", header->ihl);
+    return 0;
+  }
+
+  if (header->version != 4) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header version not 4: %x", header->version);
+    return 0;
+  }
+
+  /* rfc6145 - If any IPv4 options are present in the IPv4 packet, they MUST be
+   * ignored and the packet translated normally; there is no attempt to
+   * translate the options.
+   */
+
+  next_header = packet + header->ihl * 4;
+  len_left    = len - header->ihl * 4;
+
+  nxthdr = header->protocol;
+  if (nxthdr == IPPROTO_ICMP) {
+    // ICMP and ICMPv6 have different protocol numbers.
+    nxthdr = IPPROTO_ICMPV6;
+  }
+
+  /* Fill in the IPv6 header. We need to do this before we translate the packet because TCP and
+   * UDP include parts of the IP header in the checksum. Set the length to zero because we don't
+   * know it yet.
+   */
+  fill_ip6_header(ip6_targ, 0, nxthdr, header);
+  out[pos].iov_len = sizeof(struct ip6_hdr);
+
+  /* Calculate the pseudo-header checksum.
+   * Technically, the length that is used in the pseudo-header checksum is the transport layer
+   * length, which is not the same as len_left in the case of fragmented packets. But since
+   * translation does not change the transport layer length, the checksum is unaffected.
+   */
+  old_sum = ipv4_pseudo_header_checksum(header, len_left);
+  new_sum = ipv6_pseudo_header_checksum(ip6_targ, len_left, nxthdr);
+
+  // If the IPv4 packet is fragmented, add a Fragment header.
+  frag_hdr             = (struct ip6_frag *)out[pos + 1].iov_base;
+  frag_hdr_len         = maybe_fill_frag_header(frag_hdr, ip6_targ, header);
+  out[pos + 1].iov_len = frag_hdr_len;
+
+  if (frag_hdr_len && frag_hdr->ip6f_offlg & IP6F_OFF_MASK) {
+    // Non-first fragment. Copy the rest of the packet as is.
+    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+  } else if (nxthdr == IPPROTO_ICMPV6) {
+    iov_len = icmp_packet(out, pos + 2, (const struct icmphdr *)next_header, new_sum, len_left);
+  } else if (nxthdr == IPPROTO_TCP) {
+    iov_len =
+      tcp_packet(out, pos + 2, (const struct tcphdr *)next_header, old_sum, new_sum, len_left);
+  } else if (nxthdr == IPPROTO_UDP) {
+    iov_len =
+      udp_packet(out, pos + 2, (const struct udphdr *)next_header, old_sum, new_sum, len_left);
+  } else if (nxthdr == IPPROTO_GRE || nxthdr == IPPROTO_ESP) {
+    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+  } else {
+#if CLAT_DEBUG
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/unknown protocol: %x", header->protocol);
+    logcat_hexdump("ipv4/protocol", packet, len);
+#endif
+    return 0;
+  }
+
+  // Set the length.
+  ip6_targ->ip6_plen = htons(packet_length(out, pos));
+  return iov_len;
+}
diff --git a/clatd/ipv6.c b/clatd/ipv6.c
new file mode 100644
index 0000000..05cd3ab
--- /dev/null
+++ b/clatd/ipv6.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * ipv6.c - takes ipv6 packets, finds their headers, and then calls translation functions on them
+ */
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "checksum.h"
+#include "config.h"
+#include "debug.h"
+#include "dump.h"
+#include "logging.h"
+#include "translate.h"
+
+/* function: icmp6_packet
+ * takes an icmp6 packet and sets it up for translation
+ * out      - output packet
+ * icmp6    - pointer to icmp6 header in packet
+ * checksum - pseudo-header checksum (unused)
+ * len      - size of ip payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int icmp6_packet(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6,
+                 size_t len) {
+  const uint8_t *payload;
+  size_t payload_size;
+
+  if (len < sizeof(struct icmp6_hdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "icmp6_packet/(too small)");
+    return 0;
+  }
+
+  payload      = (const uint8_t *)(icmp6 + 1);
+  payload_size = len - sizeof(struct icmp6_hdr);
+
+  return icmp6_to_icmp(out, pos, icmp6, payload, payload_size);
+}
+
+/* function: log_bad_address
+ * logs a bad address to android's log buffer if debugging is turned on
+ * fmt     - printf-style format, use %s to place the address
+ * badaddr - the bad address in question
+ */
+#if CLAT_DEBUG
+void log_bad_address(const char *fmt, const struct in6_addr *src, const struct in6_addr *dst) {
+  char srcstr[INET6_ADDRSTRLEN];
+  char dststr[INET6_ADDRSTRLEN];
+
+  inet_ntop(AF_INET6, src, srcstr, sizeof(srcstr));
+  inet_ntop(AF_INET6, dst, dststr, sizeof(dststr));
+  logmsg_dbg(ANDROID_LOG_ERROR, fmt, srcstr, dststr);
+}
+#else
+#define log_bad_address(fmt, src, dst)
+#endif
+
+/* function: ipv6_packet
+ * takes an ipv6 packet and hands it off to the layer 4 protocol function
+ * out    - output packet
+ * packet - packet data
+ * len    - size of packet
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) {
+  const struct ip6_hdr *ip6 = (struct ip6_hdr *)packet;
+  struct iphdr *ip_targ     = (struct iphdr *)out[pos].iov_base;
+  struct ip6_frag *frag_hdr = NULL;
+  uint8_t protocol;
+  const uint8_t *next_header;
+  size_t len_left;
+  uint32_t old_sum, new_sum;
+  int iov_len;
+
+  if (len < sizeof(struct ip6_hdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for an ip6 header: %d", len);
+    return 0;
+  }
+
+  if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+    log_bad_address("ipv6_packet/multicast %s->%s", &ip6->ip6_src, &ip6->ip6_dst);
+    return 0;  // silently ignore
+  }
+
+  // If the packet is not from the plat subnet to the local subnet, or vice versa, drop it, unless
+  // it's an ICMP packet (which can come from anywhere). We do not send IPv6 packets from the plat
+  // subnet to the local subnet, but these can appear as inner packets in ICMP errors, so we need
+  // to translate them. We accept third-party ICMPv6 errors, even though their source addresses
+  // cannot be translated, so that things like unreachables and traceroute will work. fill_ip_header
+  // takes care of faking a source address for them.
+  if (!(is_in_plat_subnet(&ip6->ip6_src) &&
+        IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &Global_Clatd_Config.ipv6_local_subnet)) &&
+      !(is_in_plat_subnet(&ip6->ip6_dst) &&
+        IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet)) &&
+      ip6->ip6_nxt != IPPROTO_ICMPV6) {
+    log_bad_address("ipv6_packet/wrong source address: %s->%s", &ip6->ip6_src, &ip6->ip6_dst);
+    return 0;
+  }
+
+  next_header = packet + sizeof(struct ip6_hdr);
+  len_left    = len - sizeof(struct ip6_hdr);
+
+  protocol = ip6->ip6_nxt;
+
+  /* Fill in the IPv4 header. We need to do this before we translate the packet because TCP and
+   * UDP include parts of the IP header in the checksum. Set the length to zero because we don't
+   * know it yet.
+   */
+  fill_ip_header(ip_targ, 0, protocol, ip6);
+  out[pos].iov_len = sizeof(struct iphdr);
+
+  // If there's a Fragment header, parse it and decide what the next header is.
+  // Do this before calculating the pseudo-header checksum because it updates the next header value.
+  if (protocol == IPPROTO_FRAGMENT) {
+    frag_hdr = (struct ip6_frag *)next_header;
+    if (len_left < sizeof(*frag_hdr)) {
+      logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for fragment header: %d", len);
+      return 0;
+    }
+
+    next_header += sizeof(*frag_hdr);
+    len_left -= sizeof(*frag_hdr);
+
+    protocol = parse_frag_header(frag_hdr, ip_targ);
+  }
+
+  // ICMP and ICMPv6 have different protocol numbers.
+  if (protocol == IPPROTO_ICMPV6) {
+    protocol          = IPPROTO_ICMP;
+    ip_targ->protocol = IPPROTO_ICMP;
+  }
+
+  /* Calculate the pseudo-header checksum.
+   * Technically, the length that is used in the pseudo-header checksum is the transport layer
+   * length, which is not the same as len_left in the case of fragmented packets. But since
+   * translation does not change the transport layer length, the checksum is unaffected.
+   */
+  old_sum = ipv6_pseudo_header_checksum(ip6, len_left, protocol);
+  new_sum = ipv4_pseudo_header_checksum(ip_targ, len_left);
+
+  // Does not support IPv6 extension headers except Fragment.
+  if (frag_hdr && (frag_hdr->ip6f_offlg & IP6F_OFF_MASK)) {
+    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+  } else if (protocol == IPPROTO_ICMP) {
+    iov_len = icmp6_packet(out, pos + 2, (const struct icmp6_hdr *)next_header, len_left);
+  } else if (protocol == IPPROTO_TCP) {
+    iov_len =
+      tcp_packet(out, pos + 2, (const struct tcphdr *)next_header, old_sum, new_sum, len_left);
+  } else if (protocol == IPPROTO_UDP) {
+    iov_len =
+      udp_packet(out, pos + 2, (const struct udphdr *)next_header, old_sum, new_sum, len_left);
+  } else if (protocol == IPPROTO_GRE || protocol == IPPROTO_ESP) {
+    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+  } else {
+#if CLAT_DEBUG
+    logmsg(ANDROID_LOG_ERROR, "ipv6_packet/unknown next header type: %x", ip6->ip6_nxt);
+    logcat_hexdump("ipv6/nxthdr", packet, len);
+#endif
+    return 0;
+  }
+
+  // Set the length and calculate the checksum.
+  ip_targ->tot_len = htons(ntohs(ip_targ->tot_len) + packet_length(out, pos));
+  ip_targ->check   = ip_checksum(ip_targ, sizeof(struct iphdr));
+  return iov_len;
+}
diff --git a/clatd/logging.c b/clatd/logging.c
new file mode 100644
index 0000000..79d98e1
--- /dev/null
+++ b/clatd/logging.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * logging.c - print a log message
+ */
+
+#include <android/log.h>
+#include <stdarg.h>
+
+#include "debug.h"
+#include "logging.h"
+
+/* function: logmsg
+ * prints a log message to android's log buffer
+ * prio - the log message priority
+ * fmt  - printf format specifier
+ * ...  - printf format arguments
+ */
+void logmsg(int prio, const char *fmt, ...) {
+  va_list ap;
+
+  va_start(ap, fmt);
+  __android_log_vprint(prio, "clatd", fmt, ap);
+  va_end(ap);
+}
+
+/* function: logmsg_dbg
+ * prints a log message to android's log buffer if CLAT_DEBUG is set
+ * prio - the log message priority
+ * fmt  - printf format specifier
+ * ...  - printf format arguments
+ */
+#if CLAT_DEBUG
+void logmsg_dbg(int prio, const char *fmt, ...) {
+  va_list ap;
+
+  va_start(ap, fmt);
+  __android_log_vprint(prio, "clatd", fmt, ap);
+  va_end(ap);
+}
+#else
+void logmsg_dbg(__attribute__((unused)) int prio, __attribute__((unused)) const char *fmt, ...) {}
+#endif
diff --git a/clatd/logging.h b/clatd/logging.h
new file mode 100644
index 0000000..1f4b6b6
--- /dev/null
+++ b/clatd/logging.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * logging.h - print a log message
+ */
+
+#ifndef __LOGGING_H__
+#define __LOGGING_H__
+// for the priorities
+#include <android/log.h>
+
+void logmsg(int prio, const char *fmt, ...);
+void logmsg_dbg(int prio, const char *fmt, ...);
+
+#endif
diff --git a/clatd/main.c b/clatd/main.c
new file mode 100644
index 0000000..f888041
--- /dev/null
+++ b/clatd/main.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2018 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.
+ *
+ * main.c - main function
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/personality.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "clatd.h"
+#include "common.h"
+#include "config.h"
+#include "logging.h"
+
+#define DEVICEPREFIX "v4-"
+
+/* function: stop_loop
+ * signal handler: stop the event loop
+ */
+static void stop_loop() { running = 0; };
+
+/* function: print_help
+ * in case the user is running this on the command line
+ */
+void print_help() {
+  printf("android-clat arguments:\n");
+  printf("-i [uplink interface]\n");
+  printf("-p [plat prefix]\n");
+  printf("-4 [IPv4 address]\n");
+  printf("-6 [IPv6 address]\n");
+  printf("-t [tun file descriptor number]\n");
+  printf("-r [read socket descriptor number]\n");
+  printf("-w [write socket descriptor number]\n");
+}
+
+/* function: main
+ * allocate and setup the tun device, then run the event loop
+ */
+int main(int argc, char **argv) {
+  struct tun_data tunnel;
+  int opt;
+  char *uplink_interface = NULL, *plat_prefix = NULL;
+  char *v4_addr = NULL, *v6_addr = NULL, *tunfd_str = NULL, *read_sock_str = NULL,
+       *write_sock_str = NULL;
+  unsigned len;
+
+  while ((opt = getopt(argc, argv, "i:p:4:6:t:r:w:h")) != -1) {
+    switch (opt) {
+      case 'i':
+        uplink_interface = optarg;
+        break;
+      case 'p':
+        plat_prefix = optarg;
+        break;
+      case '4':
+        v4_addr = optarg;
+        break;
+      case '6':
+        v6_addr = optarg;
+        break;
+      case 't':
+        tunfd_str = optarg;
+        break;
+      case 'r':
+        read_sock_str = optarg;
+        break;
+      case 'w':
+        write_sock_str = optarg;
+        break;
+      case 'h':
+        print_help();
+        exit(0);
+      default:
+        logmsg(ANDROID_LOG_FATAL, "Unknown option -%c. Exiting.", (char)optopt);
+        exit(1);
+    }
+  }
+
+  if (uplink_interface == NULL) {
+    logmsg(ANDROID_LOG_FATAL, "clatd called without an interface");
+    exit(1);
+  }
+
+  if (tunfd_str != NULL && !parse_int(tunfd_str, &tunnel.fd4)) {
+    logmsg(ANDROID_LOG_FATAL, "invalid tunfd %s", tunfd_str);
+    exit(1);
+  }
+  if (!tunnel.fd4) {
+    logmsg(ANDROID_LOG_FATAL, "no tunfd specified on commandline.");
+    exit(1);
+  }
+
+  if (read_sock_str != NULL && !parse_int(read_sock_str, &tunnel.read_fd6)) {
+    logmsg(ANDROID_LOG_FATAL, "invalid read socket %s", read_sock_str);
+    exit(1);
+  }
+  if (!tunnel.read_fd6) {
+    logmsg(ANDROID_LOG_FATAL, "no read_fd6 specified on commandline.");
+    exit(1);
+  }
+
+  if (write_sock_str != NULL && !parse_int(write_sock_str, &tunnel.write_fd6)) {
+    logmsg(ANDROID_LOG_FATAL, "invalid write socket %s", write_sock_str);
+    exit(1);
+  }
+  if (!tunnel.write_fd6) {
+    logmsg(ANDROID_LOG_FATAL, "no write_fd6 specified on commandline.");
+    exit(1);
+  }
+
+  len = snprintf(tunnel.device4, sizeof(tunnel.device4), "%s%s", DEVICEPREFIX, uplink_interface);
+  if (len >= sizeof(tunnel.device4)) {
+    logmsg(ANDROID_LOG_FATAL, "interface name too long '%s'", tunnel.device4);
+    exit(1);
+  }
+
+  Global_Clatd_Config.native_ipv6_interface = uplink_interface;
+  if (!plat_prefix || inet_pton(AF_INET6, plat_prefix, &Global_Clatd_Config.plat_subnet) <= 0) {
+    logmsg(ANDROID_LOG_FATAL, "invalid IPv6 address specified for plat prefix: %s", plat_prefix);
+    exit(1);
+  }
+
+  if (!v4_addr || !inet_pton(AF_INET, v4_addr, &Global_Clatd_Config.ipv4_local_subnet.s_addr)) {
+    logmsg(ANDROID_LOG_FATAL, "Invalid IPv4 address %s", v4_addr);
+    exit(1);
+  }
+
+  if (!v6_addr || !inet_pton(AF_INET6, v6_addr, &Global_Clatd_Config.ipv6_local_subnet)) {
+    logmsg(ANDROID_LOG_FATAL, "Invalid source address %s", v6_addr);
+    exit(1);
+  }
+
+  logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s plat=%s v4=%s v6=%s", CLATD_VERSION,
+         uplink_interface, plat_prefix ? plat_prefix : "(none)", v4_addr ? v4_addr : "(none)",
+         v6_addr ? v6_addr : "(none)");
+
+  {
+    // Compile time detection of 32 vs 64-bit build. (note: C does not have 'constexpr')
+    // Avoid use of preprocessor macros to get compile time syntax checking even on 64-bit.
+    const int user_bits = sizeof(void*) * 8;
+    const bool user32 = (user_bits == 32);
+
+    // Note that on 64-bit all this personality related code simply compile optimizes out.
+    // 32-bit: fetch current personality (see 'man personality': 0xFFFFFFFF means retrieve only)
+    // On Linux fetching personality cannot fail.
+    const int prev_personality = user32 ? personality(0xFFFFFFFFuL) : PER_LINUX;
+    // 32-bit: attempt to get rid of kernel spoofing of 'uts.machine' architecture,
+    // In theory this cannot fail, as PER_LINUX should always be supported.
+    if (user32) (void)personality((prev_personality & ~PER_MASK) | PER_LINUX);
+    // 64-bit: this will compile time evaluate to false.
+    const bool was_linux32 = (prev_personality & PER_MASK) == PER_LINUX32;
+
+    struct utsname uts = {};
+    if (uname(&uts)) exit(1); // only possible error is EFAULT, but 'uts' is on stack
+
+    // sysname is likely 'Linux', release is 'kver', machine is kernel's *true* architecture
+    logmsg(ANDROID_LOG_INFO, "%d-bit userspace on %s kernel %s for %s%s.", user_bits,
+           uts.sysname, uts.release, uts.machine, was_linux32 ? " (was spoofed)" : "");
+
+    // 32-bit: try to return to the 'default' personality
+    // In theory this cannot fail, because it was already previously in use.
+    if (user32) (void)personality(prev_personality);
+  }
+
+  // Loop until someone sends us a signal or brings down the tun interface.
+  if (signal(SIGTERM, stop_loop) == SIG_ERR) {
+    logmsg(ANDROID_LOG_FATAL, "sigterm handler failed: %s", strerror(errno));
+    exit(1);
+  }
+
+  event_loop(&tunnel);
+
+  logmsg(ANDROID_LOG_INFO, "Shutting down clat on %s", uplink_interface);
+
+  if (running) {
+    logmsg(ANDROID_LOG_INFO, "Clatd on %s waiting for SIGTERM", uplink_interface);
+    // let's give higher level java code 15 seconds to kill us,
+    // but eventually terminate anyway, in case system server forgets about us...
+    // sleep() should be interrupted by SIGTERM, the handler should clear running
+    sleep(15);
+    logmsg(ANDROID_LOG_INFO, "Clatd on %s %s SIGTERM", uplink_interface,
+           running ? "timed out waiting for" : "received");
+  } else {
+    logmsg(ANDROID_LOG_INFO, "Clatd on %s already received SIGTERM", uplink_interface);
+  }
+  return 0;
+}
diff --git a/clatd/translate.c b/clatd/translate.c
new file mode 100644
index 0000000..22830d89
--- /dev/null
+++ b/clatd/translate.c
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * translate.c - CLAT functions / partial implementation of rfc6145
+ */
+#include "translate.h"
+
+#include <string.h>
+
+#include "checksum.h"
+#include "clatd.h"
+#include "common.h"
+#include "config.h"
+#include "debug.h"
+#include "icmp.h"
+#include "logging.h"
+
+/* function: packet_checksum
+ * calculates the checksum over all the packet components starting from pos
+ * checksum - checksum of packet components before pos
+ * packet   - packet to calculate the checksum of
+ * pos      - position to start counting from
+ * returns  - the completed 16-bit checksum, ready to write into a checksum header field
+ */
+uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos) {
+  int i;
+  for (i = pos; i < CLAT_POS_MAX; i++) {
+    if (packet[i].iov_len > 0) {
+      checksum = ip_checksum_add(checksum, packet[i].iov_base, packet[i].iov_len);
+    }
+  }
+  return ip_checksum_finish(checksum);
+}
+
+/* function: packet_length
+ * returns the total length of all the packet components after pos
+ * packet - packet to calculate the length of
+ * pos    - position to start counting after
+ * returns: the total length of the packet components after pos
+ */
+uint16_t packet_length(clat_packet packet, clat_packet_index pos) {
+  size_t len = 0;
+  int i;
+  for (i = pos + 1; i < CLAT_POS_MAX; i++) {
+    len += packet[i].iov_len;
+  }
+  return len;
+}
+
+/* function: is_in_plat_subnet
+ * returns true iff the given IPv6 address is in the plat subnet.
+ * addr - IPv6 address
+ */
+int is_in_plat_subnet(const struct in6_addr *addr6) {
+  // Assumes a /96 plat subnet.
+  return (addr6 != NULL) && (memcmp(addr6, &Global_Clatd_Config.plat_subnet, 12) == 0);
+}
+
+/* function: ipv6_addr_to_ipv4_addr
+ * return the corresponding ipv4 address for the given ipv6 address
+ * addr6 - ipv6 address
+ * returns: the IPv4 address
+ */
+uint32_t ipv6_addr_to_ipv4_addr(const struct in6_addr *addr6) {
+  if (is_in_plat_subnet(addr6)) {
+    // Assumes a /96 plat subnet.
+    return addr6->s6_addr32[3];
+  } else if (IN6_ARE_ADDR_EQUAL(addr6, &Global_Clatd_Config.ipv6_local_subnet)) {
+    // Special-case our own address.
+    return Global_Clatd_Config.ipv4_local_subnet.s_addr;
+  } else {
+    // Third party packet. Let the caller deal with it.
+    return INADDR_NONE;
+  }
+}
+
+/* function: ipv4_addr_to_ipv6_addr
+ * return the corresponding ipv6 address for the given ipv4 address
+ * addr4 - ipv4 address
+ */
+struct in6_addr ipv4_addr_to_ipv6_addr(uint32_t addr4) {
+  struct in6_addr addr6;
+  // Both addresses are in network byte order (addr4 comes from a network packet, and the config
+  // file entry is read using inet_ntop).
+  if (addr4 == Global_Clatd_Config.ipv4_local_subnet.s_addr) {
+    return Global_Clatd_Config.ipv6_local_subnet;
+  } else {
+    // Assumes a /96 plat subnet.
+    addr6              = Global_Clatd_Config.plat_subnet;
+    addr6.s6_addr32[3] = addr4;
+    return addr6;
+  }
+}
+
+/* function: fill_tun_header
+ * fill in the header for the tun fd
+ * tun_header - tunnel header, already allocated
+ * proto      - ethernet protocol id: ETH_P_IP(ipv4) or ETH_P_IPV6(ipv6)
+ */
+void fill_tun_header(struct tun_pi *tun_header, uint16_t proto) {
+  tun_header->flags = 0;
+  tun_header->proto = htons(proto);
+}
+
+/* function: fill_ip_header
+ * generate an ipv4 header from an ipv6 header
+ * ip_targ     - (ipv4) target packet header, source: original ipv4 addr, dest: local subnet addr
+ * payload_len - length of other data inside packet
+ * protocol    - protocol number (tcp, udp, etc)
+ * old_header  - (ipv6) source packet header, source: nat64 prefix, dest: local subnet prefix
+ */
+void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol,
+                    const struct ip6_hdr *old_header) {
+  int ttl_guess;
+  memset(ip, 0, sizeof(struct iphdr));
+
+  ip->ihl      = 5;
+  ip->version  = 4;
+  ip->tos      = 0;
+  ip->tot_len  = htons(sizeof(struct iphdr) + payload_len);
+  ip->id       = 0;
+  ip->frag_off = htons(IP_DF);
+  ip->ttl      = old_header->ip6_hlim;
+  ip->protocol = protocol;
+  ip->check    = 0;
+
+  ip->saddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_src);
+  ip->daddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_dst);
+
+  // Third-party ICMPv6 message. This may have been originated by an native IPv6 address.
+  // In that case, the source IPv6 address can't be translated and we need to make up an IPv4
+  // source address. For now, use 255.0.0.<ttl>, which at least looks useful in traceroute.
+  if ((uint32_t)ip->saddr == INADDR_NONE) {
+    ttl_guess = icmp_guess_ttl(old_header->ip6_hlim);
+    ip->saddr = htonl((0xff << 24) + ttl_guess);
+  }
+}
+
+/* function: fill_ip6_header
+ * generate an ipv6 header from an ipv4 header
+ * ip6         - (ipv6) target packet header, source: local subnet prefix, dest: nat64 prefix
+ * payload_len - length of other data inside packet
+ * protocol    - protocol number (tcp, udp, etc)
+ * old_header  - (ipv4) source packet header, source: local subnet addr, dest: internet's ipv4 addr
+ */
+void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol,
+                     const struct iphdr *old_header) {
+  memset(ip6, 0, sizeof(struct ip6_hdr));
+
+  ip6->ip6_vfc  = 6 << 4;
+  ip6->ip6_plen = htons(payload_len);
+  ip6->ip6_nxt  = protocol;
+  ip6->ip6_hlim = old_header->ttl;
+
+  ip6->ip6_src = ipv4_addr_to_ipv6_addr(old_header->saddr);
+  ip6->ip6_dst = ipv4_addr_to_ipv6_addr(old_header->daddr);
+}
+
+/* function: maybe_fill_frag_header
+ * fills a fragmentation header
+ * generate an ipv6 fragment header from an ipv4 header
+ * frag_hdr    - target (ipv6) fragmentation header
+ * ip6_targ    - target (ipv6) header
+ * old_header  - (ipv4) source packet header
+ * returns: the length of the fragmentation header if present, or zero if not present
+ */
+size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ,
+                              const struct iphdr *old_header) {
+  uint16_t frag_flags = ntohs(old_header->frag_off);
+  uint16_t frag_off   = frag_flags & IP_OFFMASK;
+  if (frag_off == 0 && (frag_flags & IP_MF) == 0) {
+    // Not a fragment.
+    return 0;
+  }
+
+  frag_hdr->ip6f_nxt      = ip6_targ->ip6_nxt;
+  frag_hdr->ip6f_reserved = 0;
+  // In IPv4, the offset is the bottom 13 bits; in IPv6 it's the top 13 bits.
+  frag_hdr->ip6f_offlg = htons(frag_off << 3);
+  if (frag_flags & IP_MF) {
+    frag_hdr->ip6f_offlg |= IP6F_MORE_FRAG;
+  }
+  frag_hdr->ip6f_ident = htonl(ntohs(old_header->id));
+  ip6_targ->ip6_nxt    = IPPROTO_FRAGMENT;
+
+  return sizeof(*frag_hdr);
+}
+
+/* function: parse_frag_header
+ * return the length of the fragmentation header if present, or zero if not present
+ * generate an ipv6 fragment header from an ipv4 header
+ * frag_hdr    - (ipv6) fragmentation header
+ * ip_targ     - target (ipv4) header
+ * returns: the next header value
+ */
+uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ) {
+  uint16_t frag_off = (ntohs(frag_hdr->ip6f_offlg & IP6F_OFF_MASK) >> 3);
+  if (frag_hdr->ip6f_offlg & IP6F_MORE_FRAG) {
+    frag_off |= IP_MF;
+  }
+  ip_targ->frag_off = htons(frag_off);
+  ip_targ->id       = htons(ntohl(frag_hdr->ip6f_ident) & 0xffff);
+  ip_targ->protocol = frag_hdr->ip6f_nxt;
+  return frag_hdr->ip6f_nxt;
+}
+
+/* function: icmp_to_icmp6
+ * translate ipv4 icmp to ipv6 icmp
+ * out          - output packet
+ * icmp         - source packet icmp header
+ * checksum     - pseudo-header checksum
+ * payload      - icmp payload
+ * payload_size - size of payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp,
+                  uint32_t checksum, const uint8_t *payload, size_t payload_size) {
+  struct icmp6_hdr *icmp6_targ = out[pos].iov_base;
+  uint8_t icmp6_type;
+  int clat_packet_len;
+
+  memset(icmp6_targ, 0, sizeof(struct icmp6_hdr));
+
+  icmp6_type             = icmp_to_icmp6_type(icmp->type, icmp->code);
+  icmp6_targ->icmp6_type = icmp6_type;
+  icmp6_targ->icmp6_code = icmp_to_icmp6_code(icmp->type, icmp->code);
+
+  out[pos].iov_len = sizeof(struct icmp6_hdr);
+
+  if (pos == CLAT_POS_TRANSPORTHDR && is_icmp_error(icmp->type) && icmp6_type != ICMP6_PARAM_PROB) {
+    // An ICMP error we understand, one level deep.
+    // Translate the nested packet (the one that caused the error).
+    clat_packet_len = ipv4_packet(out, pos + 1, payload, payload_size);
+
+    // The pseudo-header checksum was calculated on the transport length of the original IPv4
+    // packet that we were asked to translate. This transport length is 20 bytes smaller than it
+    // needs to be, because the ICMP error contains an IPv4 header, which we will be translating to
+    // an IPv6 header, which is 20 bytes longer. Fix it up here.
+    // We only need to do this for ICMP->ICMPv6, not ICMPv6->ICMP, because ICMP does not use the
+    // pseudo-header when calculating its checksum (as the IPv4 header has its own checksum).
+    checksum = checksum + htons(20);
+  } else if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) {
+    // Ping packet.
+    icmp6_targ->icmp6_id           = icmp->un.echo.id;
+    icmp6_targ->icmp6_seq          = icmp->un.echo.sequence;
+    out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload;
+    out[CLAT_POS_PAYLOAD].iov_len  = payload_size;
+    clat_packet_len                = CLAT_POS_PAYLOAD + 1;
+  } else {
+    // Unknown type/code. The type/code conversion functions have already logged an error.
+    return 0;
+  }
+
+  icmp6_targ->icmp6_cksum = 0;  // Checksum field must be 0 when calculating checksum.
+  icmp6_targ->icmp6_cksum = packet_checksum(checksum, out, pos);
+
+  return clat_packet_len;
+}
+
+/* function: icmp6_to_icmp
+ * translate ipv6 icmp to ipv4 icmp
+ * out          - output packet
+ * icmp6        - source packet icmp6 header
+ * payload      - icmp6 payload
+ * payload_size - size of payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6,
+                  const uint8_t *payload, size_t payload_size) {
+  struct icmphdr *icmp_targ = out[pos].iov_base;
+  uint8_t icmp_type;
+  int clat_packet_len;
+
+  memset(icmp_targ, 0, sizeof(struct icmphdr));
+
+  icmp_type       = icmp6_to_icmp_type(icmp6->icmp6_type, icmp6->icmp6_code);
+  icmp_targ->type = icmp_type;
+  icmp_targ->code = icmp6_to_icmp_code(icmp6->icmp6_type, icmp6->icmp6_code);
+
+  out[pos].iov_len = sizeof(struct icmphdr);
+
+  if (pos == CLAT_POS_TRANSPORTHDR && is_icmp6_error(icmp6->icmp6_type) &&
+      icmp_type != ICMP_PARAMETERPROB) {
+    // An ICMPv6 error we understand, one level deep.
+    // Translate the nested packet (the one that caused the error).
+    clat_packet_len = ipv6_packet(out, pos + 1, payload, payload_size);
+  } else if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ECHOREPLY) {
+    // Ping packet.
+    icmp_targ->un.echo.id          = icmp6->icmp6_id;
+    icmp_targ->un.echo.sequence    = icmp6->icmp6_seq;
+    out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload;
+    out[CLAT_POS_PAYLOAD].iov_len  = payload_size;
+    clat_packet_len                = CLAT_POS_PAYLOAD + 1;
+  } else {
+    // Unknown type/code. The type/code conversion functions have already logged an error.
+    return 0;
+  }
+
+  icmp_targ->checksum = 0;  // Checksum field must be 0 when calculating checksum.
+  icmp_targ->checksum = packet_checksum(0, out, pos);
+
+  return clat_packet_len;
+}
+
+/* function: generic_packet
+ * takes a generic IP packet and sets it up for translation
+ * out      - output packet
+ * pos      - position in the output packet of the transport header
+ * payload  - pointer to IP payload
+ * len      - size of ip payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len) {
+  out[pos].iov_len               = 0;
+  out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload;
+  out[CLAT_POS_PAYLOAD].iov_len  = len;
+
+  return CLAT_POS_PAYLOAD + 1;
+}
+
+/* function: udp_packet
+ * takes a udp packet and sets it up for translation
+ * out      - output packet
+ * udp      - pointer to udp header in packet
+ * old_sum  - pseudo-header checksum of old header
+ * new_sum  - pseudo-header checksum of new header
+ * len      - size of ip payload
+ */
+int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, uint32_t old_sum,
+               uint32_t new_sum, size_t len) {
+  const uint8_t *payload;
+  size_t payload_size;
+
+  if (len < sizeof(struct udphdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "udp_packet/(too small)");
+    return 0;
+  }
+
+  payload      = (const uint8_t *)(udp + 1);
+  payload_size = len - sizeof(struct udphdr);
+
+  return udp_translate(out, pos, udp, old_sum, new_sum, payload, payload_size);
+}
+
+/* function: tcp_packet
+ * takes a tcp packet and sets it up for translation
+ * out      - output packet
+ * tcp      - pointer to tcp header in packet
+ * checksum - pseudo-header checksum
+ * len      - size of ip payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, uint32_t old_sum,
+               uint32_t new_sum, size_t len) {
+  const uint8_t *payload;
+  size_t payload_size, header_size;
+
+  if (len < sizeof(struct tcphdr)) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/(too small)");
+    return 0;
+  }
+
+  if (tcp->doff < 5) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/tcp header length set to less than 5: %x", tcp->doff);
+    return 0;
+  }
+
+  if ((size_t)tcp->doff * 4 > len) {
+    logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/tcp header length set too large: %x", tcp->doff);
+    return 0;
+  }
+
+  header_size  = tcp->doff * 4;
+  payload      = ((const uint8_t *)tcp) + header_size;
+  payload_size = len - header_size;
+
+  return tcp_translate(out, pos, tcp, header_size, old_sum, new_sum, payload, payload_size);
+}
+
+/* function: udp_translate
+ * common between ipv4/ipv6 - setup checksum and send udp packet
+ * out          - output packet
+ * udp          - udp header
+ * old_sum      - pseudo-header checksum of old header
+ * new_sum      - pseudo-header checksum of new header
+ * payload      - tcp payload
+ * payload_size - size of payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp,
+                  uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size) {
+  struct udphdr *udp_targ = out[pos].iov_base;
+
+  memcpy(udp_targ, udp, sizeof(struct udphdr));
+
+  out[pos].iov_len               = sizeof(struct udphdr);
+  out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload;
+  out[CLAT_POS_PAYLOAD].iov_len  = payload_size;
+
+  if (udp_targ->check) {
+    udp_targ->check = ip_checksum_adjust(udp->check, old_sum, new_sum);
+  } else {
+    // Zero checksums are special. RFC 768 says, "An all zero transmitted checksum value means that
+    // the transmitter generated no checksum (for debugging or for higher level protocols that
+    // don't care)." However, in IPv6 zero UDP checksums were only permitted by RFC 6935 (2013). So
+    // for safety we recompute it.
+    udp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
+    udp_targ->check = packet_checksum(new_sum, out, pos);
+  }
+
+  // RFC 768: "If the computed checksum is zero, it is transmitted as all ones (the equivalent
+  // in one's complement arithmetic)."
+  if (!udp_targ->check) {
+    udp_targ->check = 0xffff;
+  }
+
+  return CLAT_POS_PAYLOAD + 1;
+}
+
+/* function: tcp_translate
+ * common between ipv4/ipv6 - setup checksum and send tcp packet
+ * out          - output packet
+ * tcp          - tcp header
+ * header_size  - size of tcp header including options
+ * checksum     - partial checksum covering ipv4/ipv6 header
+ * payload      - tcp payload
+ * payload_size - size of payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp,
+                  size_t header_size, uint32_t old_sum, uint32_t new_sum, const uint8_t *payload,
+                  size_t payload_size) {
+  struct tcphdr *tcp_targ = out[pos].iov_base;
+  out[pos].iov_len        = header_size;
+
+  if (header_size > MAX_TCP_HDR) {
+    // A TCP header cannot be more than MAX_TCP_HDR bytes long because it's a 4-bit field that
+    // counts in 4-byte words. So this can never happen unless there is a bug in the caller.
+    logmsg(ANDROID_LOG_ERROR, "tcp_translate: header too long %d > %d, truncating", header_size,
+           MAX_TCP_HDR);
+    header_size = MAX_TCP_HDR;
+  }
+
+  memcpy(tcp_targ, tcp, header_size);
+
+  out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload;
+  out[CLAT_POS_PAYLOAD].iov_len  = payload_size;
+
+  tcp_targ->check = ip_checksum_adjust(tcp->check, old_sum, new_sum);
+
+  return CLAT_POS_PAYLOAD + 1;
+}
+
+// Weak symbol so we can override it in the unit test.
+void send_rawv6(int fd, clat_packet out, int iov_len) __attribute__((weak));
+
+void send_rawv6(int fd, clat_packet out, int iov_len) {
+  // A send on a raw socket requires a destination address to be specified even if the socket's
+  // protocol is IPPROTO_RAW. This is the address that will be used in routing lookups; the
+  // destination address in the packet header only affects what appears on the wire, not where the
+  // packet is sent to.
+  static struct sockaddr_in6 sin6 = { AF_INET6, 0, 0, { { { 0, 0, 0, 0 } } }, 0 };
+  static struct msghdr msg        = {
+    .msg_name    = &sin6,
+    .msg_namelen = sizeof(sin6),
+  };
+
+  msg.msg_iov = out, msg.msg_iovlen = iov_len,
+  sin6.sin6_addr = ((struct ip6_hdr *)out[CLAT_POS_IPHDR].iov_base)->ip6_dst;
+  sendmsg(fd, &msg, 0);
+}
+
+/* function: translate_packet
+ * takes a packet, translates it, and writes it to fd
+ * fd         - fd to write translated packet to
+ * to_ipv6    - true if translating to ipv6, false if translating to ipv4
+ * packet     - packet
+ * packetsize - size of packet
+ */
+void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize) {
+  int iov_len = 0;
+
+  // Allocate buffers for all packet headers.
+  struct tun_pi tun_targ;
+  char iphdr[sizeof(struct ip6_hdr)];
+  char fraghdr[sizeof(struct ip6_frag)];
+  char transporthdr[MAX_TCP_HDR];
+  char icmp_iphdr[sizeof(struct ip6_hdr)];
+  char icmp_fraghdr[sizeof(struct ip6_frag)];
+  char icmp_transporthdr[MAX_TCP_HDR];
+
+  // iovec of the packets we'll send. This gets passed down to the translation functions.
+  clat_packet out = {
+    { &tun_targ, 0 },          // Tunnel header.
+    { iphdr, 0 },              // IP header.
+    { fraghdr, 0 },            // Fragment header.
+    { transporthdr, 0 },       // Transport layer header.
+    { icmp_iphdr, 0 },         // ICMP error inner IP header.
+    { icmp_fraghdr, 0 },       // ICMP error fragmentation header.
+    { icmp_transporthdr, 0 },  // ICMP error transport layer header.
+    { NULL, 0 },               // Payload. No buffer, it's a pointer to the original payload.
+  };
+
+  if (to_ipv6) {
+    iov_len = ipv4_packet(out, CLAT_POS_IPHDR, packet, packetsize);
+    if (iov_len > 0) {
+      send_rawv6(fd, out, iov_len);
+    }
+  } else {
+    iov_len = ipv6_packet(out, CLAT_POS_IPHDR, packet, packetsize);
+    if (iov_len > 0) {
+      fill_tun_header(&tun_targ, ETH_P_IP);
+      out[CLAT_POS_TUNHDR].iov_len = sizeof(tun_targ);
+      writev(fd, out, iov_len);
+    }
+  }
+}
diff --git a/clatd/translate.h b/clatd/translate.h
new file mode 100644
index 0000000..0e520f7
--- /dev/null
+++ b/clatd/translate.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * translate.h - translate from one version of ip to another
+ */
+#ifndef __TRANSLATE_H__
+#define __TRANSLATE_H__
+
+#include <linux/icmp.h>
+#include <linux/if_tun.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include "clatd.h"
+#include "common.h"
+
+#define MAX_TCP_HDR (15 * 4)  // Data offset field is 4 bits and counts in 32-bit words.
+
+// Calculates the checksum over all the packet components starting from pos.
+uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos);
+
+// Returns the total length of the packet components after pos.
+uint16_t packet_length(clat_packet packet, clat_packet_index pos);
+
+// Returns true iff the given IPv6 address is in the plat subnet.
+int is_in_plat_subnet(const struct in6_addr *addr6);
+
+// Functions to create tun, IPv4, and IPv6 headers.
+void fill_tun_header(struct tun_pi *tun_header, uint16_t proto);
+void fill_ip_header(struct iphdr *ip_targ, uint16_t payload_len, uint8_t protocol,
+                    const struct ip6_hdr *old_header);
+void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol,
+                     const struct iphdr *old_header);
+
+// Translate and send packets.
+void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize);
+
+// Translate IPv4 and IPv6 packets.
+int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len);
+int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len);
+
+// Deal with fragmented packets.
+size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ,
+                              const struct iphdr *old_header);
+uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ);
+
+// Deal with fragmented packets.
+size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ,
+                              const struct iphdr *old_header);
+uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ);
+
+// Translate ICMP packets.
+int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp,
+                  uint32_t checksum, const uint8_t *payload, size_t payload_size);
+int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6,
+                  const uint8_t *payload, size_t payload_size);
+
+// Translate generic IP packets.
+int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len);
+
+// Translate TCP and UDP packets.
+int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, uint32_t old_sum,
+               uint32_t new_sum, size_t len);
+int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, uint32_t old_sum,
+               uint32_t new_sum, size_t len);
+
+int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp,
+                  size_t header_size, uint32_t old_sum, uint32_t new_sum, const uint8_t *payload,
+                  size_t payload_size);
+int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp,
+                  uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size);
+
+#endif /* __TRANSLATE_H__ */
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 5ae1ef9..ba0d4d9 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -93,6 +93,9 @@
 
 // The filegroup lists files that are necessary for verifying building mdns as a standalone,
 // for use with service-connectivity-mdns-standalone-build-test
+// This filegroup should never be included in anywhere in the module build. It is only used for
+// building service-connectivity-mdns-standalone-build-test target. The files will be renamed by
+// copybara to prevent them from being shadowed by the bootclasspath copies.
 filegroup {
     name: "framework-connectivity-t-mdns-standalone-build-sources",
     srcs: [
@@ -121,6 +124,7 @@
     defaults: [
         "framework-connectivity-t-defaults",
         "enable-framework-connectivity-t-targets",
+        "FlaggedApiDefaults",
     ],
     api_srcs: framework_remoteauth_api_srcs,
     // Do not add static_libs to this library: put them in framework-connectivity instead.
@@ -175,11 +179,11 @@
         "//frameworks/base/core/tests/benchmarks",
         "//frameworks/base/core/tests/utillib",
         "//frameworks/base/tests/vcn",
-        "//frameworks/libs/net/common/testutils",
-        "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/staticlibs/testutils",
+        "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 4f816c5..e9a3f58 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -32,6 +32,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 
 import libcore.util.EmptyArray;
@@ -1245,18 +1246,21 @@
      * Return total statistics grouped by {@link #iface}; doesn't mutate the
      * original structure.
      * @hide
+     * @deprecated Use {@link #mapKeysNotNull(Function)} instead.
      */
+    @Deprecated
     public NetworkStats groupedByIface() {
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("groupedByIface is not supported");
+        }
         // Keep backward compatibility where the method filtered out tagged stats and keep the
         // operation counts as 0. The method used to deal with uid snapshot where tagged and
         // non-tagged stats were mixed. And this method was also in Android O API list,
         // so it is possible OEM can access it.
-        final NetworkStats copiedStats = this.clone();
-        copiedStats.filter(e -> e.getTag() == TAG_NONE);
-
         final Entry temp = new Entry();
-        final NetworkStats mappedStats = copiedStats.map(entry -> temp.setKeys(entry.getIface(),
-                UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
+        final NetworkStats mappedStats = this.mapKeysNotNull(entry -> entry.getTag() != TAG_NONE
+                ? null : temp.setKeys(entry.getIface(), UID_ALL,
+                SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
 
         for (int i = 0; i < mappedStats.size; i++) {
             mappedStats.operations[i] = 0L;
@@ -1268,17 +1272,20 @@
      * Return total statistics grouped by {@link #uid}; doesn't mutate the
      * original structure.
      * @hide
+     * @deprecated Use {@link #mapKeysNotNull(Function)} instead.
      */
+    @Deprecated
     public NetworkStats groupedByUid() {
+        if (SdkLevel.isAtLeastV()) {
+            throw new UnsupportedOperationException("groupedByUid is not supported");
+        }
         // Keep backward compatibility where the method filtered out tagged stats. The method used
         // to deal with uid snapshot where tagged and non-tagged stats were mixed. And
         // this method is also in Android O API list, so it is possible OEM can access it.
-        final NetworkStats copiedStats = this.clone();
-        copiedStats.filter(e -> e.getTag() == TAG_NONE);
-
         final Entry temp = new Entry();
-        return copiedStats.map(entry -> temp.setKeys(IFACE_ALL,
-                entry.getUid(), SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
+        return this.mapKeysNotNull(entry ->  entry.getTag() != TAG_NONE
+                ? null : temp.setKeys(IFACE_ALL, entry.getUid(),
+                SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
     }
 
     /**
@@ -1304,14 +1311,14 @@
     }
 
     /**
-     * Removes the interface name from all entries.
-     * This returns a newly constructed object instead of mutating the original structure.
+     * Returns a copy of this NetworkStats, replacing iface with IFACE_ALL in all entries.
+     *
      * @hide
      */
     @NonNull
-    public NetworkStats clearInterfaces() {
+    public NetworkStats withoutInterfaces() {
         final Entry temp = new Entry();
-        return map(entry -> temp.setKeys(IFACE_ALL, entry.getUid(), entry.getSet(),
+        return mapKeysNotNull(entry -> temp.setKeys(IFACE_ALL, entry.getUid(), entry.getSet(),
                 entry.getTag(), entry.getMetered(), entry.getRoaming(), entry.getDefaultNetwork()));
     }
 
@@ -1321,13 +1328,16 @@
      * Note that because NetworkStats is more akin to a map than to a list,
      * the entries will be grouped after they are mapped by the key fields,
      * e.g. uid, set, tag, defaultNetwork.
-     * Value fields with the same keys will be added together.
+     * Only the key returned by the function is used ; values will be forcefully
+     * copied from the original entry. Entries that map to the same set of keys
+     * will be added together.
      */
     @NonNull
-    private NetworkStats map(@NonNull Function<Entry, Entry> f) {
+    private NetworkStats mapKeysNotNull(@NonNull Function<Entry, Entry> f) {
         final NetworkStats ret = new NetworkStats(0, 1);
         for (Entry e : this) {
             final NetworkStats.Entry transformed = f.apply(e);
+            if (transformed == null) continue;
             if (transformed == e) {
                 throw new IllegalStateException("A new entry must be created.");
             }
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
index 2c839bc..d5dbf19 100644
--- a/framework-t/src/android/net/nsd/OffloadServiceInfo.java
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -19,7 +19,9 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.annotation.SystemApi;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,6 +40,7 @@
  * @hide
  */
 @SystemApi
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public final class OffloadServiceInfo implements Parcelable {
     @NonNull
     private final Key mKey;
diff --git a/framework-t/udc-extended-api/OWNERS b/framework-t/udc-extended-api/OWNERS
new file mode 100644
index 0000000..af583c3
--- /dev/null
+++ b/framework-t/udc-extended-api/OWNERS
@@ -0,0 +1,2 @@
+file:platform/packages/modules/Connectivity:master:/nearby/OWNERS
+file:platform/packages/modules/Connectivity:master:/remoteauth/OWNERS
diff --git a/framework-t/udc-extended-api/current.txt b/framework-t/udc-extended-api/current.txt
new file mode 100644
index 0000000..86745d4
--- /dev/null
+++ b/framework-t/udc-extended-api/current.txt
@@ -0,0 +1,267 @@
+// Signature format: 2.0
+package android.app.usage {
+
+  public final class NetworkStats implements java.lang.AutoCloseable {
+    method public void close();
+    method public boolean getNextBucket(@Nullable android.app.usage.NetworkStats.Bucket);
+    method public boolean hasNextBucket();
+  }
+
+  public static class NetworkStats.Bucket {
+    ctor public NetworkStats.Bucket();
+    method public int getDefaultNetworkStatus();
+    method public long getEndTimeStamp();
+    method public int getMetered();
+    method public int getRoaming();
+    method public long getRxBytes();
+    method public long getRxPackets();
+    method public long getStartTimeStamp();
+    method public int getState();
+    method public int getTag();
+    method public long getTxBytes();
+    method public long getTxPackets();
+    method public int getUid();
+    field public static final int DEFAULT_NETWORK_ALL = -1; // 0xffffffff
+    field public static final int DEFAULT_NETWORK_NO = 1; // 0x1
+    field public static final int DEFAULT_NETWORK_YES = 2; // 0x2
+    field public static final int METERED_ALL = -1; // 0xffffffff
+    field public static final int METERED_NO = 1; // 0x1
+    field public static final int METERED_YES = 2; // 0x2
+    field public static final int ROAMING_ALL = -1; // 0xffffffff
+    field public static final int ROAMING_NO = 1; // 0x1
+    field public static final int ROAMING_YES = 2; // 0x2
+    field public static final int STATE_ALL = -1; // 0xffffffff
+    field public static final int STATE_DEFAULT = 1; // 0x1
+    field public static final int STATE_FOREGROUND = 2; // 0x2
+    field public static final int TAG_NONE = 0; // 0x0
+    field public static final int UID_ALL = -1; // 0xffffffff
+    field public static final int UID_REMOVED = -4; // 0xfffffffc
+    field public static final int UID_TETHERING = -5; // 0xfffffffb
+  }
+
+  public class NetworkStatsManager {
+    method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, @Nullable String, long, long, int) throws java.lang.SecurityException;
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, @Nullable String, long, long, int, int) throws java.lang.SecurityException;
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, @Nullable String, long, long, int, int, int) throws java.lang.SecurityException;
+    method @WorkerThread public android.app.usage.NetworkStats querySummary(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+    method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+    method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+    method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
+    method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
+    method public void unregisterUsageCallback(@NonNull android.app.usage.NetworkStatsManager.UsageCallback);
+  }
+
+  public abstract static class NetworkStatsManager.UsageCallback {
+    ctor public NetworkStatsManager.UsageCallback();
+    method public abstract void onThresholdReached(int, @Nullable String);
+  }
+
+}
+
+package android.net {
+
+  public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public EthernetNetworkSpecifier(@NonNull String);
+    method public int describeContents();
+    method @Nullable public String getInterfaceName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
+  }
+
+  public final class IpSecAlgorithm implements android.os.Parcelable {
+    ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]);
+    ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int);
+    method public int describeContents();
+    method @NonNull public byte[] getKey();
+    method @NonNull public String getName();
+    method @NonNull public static java.util.Set<java.lang.String> getSupportedAlgorithms();
+    method public int getTruncationLengthBits();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final String AUTH_AES_CMAC = "cmac(aes)";
+    field public static final String AUTH_AES_XCBC = "xcbc(aes)";
+    field public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+    field public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
+    field public static final String AUTH_HMAC_MD5 = "hmac(md5)";
+    field public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
+    field public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
+    field public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+    field public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+    field public static final String CRYPT_AES_CBC = "cbc(aes)";
+    field public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
+  }
+
+  public class IpSecManager {
+    method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public void applyTransportModeTransform(@NonNull java.net.Socket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(@NonNull java.net.DatagramSocket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(@NonNull java.io.FileDescriptor, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public void removeTransportModeTransforms(@NonNull java.net.Socket) throws java.io.IOException;
+    method public void removeTransportModeTransforms(@NonNull java.net.DatagramSocket) throws java.io.IOException;
+    method public void removeTransportModeTransforms(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
+  }
+
+  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+  }
+
+  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+    method public void close();
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public void close() throws java.io.IOException;
+    method public java.io.FileDescriptor getFileDescriptor();
+    method public int getPort();
+  }
+
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void close();
+  }
+
+  public static class IpSecTransform.Builder {
+    ctor public IpSecTransform.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.IpSecTransform buildTransportModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method @NonNull public android.net.IpSecTransform.Builder setAuthenticatedEncryption(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setAuthentication(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setEncryption(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int);
+  }
+
+  public class TrafficStats {
+    ctor public TrafficStats();
+    method public static void clearThreadStatsTag();
+    method public static void clearThreadStatsUid();
+    method public static int getAndSetThreadStatsTag(int);
+    method public static long getMobileRxBytes();
+    method public static long getMobileRxPackets();
+    method public static long getMobileTxBytes();
+    method public static long getMobileTxPackets();
+    method public static long getRxBytes(@NonNull String);
+    method public static long getRxPackets(@NonNull String);
+    method public static int getThreadStatsTag();
+    method public static int getThreadStatsUid();
+    method public static long getTotalRxBytes();
+    method public static long getTotalRxPackets();
+    method public static long getTotalTxBytes();
+    method public static long getTotalTxPackets();
+    method public static long getTxBytes(@NonNull String);
+    method public static long getTxPackets(@NonNull String);
+    method public static long getUidRxBytes(int);
+    method public static long getUidRxPackets(int);
+    method @Deprecated public static long getUidTcpRxBytes(int);
+    method @Deprecated public static long getUidTcpRxSegments(int);
+    method @Deprecated public static long getUidTcpTxBytes(int);
+    method @Deprecated public static long getUidTcpTxSegments(int);
+    method public static long getUidTxBytes(int);
+    method public static long getUidTxPackets(int);
+    method @Deprecated public static long getUidUdpRxBytes(int);
+    method @Deprecated public static long getUidUdpRxPackets(int);
+    method @Deprecated public static long getUidUdpTxBytes(int);
+    method @Deprecated public static long getUidUdpTxPackets(int);
+    method public static void incrementOperationCount(int);
+    method public static void incrementOperationCount(int, int);
+    method public static void setThreadStatsTag(int);
+    method public static void setThreadStatsUid(int);
+    method public static void tagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+    method public static void tagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+    method public static void tagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
+    method public static void untagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+    method public static void untagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+    method public static void untagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
+    field public static final int UNSUPPORTED = -1; // 0xffffffff
+  }
+
+}
+
+package android.net.nsd {
+
+  public final class NsdManager {
+    method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
+    method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+    method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
+    method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener);
+    method public void registerServiceInfoCallback(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ServiceInfoCallback);
+    method @Deprecated public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
+    method @Deprecated public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener);
+    method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
+    method public void stopServiceResolution(@NonNull android.net.nsd.NsdManager.ResolveListener);
+    method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
+    method public void unregisterServiceInfoCallback(@NonNull android.net.nsd.NsdManager.ServiceInfoCallback);
+    field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
+    field public static final String EXTRA_NSD_STATE = "nsd_state";
+    field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
+    field public static final int FAILURE_BAD_PARAMETERS = 6; // 0x6
+    field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
+    field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
+    field public static final int FAILURE_OPERATION_NOT_RUNNING = 5; // 0x5
+    field public static final int NSD_STATE_DISABLED = 1; // 0x1
+    field public static final int NSD_STATE_ENABLED = 2; // 0x2
+    field public static final int PROTOCOL_DNS_SD = 1; // 0x1
+  }
+
+  public static interface NsdManager.DiscoveryListener {
+    method public void onDiscoveryStarted(String);
+    method public void onDiscoveryStopped(String);
+    method public void onServiceFound(android.net.nsd.NsdServiceInfo);
+    method public void onServiceLost(android.net.nsd.NsdServiceInfo);
+    method public void onStartDiscoveryFailed(String, int);
+    method public void onStopDiscoveryFailed(String, int);
+  }
+
+  public static interface NsdManager.RegistrationListener {
+    method public void onRegistrationFailed(android.net.nsd.NsdServiceInfo, int);
+    method public void onServiceRegistered(android.net.nsd.NsdServiceInfo);
+    method public void onServiceUnregistered(android.net.nsd.NsdServiceInfo);
+    method public void onUnregistrationFailed(android.net.nsd.NsdServiceInfo, int);
+  }
+
+  public static interface NsdManager.ResolveListener {
+    method public default void onResolutionStopped(@NonNull android.net.nsd.NsdServiceInfo);
+    method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
+    method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
+    method public default void onStopResolutionFailed(@NonNull android.net.nsd.NsdServiceInfo, int);
+  }
+
+  public static interface NsdManager.ServiceInfoCallback {
+    method public void onServiceInfoCallbackRegistrationFailed(int);
+    method public void onServiceInfoCallbackUnregistered();
+    method public void onServiceLost();
+    method public void onServiceUpdated(@NonNull android.net.nsd.NsdServiceInfo);
+  }
+
+  public final class NsdServiceInfo implements android.os.Parcelable {
+    ctor public NsdServiceInfo();
+    method public int describeContents();
+    method public java.util.Map<java.lang.String,byte[]> getAttributes();
+    method @Deprecated public java.net.InetAddress getHost();
+    method @NonNull public java.util.List<java.net.InetAddress> getHostAddresses();
+    method @Nullable public android.net.Network getNetwork();
+    method public int getPort();
+    method public String getServiceName();
+    method public String getServiceType();
+    method public void removeAttribute(String);
+    method public void setAttribute(String, String);
+    method @Deprecated public void setHost(java.net.InetAddress);
+    method public void setHostAddresses(@NonNull java.util.List<java.net.InetAddress>);
+    method public void setNetwork(@Nullable android.net.Network);
+    method public void setPort(int);
+    method public void setServiceName(String);
+    method public void setServiceType(String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
+  }
+
+}
+
diff --git a/framework-t/udc-extended-api/lint-baseline.txt b/framework-t/udc-extended-api/lint-baseline.txt
new file mode 100644
index 0000000..2996a3e
--- /dev/null
+++ b/framework-t/udc-extended-api/lint-baseline.txt
@@ -0,0 +1,89 @@
+// Baseline format: 1.0
+BannedThrow: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#queryDetailsForUid(int, String, long, long, int):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#queryDetailsForUidTag(int, String, long, long, int, int):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(int, String, long, long, int, int, int):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+
+
+BuilderSetStyle: android.net.IpSecTransform.Builder#buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTransportModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
+
+EqualsAndHashCode: android.net.IpSecTransform#equals(Object):
+    Must override both equals and hashCode; missing one in android.net.IpSecTransform
+
+
+ExecutorRegistration: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, android.os.Handler):
+    Registration methods should have overload that accepts delivery Executor: `registerUsageCallback`
+
+
+GenericException: android.app.usage.NetworkStats#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+GenericException: android.net.IpSecManager.SecurityParameterIndex#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+GenericException: android.net.IpSecManager.UdpEncapsulationSocket#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+GenericException: android.net.IpSecTransform#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+
+
+MissingBuildMethod: android.net.IpSecTransform.Builder:
+    android.net.IpSecTransform.Builder does not declare a `build()` method, but builder classes are expected to
+
+
+MissingNullability: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
+    Missing nullability on method `queryDetails` return
+MissingNullability: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long):
+    Missing nullability on method `querySummary` return
+MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long):
+    Missing nullability on method `querySummaryForDevice` return
+MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long):
+    Missing nullability on method `querySummaryForUser` return
+MissingNullability: android.net.IpSecAlgorithm#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `out` in method `writeToParcel`
+MissingNullability: android.net.IpSecManager.UdpEncapsulationSocket#getFileDescriptor():
+    Missing nullability on method `getFileDescriptor` return
+
+
+RethrowRemoteException: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+
+
+StaticFinalBuilder: android.net.IpSecTransform.Builder:
+    Builder must be final: android.net.IpSecTransform.Builder
+
+
+StaticUtils: android.net.TrafficStats:
+    Fully-static utility classes must not have constructor
+
+
+UseParcelFileDescriptor: android.net.IpSecManager#applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter socket in android.net.IpSecManager.applyTransportModeTransform(java.io.FileDescriptor socket, int direction, android.net.IpSecTransform transform)
+UseParcelFileDescriptor: android.net.IpSecManager#removeTransportModeTransforms(java.io.FileDescriptor) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter socket in android.net.IpSecManager.removeTransportModeTransforms(java.io.FileDescriptor socket)
+UseParcelFileDescriptor: android.net.IpSecManager.UdpEncapsulationSocket#getFileDescriptor():
+    Must use ParcelFileDescriptor instead of FileDescriptor in method android.net.IpSecManager.UdpEncapsulationSocket.getFileDescriptor()
+UseParcelFileDescriptor: android.net.TrafficStats#tagFileDescriptor(java.io.FileDescriptor) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter fd in android.net.TrafficStats.tagFileDescriptor(java.io.FileDescriptor fd)
+UseParcelFileDescriptor: android.net.TrafficStats#untagFileDescriptor(java.io.FileDescriptor) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter fd in android.net.TrafficStats.untagFileDescriptor(java.io.FileDescriptor fd)
+UseParcelFileDescriptor: com.android.server.NetworkManagementSocketTagger#tag(java.io.FileDescriptor) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter fd in com.android.server.NetworkManagementSocketTagger.tag(java.io.FileDescriptor fd)
+UseParcelFileDescriptor: com.android.server.NetworkManagementSocketTagger#untag(java.io.FileDescriptor) parameter #0:
+    Must use ParcelFileDescriptor instead of FileDescriptor in parameter fd in com.android.server.NetworkManagementSocketTagger.untag(java.io.FileDescriptor fd)
diff --git a/framework-t/udc-extended-api/module-lib-current.txt b/framework-t/udc-extended-api/module-lib-current.txt
new file mode 100644
index 0000000..5a8d47b
--- /dev/null
+++ b/framework-t/udc-extended-api/module-lib-current.txt
@@ -0,0 +1,209 @@
+// Signature format: 2.0
+package android.app.usage {
+
+  public class NetworkStatsManager {
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
+    method public static int getCollapsedRatType(int);
+    method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getMobileUidStats();
+    method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getWifiUidStats();
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void noteUidForeground(int, boolean);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException;
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats querySummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(@NonNull android.net.NetworkTemplate, long, long);
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryTaggedSummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}, conditional=true) public void registerUsageCallback(@NonNull android.net.NetworkTemplate, long, @NonNull java.util.concurrent.Executor, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long);
+    method public void setPollForce(boolean);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long);
+    field public static final int NETWORK_TYPE_5G_NSA = -2; // 0xfffffffe
+  }
+
+  public abstract static class NetworkStatsManager.UsageCallback {
+    method public void onThresholdReached(@NonNull android.net.NetworkTemplate);
+  }
+
+}
+
+package android.nearby {
+
+  public final class NearbyFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
+}
+
+package android.net {
+
+  public final class ConnectivityFrameworkInitializerTiramisu {
+    method public static void registerServiceWrappers();
+  }
+
+  public class EthernetManager {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addEthernetStateListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public java.util.List<java.lang.String> getInterfaceList();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void removeEthernetStateListener(@NonNull java.util.function.IntConsumer);
+    method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setEthernetEnabled(boolean);
+    method public void setIncludeTestInterfaces(boolean);
+    field public static final int ETHERNET_STATE_DISABLED = 0; // 0x0
+    field public static final int ETHERNET_STATE_ENABLED = 1; // 0x1
+    field public static final int ROLE_CLIENT = 1; // 0x1
+    field public static final int ROLE_NONE = 0; // 0x0
+    field public static final int ROLE_SERVER = 2; // 0x2
+    field public static final int STATE_ABSENT = 0; // 0x0
+    field public static final int STATE_LINK_DOWN = 1; // 0x1
+    field public static final int STATE_LINK_UP = 2; // 0x2
+  }
+
+  public static interface EthernetManager.InterfaceStateListener {
+    method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration);
+  }
+
+  public class IpSecManager {
+    field public static final int DIRECTION_FWD = 2; // 0x2
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public int getResourceId();
+  }
+
+  public class NetworkIdentity {
+    method public int getOemManaged();
+    method public int getRatType();
+    method public int getSubId();
+    method @Nullable public String getSubscriberId();
+    method public int getType();
+    method @Nullable public String getWifiNetworkKey();
+    method public boolean isDefaultNetwork();
+    method public boolean isMetered();
+    method public boolean isRoaming();
+  }
+
+  public static final class NetworkIdentity.Builder {
+    ctor public NetworkIdentity.Builder();
+    method @NonNull public android.net.NetworkIdentity build();
+    method @NonNull public android.net.NetworkIdentity.Builder clearRatType();
+    method @NonNull public android.net.NetworkIdentity.Builder setDefaultNetwork(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setMetered(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setNetworkStateSnapshot(@NonNull android.net.NetworkStateSnapshot);
+    method @NonNull public android.net.NetworkIdentity.Builder setOemManaged(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setRatType(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setRoaming(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setSubId(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkIdentity.Builder setType(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setWifiNetworkKey(@Nullable String);
+  }
+
+  public final class NetworkStateSnapshot implements android.os.Parcelable {
+    ctor public NetworkStateSnapshot(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @Nullable String, int);
+    method public int describeContents();
+    method public int getLegacyType();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public int getSubId();
+    method @Deprecated @Nullable public String getSubscriberId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR;
+  }
+
+  public class NetworkStatsCollection {
+    method @NonNull public java.util.Map<android.net.NetworkStatsCollection.Key,android.net.NetworkStatsHistory> getEntries();
+  }
+
+  public static final class NetworkStatsCollection.Builder {
+    ctor public NetworkStatsCollection.Builder(long);
+    method @NonNull public android.net.NetworkStatsCollection.Builder addEntry(@NonNull android.net.NetworkStatsCollection.Key, @NonNull android.net.NetworkStatsHistory);
+    method @NonNull public android.net.NetworkStatsCollection build();
+  }
+
+  public static final class NetworkStatsCollection.Key {
+    ctor public NetworkStatsCollection.Key(@NonNull java.util.Set<android.net.NetworkIdentity>, int, int, int);
+  }
+
+  public final class NetworkStatsHistory implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStatsHistory> CREATOR;
+  }
+
+  public static final class NetworkStatsHistory.Builder {
+    ctor public NetworkStatsHistory.Builder(long, int);
+    method @NonNull public android.net.NetworkStatsHistory.Builder addEntry(@NonNull android.net.NetworkStatsHistory.Entry);
+    method @NonNull public android.net.NetworkStatsHistory build();
+  }
+
+  public static final class NetworkStatsHistory.Entry {
+    ctor public NetworkStatsHistory.Entry(long, long, long, long, long, long, long);
+    method public long getActiveTime();
+    method public long getBucketStart();
+    method public long getOperations();
+    method public long getRxBytes();
+    method public long getRxPackets();
+    method public long getTxBytes();
+    method public long getTxPackets();
+  }
+
+  public final class NetworkTemplate implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDefaultNetworkStatus();
+    method public int getMatchRule();
+    method public int getMeteredness();
+    method public int getOemManaged();
+    method public int getRatType();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.String> getSubscriberIds();
+    method @NonNull public java.util.Set<java.lang.String> getWifiNetworkKeys();
+    method public boolean matches(@NonNull android.net.NetworkIdentity);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkTemplate> CREATOR;
+    field public static final int MATCH_BLUETOOTH = 8; // 0x8
+    field public static final int MATCH_CARRIER = 10; // 0xa
+    field public static final int MATCH_ETHERNET = 5; // 0x5
+    field public static final int MATCH_MOBILE = 1; // 0x1
+    field public static final int MATCH_PROXY = 9; // 0x9
+    field public static final int MATCH_WIFI = 4; // 0x4
+    field public static final int NETWORK_TYPE_ALL = -1; // 0xffffffff
+    field public static final int OEM_MANAGED_ALL = -1; // 0xffffffff
+    field public static final int OEM_MANAGED_NO = 0; // 0x0
+    field public static final int OEM_MANAGED_PAID = 1; // 0x1
+    field public static final int OEM_MANAGED_PRIVATE = 2; // 0x2
+    field public static final int OEM_MANAGED_YES = -2; // 0xfffffffe
+  }
+
+  public static final class NetworkTemplate.Builder {
+    ctor public NetworkTemplate.Builder(int);
+    method @NonNull public android.net.NetworkTemplate build();
+    method @NonNull public android.net.NetworkTemplate.Builder setDefaultNetworkStatus(int);
+    method @NonNull public android.net.NetworkTemplate.Builder setMeteredness(int);
+    method @NonNull public android.net.NetworkTemplate.Builder setOemManaged(int);
+    method @NonNull public android.net.NetworkTemplate.Builder setRatType(int);
+    method @NonNull public android.net.NetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.NetworkTemplate.Builder setSubscriberIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.NetworkTemplate.Builder setWifiNetworkKeys(@NonNull java.util.Set<java.lang.String>);
+  }
+
+  public class TrafficStats {
+    method public static void attachSocketTagger();
+    method public static void init(@NonNull android.content.Context);
+    method public static void setThreadStatsTagDownload();
+  }
+
+  public final class UnderlyingNetworkInfo implements android.os.Parcelable {
+    ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
+    method public int describeContents();
+    method @NonNull public String getInterface();
+    method public int getOwnerUid();
+    method @NonNull public java.util.List<java.lang.String> getUnderlyingInterfaces();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.UnderlyingNetworkInfo> CREATOR;
+  }
+
+}
+
diff --git a/framework-t/udc-extended-api/module-lib-lint-baseline.txt b/framework-t/udc-extended-api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..3158bd4
--- /dev/null
+++ b/framework-t/udc-extended-api/module-lib-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+BannedThrow: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(android.net.NetworkTemplate, long, long, int, int, int):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#querySummary(android.net.NetworkTemplate, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+BannedThrow: android.app.usage.NetworkStatsManager#queryTaggedSummary(android.net.NetworkTemplate, long, long):
+    Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
diff --git a/framework-t/udc-extended-api/module-lib-removed.txt b/framework-t/udc-extended-api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/udc-extended-api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-t/udc-extended-api/removed.txt b/framework-t/udc-extended-api/removed.txt
new file mode 100644
index 0000000..1ba87d8
--- /dev/null
+++ b/framework-t/udc-extended-api/removed.txt
@@ -0,0 +1,9 @@
+// Signature format: 2.0
+package android.net {
+
+  public class TrafficStats {
+    method @Deprecated public static void setThreadStatsUidSelf();
+  }
+
+}
+
diff --git a/framework-t/udc-extended-api/system-current.txt b/framework-t/udc-extended-api/system-current.txt
new file mode 100644
index 0000000..1549089
--- /dev/null
+++ b/framework-t/udc-extended-api/system-current.txt
@@ -0,0 +1,416 @@
+// Signature format: 2.0
+package android.app.usage {
+
+  public class NetworkStatsManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
+  }
+
+}
+
+package android.nearby {
+
+  public interface BroadcastCallback {
+    method public void onStatusChanged(int);
+    field public static final int STATUS_FAILURE = 1; // 0x1
+    field public static final int STATUS_FAILURE_ALREADY_REGISTERED = 2; // 0x2
+    field public static final int STATUS_FAILURE_MISSING_PERMISSIONS = 4; // 0x4
+    field public static final int STATUS_FAILURE_SIZE_EXCEED_LIMIT = 3; // 0x3
+    field public static final int STATUS_OK = 0; // 0x0
+  }
+
+  public abstract class BroadcastRequest {
+    method @NonNull public java.util.List<java.lang.Integer> getMediums();
+    method @IntRange(from=0xffffff81, to=126) public int getTxPower();
+    method public int getType();
+    method public int getVersion();
+    field public static final int BROADCAST_TYPE_NEARBY_PRESENCE = 3; // 0x3
+    field public static final int BROADCAST_TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int MEDIUM_BLE = 1; // 0x1
+    field public static final int PRESENCE_VERSION_UNKNOWN = -1; // 0xffffffff
+    field public static final int PRESENCE_VERSION_V0 = 0; // 0x0
+    field public static final int PRESENCE_VERSION_V1 = 1; // 0x1
+    field public static final int UNKNOWN_TX_POWER = -127; // 0xffffff81
+  }
+
+  public final class CredentialElement implements android.os.Parcelable {
+    ctor public CredentialElement(@NonNull String, @NonNull byte[]);
+    method public int describeContents();
+    method @NonNull public String getKey();
+    method @NonNull public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.CredentialElement> CREATOR;
+  }
+
+  public final class DataElement implements android.os.Parcelable {
+    ctor public DataElement(int, @NonNull byte[]);
+    method public int describeContents();
+    method public int getKey();
+    method @NonNull public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.DataElement> CREATOR;
+  }
+
+  public abstract class NearbyDevice {
+    method @NonNull public java.util.List<java.lang.Integer> getMediums();
+    method @Nullable public String getName();
+    method @IntRange(from=0xffffff81, to=126) public int getRssi();
+    method public static boolean isValidMedium(int);
+  }
+
+  public class NearbyManager {
+    method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.nearby.OffloadCapability>);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback);
+  }
+
+  public final class OffloadCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getVersion();
+    method public boolean isFastPairSupported();
+    method public boolean isNearbyShareSupported();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.OffloadCapability> CREATOR;
+  }
+
+  public static final class OffloadCapability.Builder {
+    ctor public OffloadCapability.Builder();
+    method @NonNull public android.nearby.OffloadCapability build();
+    method @NonNull public android.nearby.OffloadCapability.Builder setFastPairSupported(boolean);
+    method @NonNull public android.nearby.OffloadCapability.Builder setNearbyShareSupported(boolean);
+    method @NonNull public android.nearby.OffloadCapability.Builder setVersion(long);
+  }
+
+  public final class PresenceBroadcastRequest extends android.nearby.BroadcastRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.Integer> getActions();
+    method @NonNull public android.nearby.PrivateCredential getCredential();
+    method @NonNull public java.util.List<android.nearby.DataElement> getExtendedProperties();
+    method @NonNull public byte[] getSalt();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.PresenceBroadcastRequest> CREATOR;
+  }
+
+  public static final class PresenceBroadcastRequest.Builder {
+    ctor public PresenceBroadcastRequest.Builder(@NonNull java.util.List<java.lang.Integer>, @NonNull byte[], @NonNull android.nearby.PrivateCredential);
+    method @NonNull public android.nearby.PresenceBroadcastRequest.Builder addAction(@IntRange(from=1, to=255) int);
+    method @NonNull public android.nearby.PresenceBroadcastRequest.Builder addExtendedProperty(@NonNull android.nearby.DataElement);
+    method @NonNull public android.nearby.PresenceBroadcastRequest build();
+    method @NonNull public android.nearby.PresenceBroadcastRequest.Builder setTxPower(@IntRange(from=0xffffff81, to=126) int);
+    method @NonNull public android.nearby.PresenceBroadcastRequest.Builder setVersion(int);
+  }
+
+  public abstract class PresenceCredential {
+    method @NonNull public byte[] getAuthenticityKey();
+    method @NonNull public java.util.List<android.nearby.CredentialElement> getCredentialElements();
+    method public int getIdentityType();
+    method @NonNull public byte[] getSecretId();
+    method public int getType();
+    field public static final int CREDENTIAL_TYPE_PRIVATE = 0; // 0x0
+    field public static final int CREDENTIAL_TYPE_PUBLIC = 1; // 0x1
+    field public static final int IDENTITY_TYPE_PRIVATE = 1; // 0x1
+    field public static final int IDENTITY_TYPE_PROVISIONED = 2; // 0x2
+    field public static final int IDENTITY_TYPE_TRUSTED = 3; // 0x3
+    field public static final int IDENTITY_TYPE_UNKNOWN = 0; // 0x0
+  }
+
+  public final class PresenceDevice extends android.nearby.NearbyDevice implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getDeviceId();
+    method @Nullable public String getDeviceImageUrl();
+    method public int getDeviceType();
+    method public long getDiscoveryTimestampMillis();
+    method @NonNull public byte[] getEncryptedIdentity();
+    method @NonNull public java.util.List<android.nearby.DataElement> getExtendedProperties();
+    method @NonNull public byte[] getSalt();
+    method @NonNull public byte[] getSecretId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.PresenceDevice> CREATOR;
+  }
+
+  public static final class PresenceDevice.Builder {
+    ctor public PresenceDevice.Builder(@NonNull String, @NonNull byte[], @NonNull byte[], @NonNull byte[]);
+    method @NonNull public android.nearby.PresenceDevice.Builder addExtendedProperty(@NonNull android.nearby.DataElement);
+    method @NonNull public android.nearby.PresenceDevice.Builder addMedium(int);
+    method @NonNull public android.nearby.PresenceDevice build();
+    method @NonNull public android.nearby.PresenceDevice.Builder setDeviceImageUrl(@Nullable String);
+    method @NonNull public android.nearby.PresenceDevice.Builder setDeviceType(int);
+    method @NonNull public android.nearby.PresenceDevice.Builder setDiscoveryTimestampMillis(long);
+    method @NonNull public android.nearby.PresenceDevice.Builder setName(@Nullable String);
+    method @NonNull public android.nearby.PresenceDevice.Builder setRssi(int);
+  }
+
+  public final class PresenceScanFilter extends android.nearby.ScanFilter implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.nearby.PublicCredential> getCredentials();
+    method @NonNull public java.util.List<android.nearby.DataElement> getExtendedProperties();
+    method @NonNull public java.util.List<java.lang.Integer> getPresenceActions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.PresenceScanFilter> CREATOR;
+  }
+
+  public static final class PresenceScanFilter.Builder {
+    ctor public PresenceScanFilter.Builder();
+    method @NonNull public android.nearby.PresenceScanFilter.Builder addCredential(@NonNull android.nearby.PublicCredential);
+    method @NonNull public android.nearby.PresenceScanFilter.Builder addExtendedProperty(@NonNull android.nearby.DataElement);
+    method @NonNull public android.nearby.PresenceScanFilter.Builder addPresenceAction(@IntRange(from=1, to=255) int);
+    method @NonNull public android.nearby.PresenceScanFilter build();
+    method @NonNull public android.nearby.PresenceScanFilter.Builder setMaxPathLoss(@IntRange(from=0, to=127) int);
+  }
+
+  public final class PrivateCredential extends android.nearby.PresenceCredential implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getDeviceName();
+    method @NonNull public byte[] getMetadataEncryptionKey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.PrivateCredential> CREATOR;
+  }
+
+  public static final class PrivateCredential.Builder {
+    ctor public PrivateCredential.Builder(@NonNull byte[], @NonNull byte[], @NonNull byte[], @NonNull String);
+    method @NonNull public android.nearby.PrivateCredential.Builder addCredentialElement(@NonNull android.nearby.CredentialElement);
+    method @NonNull public android.nearby.PrivateCredential build();
+    method @NonNull public android.nearby.PrivateCredential.Builder setIdentityType(int);
+  }
+
+  public final class PublicCredential extends android.nearby.PresenceCredential implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public byte[] getEncryptedMetadata();
+    method @NonNull public byte[] getEncryptedMetadataKeyTag();
+    method @NonNull public byte[] getPublicKey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.PublicCredential> CREATOR;
+  }
+
+  public static final class PublicCredential.Builder {
+    ctor public PublicCredential.Builder(@NonNull byte[], @NonNull byte[], @NonNull byte[], @NonNull byte[], @NonNull byte[]);
+    method @NonNull public android.nearby.PublicCredential.Builder addCredentialElement(@NonNull android.nearby.CredentialElement);
+    method @NonNull public android.nearby.PublicCredential build();
+    method @NonNull public android.nearby.PublicCredential.Builder setIdentityType(int);
+  }
+
+  public interface ScanCallback {
+    method public void onDiscovered(@NonNull android.nearby.NearbyDevice);
+    method public default void onError(int);
+    method public void onLost(@NonNull android.nearby.NearbyDevice);
+    method public void onUpdated(@NonNull android.nearby.NearbyDevice);
+    field public static final int ERROR_INVALID_ARGUMENT = 2; // 0x2
+    field public static final int ERROR_PERMISSION_DENIED = 3; // 0x3
+    field public static final int ERROR_RESOURCE_EXHAUSTED = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+  }
+
+  public abstract class ScanFilter {
+    method @IntRange(from=0, to=127) public int getMaxPathLoss();
+    method public int getType();
+  }
+
+  public final class ScanRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.nearby.ScanFilter> getScanFilters();
+    method public int getScanMode();
+    method public int getScanType();
+    method @NonNull public android.os.WorkSource getWorkSource();
+    method public boolean isBleEnabled();
+    method public boolean isOffloadOnly();
+    method public static boolean isValidScanMode(int);
+    method public static boolean isValidScanType(int);
+    method @NonNull public static String scanModeToString(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nearby.ScanRequest> CREATOR;
+    field public static final int SCAN_MODE_BALANCED = 1; // 0x1
+    field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
+    field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
+    field public static final int SCAN_MODE_NO_POWER = -1; // 0xffffffff
+    field public static final int SCAN_TYPE_FAST_PAIR = 1; // 0x1
+    field public static final int SCAN_TYPE_NEARBY_PRESENCE = 2; // 0x2
+  }
+
+  public static final class ScanRequest.Builder {
+    ctor public ScanRequest.Builder();
+    method @NonNull public android.nearby.ScanRequest.Builder addScanFilter(@NonNull android.nearby.ScanFilter);
+    method @NonNull public android.nearby.ScanRequest build();
+    method @NonNull public android.nearby.ScanRequest.Builder setBleEnabled(boolean);
+    method @NonNull public android.nearby.ScanRequest.Builder setOffloadOnly(boolean);
+    method @NonNull public android.nearby.ScanRequest.Builder setScanMode(int);
+    method @NonNull public android.nearby.ScanRequest.Builder setScanType(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.nearby.ScanRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
+  }
+
+}
+
+package android.net {
+
+  public class EthernetManager {
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void disableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void enableInterface(@NonNull String, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}) public void updateConfiguration(@NonNull String, @NonNull android.net.EthernetNetworkUpdateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>);
+  }
+
+  public static interface EthernetManager.TetheredInterfaceCallback {
+    method public void onAvailable(@NonNull String);
+    method public void onUnavailable();
+  }
+
+  public static class EthernetManager.TetheredInterfaceRequest {
+    method public void release();
+  }
+
+  public final class EthernetNetworkManagementException extends java.lang.RuntimeException implements android.os.Parcelable {
+    ctor public EthernetNetworkManagementException(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkManagementException> CREATOR;
+  }
+
+  public final class EthernetNetworkUpdateRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.IpConfiguration getIpConfiguration();
+    method @Nullable public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkUpdateRequest> CREATOR;
+  }
+
+  public static final class EthernetNetworkUpdateRequest.Builder {
+    ctor public EthernetNetworkUpdateRequest.Builder();
+    ctor public EthernetNetworkUpdateRequest.Builder(@NonNull android.net.EthernetNetworkUpdateRequest);
+    method @NonNull public android.net.EthernetNetworkUpdateRequest build();
+    method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setIpConfiguration(@Nullable android.net.IpConfiguration);
+    method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+  }
+
+  public class IpSecManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void startTunnelModeTransformMigration(@NonNull android.net.IpSecTransform, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress);
+  }
+
+  public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void addAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method public void close();
+    method @NonNull public String getInterfaceName();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
+  }
+
+  public static class IpSecTransform.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+  }
+
+  public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable {
+    ctor public NetworkStats(long, int);
+    method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+    method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
+    method public int describeContents();
+    method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator();
+    method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR;
+    field public static final int DEFAULT_NETWORK_ALL = -1; // 0xffffffff
+    field public static final int DEFAULT_NETWORK_NO = 0; // 0x0
+    field public static final int DEFAULT_NETWORK_YES = 1; // 0x1
+    field public static final String IFACE_VT = "vt_data0";
+    field public static final int METERED_ALL = -1; // 0xffffffff
+    field public static final int METERED_NO = 0; // 0x0
+    field public static final int METERED_YES = 1; // 0x1
+    field public static final int ROAMING_ALL = -1; // 0xffffffff
+    field public static final int ROAMING_NO = 0; // 0x0
+    field public static final int ROAMING_YES = 1; // 0x1
+    field public static final int SET_ALL = -1; // 0xffffffff
+    field public static final int SET_DEFAULT = 0; // 0x0
+    field public static final int SET_FOREGROUND = 1; // 0x1
+    field public static final int TAG_NONE = 0; // 0x0
+    field public static final int UID_ALL = -1; // 0xffffffff
+    field public static final int UID_TETHERING = -5; // 0xfffffffb
+  }
+
+  public static class NetworkStats.Entry {
+    ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
+    method public int getDefaultNetwork();
+    method public int getMetered();
+    method public long getOperations();
+    method public int getRoaming();
+    method public long getRxBytes();
+    method public long getRxPackets();
+    method public int getSet();
+    method public int getTag();
+    method public long getTxBytes();
+    method public long getTxPackets();
+    method public int getUid();
+  }
+
+  public class TrafficStats {
+    method public static void setThreadStatsTagApp();
+    method public static void setThreadStatsTagBackup();
+    method public static void setThreadStatsTagRestore();
+    field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = -113; // 0xffffff8f
+    field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = -128; // 0xffffff80
+    field public static final int TAG_NETWORK_STACK_RANGE_END = -257; // 0xfffffeff
+    field public static final int TAG_NETWORK_STACK_RANGE_START = -768; // 0xfffffd00
+    field public static final int TAG_SYSTEM_IMPERSONATION_RANGE_END = -241; // 0xffffff0f
+    field public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = -256; // 0xffffff00
+  }
+
+}
+
+package android.net.netstats.provider {
+
+  public abstract class NetworkStatsProvider {
+    ctor public NetworkStatsProvider();
+    method public void notifyAlertReached();
+    method public void notifyLimitReached();
+    method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+    method public void notifyWarningReached();
+    method public abstract void onRequestStatsUpdate(int);
+    method public abstract void onSetAlert(long);
+    method public abstract void onSetLimit(@NonNull String, long);
+    method public void onSetWarningAndLimit(@NonNull String, long, long);
+    field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
+  }
+
+}
+
+package android.net.nsd {
+
+  public final class NsdManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine);
+  }
+
+  public interface OffloadEngine {
+    method public void onOffloadServiceRemoved(@NonNull android.net.nsd.OffloadServiceInfo);
+    method public void onOffloadServiceUpdated(@NonNull android.net.nsd.OffloadServiceInfo);
+    field public static final int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; // 0x1
+    field public static final int OFFLOAD_TYPE_FILTER_QUERIES = 2; // 0x2
+    field public static final int OFFLOAD_TYPE_FILTER_REPLIES = 4; // 0x4
+    field public static final int OFFLOAD_TYPE_REPLY = 1; // 0x1
+  }
+
+  public final class OffloadServiceInfo implements android.os.Parcelable {
+    ctor public OffloadServiceInfo(@NonNull android.net.nsd.OffloadServiceInfo.Key, @NonNull java.util.List<java.lang.String>, @NonNull String, @Nullable byte[], @IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int, long);
+    method public int describeContents();
+    method @NonNull public String getHostname();
+    method @NonNull public android.net.nsd.OffloadServiceInfo.Key getKey();
+    method @Nullable public byte[] getOffloadPayload();
+    method public long getOffloadType();
+    method public int getPriority();
+    method @NonNull public java.util.List<java.lang.String> getSubtypes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo> CREATOR;
+  }
+
+  public static final class OffloadServiceInfo.Key implements android.os.Parcelable {
+    ctor public OffloadServiceInfo.Key(@NonNull String, @NonNull String);
+    method public int describeContents();
+    method @NonNull public String getServiceName();
+    method @NonNull public String getServiceType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo.Key> CREATOR;
+  }
+
+}
+
diff --git a/framework-t/udc-extended-api/system-lint-baseline.txt b/framework-t/udc-extended-api/system-lint-baseline.txt
new file mode 100644
index 0000000..9baf991
--- /dev/null
+++ b/framework-t/udc-extended-api/system-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
+
+GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
diff --git a/framework-t/udc-extended-api/system-removed.txt b/framework-t/udc-extended-api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework-t/udc-extended-api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework/Android.bp b/framework/Android.bp
index e577e6d..182c558 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -19,6 +19,15 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+// In the branch which does not support FlaggedAPI, use this default to ignore the annotated APIs.
+java_defaults {
+    name: "FlaggedApiDefaults",
+}
+
+// The above variables may have different values
+// depending on the branch, and this comment helps
+// separate them from the rest of the file to avoid merge conflicts
+
 filegroup {
     name: "framework-connectivity-internal-sources",
     srcs: [
@@ -152,6 +161,7 @@
     defaults: [
         "framework-connectivity-defaults",
         "CronetJavaDefaults",
+        "FlaggedApiDefaults",
     ],
     installable: true,
     jarjar_rules: ":framework-connectivity-jarjar-rules",
@@ -175,11 +185,11 @@
         "//frameworks/base/core/tests/benchmarks",
         "//frameworks/base/core/tests/utillib",
         "//frameworks/base/tests/vcn",
-        "//frameworks/libs/net/common/testutils",
-        "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/staticlibs/testutils",
+        "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
         "//packages/modules/Connectivity/Cronet/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java
index c6034f1..5fefcd6 100644
--- a/framework/src/android/net/DnsResolver.java
+++ b/framework/src/android/net/DnsResolver.java
@@ -77,6 +77,15 @@
     @interface QueryType {}
     public static final int TYPE_A = 1;
     public static final int TYPE_AAAA = 28;
+    // TODO: add below constants as part of QueryType and the public API
+    /** @hide */
+    public static final int TYPE_PTR = 12;
+    /** @hide */
+    public static final int TYPE_TXT = 16;
+    /** @hide */
+    public static final int TYPE_SRV = 33;
+    /** @hide */
+    public static final int TYPE_ANY = 255;
 
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_EMPTY,
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 8e219a6..abda1fa 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -260,6 +260,19 @@
     private int mEnterpriseId;
 
     /**
+     * Gets the enterprise IDs as an int. Internal callers only.
+     *
+     * DO NOT USE THIS if not immediately collapsing back into a scalar. Instead,
+     * prefer getEnterpriseIds/hasEnterpriseId.
+     *
+     * @return the internal, version-dependent int representing enterprise ids
+     * @hide
+     */
+    public int getEnterpriseIdsInternal() {
+        return mEnterpriseId;
+    }
+
+    /**
      * Get enteprise identifiers set.
      *
      * Get all the enterprise capabilities identifier set on this {@code NetworkCapability}
@@ -741,8 +754,10 @@
 
     /**
      * Capabilities that are managed by ConnectivityService.
+     * @hide
      */
-    private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
+    @VisibleForTesting
+    public static final long CONNECTIVITY_MANAGED_CAPABILITIES =
             BitUtils.packBitList(
                     NET_CAPABILITY_VALIDATED,
                     NET_CAPABILITY_CAPTIVE_PORTAL,
@@ -859,6 +874,19 @@
     }
 
     /**
+     * Gets the capabilities as an int. Internal callers only.
+     *
+     * DO NOT USE THIS if not immediately collapsing back into a scalar. Instead,
+     * prefer getCapabilities/hasCapability.
+     *
+     * @return an internal, version-dependent int representing the capabilities
+     * @hide
+     */
+    public long getCapabilitiesInternal() {
+        return mNetworkCapabilities;
+    }
+
+    /**
      * Gets all the capabilities set on this {@code NetworkCapability} instance.
      *
      * @return an array of capability values for this instance.
diff --git a/framework/udc-extended-api/current.txt b/framework/udc-extended-api/current.txt
new file mode 100644
index 0000000..6860c3c
--- /dev/null
+++ b/framework/udc-extended-api/current.txt
@@ -0,0 +1,816 @@
+// Signature format: 2.0
+package android.net {
+
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
+  public class ConnectivityDiagnosticsManager {
+    method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+    method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+  }
+
+  public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+    method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+    method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+    method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+  }
+
+  public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getAdditionalInfo();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public long getReportTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
+    field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted";
+    field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded";
+    field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult";
+    field public static final int NETWORK_PROBE_DNS = 4; // 0x4
+    field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20
+    field public static final int NETWORK_PROBE_HTTP = 8; // 0x8
+    field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10
+    field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40
+    field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0
+    field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2
+    field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3
+    field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1
+  }
+
+  public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method public int getDetectionMethod();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public long getReportTimestamp();
+    method @NonNull public android.os.PersistableBundle getStallDetails();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR;
+    field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+    field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+    field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts";
+    field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis";
+    field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate";
+  }
+
+  public class ConnectivityManager {
+    method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
+    method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+    method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
+    method @Deprecated public boolean getBackgroundDataSetting();
+    method @Nullable public android.net.Network getBoundNetworkForProcess();
+    method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress);
+    method @Nullable public android.net.ProxyInfo getDefaultProxy();
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference();
+    method @Nullable public byte[] getNetworkWatchlistConfigHash();
+    method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork();
+    method public int getRestrictBackgroundStatus();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered();
+    method public boolean isDefaultNetworkActive();
+    method @Deprecated public static boolean isNetworkTypeValid(int);
+    method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+    method public void releaseNetworkRequest(@NonNull android.app.PendingIntent);
+    method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener);
+    method @Deprecated public void reportBadNetwork(@Nullable android.net.Network);
+    method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean);
+    method public boolean requestBandwidthUpdate(@NonNull android.net.Network);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+    method @Deprecated public void setNetworkPreference(int);
+    method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network);
+    method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+    method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent);
+    field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+    field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+    field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+    field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+    field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
+    field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
+    field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
+    field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo";
+    field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover";
+    field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
+    field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo";
+    field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
+    field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType";
+    field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+    field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+    field public static final String EXTRA_REASON = "reason";
+    field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1
+    field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4
+    field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2
+    field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+    field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+    field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+    field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7
+    field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8
+    field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9
+    field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0
+    field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4
+    field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5
+    field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2
+    field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3
+    field @Deprecated public static final int TYPE_VPN = 17; // 0x11
+    field @Deprecated public static final int TYPE_WIFI = 1; // 0x1
+    field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6
+  }
+
+  public static class ConnectivityManager.NetworkCallback {
+    ctor public ConnectivityManager.NetworkCallback();
+    ctor public ConnectivityManager.NetworkCallback(int);
+    method public void onAvailable(@NonNull android.net.Network);
+    method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean);
+    method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities);
+    method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties);
+    method public void onLosing(@NonNull android.net.Network, int);
+    method public void onLost(@NonNull android.net.Network);
+    method public void onUnavailable();
+    field public static final int FLAG_INCLUDE_LOCATION_INFO = 1; // 0x1
+  }
+
+  public static interface ConnectivityManager.OnNetworkActiveListener {
+    method public void onNetworkActive();
+  }
+
+  public class DhcpInfo implements android.os.Parcelable {
+    ctor public DhcpInfo();
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
+    field public int dns1;
+    field public int dns2;
+    field public int gateway;
+    field public int ipAddress;
+    field public int leaseDuration;
+    field public int netmask;
+    field public int serverAddress;
+  }
+
+  public final class DnsResolver {
+    method @NonNull public static android.net.DnsResolver getInstance();
+    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+    method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+    method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+    method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+    field public static final int CLASS_IN = 1; // 0x1
+    field public static final int ERROR_PARSE = 0; // 0x0
+    field public static final int ERROR_SYSTEM = 1; // 0x1
+    field public static final int FLAG_EMPTY = 0; // 0x0
+    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+    field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+    field public static final int FLAG_NO_RETRY = 1; // 0x1
+    field public static final int TYPE_A = 1; // 0x1
+    field public static final int TYPE_AAAA = 28; // 0x1c
+  }
+
+  public static interface DnsResolver.Callback<T> {
+    method public void onAnswer(@NonNull T, int);
+    method public void onError(@NonNull android.net.DnsResolver.DnsException);
+  }
+
+  public static class DnsResolver.DnsException extends java.lang.Exception {
+    ctor public DnsResolver.DnsException(int, @Nullable Throwable);
+    field public final int code;
+  }
+
+  public class InetAddresses {
+    method public static boolean isNumericAddress(@NonNull String);
+    method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
+  }
+
+  public final class IpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.ProxyInfo getHttpProxy();
+    method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
+  }
+
+  public static final class IpConfiguration.Builder {
+    ctor public IpConfiguration.Builder();
+    method @NonNull public android.net.IpConfiguration build();
+    method @NonNull public android.net.IpConfiguration.Builder setHttpProxy(@Nullable android.net.ProxyInfo);
+    method @NonNull public android.net.IpConfiguration.Builder setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+  }
+
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+    method public boolean contains(@NonNull java.net.InetAddress);
+    method public int describeContents();
+    method @NonNull public java.net.InetAddress getAddress();
+    method @IntRange(from=0, to=128) public int getPrefixLength();
+    method @NonNull public byte[] getRawAddress();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
+  }
+
+  public class LinkAddress implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.net.InetAddress getAddress();
+    method public int getFlags();
+    method @IntRange(from=0, to=128) public int getPrefixLength();
+    method public int getScope();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR;
+  }
+
+  public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties();
+    method public boolean addRoute(@NonNull android.net.RouteInfo);
+    method public void clear();
+    method public int describeContents();
+    method @Nullable public java.net.Inet4Address getDhcpServerAddress();
+    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+    method @Nullable public String getDomains();
+    method @Nullable public android.net.ProxyInfo getHttpProxy();
+    method @Nullable public String getInterfaceName();
+    method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
+    method public int getMtu();
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method @Nullable public String getPrivateDnsServerName();
+    method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
+    method public boolean isPrivateDnsActive();
+    method public boolean isWakeOnLanSupported();
+    method public void setDhcpServerAddress(@Nullable java.net.Inet4Address);
+    method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+    method public void setDomains(@Nullable String);
+    method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+    method public void setInterfaceName(@Nullable String);
+    method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
+    method public void setMtu(int);
+    method public void setNat64Prefix(@Nullable android.net.IpPrefix);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
+  }
+
+  public final class MacAddress implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]);
+    method @NonNull public static android.net.MacAddress fromString(@NonNull String);
+    method public int getAddressType();
+    method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac();
+    method public boolean isLocallyAssigned();
+    method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
+    method @NonNull public byte[] toByteArray();
+    method @NonNull public String toOuiString();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.net.MacAddress BROADCAST_ADDRESS;
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
+    field public static final int TYPE_BROADCAST = 3; // 0x3
+    field public static final int TYPE_MULTICAST = 2; // 0x2
+    field public static final int TYPE_UNICAST = 1; // 0x1
+  }
+
+  public class Network implements android.os.Parcelable {
+    method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException;
+    method public void bindSocket(java.net.Socket) throws java.io.IOException;
+    method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException;
+    method public int describeContents();
+    method public static android.net.Network fromNetworkHandle(long);
+    method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException;
+    method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException;
+    method public long getNetworkHandle();
+    method public javax.net.SocketFactory getSocketFactory();
+    method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
+    method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    ctor public NetworkCapabilities();
+    ctor public NetworkCapabilities(android.net.NetworkCapabilities);
+    method public int describeContents();
+    method @NonNull public int[] getCapabilities();
+    method @NonNull public int[] getEnterpriseIds();
+    method public int getLinkDownstreamBandwidthKbps();
+    method public int getLinkUpstreamBandwidthKbps();
+    method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method public int getOwnerUid();
+    method public int getSignalStrength();
+    method @Nullable public android.net.TransportInfo getTransportInfo();
+    method public boolean hasCapability(int);
+    method public boolean hasEnterpriseId(int);
+    method public boolean hasTransport(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
+    field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11
+    field public static final int NET_CAPABILITY_CBS = 5; // 0x5
+    field public static final int NET_CAPABILITY_DUN = 2; // 0x2
+    field public static final int NET_CAPABILITY_EIMS = 10; // 0xa
+    field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d
+    field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13
+    field public static final int NET_CAPABILITY_FOTA = 3; // 0x3
+    field public static final int NET_CAPABILITY_HEAD_UNIT = 32; // 0x20
+    field public static final int NET_CAPABILITY_IA = 7; // 0x7
+    field public static final int NET_CAPABILITY_IMS = 4; // 0x4
+    field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
+    field public static final int NET_CAPABILITY_MCX = 23; // 0x17
+    field public static final int NET_CAPABILITY_MMS = 0; // 0x0
+    field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+    field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
+    field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
+    field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
+    field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
+    field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
+    field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+    field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+    field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
+    field public static final int NET_CAPABILITY_RCS = 8; // 0x8
+    field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+    field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
+    field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
+    field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
+    field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
+    field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+    field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1
+    field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2
+    field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3
+    field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4
+    field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5
+    field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
+    field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
+    field public static final int TRANSPORT_CELLULAR = 0; // 0x0
+    field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field public static final int TRANSPORT_THREAD = 9; // 0x9
+    field public static final int TRANSPORT_USB = 8; // 0x8
+    field public static final int TRANSPORT_VPN = 4; // 0x4
+    field public static final int TRANSPORT_WIFI = 1; // 0x1
+    field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
+  }
+
+  @Deprecated public class NetworkInfo implements android.os.Parcelable {
+    ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String);
+    method @Deprecated public int describeContents();
+    method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState();
+    method @Deprecated public String getExtraInfo();
+    method @Deprecated public String getReason();
+    method @Deprecated public android.net.NetworkInfo.State getState();
+    method @Deprecated public int getSubtype();
+    method @Deprecated public String getSubtypeName();
+    method @Deprecated public int getType();
+    method @Deprecated public String getTypeName();
+    method @Deprecated public boolean isAvailable();
+    method @Deprecated public boolean isConnected();
+    method @Deprecated public boolean isConnectedOrConnecting();
+    method @Deprecated public boolean isFailover();
+    method @Deprecated public boolean isRoaming();
+    method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
+  }
+
+  @Deprecated public enum NetworkInfo.DetailedState {
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
+  }
+
+  @Deprecated public enum NetworkInfo.State {
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN;
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
+    method public int describeContents();
+    method @NonNull public int[] getCapabilities();
+    method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method @NonNull public int[] getTransportTypes();
+    method public boolean hasCapability(int);
+    method public boolean hasTransport(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR;
+  }
+
+  public static class NetworkRequest.Builder {
+    ctor public NetworkRequest.Builder();
+    ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest);
+    method public android.net.NetworkRequest.Builder addCapability(int);
+    method public android.net.NetworkRequest.Builder addTransportType(int);
+    method public android.net.NetworkRequest build();
+    method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
+    method public android.net.NetworkRequest.Builder removeCapability(int);
+    method public android.net.NetworkRequest.Builder removeTransportType(int);
+    method @NonNull public android.net.NetworkRequest.Builder setIncludeOtherUidNetworks(boolean);
+    method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+  }
+
+  public class ParseException extends java.lang.RuntimeException {
+    ctor public ParseException(@NonNull String);
+    ctor public ParseException(@NonNull String, @NonNull Throwable);
+    field public String response;
+  }
+
+  public class ProxyInfo implements android.os.Parcelable {
+    ctor public ProxyInfo(@Nullable android.net.ProxyInfo);
+    method public static android.net.ProxyInfo buildDirectProxy(String, int);
+    method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>);
+    method public static android.net.ProxyInfo buildPacProxy(android.net.Uri);
+    method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int);
+    method public int describeContents();
+    method public String[] getExclusionList();
+    method public String getHost();
+    method public android.net.Uri getPacFileUrl();
+    method public int getPort();
+    method public boolean isValid();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR;
+  }
+
+  public final class RouteInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.IpPrefix getDestination();
+    method @Nullable public java.net.InetAddress getGateway();
+    method @Nullable public String getInterface();
+    method public int getType();
+    method public boolean hasGateway();
+    method public boolean isDefaultRoute();
+    method public boolean matches(java.net.InetAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
+    field public static final int RTN_THROW = 9; // 0x9
+    field public static final int RTN_UNICAST = 1; // 0x1
+    field public static final int RTN_UNREACHABLE = 7; // 0x7
+  }
+
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void close();
+    method public final void start(@IntRange(from=0xa, to=0xe10) int);
+    method public final void stop();
+    field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+    field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0
+    field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+    field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+    field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+    field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2
+  }
+
+  public static class SocketKeepalive.Callback {
+    ctor public SocketKeepalive.Callback();
+    method public void onDataReceived();
+    method public void onError(int);
+    method public void onStarted();
+    method public void onStopped();
+  }
+
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+    method @Nullable public String getDomains();
+    method @Nullable public java.net.InetAddress getGateway();
+    method @NonNull public android.net.LinkAddress getIpAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
+  public static final class StaticIpConfiguration.Builder {
+    ctor public StaticIpConfiguration.Builder();
+    method @NonNull public android.net.StaticIpConfiguration build();
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@NonNull android.net.LinkAddress);
+  }
+
+  public interface TransportInfo {
+  }
+
+}
+
+package android.net.http {
+
+  public abstract class BidirectionalStream {
+    ctor public BidirectionalStream();
+    method public abstract void cancel();
+    method public abstract void flush();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @NonNull public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isDelayRequestHeadersUntilFirstFlushEnabled();
+    method public abstract boolean isDone();
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
+    method public abstract void start();
+    method public abstract void write(@NonNull java.nio.ByteBuffer, boolean);
+    field public static final int STREAM_PRIORITY_HIGHEST = 4; // 0x4
+    field public static final int STREAM_PRIORITY_IDLE = 0; // 0x0
+    field public static final int STREAM_PRIORITY_LOW = 2; // 0x2
+    field public static final int STREAM_PRIORITY_LOWEST = 1; // 0x1
+    field public static final int STREAM_PRIORITY_MEDIUM = 3; // 0x3
+  }
+
+  public abstract static class BidirectionalStream.Builder {
+    ctor public BidirectionalStream.Builder();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream build();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setDelayRequestHeadersUntilFirstFlushEnabled(boolean);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsUid(int);
+  }
+
+  public static interface BidirectionalStream.Callback {
+    method public void onCanceled(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
+    method public void onResponseHeadersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onResponseTrailersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull android.net.http.HeaderBlock);
+    method public void onStreamReady(@NonNull android.net.http.BidirectionalStream);
+    method public void onSucceeded(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onWriteCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
+  }
+
+  public abstract class CallbackException extends android.net.http.HttpException {
+    ctor protected CallbackException(@Nullable String, @Nullable Throwable);
+  }
+
+  public class ConnectionMigrationOptions {
+    method public int getAllowNonDefaultNetworkUsage();
+    method public int getDefaultNetworkMigration();
+    method public int getPathDegradationMigration();
+    field public static final int MIGRATION_OPTION_DISABLED = 2; // 0x2
+    field public static final int MIGRATION_OPTION_ENABLED = 1; // 0x1
+    field public static final int MIGRATION_OPTION_UNSPECIFIED = 0; // 0x0
+  }
+
+  public static final class ConnectionMigrationOptions.Builder {
+    ctor public ConnectionMigrationOptions.Builder();
+    method @NonNull public android.net.http.ConnectionMigrationOptions build();
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setAllowNonDefaultNetworkUsage(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setDefaultNetworkMigration(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setPathDegradationMigration(int);
+  }
+
+  public final class DnsOptions {
+    method public int getPersistHostCache();
+    method @Nullable public java.time.Duration getPersistHostCachePeriod();
+    method public int getPreestablishConnectionsToStaleDnsResults();
+    method public int getStaleDns();
+    method @Nullable public android.net.http.DnsOptions.StaleDnsOptions getStaleDnsOptions();
+    method public int getUseHttpStackDnsResolver();
+    field public static final int DNS_OPTION_DISABLED = 2; // 0x2
+    field public static final int DNS_OPTION_ENABLED = 1; // 0x1
+    field public static final int DNS_OPTION_UNSPECIFIED = 0; // 0x0
+  }
+
+  public static final class DnsOptions.Builder {
+    ctor public DnsOptions.Builder();
+    method @NonNull public android.net.http.DnsOptions build();
+    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCache(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCachePeriod(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.Builder setPreestablishConnectionsToStaleDnsResults(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setStaleDns(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setStaleDnsOptions(@NonNull android.net.http.DnsOptions.StaleDnsOptions);
+    method @NonNull public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolver(int);
+  }
+
+  public static class DnsOptions.StaleDnsOptions {
+    method public int getAllowCrossNetworkUsage();
+    method @Nullable public java.time.Duration getFreshLookupTimeout();
+    method @Nullable public java.time.Duration getMaxExpiredDelay();
+    method public int getUseStaleOnNameNotResolved();
+  }
+
+  public static final class DnsOptions.StaleDnsOptions.Builder {
+    ctor public DnsOptions.StaleDnsOptions.Builder();
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions build();
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setAllowCrossNetworkUsage(int);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setFreshLookupTimeout(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setMaxExpiredDelay(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setUseStaleOnNameNotResolved(int);
+  }
+
+  public abstract class HeaderBlock {
+    ctor public HeaderBlock();
+    method @NonNull public abstract java.util.List<java.util.Map.Entry<java.lang.String,java.lang.String>> getAsList();
+    method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> getAsMap();
+  }
+
+  public abstract class HttpEngine {
+    method public void bindToNetwork(@Nullable android.net.Network);
+    method @NonNull public abstract java.net.URLStreamHandlerFactory createUrlStreamHandlerFactory();
+    method @NonNull public static String getVersionString();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder newBidirectionalStreamBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.BidirectionalStream.Callback);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder newUrlRequestBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.UrlRequest.Callback);
+    method @NonNull public abstract java.net.URLConnection openConnection(@NonNull java.net.URL) throws java.io.IOException;
+    method public abstract void shutdown();
+  }
+
+  public static class HttpEngine.Builder {
+    ctor public HttpEngine.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.http.HttpEngine.Builder addPublicKeyPins(@NonNull String, @NonNull java.util.Set<byte[]>, boolean, @NonNull java.time.Instant);
+    method @NonNull public android.net.http.HttpEngine.Builder addQuicHint(@NonNull String, int, int);
+    method @NonNull public android.net.http.HttpEngine build();
+    method @NonNull public String getDefaultUserAgent();
+    method @NonNull public android.net.http.HttpEngine.Builder setConnectionMigrationOptions(@NonNull android.net.http.ConnectionMigrationOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setDnsOptions(@NonNull android.net.http.DnsOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableBrotli(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttp2(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttpCache(int, long);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableQuic(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setQuicOptions(@NonNull android.net.http.QuicOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setStoragePath(@NonNull String);
+    method @NonNull public android.net.http.HttpEngine.Builder setUserAgent(@NonNull String);
+    field public static final int HTTP_CACHE_DISABLED = 0; // 0x0
+    field public static final int HTTP_CACHE_DISK = 3; // 0x3
+    field public static final int HTTP_CACHE_DISK_NO_HTTP = 2; // 0x2
+    field public static final int HTTP_CACHE_IN_MEMORY = 1; // 0x1
+  }
+
+  public class HttpException extends java.io.IOException {
+    ctor public HttpException(@Nullable String, @Nullable Throwable);
+  }
+
+  public final class InlineExecutionProhibitedException extends java.util.concurrent.RejectedExecutionException {
+    ctor public InlineExecutionProhibitedException();
+  }
+
+  public abstract class NetworkException extends android.net.http.HttpException {
+    ctor public NetworkException(@Nullable String, @Nullable Throwable);
+    method public abstract int getErrorCode();
+    method public abstract boolean isImmediatelyRetryable();
+    field public static final int ERROR_ADDRESS_UNREACHABLE = 9; // 0x9
+    field public static final int ERROR_CONNECTION_CLOSED = 5; // 0x5
+    field public static final int ERROR_CONNECTION_REFUSED = 7; // 0x7
+    field public static final int ERROR_CONNECTION_RESET = 8; // 0x8
+    field public static final int ERROR_CONNECTION_TIMED_OUT = 6; // 0x6
+    field public static final int ERROR_HOSTNAME_NOT_RESOLVED = 1; // 0x1
+    field public static final int ERROR_INTERNET_DISCONNECTED = 2; // 0x2
+    field public static final int ERROR_NETWORK_CHANGED = 3; // 0x3
+    field public static final int ERROR_OTHER = 11; // 0xb
+    field public static final int ERROR_QUIC_PROTOCOL_FAILED = 10; // 0xa
+    field public static final int ERROR_TIMED_OUT = 4; // 0x4
+  }
+
+  public abstract class QuicException extends android.net.http.NetworkException {
+    ctor protected QuicException(@Nullable String, @Nullable Throwable);
+  }
+
+  public class QuicOptions {
+    method @NonNull public java.util.Set<java.lang.String> getAllowedQuicHosts();
+    method @Nullable public String getHandshakeUserAgent();
+    method @Nullable public java.time.Duration getIdleConnectionTimeout();
+    method public int getInMemoryServerConfigsCacheSize();
+    method public boolean hasInMemoryServerConfigsCacheSize();
+  }
+
+  public static final class QuicOptions.Builder {
+    ctor public QuicOptions.Builder();
+    method @NonNull public android.net.http.QuicOptions.Builder addAllowedQuicHost(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions build();
+    method @NonNull public android.net.http.QuicOptions.Builder setHandshakeUserAgent(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions.Builder setIdleConnectionTimeout(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.QuicOptions.Builder setInMemoryServerConfigsCacheSize(int);
+  }
+
+  public abstract class UploadDataProvider implements java.io.Closeable {
+    ctor public UploadDataProvider();
+    method public void close() throws java.io.IOException;
+    method public abstract long getLength() throws java.io.IOException;
+    method public abstract void read(@NonNull android.net.http.UploadDataSink, @NonNull java.nio.ByteBuffer) throws java.io.IOException;
+    method public abstract void rewind(@NonNull android.net.http.UploadDataSink) throws java.io.IOException;
+  }
+
+  public abstract class UploadDataSink {
+    ctor public UploadDataSink();
+    method public abstract void onReadError(@NonNull Exception);
+    method public abstract void onReadSucceeded(boolean);
+    method public abstract void onRewindError(@NonNull Exception);
+    method public abstract void onRewindSucceeded();
+  }
+
+  public abstract class UrlRequest {
+    method public abstract void cancel();
+    method public abstract void followRedirect();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @Nullable public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract void getStatus(@NonNull android.net.http.UrlRequest.StatusListener);
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isCacheDisabled();
+    method public abstract boolean isDirectExecutorAllowed();
+    method public abstract boolean isDone();
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
+    method public abstract void start();
+    field public static final int REQUEST_PRIORITY_HIGHEST = 4; // 0x4
+    field public static final int REQUEST_PRIORITY_IDLE = 0; // 0x0
+    field public static final int REQUEST_PRIORITY_LOW = 2; // 0x2
+    field public static final int REQUEST_PRIORITY_LOWEST = 1; // 0x1
+    field public static final int REQUEST_PRIORITY_MEDIUM = 3; // 0x3
+  }
+
+  public abstract static class UrlRequest.Builder {
+    method @NonNull public abstract android.net.http.UrlRequest.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder bindToNetwork(@Nullable android.net.Network);
+    method @NonNull public abstract android.net.http.UrlRequest build();
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setCacheDisabled(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setDirectExecutorAllowed(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsUid(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setUploadDataProvider(@NonNull android.net.http.UploadDataProvider, @NonNull java.util.concurrent.Executor);
+  }
+
+  public static interface UrlRequest.Callback {
+    method public void onCanceled(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer) throws java.lang.Exception;
+    method public void onRedirectReceived(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull String) throws java.lang.Exception;
+    method public void onResponseStarted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo) throws java.lang.Exception;
+    method public void onSucceeded(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo);
+  }
+
+  public static class UrlRequest.Status {
+    field public static final int CONNECTING = 10; // 0xa
+    field public static final int DOWNLOADING_PAC_FILE = 5; // 0x5
+    field public static final int ESTABLISHING_PROXY_TUNNEL = 8; // 0x8
+    field public static final int IDLE = 0; // 0x0
+    field public static final int INVALID = -1; // 0xffffffff
+    field public static final int READING_RESPONSE = 14; // 0xe
+    field public static final int RESOLVING_HOST = 9; // 0x9
+    field public static final int RESOLVING_HOST_IN_PAC_FILE = 7; // 0x7
+    field public static final int RESOLVING_PROXY_FOR_URL = 6; // 0x6
+    field public static final int SENDING_REQUEST = 12; // 0xc
+    field public static final int SSL_HANDSHAKE = 11; // 0xb
+    field public static final int WAITING_FOR_AVAILABLE_SOCKET = 2; // 0x2
+    field public static final int WAITING_FOR_CACHE = 4; // 0x4
+    field public static final int WAITING_FOR_DELEGATE = 3; // 0x3
+    field public static final int WAITING_FOR_RESPONSE = 13; // 0xd
+    field public static final int WAITING_FOR_STALLED_SOCKET_POOL = 1; // 0x1
+  }
+
+  public static interface UrlRequest.StatusListener {
+    method public void onStatus(int);
+  }
+
+  public abstract class UrlResponseInfo {
+    ctor public UrlResponseInfo();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method public abstract int getHttpStatusCode();
+    method @NonNull public abstract String getHttpStatusText();
+    method @NonNull public abstract String getNegotiatedProtocol();
+    method public abstract long getReceivedByteCount();
+    method @NonNull public abstract String getUrl();
+    method @NonNull public abstract java.util.List<java.lang.String> getUrlChain();
+    method public abstract boolean wasCached();
+  }
+
+}
+
diff --git a/framework/udc-extended-api/lint-baseline.txt b/framework/udc-extended-api/lint-baseline.txt
new file mode 100644
index 0000000..2f4004a
--- /dev/null
+++ b/framework/udc-extended-api/lint-baseline.txt
@@ -0,0 +1,4 @@
+// Baseline format: 1.0
+VisiblySynchronized: android.net.NetworkInfo#toString():
+    Internal locks must not be exposed (synchronizing on this or class is still
+    externally observable): method android.net.NetworkInfo.toString()
diff --git a/framework/udc-extended-api/module-lib-current.txt b/framework/udc-extended-api/module-lib-current.txt
new file mode 100644
index 0000000..193bd92
--- /dev/null
+++ b/framework/udc-extended-api/module-lib-current.txt
@@ -0,0 +1,239 @@
+// Signature format: 2.0
+package android.net {
+
+  public final class ConnectivityFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
+  public class ConnectivityManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkAllowList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkDenyList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset();
+    method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshots();
+    method @Nullable public android.net.ProxyInfo getGlobalProxy();
+    method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkDenyList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setVpnDefaultForUids(@NonNull String, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
+    method public void systemReady();
+    field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE";
+    field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
+    field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
+    field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED";
+    field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
+    field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
+    field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000
+    field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
+    field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
+    field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
+    field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
+    field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
+    field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
+    field public static final int BLOCKED_REASON_NONE = 0; // 0x0
+    field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
+    field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
+    field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
+    field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7
+    field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8
+    field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9
+    field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
+    field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
+    field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
+    field public static final int FIREWALL_RULE_ALLOW = 1; // 0x1
+    field public static final int FIREWALL_RULE_DEFAULT = 0; // 0x0
+    field public static final int FIREWALL_RULE_DENY = 2; // 0x2
+    field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; // 0x3
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
+  }
+
+  public static class ConnectivityManager.NetworkCallback {
+    method public void onBlockedStatusChanged(@NonNull android.net.Network, int);
+  }
+
+  public class ConnectivitySettingsManager {
+    method public static void clearGlobalProxy(@NonNull android.content.Context);
+    method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
+    method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
+    method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context);
+    method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
+    method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+    method public static long getIngressRateLimitInBytesPerSecond(@NonNull android.content.Context);
+    method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+    method @NonNull public static java.util.Set<java.lang.Integer> getMobileDataPreferredUids(@NonNull android.content.Context);
+    method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context);
+    method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context);
+    method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int);
+    method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
+    method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
+    method public static int getPrivateDnsMode(@NonNull android.content.Context);
+    method @NonNull public static java.util.Set<java.lang.Integer> getUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
+    method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+    method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
+    method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
+    method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>);
+    method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
+    method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+    method public static void setIngressRateLimitInBytesPerSecond(@NonNull android.content.Context, @IntRange(from=-1L, to=4294967295L) long);
+    method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+    method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
+    method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int);
+    method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String);
+    method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int);
+    method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int);
+    method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
+    method public static void setPrivateDnsMode(@NonNull android.content.Context, int);
+    method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
+    method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+    method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+    field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+    field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2
+    field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0
+    field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1
+    field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1
+    field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2
+    field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3
+  }
+
+  public final class DhcpOption implements android.os.Parcelable {
+    ctor public DhcpOption(byte, @Nullable byte[]);
+    method public int describeContents();
+    method public byte getType();
+    method @Nullable public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpOption> CREATOR;
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method @Nullable public String getSubscriberId();
+    method public boolean isBypassableVpn();
+    method public boolean isVpnValidationRequired();
+  }
+
+  public static final class NetworkAgentConfig.Builder {
+    method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLocalRoutesExcludedForVpn(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setVpnRequiresValidation(boolean);
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAllowedUids();
+    method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+    method public boolean hasForbiddenCapability(int);
+    field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
+    field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
+    field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
+    field public static final long REDACT_FOR_NETWORK_SETTINGS = 4L; // 0x4L
+    field public static final long REDACT_NONE = 0L; // 0x0L
+    field public static final int TRANSPORT_TEST = 7; // 0x7
+  }
+
+  public static final class NetworkCapabilities.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAllowedUids(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method @NonNull public int[] getEnterpriseIds();
+    method @NonNull public int[] getForbiddenCapabilities();
+    method public boolean hasEnterpriseId(int);
+    method public boolean hasForbiddenCapability(int);
+  }
+
+  public static class NetworkRequest.Builder {
+    method @NonNull public android.net.NetworkRequest.Builder addForbiddenCapability(int);
+    method @NonNull public android.net.NetworkRequest.Builder removeForbiddenCapability(int);
+    method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
+  }
+
+  public final class ProfileNetworkPreference implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int[] getExcludedUids();
+    method @NonNull public int[] getIncludedUids();
+    method public int getPreference();
+    method public int getPreferenceEnterpriseId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ProfileNetworkPreference> CREATOR;
+  }
+
+  public static final class ProfileNetworkPreference.Builder {
+    ctor public ProfileNetworkPreference.Builder();
+    method @NonNull public android.net.ProfileNetworkPreference build();
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@NonNull int[]);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@NonNull int[]);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int);
+  }
+
+  public final class TestNetworkInterface implements android.os.Parcelable {
+    ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String);
+    method public int describeContents();
+    method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
+    method @NonNull public String getInterfaceName();
+    method @Nullable public android.net.MacAddress getMacAddress();
+    method public int getMtu();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
+  }
+
+  public class TestNetworkManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTapInterface();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void teardownTestNetwork(@NonNull android.net.Network);
+    field public static final String TEST_TAP_PREFIX = "testtap";
+  }
+
+  public final class TestNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public TestNetworkSpecifier(@NonNull String);
+    method public int describeContents();
+    method @Nullable public String getInterfaceName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkSpecifier> CREATOR;
+  }
+
+  public interface TransportInfo {
+    method public default long getApplicableRedactions();
+    method @NonNull public default android.net.TransportInfo makeCopy(long);
+  }
+
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor @Deprecated public VpnTransportInfo(int, @Nullable String);
+    method @Nullable public String getSessionId();
+    method @NonNull public android.net.VpnTransportInfo makeCopy(long);
+  }
+
+}
+
diff --git a/framework/udc-extended-api/module-lib-removed.txt b/framework/udc-extended-api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework/udc-extended-api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework/udc-extended-api/removed.txt b/framework/udc-extended-api/removed.txt
new file mode 100644
index 0000000..303a1e6
--- /dev/null
+++ b/framework/udc-extended-api/removed.txt
@@ -0,0 +1,11 @@
+// Signature format: 2.0
+package android.net {
+
+  public class ConnectivityManager {
+    method @Deprecated public boolean requestRouteToHost(int, int);
+    method @Deprecated public int startUsingNetworkFeature(int, String);
+    method @Deprecated public int stopUsingNetworkFeature(int, String);
+  }
+
+}
+
diff --git a/framework/udc-extended-api/system-current.txt b/framework/udc-extended-api/system-current.txt
new file mode 100644
index 0000000..4a2ed8a
--- /dev/null
+++ b/framework/udc-extended-api/system-current.txt
@@ -0,0 +1,544 @@
+// Signature format: 2.0
+package android.net {
+
+  public class CaptivePortal implements android.os.Parcelable {
+    method @Deprecated public void logEvent(int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
+    method public void useNetwork();
+    field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
+    field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+    field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+    field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+  }
+
+  public final class CaptivePortalData implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getByteLimit();
+    method public long getExpiryTimeMillis();
+    method public long getRefreshTimeMillis();
+    method @Nullable public android.net.Uri getUserPortalUrl();
+    method public int getUserPortalUrlSource();
+    method @Nullable public CharSequence getVenueFriendlyName();
+    method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public int getVenueInfoUrlSource();
+    method public boolean isCaptive();
+    method public boolean isSessionExtendable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
+  }
+
+  public static class CaptivePortalData.Builder {
+    ctor public CaptivePortalData.Builder();
+    ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
+    method @NonNull public android.net.CaptivePortalData build();
+    method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable CharSequence);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
+  }
+
+  public class ConnectivityManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
+    method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
+    method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
+    method public void unregisterQosCallback(@NonNull android.net.QosCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+    field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
+    field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TYPE_NONE = -1; // 0xffffffff
+    field @Deprecated public static final int TYPE_PROXY = 16; // 0x10
+    field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
+  }
+
+  @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
+    ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
+    method @Deprecated public void onTetheringFailed();
+    method @Deprecated public void onTetheringStarted();
+  }
+
+  @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
+    method @Deprecated public void onTetheringEntitlementResult(int);
+  }
+
+  @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback {
+    ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback();
+    method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  public final class DscpPolicy implements android.os.Parcelable {
+    method @Nullable public java.net.InetAddress getDestinationAddress();
+    method @Nullable public android.util.Range<java.lang.Integer> getDestinationPortRange();
+    method public int getDscpValue();
+    method public int getPolicyId();
+    method public int getProtocol();
+    method @Nullable public java.net.InetAddress getSourceAddress();
+    method public int getSourcePort();
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
+    field public static final int PROTOCOL_ANY = -1; // 0xffffffff
+    field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
+  }
+
+  public static final class DscpPolicy.Builder {
+    ctor public DscpPolicy.Builder(int, int);
+    method @NonNull public android.net.DscpPolicy build();
+    method @NonNull public android.net.DscpPolicy.Builder setDestinationAddress(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.DscpPolicy.Builder setDestinationPortRange(@NonNull android.util.Range<java.lang.Integer>);
+    method @NonNull public android.net.DscpPolicy.Builder setProtocol(int);
+    method @NonNull public android.net.DscpPolicy.Builder setSourceAddress(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.DscpPolicy.Builder setSourcePort(int);
+  }
+
+  public final class InvalidPacketException extends java.lang.Exception {
+    ctor public InvalidPacketException(int);
+    method public int getError();
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+  }
+
+  public final class IpConfiguration implements android.os.Parcelable {
+    ctor public IpConfiguration();
+    ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
+    method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
+    method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
+    method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+    method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
+    method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
+    method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+  }
+
+  public enum IpConfiguration.IpAssignment {
+    enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP;
+    enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC;
+    enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED;
+  }
+
+  public enum IpConfiguration.ProxySettings {
+    enum_constant public static final android.net.IpConfiguration.ProxySettings NONE;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings PAC;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED;
+  }
+
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(@NonNull String);
+  }
+
+  public class KeepalivePacketData {
+    ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method @NonNull public java.net.InetAddress getDstAddress();
+    method public int getDstPort();
+    method @NonNull public byte[] getPacket();
+    method @NonNull public java.net.InetAddress getSrcAddress();
+    method public int getSrcPort();
+  }
+
+  public class LinkAddress implements android.os.Parcelable {
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long);
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+    ctor public LinkAddress(@NonNull String);
+    ctor public LinkAddress(@NonNull String, int, int);
+    method public long getDeprecationTime();
+    method public long getExpirationTime();
+    method public boolean isGlobalPreferred();
+    method public boolean isIpv4();
+    method public boolean isIpv6();
+    method public boolean isSameAddressAs(@Nullable android.net.LinkAddress);
+    field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL
+    field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+  }
+
+  public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties(@Nullable android.net.LinkProperties);
+    ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean);
+    method public boolean addDnsServer(@NonNull java.net.InetAddress);
+    method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
+    method public boolean addPcscfServer(@NonNull java.net.InetAddress);
+    method @NonNull public java.util.List<java.net.InetAddress> getAddresses();
+    method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
+    method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
+    method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
+    method @Nullable public android.net.Uri getCaptivePortalApiUrl();
+    method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
+    method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
+    method @Nullable public String getTcpBufferSizes();
+    method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+    method public boolean hasGlobalIpv6Address();
+    method public boolean hasIpv4Address();
+    method public boolean hasIpv4DefaultRoute();
+    method public boolean hasIpv4DnsServer();
+    method public boolean hasIpv6DefaultRoute();
+    method public boolean hasIpv6DnsServer();
+    method public boolean isIpv4Provisioned();
+    method public boolean isIpv6Provisioned();
+    method public boolean isProvisioned();
+    method public boolean isReachable(@NonNull java.net.InetAddress);
+    method public boolean removeDnsServer(@NonNull java.net.InetAddress);
+    method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
+    method public boolean removeRoute(@NonNull android.net.RouteInfo);
+    method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
+    method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
+    method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
+    method public void setPrivateDnsServerName(@Nullable String);
+    method public void setTcpBufferSizes(@Nullable String);
+    method public void setUsePrivateDns(boolean);
+    method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+  }
+
+  public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+    ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+  }
+
+  public class Network implements android.os.Parcelable {
+    ctor public Network(@NonNull android.net.Network);
+    method public int getNetId();
+    method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
+  }
+
+  public abstract class NetworkAgent {
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    method @Nullable public android.net.Network getNetwork();
+    method public void markConnected();
+    method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+    method public void onAutomaticReconnectDisabled();
+    method public void onBandwidthUpdateRequested();
+    method public void onDscpPolicyStatusUpdated(int, int);
+    method public void onNetworkCreated();
+    method public void onNetworkDestroyed();
+    method public void onNetworkUnwanted();
+    method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
+    method public void onQosCallbackUnregistered(int);
+    method public void onRemoveKeepalivePacketFilter(int);
+    method public void onSaveAcceptUnvalidated(boolean);
+    method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+    method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData);
+    method public void onStopSocketKeepalive(int);
+    method public void onValidationStatus(int, @Nullable android.net.Uri);
+    method @NonNull public android.net.Network register();
+    method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy);
+    method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+    method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+    method public void sendNetworkScore(@NonNull android.net.NetworkScore);
+    method public void sendNetworkScore(@IntRange(from=0, to=99) int);
+    method public final void sendQosCallbackError(int, int);
+    method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
+    method public final void sendQosSessionLost(int, int, int);
+    method public void sendRemoveAllDscpPolicies();
+    method public void sendRemoveDscpPolicy(int);
+    method public final void sendSocketKeepaliveEvent(int, int);
+    method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void setLingerDuration(@NonNull java.time.Duration);
+    method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int);
+    method public void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+    method public void unregister();
+    method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int);
+    field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4
+    field public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3
+    field public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5; // 0x5
+    field public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2
+    field public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1; // 0x1
+    field public static final int DSCP_POLICY_STATUS_SUCCESS = 0; // 0x0
+    field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+    field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLegacyType();
+    method @NonNull public String getLegacyTypeName();
+    method public boolean isExplicitlySelected();
+    method public boolean isPartialConnectivityAcceptable();
+    method public boolean isUnvalidatedConnectivityAcceptable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+  }
+
+  public static final class NetworkAgentConfig.Builder {
+    ctor public NetworkAgentConfig.Builder();
+    method @NonNull public android.net.NetworkAgentConfig build();
+    method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setNat64DetectionEnabled(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setProvisioningNotificationEnabled(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull public int[] getAdministratorUids();
+    method @Nullable public static String getCapabilityCarrierName(int);
+    method @Nullable public String getSsid();
+    method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
+    method @NonNull public int[] getTransportTypes();
+    method @Nullable public java.util.List<android.net.Network> getUnderlyingNetworks();
+    method public boolean isPrivateDnsBroken();
+    method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+    field public static final int NET_CAPABILITY_BIP = 31; // 0x1f
+    field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
+    field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+    field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
+    field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
+    field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b
+    field public static final int NET_CAPABILITY_VSIM = 30; // 0x1e
+  }
+
+  public static final class NetworkCapabilities.Builder {
+    ctor public NetworkCapabilities.Builder();
+    ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
+    method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
+    method @NonNull public android.net.NetworkCapabilities build();
+    method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
+    method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
+    method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+    method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
+  }
+
+  public class NetworkProvider {
+    ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+    method public int getProviderId();
+    method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
+    method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback);
+    field public static final int ID_NONE = -1; // 0xffffffff
+  }
+
+  public static interface NetworkProvider.NetworkOfferCallback {
+    method public void onNetworkNeeded(@NonNull android.net.NetworkRequest);
+    method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest);
+  }
+
+  public class NetworkReleasedException extends java.lang.Exception {
+    ctor public NetworkReleasedException();
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method @Nullable public String getRequestorPackageName();
+    method public int getRequestorUid();
+  }
+
+  public static class NetworkRequest.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
+    method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class NetworkScore implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getKeepConnectedReason();
+    method public int getLegacyInt();
+    method public boolean isExiting();
+    method public boolean isTransportPrimary();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+    field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1
+    field public static final int KEEP_CONNECTED_NONE = 0; // 0x0
+  }
+
+  public static final class NetworkScore.Builder {
+    ctor public NetworkScore.Builder();
+    method @NonNull public android.net.NetworkScore build();
+    method @NonNull public android.net.NetworkScore.Builder setExiting(boolean);
+    method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int);
+    method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+    method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean);
+  }
+
+  public final class OemNetworkPreferences implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR;
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4
+    field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0
+  }
+
+  public static final class OemNetworkPreferences.Builder {
+    ctor public OemNetworkPreferences.Builder();
+    ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences);
+    method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int);
+    method @NonNull public android.net.OemNetworkPreferences build();
+    method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String);
+  }
+
+  public abstract class QosCallback {
+    ctor public QosCallback();
+    method public void onError(@NonNull android.net.QosCallbackException);
+    method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes);
+    method public void onQosSessionLost(@NonNull android.net.QosSession);
+  }
+
+  public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException {
+  }
+
+  public final class QosCallbackException extends java.lang.Exception {
+    ctor public QosCallbackException(@NonNull String);
+    ctor public QosCallbackException(@NonNull Throwable);
+  }
+
+  public abstract class QosFilter {
+    method @NonNull public abstract android.net.Network getNetwork();
+    method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int);
+    method public boolean matchesProtocol(int);
+    method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int);
+  }
+
+  public final class QosSession implements android.os.Parcelable {
+    ctor public QosSession(int, int);
+    method public int describeContents();
+    method public int getSessionId();
+    method public int getSessionType();
+    method public long getUniqueId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR;
+    field public static final int TYPE_EPS_BEARER = 1; // 0x1
+    field public static final int TYPE_NR_BEARER = 2; // 0x2
+  }
+
+  public interface QosSessionAttributes {
+  }
+
+  public final class QosSocketInfo implements android.os.Parcelable {
+    ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException;
+    ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException;
+    method public int describeContents();
+    method @NonNull public java.net.InetSocketAddress getLocalSocketAddress();
+    method @NonNull public android.net.Network getNetwork();
+    method @Nullable public java.net.InetSocketAddress getRemoteSocketAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR;
+  }
+
+  public final class RouteInfo implements android.os.Parcelable {
+    ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
+    ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
+    method public int getMtu();
+  }
+
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @Nullable android.net.Network);
+    field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
+    field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public class SocketLocalAddressChangedException extends java.lang.Exception {
+    ctor public SocketLocalAddressChangedException();
+  }
+
+  public class SocketNotBoundException extends java.lang.Exception {
+    ctor public SocketNotBoundException();
+  }
+
+  public class SocketNotConnectedException extends java.lang.Exception {
+    ctor public SocketNotConnectedException();
+  }
+
+  public class SocketRemoteAddressChangedException extends java.lang.Exception {
+    ctor public SocketRemoteAddressChangedException();
+  }
+
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    ctor public StaticIpConfiguration();
+    ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+    method public void addDnsServer(@NonNull java.net.InetAddress);
+    method public void clear();
+    method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
+  }
+
+  public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+    ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException;
+    method public int describeContents();
+    method public int getIpTos();
+    method public int getIpTtl();
+    method public int getTcpAck();
+    method public int getTcpSeq();
+    method public int getTcpWindow();
+    method public int getTcpWindowScale();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR;
+  }
+
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor public VpnTransportInfo(int, @Nullable String, boolean, boolean);
+    method public boolean areLongLivedTcpConnectionsExpensive();
+    method public int describeContents();
+    method public int getType();
+    method public boolean isBypassable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+  }
+
+}
+
+package android.net.apf {
+
+  public final class ApfCapabilities implements android.os.Parcelable {
+    ctor public ApfCapabilities(int, int, int);
+    method public int describeContents();
+    method public static boolean getApfDrop8023Frames();
+    method @NonNull public static int[] getApfEtherTypeBlackList();
+    method public boolean hasDataAccess();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR;
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
diff --git a/framework/udc-extended-api/system-lint-baseline.txt b/framework/udc-extended-api/system-lint-baseline.txt
new file mode 100644
index 0000000..9a97707
--- /dev/null
+++ b/framework/udc-extended-api/system-lint-baseline.txt
@@ -0,0 +1 @@
+// Baseline format: 1.0
diff --git a/framework/udc-extended-api/system-removed.txt b/framework/udc-extended-api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/framework/udc-extended-api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index 278f823..f329295 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -52,5 +52,7 @@
     static_libs: [
         "modules-utils-preconditions",
     ],
-    visibility: ["//packages/modules/Connectivity/nearby/tests:__subpackages__"],
+    visibility: [
+        "//packages/modules/Connectivity/nearby/tests:__subpackages__",
+    ],
 }
diff --git a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java
deleted file mode 100644
index d42fbf4..0000000
--- a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-
-/**
- * Class for metadata of a Fast Pair device associated with an account.
- *
- * @hide
- */
-public class FastPairAccountKeyDeviceMetadata {
-
-    FastPairAccountKeyDeviceMetadataParcel mMetadataParcel;
-
-    FastPairAccountKeyDeviceMetadata(FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
-        this.mMetadataParcel = metadataParcel;
-    }
-
-    /**
-     * Get Device Account Key, which uniquely identifies a Fast Pair device associated with an
-     * account. AccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated.
-     *
-     * @return 16-byte Account Key.
-     * @hide
-     */
-    @Nullable
-    public byte[] getDeviceAccountKey() {
-        return mMetadataParcel.deviceAccountKey;
-    }
-
-    /**
-     * Get a hash value of device's account key and public bluetooth address without revealing the
-     * public bluetooth address. Sha256 hash value is 32 bytes.
-     *
-     * @return 32-byte Sha256 hash value.
-     * @hide
-     */
-    @Nullable
-    public byte[] getSha256DeviceAccountKeyPublicAddress() {
-        return mMetadataParcel.sha256DeviceAccountKeyPublicAddress;
-    }
-
-    /**
-     * Get metadata of a Fast Pair device type.
-     *
-     * @hide
-     */
-    @Nullable
-    public FastPairDeviceMetadata getFastPairDeviceMetadata() {
-        if (mMetadataParcel.metadata == null) {
-            return null;
-        }
-        return new FastPairDeviceMetadata(mMetadataParcel.metadata);
-    }
-
-    /**
-     * Get Fast Pair discovery item, which is tied to both the device type and the account.
-     *
-     * @hide
-     */
-    @Nullable
-    public FastPairDiscoveryItem getFastPairDiscoveryItem() {
-        if (mMetadataParcel.discoveryItem == null) {
-            return null;
-        }
-        return new FastPairDiscoveryItem(mMetadataParcel.discoveryItem);
-    }
-
-    /**
-     * Builder used to create FastPairAccountKeyDeviceMetadata.
-     *
-     * @hide
-     */
-    public static final class Builder {
-
-        private final FastPairAccountKeyDeviceMetadataParcel mBuilderParcel;
-
-        /**
-         * Default constructor of Builder.
-         *
-         * @hide
-         */
-        public Builder() {
-            mBuilderParcel = new FastPairAccountKeyDeviceMetadataParcel();
-            mBuilderParcel.deviceAccountKey = null;
-            mBuilderParcel.sha256DeviceAccountKeyPublicAddress = null;
-            mBuilderParcel.metadata = null;
-            mBuilderParcel.discoveryItem = null;
-        }
-
-        /**
-         * Set Account Key.
-         *
-         * @param deviceAccountKey Fast Pair device account key, which is 16 bytes: first byte is
-         *                         0x04. Next 15 bytes are randomly generated.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDeviceAccountKey(@Nullable byte[] deviceAccountKey) {
-            mBuilderParcel.deviceAccountKey = deviceAccountKey;
-            return this;
-        }
-
-        /**
-         * Set sha256 hash value of account key and public bluetooth address.
-         *
-         * @param sha256DeviceAccountKeyPublicAddress 32-byte sha256 hash value of account key and
-         *                                            public bluetooth address.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setSha256DeviceAccountKeyPublicAddress(
-                @Nullable byte[] sha256DeviceAccountKeyPublicAddress) {
-            mBuilderParcel.sha256DeviceAccountKeyPublicAddress =
-                    sha256DeviceAccountKeyPublicAddress;
-            return this;
-        }
-
-
-        /**
-         * Set Fast Pair metadata.
-         *
-         * @param metadata Fast Pair metadata.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
-            if (metadata == null) {
-                mBuilderParcel.metadata = null;
-            } else {
-                mBuilderParcel.metadata = metadata.mMetadataParcel;
-            }
-            return this;
-        }
-
-        /**
-         * Set Fast Pair discovery item.
-         *
-         * @param discoveryItem Fast Pair discovery item.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setFastPairDiscoveryItem(@Nullable FastPairDiscoveryItem discoveryItem) {
-            if (discoveryItem == null) {
-                mBuilderParcel.discoveryItem = null;
-            } else {
-                mBuilderParcel.discoveryItem = discoveryItem.mMetadataParcel;
-            }
-            return this;
-        }
-
-        /**
-         * Build {@link FastPairAccountKeyDeviceMetadata} with the currently set configuration.
-         *
-         * @hide
-         */
-        @NonNull
-        public FastPairAccountKeyDeviceMetadata build() {
-            return new FastPairAccountKeyDeviceMetadata(mBuilderParcel);
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java
deleted file mode 100644
index 74831d5..0000000
--- a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.nearby;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
-
-/**
- * Class for a type of registered Fast Pair device keyed by modelID, or antispoofKey.
- *
- * @hide
- */
-public class FastPairAntispoofKeyDeviceMetadata {
-
-    FastPairAntispoofKeyDeviceMetadataParcel mMetadataParcel;
-    FastPairAntispoofKeyDeviceMetadata(
-            FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) {
-        this.mMetadataParcel = metadataParcel;
-    }
-
-    /**
-     * Get Antispoof public key.
-     *
-     * @hide
-     */
-    @Nullable
-    public byte[] getAntispoofPublicKey() {
-        return this.mMetadataParcel.antispoofPublicKey;
-    }
-
-    /**
-     * Get metadata of a Fast Pair device type.
-     *
-     * @hide
-     */
-    @Nullable
-    public FastPairDeviceMetadata getFastPairDeviceMetadata() {
-        if (this.mMetadataParcel.deviceMetadata == null) {
-            return null;
-        }
-        return new FastPairDeviceMetadata(this.mMetadataParcel.deviceMetadata);
-    }
-
-    /**
-     * Builder used to create FastPairAntispoofkeyDeviceMetadata.
-     *
-     * @hide
-     */
-    public static final class Builder {
-
-        private final FastPairAntispoofKeyDeviceMetadataParcel mBuilderParcel;
-
-        /**
-         * Default constructor of Builder.
-         *
-         * @hide
-         */
-        public Builder() {
-            mBuilderParcel = new FastPairAntispoofKeyDeviceMetadataParcel();
-            mBuilderParcel.antispoofPublicKey = null;
-            mBuilderParcel.deviceMetadata = null;
-        }
-
-        /**
-         * Set AntiSpoof public key, which uniquely identify a Fast Pair device type.
-         *
-         * @param antispoofPublicKey is 64 bytes, see <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setAntispoofPublicKey(@Nullable byte[] antispoofPublicKey) {
-            mBuilderParcel.antispoofPublicKey = antispoofPublicKey;
-            return this;
-        }
-
-        /**
-         * Set Fast Pair metadata, which is the property of a Fast Pair device type, including
-         * device images and strings.
-         *
-         * @param metadata Fast Pair device meta data.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
-            if (metadata != null) {
-                mBuilderParcel.deviceMetadata = metadata.mMetadataParcel;
-            } else {
-                mBuilderParcel.deviceMetadata = null;
-            }
-            return this;
-        }
-
-        /**
-         * Build {@link FastPairAntispoofKeyDeviceMetadata} with the currently set configuration.
-         *
-         * @hide
-         */
-        @NonNull
-        public FastPairAntispoofKeyDeviceMetadata build() {
-            return new FastPairAntispoofKeyDeviceMetadata(mBuilderParcel);
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairDataProviderService.java b/nearby/framework/java/android/nearby/FastPairDataProviderService.java
deleted file mode 100644
index f1d5074..0000000
--- a/nearby/framework/java/android/nearby/FastPairDataProviderService.java
+++ /dev/null
@@ -1,714 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby;
-
-import android.accounts.Account;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.nearby.aidl.ByteArrayParcel;
-import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
-import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
-import android.nearby.aidl.FastPairManageAccountRequestParcel;
-import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
-import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
-import android.nearby.aidl.IFastPairDataProvider;
-import android.nearby.aidl.IFastPairEligibleAccountsCallback;
-import android.nearby.aidl.IFastPairManageAccountCallback;
-import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * A service class for fast pair data providers outside the system server.
- *
- * Fast pair providers should be wrapped in a non-exported service which returns the result of
- * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The
- * service should not be exported so that components other than the system server cannot bind to it.
- * Alternatively, the service may be guarded by a permission that only system server can obtain.
- *
- * <p>Fast Pair providers are identified by their UID / package name.
- *
- * @hide
- */
-public abstract class FastPairDataProviderService extends Service {
-    /**
-     * The action the wrapping service should have in its intent filter to implement the
-     * {@link android.nearby.FastPairDataProviderBase}.
-     *
-     * @hide
-     */
-    public static final String ACTION_FAST_PAIR_DATA_PROVIDER =
-            "android.nearby.action.FAST_PAIR_DATA_PROVIDER";
-
-    /**
-     * Manage request type to add, or opt-in.
-     *
-     * @hide
-     */
-    public static final int MANAGE_REQUEST_ADD = 0;
-
-    /**
-     * Manage request type to remove, or opt-out.
-     *
-     * @hide
-     */
-    public static final int MANAGE_REQUEST_REMOVE = 1;
-
-    /**
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            MANAGE_REQUEST_ADD,
-            MANAGE_REQUEST_REMOVE})
-    @interface ManageRequestType {}
-
-    /**
-     * Error code for bad request.
-     *
-     * @hide
-     */
-    public static final int ERROR_CODE_BAD_REQUEST = 0;
-
-    /**
-     * Error code for internal error.
-     *
-     * @hide
-     */
-    public static final int ERROR_CODE_INTERNAL_ERROR = 1;
-
-    /**
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            ERROR_CODE_BAD_REQUEST,
-            ERROR_CODE_INTERNAL_ERROR})
-    @interface ErrorCode {}
-
-    private final IBinder mBinder;
-    private final String mTag;
-
-    /**
-     * Constructor of FastPairDataProviderService.
-     *
-     * @param tag TAG for on device logging.
-     * @hide
-     */
-    public FastPairDataProviderService(@NonNull String tag) {
-        mBinder = new Service();
-        mTag = tag;
-    }
-
-    @Override
-    @NonNull
-    public final IBinder onBind(@NonNull Intent intent) {
-        return mBinder;
-    }
-
-    /**
-     * Callback to be invoked when an AntispoofKeyed device metadata is loaded.
-     *
-     * @hide
-     */
-    public interface FastPairAntispoofKeyDeviceMetadataCallback {
-
-        /**
-         * Invoked once the meta data is loaded.
-         *
-         * @hide
-         */
-        void onFastPairAntispoofKeyDeviceMetadataReceived(
-                @NonNull FastPairAntispoofKeyDeviceMetadata metadata);
-
-        /** Invoked in case of error.
-         *
-         * @hide
-         */
-        void onError(@ErrorCode int code, @Nullable String message);
-    }
-
-    /**
-     * Callback to be invoked when Fast Pair devices of a given account is loaded.
-     *
-     * @hide
-     */
-    public interface FastPairAccountDevicesMetadataCallback {
-
-        /**
-         * Should be invoked once the metadatas are loaded.
-         *
-         * @hide
-         */
-        void onFastPairAccountDevicesMetadataReceived(
-                @NonNull Collection<FastPairAccountKeyDeviceMetadata> metadatas);
-        /**
-         * Invoked in case of error.
-         *
-         * @hide
-         */
-        void onError(@ErrorCode int code, @Nullable String message);
-    }
-
-    /**
-     * Callback to be invoked when FastPair eligible accounts are loaded.
-     *
-     * @hide
-     */
-    public interface FastPairEligibleAccountsCallback {
-
-        /**
-         * Should be invoked once the eligible accounts are loaded.
-         *
-         * @hide
-         */
-        void onFastPairEligibleAccountsReceived(
-                @NonNull Collection<FastPairEligibleAccount> accounts);
-        /**
-         * Invoked in case of error.
-         *
-         * @hide
-         */
-        void onError(@ErrorCode int code, @Nullable String message);
-    }
-
-    /**
-     * Callback to be invoked when a management action is finished.
-     *
-     * @hide
-     */
-    public interface FastPairManageActionCallback {
-
-        /**
-         * Should be invoked once the manage action is successful.
-         *
-         * @hide
-         */
-        void onSuccess();
-        /**
-         * Invoked in case of error.
-         *
-         * @hide
-         */
-        void onError(@ErrorCode int code, @Nullable String message);
-    }
-
-    /**
-     * Fulfills the Fast Pair device metadata request by using callback to send back the
-     * device meta data of a given modelId.
-     *
-     * @hide
-     */
-    public abstract void onLoadFastPairAntispoofKeyDeviceMetadata(
-            @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
-            @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback);
-
-    /**
-     * Fulfills the account tied Fast Pair devices metadata request by using callback to send back
-     * all Fast Pair device's metadata of a given account.
-     *
-     * @hide
-     */
-    public abstract void onLoadFastPairAccountDevicesMetadata(
-            @NonNull FastPairAccountDevicesMetadataRequest request,
-            @NonNull FastPairAccountDevicesMetadataCallback callback);
-
-    /**
-     * Fulfills the Fast Pair eligible accounts request by using callback to send back Fast Pair
-     * eligible accounts.
-     *
-     * @hide
-     */
-    public abstract void onLoadFastPairEligibleAccounts(
-            @NonNull FastPairEligibleAccountsRequest request,
-            @NonNull FastPairEligibleAccountsCallback callback);
-
-    /**
-     * Fulfills the Fast Pair account management request by using callback to send back result.
-     *
-     * @hide
-     */
-    public abstract void onManageFastPairAccount(
-            @NonNull FastPairManageAccountRequest request,
-            @NonNull FastPairManageActionCallback callback);
-
-    /**
-     * Fulfills the request to manage device-account mapping by using callback to send back result.
-     *
-     * @hide
-     */
-    public abstract void onManageFastPairAccountDevice(
-            @NonNull FastPairManageAccountDeviceRequest request,
-            @NonNull FastPairManageActionCallback callback);
-
-    /**
-     * Class for reading FastPairAntispoofKeyDeviceMetadataRequest, which specifies the model ID of
-     * a Fast Pair device. To fulfill this request, corresponding
-     * {@link FastPairAntispoofKeyDeviceMetadata} should be fetched and returned.
-     *
-     * @hide
-     */
-    public static class FastPairAntispoofKeyDeviceMetadataRequest {
-
-        private final FastPairAntispoofKeyDeviceMetadataRequestParcel mMetadataRequestParcel;
-
-        private FastPairAntispoofKeyDeviceMetadataRequest(
-                final FastPairAntispoofKeyDeviceMetadataRequestParcel metaDataRequestParcel) {
-            this.mMetadataRequestParcel = metaDataRequestParcel;
-        }
-
-        /**
-         * Get modelId (24 bit), the key for FastPairAntispoofKeyDeviceMetadata in the same format
-         * returned by Google at device registration time.
-         *
-         * ModelId format is defined at device registration time, see
-         * <a href="https://developers.google.com/nearby/fast-pair/spec#model_id">Model ID</a>.
-         * @return raw bytes of modelId in the same format returned by Google at device registration
-         *         time.
-         * @hide
-         */
-        public @NonNull byte[] getModelId() {
-            return this.mMetadataRequestParcel.modelId;
-        }
-    }
-
-    /**
-     * Class for reading FastPairAccountDevicesMetadataRequest, which specifies the Fast Pair
-     * account and the allow list of the FastPair device keys saved to the account (i.e., FastPair
-     * accountKeys).
-     *
-     * A Fast Pair accountKey is created when a Fast Pair device is saved to an account. It is per
-     * Fast Pair device per account.
-     *
-     * To retrieve all Fast Pair accountKeys saved to an account, the caller needs to set
-     * account with an empty allow list.
-     *
-     * To retrieve metadata of a selected list of Fast Pair devices saved to an account, the caller
-     * needs to set account with a non-empty allow list.
-     * @hide
-     */
-    public static class FastPairAccountDevicesMetadataRequest {
-
-        private final FastPairAccountDevicesMetadataRequestParcel mMetadataRequestParcel;
-
-        private FastPairAccountDevicesMetadataRequest(
-                final FastPairAccountDevicesMetadataRequestParcel metaDataRequestParcel) {
-            this.mMetadataRequestParcel = metaDataRequestParcel;
-        }
-
-        /**
-         * Get FastPair account, whose Fast Pair devices' metadata is requested.
-         *
-         * @return a FastPair account.
-         * @hide
-         */
-        public @NonNull Account getAccount() {
-            return this.mMetadataRequestParcel.account;
-        }
-
-        /**
-         * Get allowlist of Fast Pair devices using a collection of deviceAccountKeys.
-         * Note that as a special case, empty list actually means all FastPair devices under the
-         * account instead of none.
-         *
-         * DeviceAccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated.
-         *
-         * @return allowlist of Fast Pair devices using a collection of deviceAccountKeys.
-         * @hide
-         */
-        public @NonNull Collection<byte[]> getDeviceAccountKeys()  {
-            if (this.mMetadataRequestParcel.deviceAccountKeys == null) {
-                return new ArrayList<byte[]>(0);
-            }
-            List<byte[]> deviceAccountKeys =
-                    new ArrayList<>(this.mMetadataRequestParcel.deviceAccountKeys.length);
-            for (ByteArrayParcel deviceAccountKey : this.mMetadataRequestParcel.deviceAccountKeys) {
-                deviceAccountKeys.add(deviceAccountKey.byteArray);
-            }
-            return deviceAccountKeys;
-        }
-    }
-
-    /**
-     *  Class for reading FastPairEligibleAccountsRequest. Upon receiving this request, Fast Pair
-     *  eligible accounts should be returned to bind Fast Pair devices.
-     *
-     * @hide
-     */
-    public static class FastPairEligibleAccountsRequest {
-        @SuppressWarnings("UnusedVariable")
-        private final FastPairEligibleAccountsRequestParcel mAccountsRequestParcel;
-
-        private FastPairEligibleAccountsRequest(
-                final FastPairEligibleAccountsRequestParcel accountsRequestParcel) {
-            this.mAccountsRequestParcel = accountsRequestParcel;
-        }
-    }
-
-    /**
-     * Class for reading FastPairManageAccountRequest. If the request type is MANAGE_REQUEST_ADD,
-     * the account is enabled to bind Fast Pair devices; If the request type is
-     * MANAGE_REQUEST_REMOVE, the account is disabled to bind more Fast Pair devices. Furthermore,
-     * all existing bounded Fast Pair devices are unbounded.
-     *
-     * @hide
-     */
-    public static class FastPairManageAccountRequest {
-
-        private final FastPairManageAccountRequestParcel mAccountRequestParcel;
-
-        private FastPairManageAccountRequest(
-                final FastPairManageAccountRequestParcel accountRequestParcel) {
-            this.mAccountRequestParcel = accountRequestParcel;
-        }
-
-        /**
-         * Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE.
-         *
-         * @hide
-         */
-        public @ManageRequestType int getRequestType() {
-            return this.mAccountRequestParcel.requestType;
-        }
-        /**
-         * Get account.
-         *
-         * @hide
-         */
-        public @NonNull Account getAccount() {
-            return this.mAccountRequestParcel.account;
-        }
-    }
-
-    /**
-     *  Class for reading FastPairManageAccountDeviceRequest. If the request type is
-     *  MANAGE_REQUEST_ADD, then a Fast Pair device is bounded to a Fast Pair account. If the
-     *  request type is MANAGE_REQUEST_REMOVE, then a Fast Pair device is removed from a Fast Pair
-     *  account.
-     *
-     * @hide
-     */
-    public static class FastPairManageAccountDeviceRequest {
-
-        private final FastPairManageAccountDeviceRequestParcel mRequestParcel;
-
-        private FastPairManageAccountDeviceRequest(
-                final FastPairManageAccountDeviceRequestParcel requestParcel) {
-            this.mRequestParcel = requestParcel;
-        }
-
-        /**
-         * Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE.
-         *
-         * @hide
-         */
-        public @ManageRequestType int getRequestType() {
-            return this.mRequestParcel.requestType;
-        }
-        /**
-         * Get account.
-         *
-         * @hide
-         */
-        public @NonNull Account getAccount() {
-            return this.mRequestParcel.account;
-        }
-        /**
-         * Get account key device metadata.
-         *
-         * @hide
-         */
-        public @NonNull FastPairAccountKeyDeviceMetadata getAccountKeyDeviceMetadata() {
-            return new FastPairAccountKeyDeviceMetadata(
-                    this.mRequestParcel.accountKeyDeviceMetadata);
-        }
-    }
-
-    /**
-     * Callback class that sends back FastPairAntispoofKeyDeviceMetadata.
-     */
-    private final class WrapperFastPairAntispoofKeyDeviceMetadataCallback implements
-            FastPairAntispoofKeyDeviceMetadataCallback {
-
-        private IFastPairAntispoofKeyDeviceMetadataCallback mCallback;
-
-        private WrapperFastPairAntispoofKeyDeviceMetadataCallback(
-                IFastPairAntispoofKeyDeviceMetadataCallback callback) {
-            mCallback = callback;
-        }
-
-        /**
-         * Sends back FastPairAntispoofKeyDeviceMetadata.
-         */
-        @Override
-        public void onFastPairAntispoofKeyDeviceMetadataReceived(
-                @NonNull FastPairAntispoofKeyDeviceMetadata metadata) {
-            try {
-                mCallback.onFastPairAntispoofKeyDeviceMetadataReceived(metadata.mMetadataParcel);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-
-        @Override
-        public void onError(@ErrorCode int code, @Nullable String message) {
-            try {
-                mCallback.onError(code, message);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    /**
-     * Callback class that sends back collection of FastPairAccountKeyDeviceMetadata.
-     */
-    private final class WrapperFastPairAccountDevicesMetadataCallback implements
-            FastPairAccountDevicesMetadataCallback {
-
-        private IFastPairAccountDevicesMetadataCallback mCallback;
-
-        private WrapperFastPairAccountDevicesMetadataCallback(
-                IFastPairAccountDevicesMetadataCallback callback) {
-            mCallback = callback;
-        }
-
-        /**
-         * Sends back collection of FastPairAccountKeyDeviceMetadata.
-         */
-        @Override
-        public void onFastPairAccountDevicesMetadataReceived(
-                @NonNull Collection<FastPairAccountKeyDeviceMetadata> metadatas) {
-            FastPairAccountKeyDeviceMetadataParcel[] metadataParcels =
-                    new FastPairAccountKeyDeviceMetadataParcel[metadatas.size()];
-            int i = 0;
-            for (FastPairAccountKeyDeviceMetadata metadata : metadatas) {
-                metadataParcels[i] = metadata.mMetadataParcel;
-                i = i + 1;
-            }
-            try {
-                mCallback.onFastPairAccountDevicesMetadataReceived(metadataParcels);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-
-        @Override
-        public void onError(@ErrorCode int code, @Nullable String message) {
-            try {
-                mCallback.onError(code, message);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    /**
-     * Callback class that sends back eligible Fast Pair accounts.
-     */
-    private final class WrapperFastPairEligibleAccountsCallback implements
-            FastPairEligibleAccountsCallback {
-
-        private IFastPairEligibleAccountsCallback mCallback;
-
-        private WrapperFastPairEligibleAccountsCallback(
-                IFastPairEligibleAccountsCallback callback) {
-            mCallback = callback;
-        }
-
-        /**
-         * Sends back the eligible Fast Pair accounts.
-         */
-        @Override
-        public void onFastPairEligibleAccountsReceived(
-                @NonNull Collection<FastPairEligibleAccount> accounts) {
-            int i = 0;
-            FastPairEligibleAccountParcel[] accountParcels =
-                    new FastPairEligibleAccountParcel[accounts.size()];
-            for (FastPairEligibleAccount account: accounts) {
-                accountParcels[i] = account.mAccountParcel;
-                i = i + 1;
-            }
-            try {
-                mCallback.onFastPairEligibleAccountsReceived(accountParcels);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-
-        @Override
-        public void onError(@ErrorCode int code, @Nullable String message) {
-            try {
-                mCallback.onError(code, message);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    /**
-     * Callback class that sends back Fast Pair account management result.
-     */
-    private final class WrapperFastPairManageAccountCallback implements
-            FastPairManageActionCallback {
-
-        private IFastPairManageAccountCallback mCallback;
-
-        private WrapperFastPairManageAccountCallback(
-                IFastPairManageAccountCallback callback) {
-            mCallback = callback;
-        }
-
-        /**
-         * Sends back Fast Pair account opt in result.
-         */
-        @Override
-        public void onSuccess() {
-            try {
-                mCallback.onSuccess();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-
-        @Override
-        public void onError(@ErrorCode int code, @Nullable String message) {
-            try {
-                mCallback.onError(code, message);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    /**
-     * Call back class that sends back account-device mapping management result.
-     */
-    private final class WrapperFastPairManageAccountDeviceCallback implements
-            FastPairManageActionCallback {
-
-        private IFastPairManageAccountDeviceCallback mCallback;
-
-        private WrapperFastPairManageAccountDeviceCallback(
-                IFastPairManageAccountDeviceCallback callback) {
-            mCallback = callback;
-        }
-
-        /**
-         * Sends back the account-device mapping management result.
-         */
-        @Override
-        public void onSuccess() {
-            try {
-                mCallback.onSuccess();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-
-        @Override
-        public void onError(@ErrorCode int code, @Nullable String message) {
-            try {
-                mCallback.onError(code, message);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } catch (RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    private final class Service extends IFastPairDataProvider.Stub {
-
-        Service() {
-        }
-
-        @Override
-        public void loadFastPairAntispoofKeyDeviceMetadata(
-                @NonNull FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel,
-                IFastPairAntispoofKeyDeviceMetadataCallback callback) {
-            onLoadFastPairAntispoofKeyDeviceMetadata(
-                    new FastPairAntispoofKeyDeviceMetadataRequest(requestParcel),
-                    new WrapperFastPairAntispoofKeyDeviceMetadataCallback(callback));
-        }
-
-        @Override
-        public void loadFastPairAccountDevicesMetadata(
-                @NonNull FastPairAccountDevicesMetadataRequestParcel requestParcel,
-                IFastPairAccountDevicesMetadataCallback callback) {
-            onLoadFastPairAccountDevicesMetadata(
-                    new FastPairAccountDevicesMetadataRequest(requestParcel),
-                    new WrapperFastPairAccountDevicesMetadataCallback(callback));
-        }
-
-        @Override
-        public void loadFastPairEligibleAccounts(
-                @NonNull FastPairEligibleAccountsRequestParcel requestParcel,
-                IFastPairEligibleAccountsCallback callback) {
-            onLoadFastPairEligibleAccounts(new FastPairEligibleAccountsRequest(requestParcel),
-                    new WrapperFastPairEligibleAccountsCallback(callback));
-        }
-
-        @Override
-        public void manageFastPairAccount(
-                @NonNull FastPairManageAccountRequestParcel requestParcel,
-                IFastPairManageAccountCallback callback) {
-            onManageFastPairAccount(new FastPairManageAccountRequest(requestParcel),
-                    new WrapperFastPairManageAccountCallback(callback));
-        }
-
-        @Override
-        public void manageFastPairAccountDevice(
-                @NonNull FastPairManageAccountDeviceRequestParcel requestParcel,
-                IFastPairManageAccountDeviceCallback callback) {
-            onManageFastPairAccountDevice(new FastPairManageAccountDeviceRequest(requestParcel),
-                    new WrapperFastPairManageAccountDeviceCallback(callback));
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
deleted file mode 100644
index 0e2e79d..0000000
--- a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java
+++ /dev/null
@@ -1,683 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairDeviceMetadataParcel;
-
-/**
- * Class for the properties of a given type of Fast Pair device, including images and text.
- *
- * @hide
- */
-public class FastPairDeviceMetadata {
-
-    FastPairDeviceMetadataParcel mMetadataParcel;
-
-    FastPairDeviceMetadata(
-            FastPairDeviceMetadataParcel metadataParcel) {
-        this.mMetadataParcel = metadataParcel;
-    }
-
-    /**
-     * Get ImageUrl, which will be displayed in notification.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getImageUrl() {
-        return mMetadataParcel.imageUrl;
-    }
-
-    /**
-     * Get IntentUri, which will be launched to install companion app.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getIntentUri() {
-        return mMetadataParcel.intentUri;
-    }
-
-    /**
-     * Get BLE transmit power, as described in Fast Pair spec, see
-     * <a href="https://developers.google.com/nearby/fast-pair/spec#transmit_power">Transmit Power</a>
-     *
-     * @hide
-     */
-    public int getBleTxPower() {
-        return mMetadataParcel.bleTxPower;
-    }
-
-    /**
-     * Get Fast Pair Half Sheet trigger distance in meters.
-     *
-     * @hide
-     */
-    public float getTriggerDistance() {
-        return mMetadataParcel.triggerDistance;
-    }
-
-    /**
-     * Get Fast Pair device image, which is submitted at device registration time to display on
-     * notification. It is a 32-bit PNG with dimensions of 512px by 512px.
-     *
-     * @return Fast Pair device image in 32-bit PNG with dimensions of 512px by 512px.
-     * @hide
-     */
-    @Nullable
-    public byte[] getImage() {
-        return mMetadataParcel.image;
-    }
-
-    /**
-     * Get Fast Pair device type.
-     * DEVICE_TYPE_UNSPECIFIED = 0;
-     * HEADPHONES = 1;
-     * TRUE_WIRELESS_HEADPHONES = 7;
-     * @hide
-     */
-    public int getDeviceType() {
-        return mMetadataParcel.deviceType;
-    }
-
-    /**
-     * Get Fast Pair device name. e.g., "Pixel Buds A-Series".
-     *
-     * @hide
-     */
-    @Nullable
-    public String getName() {
-        return mMetadataParcel.name;
-    }
-
-    /**
-     * Get true wireless image url for left bud.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getTrueWirelessImageUrlLeftBud() {
-        return mMetadataParcel.trueWirelessImageUrlLeftBud;
-    }
-
-    /**
-     * Get true wireless image url for right bud.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getTrueWirelessImageUrlRightBud() {
-        return mMetadataParcel.trueWirelessImageUrlRightBud;
-    }
-
-    /**
-     * Get true wireless image url for case.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getTrueWirelessImageUrlCase() {
-        return mMetadataParcel.trueWirelessImageUrlCase;
-    }
-
-    /**
-     * Get InitialNotificationDescription, which is a translated string of
-     * "Tap to pair. Earbuds will be tied to %s" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getInitialNotificationDescription() {
-        return mMetadataParcel.initialNotificationDescription;
-    }
-
-    /**
-     * Get InitialNotificationDescriptionNoAccount, which is a translated string of
-     * "Tap to pair with this device" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getInitialNotificationDescriptionNoAccount() {
-        return mMetadataParcel.initialNotificationDescriptionNoAccount;
-    }
-
-    /**
-     * Get OpenCompanionAppDescription, which is a translated string of
-     * "Tap to finish setup" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getOpenCompanionAppDescription() {
-        return mMetadataParcel.openCompanionAppDescription;
-    }
-
-    /**
-     * Get UpdateCompanionAppDescription, which is a translated string of
-     * "Tap to update device settings and finish setup" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getUpdateCompanionAppDescription() {
-        return mMetadataParcel.updateCompanionAppDescription;
-    }
-
-    /**
-     * Get DownloadCompanionAppDescription, which is a translated string of
-     * "Tap to download device app on Google Play and see all features" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getDownloadCompanionAppDescription() {
-        return mMetadataParcel.downloadCompanionAppDescription;
-    }
-
-    /**
-     * Get UnableToConnectTitle, which is a translated string of
-     * "Unable to connect" based on locale.
-     */
-    @Nullable
-    public String getUnableToConnectTitle() {
-        return mMetadataParcel.unableToConnectTitle;
-    }
-
-    /**
-     * Get UnableToConnectDescription, which is a translated string of
-     * "Try manually pairing to the device" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getUnableToConnectDescription() {
-        return mMetadataParcel.unableToConnectDescription;
-    }
-
-    /**
-     * Get InitialPairingDescription, which is a translated string of
-     * "%s will appear on devices linked with %s" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getInitialPairingDescription() {
-        return mMetadataParcel.initialPairingDescription;
-    }
-
-    /**
-     * Get ConnectSuccessCompanionAppInstalled, which is a translated string of
-     * "Your device is ready to be set up" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getConnectSuccessCompanionAppInstalled() {
-        return mMetadataParcel.connectSuccessCompanionAppInstalled;
-    }
-
-    /**
-     * Get ConnectSuccessCompanionAppNotInstalled, which is a translated string of
-     * "Download the device app on Google Play to see all available features" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getConnectSuccessCompanionAppNotInstalled() {
-        return mMetadataParcel.connectSuccessCompanionAppNotInstalled;
-    }
-
-    /**
-     * Get SubsequentPairingDescription, which is a translated string of
-     * "Connect %s to this phone" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getSubsequentPairingDescription() {
-        return mMetadataParcel.subsequentPairingDescription;
-    }
-
-    /**
-     * Get RetroactivePairingDescription, which is a translated string of
-     * "Save device to %s for faster pairing to your other devices" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getRetroactivePairingDescription() {
-        return mMetadataParcel.retroactivePairingDescription;
-    }
-
-    /**
-     * Get WaitLaunchCompanionAppDescription, which is a translated string of
-     * "This will take a few moments" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getWaitLaunchCompanionAppDescription() {
-        return mMetadataParcel.waitLaunchCompanionAppDescription;
-    }
-
-    /**
-     * Get FailConnectGoToSettingsDescription, which is a translated string of
-     * "Try manually pairing to the device by going to Settings" based on locale.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getFailConnectGoToSettingsDescription() {
-        return mMetadataParcel.failConnectGoToSettingsDescription;
-    }
-
-    /**
-     * Builder used to create FastPairDeviceMetadata.
-     *
-     * @hide
-     */
-    public static final class Builder {
-
-        private final FastPairDeviceMetadataParcel mBuilderParcel;
-
-        /**
-         * Default constructor of Builder.
-         *
-         * @hide
-         */
-        public Builder() {
-            mBuilderParcel = new FastPairDeviceMetadataParcel();
-            mBuilderParcel.imageUrl = null;
-            mBuilderParcel.intentUri = null;
-            mBuilderParcel.name = null;
-            mBuilderParcel.bleTxPower = 0;
-            mBuilderParcel.triggerDistance = 0;
-            mBuilderParcel.image = null;
-            mBuilderParcel.deviceType = 0;  // DEVICE_TYPE_UNSPECIFIED
-            mBuilderParcel.trueWirelessImageUrlLeftBud = null;
-            mBuilderParcel.trueWirelessImageUrlRightBud = null;
-            mBuilderParcel.trueWirelessImageUrlCase = null;
-            mBuilderParcel.initialNotificationDescription = null;
-            mBuilderParcel.initialNotificationDescriptionNoAccount = null;
-            mBuilderParcel.openCompanionAppDescription = null;
-            mBuilderParcel.updateCompanionAppDescription = null;
-            mBuilderParcel.downloadCompanionAppDescription = null;
-            mBuilderParcel.unableToConnectTitle = null;
-            mBuilderParcel.unableToConnectDescription = null;
-            mBuilderParcel.initialPairingDescription = null;
-            mBuilderParcel.connectSuccessCompanionAppInstalled = null;
-            mBuilderParcel.connectSuccessCompanionAppNotInstalled = null;
-            mBuilderParcel.subsequentPairingDescription = null;
-            mBuilderParcel.retroactivePairingDescription = null;
-            mBuilderParcel.waitLaunchCompanionAppDescription = null;
-            mBuilderParcel.failConnectGoToSettingsDescription = null;
-        }
-
-        /**
-         * Set ImageUlr.
-         *
-         * @param imageUrl Image Ulr.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setImageUrl(@Nullable String imageUrl) {
-            mBuilderParcel.imageUrl = imageUrl;
-            return this;
-        }
-
-        /**
-         * Set IntentUri.
-         *
-         * @param intentUri Intent uri.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setIntentUri(@Nullable String intentUri) {
-            mBuilderParcel.intentUri = intentUri;
-            return this;
-        }
-
-        /**
-         * Set device name.
-         *
-         * @param name Device name.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setName(@Nullable String name) {
-            mBuilderParcel.name = name;
-            return this;
-        }
-
-        /**
-         * Set ble transmission power.
-         *
-         * @param bleTxPower Ble transmission power.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setBleTxPower(int bleTxPower) {
-            mBuilderParcel.bleTxPower = bleTxPower;
-            return this;
-        }
-
-        /**
-         * Set trigger distance.
-         *
-         * @param triggerDistance Fast Pair trigger distance.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTriggerDistance(float triggerDistance) {
-            mBuilderParcel.triggerDistance = triggerDistance;
-            return this;
-        }
-
-        /**
-         * Set image.
-         *
-         * @param image Fast Pair device image, which is submitted at device registration time to
-         *              display on notification. It is a 32-bit PNG with dimensions of
-         *              512px by 512px.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setImage(@Nullable byte[] image) {
-            mBuilderParcel.image = image;
-            return this;
-        }
-
-        /**
-         * Set device type.
-         *
-         * @param deviceType Fast Pair device type.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDeviceType(int deviceType) {
-            mBuilderParcel.deviceType = deviceType;
-            return this;
-        }
-
-        /**
-         * Set true wireless image url for left bud.
-         *
-         * @param trueWirelessImageUrlLeftBud True wireless image url for left bud.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTrueWirelessImageUrlLeftBud(
-                @Nullable String trueWirelessImageUrlLeftBud) {
-            mBuilderParcel.trueWirelessImageUrlLeftBud = trueWirelessImageUrlLeftBud;
-            return this;
-        }
-
-        /**
-         * Set true wireless image url for right bud.
-         *
-         * @param trueWirelessImageUrlRightBud True wireless image url for right bud.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTrueWirelessImageUrlRightBud(
-                @Nullable String trueWirelessImageUrlRightBud) {
-            mBuilderParcel.trueWirelessImageUrlRightBud = trueWirelessImageUrlRightBud;
-            return this;
-        }
-
-        /**
-         * Set true wireless image url for case.
-         *
-         * @param trueWirelessImageUrlCase True wireless image url for case.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTrueWirelessImageUrlCase(@Nullable String trueWirelessImageUrlCase) {
-            mBuilderParcel.trueWirelessImageUrlCase = trueWirelessImageUrlCase;
-            return this;
-        }
-
-        /**
-         * Set InitialNotificationDescription.
-         *
-         * @param initialNotificationDescription Initial notification description.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setInitialNotificationDescription(
-                @Nullable String initialNotificationDescription) {
-            mBuilderParcel.initialNotificationDescription = initialNotificationDescription;
-            return this;
-        }
-
-        /**
-         * Set InitialNotificationDescriptionNoAccount.
-         *
-         * @param initialNotificationDescriptionNoAccount Initial notification description when
-         *                                                account is not present.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setInitialNotificationDescriptionNoAccount(
-                @Nullable String initialNotificationDescriptionNoAccount) {
-            mBuilderParcel.initialNotificationDescriptionNoAccount =
-                    initialNotificationDescriptionNoAccount;
-            return this;
-        }
-
-        /**
-         * Set OpenCompanionAppDescription.
-         *
-         * @param openCompanionAppDescription Description for opening companion app.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setOpenCompanionAppDescription(
-                @Nullable String openCompanionAppDescription) {
-            mBuilderParcel.openCompanionAppDescription = openCompanionAppDescription;
-            return this;
-        }
-
-        /**
-         * Set UpdateCompanionAppDescription.
-         *
-         * @param updateCompanionAppDescription Description for updating companion app.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setUpdateCompanionAppDescription(
-                @Nullable String updateCompanionAppDescription) {
-            mBuilderParcel.updateCompanionAppDescription = updateCompanionAppDescription;
-            return this;
-        }
-
-        /**
-         * Set DownloadCompanionAppDescription.
-         *
-         * @param downloadCompanionAppDescription Description for downloading companion app.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDownloadCompanionAppDescription(
-                @Nullable String downloadCompanionAppDescription) {
-            mBuilderParcel.downloadCompanionAppDescription = downloadCompanionAppDescription;
-            return this;
-        }
-
-        /**
-         * Set UnableToConnectTitle.
-         *
-         * @param unableToConnectTitle Title when Fast Pair device is unable to be connected to.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setUnableToConnectTitle(@Nullable String unableToConnectTitle) {
-            mBuilderParcel.unableToConnectTitle = unableToConnectTitle;
-            return this;
-        }
-
-        /**
-         * Set UnableToConnectDescription.
-         *
-         * @param unableToConnectDescription Description when Fast Pair device is unable to be
-         *                                   connected to.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setUnableToConnectDescription(
-                @Nullable String unableToConnectDescription) {
-            mBuilderParcel.unableToConnectDescription = unableToConnectDescription;
-            return this;
-        }
-
-        /**
-         * Set InitialPairingDescription.
-         *
-         * @param initialPairingDescription Description for Fast Pair initial pairing.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setInitialPairingDescription(@Nullable String initialPairingDescription) {
-            mBuilderParcel.initialPairingDescription = initialPairingDescription;
-            return this;
-        }
-
-        /**
-         * Set ConnectSuccessCompanionAppInstalled.
-         *
-         * @param connectSuccessCompanionAppInstalled Description that let user open the companion
-         *                                            app.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setConnectSuccessCompanionAppInstalled(
-                @Nullable String connectSuccessCompanionAppInstalled) {
-            mBuilderParcel.connectSuccessCompanionAppInstalled =
-                    connectSuccessCompanionAppInstalled;
-            return this;
-        }
-
-        /**
-         * Set ConnectSuccessCompanionAppNotInstalled.
-         *
-         * @param connectSuccessCompanionAppNotInstalled Description that let user download the
-         *                                               companion app.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setConnectSuccessCompanionAppNotInstalled(
-                @Nullable String connectSuccessCompanionAppNotInstalled) {
-            mBuilderParcel.connectSuccessCompanionAppNotInstalled =
-                    connectSuccessCompanionAppNotInstalled;
-            return this;
-        }
-
-        /**
-         * Set SubsequentPairingDescription.
-         *
-         * @param subsequentPairingDescription Description that reminds user there is a paired
-         *                                     device nearby.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setSubsequentPairingDescription(
-                @Nullable String subsequentPairingDescription) {
-            mBuilderParcel.subsequentPairingDescription = subsequentPairingDescription;
-            return this;
-        }
-
-        /**
-         * Set RetroactivePairingDescription.
-         *
-         * @param retroactivePairingDescription Description that reminds users opt in their device.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setRetroactivePairingDescription(
-                @Nullable String retroactivePairingDescription) {
-            mBuilderParcel.retroactivePairingDescription = retroactivePairingDescription;
-            return this;
-        }
-
-        /**
-         * Set WaitLaunchCompanionAppDescription.
-         *
-         * @param waitLaunchCompanionAppDescription Description that indicates companion app is
-         *                                          about to launch.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setWaitLaunchCompanionAppDescription(
-                @Nullable String waitLaunchCompanionAppDescription) {
-            mBuilderParcel.waitLaunchCompanionAppDescription =
-                    waitLaunchCompanionAppDescription;
-            return this;
-        }
-
-        /**
-         * Set FailConnectGoToSettingsDescription.
-         *
-         * @param failConnectGoToSettingsDescription Description that indicates go to bluetooth
-         *                                           settings when connection fail.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setFailConnectGoToSettingsDescription(
-                @Nullable String failConnectGoToSettingsDescription) {
-            mBuilderParcel.failConnectGoToSettingsDescription =
-                    failConnectGoToSettingsDescription;
-            return this;
-        }
-
-        /**
-         * Build {@link FastPairDeviceMetadata} with the currently set configuration.
-         *
-         * @hide
-         */
-        @NonNull
-        public FastPairDeviceMetadata build() {
-            return new FastPairDeviceMetadata(mBuilderParcel);
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java b/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java
deleted file mode 100644
index d8dfe29..0000000
--- a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairDiscoveryItemParcel;
-
-/**
- * Class for FastPairDiscoveryItem and its builder.
- *
- * @hide
- */
-public class FastPairDiscoveryItem {
-
-    FastPairDiscoveryItemParcel mMetadataParcel;
-
-    FastPairDiscoveryItem(
-            FastPairDiscoveryItemParcel metadataParcel) {
-        this.mMetadataParcel = metadataParcel;
-    }
-
-    /**
-     * Get Id.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getId() {
-        return mMetadataParcel.id;
-    }
-
-    /**
-     * Get MacAddress.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getMacAddress() {
-        return mMetadataParcel.macAddress;
-    }
-
-    /**
-     * Get ActionUrl.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getActionUrl() {
-        return mMetadataParcel.actionUrl;
-    }
-
-    /**
-     * Get DeviceName.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getDeviceName() {
-        return mMetadataParcel.deviceName;
-    }
-
-    /**
-     * Get Title.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getTitle() {
-        return mMetadataParcel.title;
-    }
-
-    /**
-     * Get Description.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getDescription() {
-        return mMetadataParcel.description;
-    }
-
-    /**
-     * Get DisplayUrl.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getDisplayUrl() {
-        return mMetadataParcel.displayUrl;
-    }
-
-    /**
-     * Get LastObservationTimestampMillis.
-     *
-     * @hide
-     */
-    public long getLastObservationTimestampMillis() {
-        return mMetadataParcel.lastObservationTimestampMillis;
-    }
-
-    /**
-     * Get FirstObservationTimestampMillis.
-     *
-     * @hide
-     */
-    public long getFirstObservationTimestampMillis() {
-        return mMetadataParcel.firstObservationTimestampMillis;
-    }
-
-    /**
-     * Get State.
-     *
-     * @hide
-     */
-    public int getState() {
-        return mMetadataParcel.state;
-    }
-
-    /**
-     * Get ActionUrlType.
-     *
-     * @hide
-     */
-    public int getActionUrlType() {
-        return mMetadataParcel.actionUrlType;
-    }
-
-    /**
-     * Get Rssi.
-     *
-     * @hide
-     */
-    public int getRssi() {
-        return mMetadataParcel.rssi;
-    }
-
-    /**
-     * Get PendingAppInstallTimestampMillis.
-     *
-     * @hide
-     */
-    public long getPendingAppInstallTimestampMillis() {
-        return mMetadataParcel.pendingAppInstallTimestampMillis;
-    }
-
-    /**
-     * Get TxPower.
-     *
-     * @hide
-     */
-    public int getTxPower() {
-        return mMetadataParcel.txPower;
-    }
-
-    /**
-     * Get AppName.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getAppName() {
-        return mMetadataParcel.appName;
-    }
-
-    /**
-     * Get PackageName.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getPackageName() {
-        return mMetadataParcel.packageName;
-    }
-
-    /**
-     * Get TriggerId.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getTriggerId() {
-        return mMetadataParcel.triggerId;
-    }
-
-    /**
-     * Get IconPng, which is submitted at device registration time to display on notification. It is
-     * a 32-bit PNG with dimensions of 512px by 512px.
-     *
-     * @return IconPng in 32-bit PNG with dimensions of 512px by 512px.
-     * @hide
-     */
-    @Nullable
-    public byte[] getIconPng() {
-        return mMetadataParcel.iconPng;
-    }
-
-    /**
-     * Get IconFifeUrl.
-     *
-     * @hide
-     */
-    @Nullable
-    public String getIconFfeUrl() {
-        return mMetadataParcel.iconFifeUrl;
-    }
-
-    /**
-     * Get authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see
-     * <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>.
-     *
-     * @return 64-byte authenticationPublicKeySecp256r1.
-     * @hide
-     */
-    @Nullable
-    public byte[] getAuthenticationPublicKeySecp256r1() {
-        return mMetadataParcel.authenticationPublicKeySecp256r1;
-    }
-
-    /**
-     * Builder used to create FastPairDiscoveryItem.
-     *
-     * @hide
-     */
-    public static final class Builder {
-
-        private final FastPairDiscoveryItemParcel mBuilderParcel;
-
-        /**
-         * Default constructor of Builder.
-         *
-         * @hide
-         */
-        public Builder() {
-            mBuilderParcel = new FastPairDiscoveryItemParcel();
-        }
-
-        /**
-         * Set Id.
-         *
-         * @param id Unique id.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         *
-         * @hide
-         */
-        @NonNull
-        public Builder setId(@Nullable String id) {
-            mBuilderParcel.id = id;
-            return this;
-        }
-
-        /**
-         * Set MacAddress.
-         *
-         * @param macAddress Fast Pair device rotating mac address.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setMacAddress(@Nullable String macAddress) {
-            mBuilderParcel.macAddress = macAddress;
-            return this;
-        }
-
-        /**
-         * Set ActionUrl.
-         *
-         * @param actionUrl Action Url of Fast Pair device.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setActionUrl(@Nullable String actionUrl) {
-            mBuilderParcel.actionUrl = actionUrl;
-            return this;
-        }
-
-        /**
-         * Set DeviceName.
-         * @param deviceName Fast Pair device name.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDeviceName(@Nullable String deviceName) {
-            mBuilderParcel.deviceName = deviceName;
-            return this;
-        }
-
-        /**
-         * Set Title.
-         *
-         * @param title Title of Fast Pair device.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTitle(@Nullable String title) {
-            mBuilderParcel.title = title;
-            return this;
-        }
-
-        /**
-         * Set Description.
-         *
-         * @param description Description of Fast Pair device.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDescription(@Nullable String description) {
-            mBuilderParcel.description = description;
-            return this;
-        }
-
-        /**
-         * Set DisplayUrl.
-         *
-         * @param displayUrl Display Url of Fast Pair device.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setDisplayUrl(@Nullable String displayUrl) {
-            mBuilderParcel.displayUrl = displayUrl;
-            return this;
-        }
-
-        /**
-         * Set LastObservationTimestampMillis.
-         *
-         * @param lastObservationTimestampMillis Last observed timestamp of Fast Pair device, keyed
-         *                                       by a rotating id.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setLastObservationTimestampMillis(
-                long lastObservationTimestampMillis) {
-            mBuilderParcel.lastObservationTimestampMillis = lastObservationTimestampMillis;
-            return this;
-        }
-
-        /**
-         * Set FirstObservationTimestampMillis.
-         *
-         * @param firstObservationTimestampMillis First observed timestamp of Fast Pair device,
-         *                                        keyed by a rotating id.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setFirstObservationTimestampMillis(
-                long firstObservationTimestampMillis) {
-            mBuilderParcel.firstObservationTimestampMillis = firstObservationTimestampMillis;
-            return this;
-        }
-
-        /**
-         * Set State.
-         *
-         * @param state Item's current state. e.g. if the item is blocked.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setState(int state) {
-            mBuilderParcel.state = state;
-            return this;
-        }
-
-        /**
-         * Set ActionUrlType.
-         *
-         * @param actionUrlType The resolved url type for the action_url.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setActionUrlType(int actionUrlType) {
-            mBuilderParcel.actionUrlType = actionUrlType;
-            return this;
-        }
-
-        /**
-         * Set Rssi.
-         *
-         * @param rssi Beacon's RSSI value.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setRssi(int rssi) {
-            mBuilderParcel.rssi = rssi;
-            return this;
-        }
-
-        /**
-         * Set PendingAppInstallTimestampMillis.
-         *
-         * @param pendingAppInstallTimestampMillis The timestamp when the user is redirected to App
-         *                                         Store after clicking on the item.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setPendingAppInstallTimestampMillis(long pendingAppInstallTimestampMillis) {
-            mBuilderParcel.pendingAppInstallTimestampMillis = pendingAppInstallTimestampMillis;
-            return this;
-        }
-
-        /**
-         * Set TxPower.
-         *
-         * @param txPower Beacon's tx power.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTxPower(int txPower) {
-            mBuilderParcel.txPower = txPower;
-            return this;
-        }
-
-        /**
-         * Set AppName.
-         *
-         * @param appName Human readable name of the app designated to open the uri.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setAppName(@Nullable String appName) {
-            mBuilderParcel.appName = appName;
-            return this;
-        }
-
-        /**
-         * Set PackageName.
-         *
-         * @param packageName Package name of the App that owns this item.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setPackageName(@Nullable String packageName) {
-            mBuilderParcel.packageName = packageName;
-            return this;
-        }
-
-        /**
-         * Set TriggerId.
-         *
-         * @param triggerId TriggerId identifies the trigger/beacon that is attached with a message.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setTriggerId(@Nullable String triggerId) {
-            mBuilderParcel.triggerId = triggerId;
-            return this;
-        }
-
-        /**
-         * Set IconPng.
-         *
-         * @param iconPng Bytes of item icon in PNG format displayed in Discovery item list.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setIconPng(@Nullable byte[] iconPng) {
-            mBuilderParcel.iconPng = iconPng;
-            return this;
-        }
-
-        /**
-         * Set IconFifeUrl.
-         *
-         * @param iconFifeUrl A FIFE URL of the item icon displayed in Discovery item list.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setIconFfeUrl(@Nullable String iconFifeUrl) {
-            mBuilderParcel.iconFifeUrl = iconFifeUrl;
-            return this;
-        }
-
-        /**
-         * Set authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see
-         * <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>
-         *
-         * @param authenticationPublicKeySecp256r1 64-byte Fast Pair device public key.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setAuthenticationPublicKeySecp256r1(
-                @Nullable byte[] authenticationPublicKeySecp256r1) {
-            mBuilderParcel.authenticationPublicKeySecp256r1 = authenticationPublicKeySecp256r1;
-            return this;
-        }
-
-        /**
-         * Build {@link FastPairDiscoveryItem} with the currently set configuration.
-         *
-         * @hide
-         */
-        @NonNull
-        public FastPairDiscoveryItem build() {
-            return new FastPairDiscoveryItem(mBuilderParcel);
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java b/nearby/framework/java/android/nearby/FastPairEligibleAccount.java
deleted file mode 100644
index 8be4cca..0000000
--- a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby;
-
-import android.accounts.Account;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-
-/**
- * Class for FastPairEligibleAccount and its builder.
- *
- * @hide
- */
-public class FastPairEligibleAccount {
-
-    FastPairEligibleAccountParcel mAccountParcel;
-
-    FastPairEligibleAccount(FastPairEligibleAccountParcel accountParcel) {
-        this.mAccountParcel = accountParcel;
-    }
-
-    /**
-     * Get Account.
-     *
-     * @hide
-     */
-    @Nullable
-    public Account getAccount() {
-        return this.mAccountParcel.account;
-    }
-
-    /**
-     * Get OptIn Status.
-     *
-     * @hide
-     */
-    public boolean isOptIn() {
-        return this.mAccountParcel.optIn;
-    }
-
-    /**
-     * Builder used to create FastPairEligibleAccount.
-     *
-     * @hide
-     */
-    public static final class Builder {
-
-        private final FastPairEligibleAccountParcel mBuilderParcel;
-
-        /**
-         * Default constructor of Builder.
-         *
-         * @hide
-         */
-        public Builder() {
-            mBuilderParcel = new FastPairEligibleAccountParcel();
-            mBuilderParcel.account = null;
-            mBuilderParcel.optIn = false;
-        }
-
-        /**
-         * Set Account.
-         *
-         * @param account Fast Pair eligible account.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setAccount(@Nullable Account account) {
-            mBuilderParcel.account = account;
-            return this;
-        }
-
-        /**
-         * Set whether the account is opt into Fast Pair.
-         *
-         * @param optIn Whether the Fast Pair eligible account opts into Fast Pair.
-         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
-         * @hide
-         */
-        @NonNull
-        public Builder setOptIn(boolean optIn) {
-            mBuilderParcel.optIn = optIn;
-            return this;
-        }
-
-        /**
-         * Build {@link FastPairEligibleAccount} with the currently set configuration.
-         *
-         * @hide
-         */
-        @NonNull
-        public FastPairEligibleAccount build() {
-            return new FastPairEligibleAccount(mBuilderParcel);
-        }
-    }
-}
diff --git a/nearby/framework/java/android/nearby/FastPairStatusCallback.java b/nearby/framework/java/android/nearby/FastPairStatusCallback.java
deleted file mode 100644
index 1567828..0000000
--- a/nearby/framework/java/android/nearby/FastPairStatusCallback.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 android.nearby;
-
-import android.annotation.NonNull;
-
-/**
- * Reports the pair status for an ongoing pair with a {@link FastPairDevice}.
- * @hide
- */
-public interface FastPairStatusCallback {
-
-    /** Reports a pair status related metadata associated with a {@link FastPairDevice} */
-    void onPairUpdate(@NonNull FastPairDevice fastPairDevice,
-            PairStatusMetadata pairStatusMetadata);
-}
diff --git a/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl b/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
deleted file mode 100644
index 2e6fc87..0000000
--- a/nearby/framework/java/android/nearby/IFastPairHalfSheetCallback.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby;
-
-import android.content.Intent;
-/**
-  * Provides callback interface for halfsheet to send FastPair call back.
-  *
-  * {@hide}
-  */
-interface IFastPairHalfSheetCallback {
-     void onHalfSheetConnectionConfirm(in Intent intent);
- }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.aidl b/nearby/framework/java/android/nearby/PairStatusMetadata.aidl
deleted file mode 100644
index 911a300..0000000
--- a/nearby/framework/java/android/nearby/PairStatusMetadata.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 android.nearby;
-
-/**
- * Metadata about an ongoing paring. Wraps transient data like status and progress.
- *
- * @hide
- */
-parcelable PairStatusMetadata;
diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.java b/nearby/framework/java/android/nearby/PairStatusMetadata.java
deleted file mode 100644
index 438cd6b..0000000
--- a/nearby/framework/java/android/nearby/PairStatusMetadata.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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 android.nearby;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Metadata about an ongoing paring. Wraps transient data like status and progress.
- *
- * @hide
- */
-public final class PairStatusMetadata implements Parcelable {
-
-    @Status
-    private final int mStatus;
-
-    /** The status of the pairing. */
-    @IntDef({
-            Status.UNKNOWN,
-            Status.SUCCESS,
-            Status.FAIL,
-            Status.DISMISS
-    })
-    public @interface Status {
-        int UNKNOWN = 1000;
-        int SUCCESS = 1001;
-        int FAIL = 1002;
-        int DISMISS = 1003;
-    }
-
-    /** Converts the status to readable string. */
-    public static String statusToString(@Status int status) {
-        switch (status) {
-            case Status.SUCCESS:
-                return "SUCCESS";
-            case Status.FAIL:
-                return "FAIL";
-            case Status.DISMISS:
-                return "DISMISS";
-            case Status.UNKNOWN:
-            default:
-                return "UNKNOWN";
-        }
-    }
-
-    public int getStatus() {
-        return mStatus;
-    }
-
-    @Override
-    public String toString() {
-        return "PairStatusMetadata[ status=" + statusToString(mStatus) + "]";
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (other instanceof PairStatusMetadata) {
-            return mStatus == ((PairStatusMetadata) other).mStatus;
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mStatus);
-    }
-
-    public PairStatusMetadata(@Status int status) {
-        mStatus = status;
-    }
-
-    public static final Creator<PairStatusMetadata> CREATOR = new Creator<PairStatusMetadata>() {
-        @Override
-        public PairStatusMetadata createFromParcel(Parcel in) {
-            return new PairStatusMetadata(in.readInt());
-        }
-
-        @Override
-        public PairStatusMetadata[] newArray(int size) {
-            return new PairStatusMetadata[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public int getStability() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mStatus);
-    }
-}
diff --git a/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl b/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl
deleted file mode 100644
index 53c73bd..0000000
--- a/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-/**
- * This is to support 2D byte arrays.
- * {@hide}
- */
-parcelable ByteArrayParcel {
-    byte[] byteArray;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl
deleted file mode 100644
index fc3ba22..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-import android.accounts.Account;
-import android.nearby.aidl.ByteArrayParcel;
-
-/**
- * Request details for Metadata of Fast Pair devices associated with an account.
- * {@hide}
- */
-parcelable FastPairAccountDevicesMetadataRequestParcel {
-    Account account;
-    ByteArrayParcel[] deviceAccountKeys;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl
deleted file mode 100644
index 8014323..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.nearby.aidl.FastPairDeviceMetadataParcel;
-import android.nearby.aidl.FastPairDiscoveryItemParcel;
-
-/**
- * Metadata of a Fast Pair device associated with an account.
- * {@hide}
- */
- // TODO(b/204780849): remove unnecessary fields and polish comments.
-parcelable FastPairAccountKeyDeviceMetadataParcel {
-    // Key of the Fast Pair device associated with the account.
-    byte[] deviceAccountKey;
-    // Hash function of device account key and public bluetooth address.
-    byte[] sha256DeviceAccountKeyPublicAddress;
-    // Fast Pair device metadata for the Fast Pair device.
-    FastPairDeviceMetadataParcel metadata;
-    // Fast Pair discovery item tied to both the Fast Pair device and the
-    // account.
-    FastPairDiscoveryItemParcel discoveryItem;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl
deleted file mode 100644
index 4fd4d4b..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.nearby.aidl.FastPairDeviceMetadataParcel;
-
-/**
- * Metadata of a Fast Pair device keyed by AntispoofKey,
- * Used by initial pairing without account association.
- *
- * {@hide}
- */
-parcelable FastPairAntispoofKeyDeviceMetadataParcel {
-    // Anti-spoof public key.
-    byte[] antispoofPublicKey;
-
-    // Fast Pair device metadata for the Fast Pair device.
-    FastPairDeviceMetadataParcel deviceMetadata;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataRequestParcel.aidl
deleted file mode 100644
index afdcf15..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataRequestParcel.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-/**
- * Request details for metadata of a Fast Pair device keyed by either
- * antispoofKey or modelId.
- * {@hide}
- */
-parcelable FastPairAntispoofKeyDeviceMetadataRequestParcel {
-    byte[] modelId;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl
deleted file mode 100644
index d90f6a1..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-/**
- * Fast Pair Device Metadata for a given device model ID.
- * @hide
- */
-// TODO(b/204780849): remove unnecessary fields and polish comments.
-parcelable FastPairDeviceMetadataParcel {
-    // The image to show on the notification.
-    String imageUrl;
-
-    // The intent that will be launched via the notification.
-    String intentUri;
-
-    // The transmit power of the device's BLE chip.
-    int bleTxPower;
-
-    // The distance that the device must be within to show a notification.
-    // If no distance is set, we default to 0.6 meters. Only Nearby admins can
-    // change this.
-    float triggerDistance;
-
-    // The image icon that shows in the notification.
-    byte[] image;
-
-    // The name of the device.
-    String name;
-
-    int deviceType;
-
-    // The image urls for device with device type "true wireless".
-    String trueWirelessImageUrlLeftBud;
-    String trueWirelessImageUrlRightBud;
-    String trueWirelessImageUrlCase;
-
-    // The notification description for when the device is initially discovered.
-    String initialNotificationDescription;
-
-    // The notification description for when the device is initially discovered
-    // and no account is logged in.
-    String initialNotificationDescriptionNoAccount;
-
-    // The notification description for once we have finished pairing and the
-    // companion app has been opened. For Bisto devices, this String will point
-    // users to setting up the assistant.
-    String openCompanionAppDescription;
-
-    // The notification description for once we have finished pairing and the
-    // companion app needs to be updated before use.
-    String updateCompanionAppDescription;
-
-    // The notification description for once we have finished pairing and the
-    // companion app needs to be installed.
-    String downloadCompanionAppDescription;
-
-    // The notification title when a pairing fails.
-    String unableToConnectTitle;
-
-    // The notification summary when a pairing fails.
-    String unableToConnectDescription;
-
-    // The description that helps user initially paired with device.
-    String initialPairingDescription;
-
-    // The description that let user open the companion app.
-    String connectSuccessCompanionAppInstalled;
-
-    // The description that let user download the companion app.
-    String connectSuccessCompanionAppNotInstalled;
-
-    // The description that reminds user there is a paired device nearby.
-    String subsequentPairingDescription;
-
-    // The description that reminds users opt in their device.
-    String retroactivePairingDescription;
-
-    // The description that indicates companion app is about to launch.
-    String waitLaunchCompanionAppDescription;
-
-    // The description that indicates go to bluetooth settings when connection
-    // fail.
-    String failConnectGoToSettingsDescription;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl
deleted file mode 100644
index 2cc2daa..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-/**
- * Fast Pair Discovery Item.
- * @hide
- */
-// TODO(b/204780849): remove unnecessary fields and polish comments.
-parcelable FastPairDiscoveryItemParcel {
-  // Offline item: unique ID generated on client.
-  // Online item: unique ID generated on server.
-  String id;
-
-  // The most recent all upper case mac associated with this item.
-  // (Mac-to-DiscoveryItem is a many-to-many relationship)
-  String macAddress;
-
-  String actionUrl;
-
-  // The bluetooth device name from advertisement
-  String deviceName;
-
-  // Item's title
-  String title;
-
-  // Item's description.
-  String description;
-
-  // The URL for display
-  String displayUrl;
-
-  // Client timestamp when the beacon was last observed in BLE scan.
-  long lastObservationTimestampMillis;
-
-  // Client timestamp when the beacon was first observed in BLE scan.
-  long firstObservationTimestampMillis;
-
-  // Item's current state. e.g. if the item is blocked.
-  int state;
-
-  // The resolved url type for the action_url.
-  int actionUrlType;
-
-  // The timestamp when the user is redirected to Play Store after clicking on
-  // the item.
-  long pendingAppInstallTimestampMillis;
-
-  // Beacon's RSSI value
-  int rssi;
-
-  // Beacon's tx power
-  int txPower;
-
-  // Human readable name of the app designated to open the uri
-  // Used in the second line of the notification, "Open in {} app"
-  String appName;
-
-  // Package name of the App that owns this item.
-  String packageName;
-
-  // TriggerId identifies the trigger/beacon that is attached with a message.
-  // It's generated from server for online messages to synchronize formatting
-  // across client versions.
-  // Example:
-  // * BLE_UID: 3||deadbeef
-  // * BLE_URL: http://trigger.id
-  // See go/discovery-store-message-and-trigger-id for more details.
-  String triggerId;
-
-  // Bytes of item icon in PNG format displayed in Discovery item list.
-  byte[] iconPng;
-
-  // A FIFE URL of the item icon displayed in Discovery item list.
-  String iconFifeUrl;
-
-  // Fast Pair antispoof key.
-  byte[] authenticationPublicKeySecp256r1;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountParcel.aidl
deleted file mode 100644
index 747758d..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountParcel.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-import android.accounts.Account;
-
-/**
- * Fast Pair Eligible Account.
- * {@hide}
- */
-parcelable FastPairEligibleAccountParcel {
-    Account account;
-    // Whether the account opts in Fast Pair.
-    boolean optIn;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountsRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountsRequestParcel.aidl
deleted file mode 100644
index 8db3356..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairEligibleAccountsRequestParcel.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-/**
- * Request details for Fast Pair eligible accounts.
- * Empty place holder for future expansion.
- * {@hide}
- */
-parcelable FastPairEligibleAccountsRequestParcel {
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl
deleted file mode 100644
index 59834b2..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-import android.accounts.Account;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-
-/**
- * Request details for managing Fast Pair device-account mapping.
- * {@hide}
- */
- // TODO(b/204780849): remove unnecessary fields and polish comments.
-parcelable FastPairManageAccountDeviceRequestParcel {
-    Account account;
-    // MANAGE_ACCOUNT_DEVICE_ADD: add Fast Pair device to the account.
-    // MANAGE_ACCOUNT_DEVICE_REMOVE: remove Fast Pair device from the account.
-    int requestType;
-    // Fast Pair account key-ed device metadata.
-    FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadata;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairManageAccountRequestParcel.aidl
deleted file mode 100644
index 3d92064..0000000
--- a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountRequestParcel.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.aidl;
-
-import android.accounts.Account;
-
-/**
- * Request details for managing a Fast Pair account.
- *
- * {@hide}
- */
-parcelable FastPairManageAccountRequestParcel {
-    Account account;
-    // MANAGE_ACCOUNT_OPT_IN: opt account into Fast Pair.
-    // MANAGE_ACCOUNT_OPT_OUT: opt account out of Fast Pair.
-    int requestType;
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl
deleted file mode 100644
index 7db18d0..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-
-/**
-  * Provides callback interface for OEMs to send back metadata of FastPair
-  * devices associated with an account.
-  *
-  * {@hide}
-  */
-interface IFastPairAccountDevicesMetadataCallback {
-     void onFastPairAccountDevicesMetadataReceived(in FastPairAccountKeyDeviceMetadataParcel[] accountDevicesMetadata);
-     void onError(int code, String message);
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl
deleted file mode 100644
index 38abba4..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
-
-/**
-  * Provides callback interface for OEMs to send FastPair AntispoofKey Device metadata back.
-  *
-  * {@hide}
-  */
-interface IFastPairAntispoofKeyDeviceMetadataCallback {
-     void onFastPairAntispoofKeyDeviceMetadataReceived(in FastPairAntispoofKeyDeviceMetadataParcel metadata);
-     void onError(int code, String message);
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl
deleted file mode 100644
index 2956211..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
-import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
-import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
-import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
-import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
-import android.nearby.aidl.IFastPairEligibleAccountsCallback;
-import android.nearby.aidl.FastPairManageAccountRequestParcel;
-import android.nearby.aidl.IFastPairManageAccountCallback;
-import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
-import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
-
-/**
- * Interface for communicating with the fast pair providers.
- *
- * {@hide}
- */
-oneway interface IFastPairDataProvider {
-    void loadFastPairAntispoofKeyDeviceMetadata(in FastPairAntispoofKeyDeviceMetadataRequestParcel request,
-        in IFastPairAntispoofKeyDeviceMetadataCallback callback);
-    void loadFastPairAccountDevicesMetadata(in FastPairAccountDevicesMetadataRequestParcel request,
-        in IFastPairAccountDevicesMetadataCallback callback);
-    void loadFastPairEligibleAccounts(in FastPairEligibleAccountsRequestParcel request,
-        in IFastPairEligibleAccountsCallback callback);
-    void manageFastPairAccount(in FastPairManageAccountRequestParcel request,
-        in IFastPairManageAccountCallback callback);
-    void manageFastPairAccountDevice(in FastPairManageAccountDeviceRequestParcel request,
-        in IFastPairManageAccountDeviceCallback callback);
-}
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl
deleted file mode 100644
index 9990014..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-import android.accounts.Account;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-
-/**
-  * Provides callback interface for OEMs to return FastPair Eligible accounts.
-  *
-  * {@hide}
-  */
-interface IFastPairEligibleAccountsCallback {
-     void onFastPairEligibleAccountsReceived(in FastPairEligibleAccountParcel[] accounts);
-     void onError(int code, String message);
- }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl
deleted file mode 100644
index 6b4aaee..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-/**
-  * Provides callback interface to send response for account management request.
-  *
-  * {@hide}
-  */
-interface IFastPairManageAccountCallback {
-     void onSuccess();
-     void onError(int code, String message);
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountDeviceCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountDeviceCallback.aidl
deleted file mode 100644
index bffc533..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountDeviceCallback.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android.nearby.aidl;
-
-/**
-  * Provides callback interface to send response for account-device mapping
-  * management request.
-  *
-  * {@hide}
-  */
-interface IFastPairManageAccountDeviceCallback {
-     void onSuccess();
-     void onError(int code, String message);
-}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl
deleted file mode 100644
index d844c06..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairStatusCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 android.nearby.aidl;
-
-import android.nearby.FastPairDevice;
-import android.nearby.PairStatusMetadata;
-
-/**
- *
- * Provides callbacks for Fast Pair foreground activity to learn about paring status from backend.
- *
- * {@hide}
- */
-interface IFastPairStatusCallback {
-
-    /** Reports a pair status related metadata associated with a {@link FastPairDevice} */
-    void onPairUpdate(in FastPairDevice fastPairDevice, in PairStatusMetadata pairStatusMetadata);
-}
diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl
deleted file mode 100644
index 9200a9d..0000000
--- a/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 android.nearby.aidl;
-
-import android.nearby.aidl.IFastPairStatusCallback;
-import android.nearby.FastPairDevice;
-
-/**
- * 0p API for controlling Fast Pair. Used to talk between foreground activities
- * and background services.
- *
- * {@hide}
- */
-interface IFastPairUiService {
-
-    void registerCallback(in IFastPairStatusCallback fastPairStatusCallback);
-
-    void unregisterCallback(in IFastPairStatusCallback fastPairStatusCallback);
-
-    void connect(in FastPairDevice fastPairDevice);
-
-    void cancel(in FastPairDevice fastPairDevice);
-}
\ No newline at end of file
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
deleted file mode 100644
index 8011dc6..0000000
--- a/nearby/halfsheet/Android.bp
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2021 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
-    name: "HalfSheetUX",
-    defaults: ["platform_app_defaults"],
-    srcs: ["src/**/*.java"],
-    sdk_version: "module_current",
-    // This is included in tethering apex, which uses min SDK 30
-    min_sdk_version: "30",
-    updatable: true,
-    certificate: ":com.android.nearby.halfsheetcertificate",
-    libs: [
-        "framework-bluetooth",
-        "framework-connectivity-t.impl",
-        "nearby-service-string",
-    ],
-    static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.fragment_fragment",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.localbroadcastmanager_localbroadcastmanager",
-        "androidx.core_core",
-        "androidx.appcompat_appcompat",
-        "androidx.recyclerview_recyclerview",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.lifecycle_lifecycle-extensions",
-        "com.google.android.material_material",
-        "fast-pair-lite-protos",
-    ],
-    manifest: "AndroidManifest.xml",
-    jarjar_rules: ":nearby-jarjar-rules",
-    apex_available: ["com.android.tethering",],
-    lint: { strict_updatability_linting: true }
-}
-
-android_app_certificate {
-    name: "com.android.nearby.halfsheetcertificate",
-    certificate: "apk-certs/com.android.nearby.halfsheet"
-}
diff --git a/nearby/halfsheet/AndroidManifest.xml b/nearby/halfsheet/AndroidManifest.xml
deleted file mode 100644
index 22987fb..0000000
--- a/nearby/halfsheet/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.nearby.halfsheet">
-    <application>
-        <activity
-            android:name="com.android.nearby.halfsheet.HalfSheetActivity"
-            android:exported="true"
-            android:launchMode="singleInstance"
-            android:theme="@style/HalfSheetStyle" >
-            <intent-filter>
-                <action android:name="android.nearby.SHOW_HALFSHEET"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
deleted file mode 100644
index 187d51e..0000000
--- a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8
+++ /dev/null
Binary files differ
diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
deleted file mode 100644
index 440c524..0000000
--- a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem
+++ /dev/null
@@ -1,34 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIF6zCCA9OgAwIBAgIUU5ATKevcNA5ZSurwgwGenwrr4c4wDQYJKoZIhvcNAQEL
-BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQwwCgYDVQQH
-DANNVFYxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGbmVhcmJ5MQswCQYDVQQD
-DAJ3czEiMCAGCSqGSIb3DQEJARYTd2VpY2VzdW5AZ29vZ2xlLmNvbTAgFw0yMTEy
-MDgwMTMxMzFaGA80NzU5MTEwNDAxMzEzMVowgYMxCzAJBgNVBAYTAlVTMRMwEQYD
-VQQIDApDYWxpZm9ybmlhMQwwCgYDVQQHDANNVFYxDzANBgNVBAoMBkdvb2dsZTEP
-MA0GA1UECwwGbmVhcmJ5MQswCQYDVQQDDAJ3czEiMCAGCSqGSIb3DQEJARYTd2Vp
-Y2VzdW5AZ29vZ2xlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
-AO0JW1YZ5bKHZG5B9eputz3kGREmXcWZ97dg/ODDs3+op4ulBmgaYeo5yeCy29GI
-Sjgxo4G+9fNZ7Fejrk5/LLWovAoRvVxnkRxCkTfp15jZpKNnZjT2iTRLXzNz2O04
-cC0jB81mu5vJ9a8pt+EQkuSwjDMiUi6q4Sf6IRxtTCd5a1yn9eHf1y2BbCmU+Eys
-bs97HJl9PgMCp7hP+dYDxEtNTAESg5IpJ1i7uINgPNl8d0tvJ9rOEdy0IcdeGwt/
-t0L9fIoRCePttH+idKIyDjcNyp9WtX2/wZKlsGap83rGzLdL2PI4DYJ2Ytmy8W3a
-9qFJNrhl3Q3BYgPlcCg9qQOIKq6ZJgFFH3snVDKvtSFd8b9ofK7UzD5g2SllTqDA
-4YvrdK4GETQunSjG7AC/2PpvN/FdhHm7pBi0fkgwykMh35gv0h8mmb6pBISYgr85
-+GMBilNiNJ4G6j3cdOa72pvfDW5qn5dn5ks8cIgW2X1uF/GT8rR6Mb2rwhjY9eXk
-TaP0RykyzheMY/7dWeA/PdN3uMCEJEt72ZakDIswgQVPCIw8KQPIf6pl0d5hcLSV
-QzhqBaXudseVg0QlZ86iaobpZvCrW0KqQmMU5GVhEtDc2sPe5e+TCmUC/H+vo8F8
-1UYu3MJaBcpePFlgIsLhW0niUTfCq2FiNrPykOJT7U9NAgMBAAGjUzBRMB0GA1Ud
-DgQWBBQKSepRcKTv9hr8mmKjYCL7NeG2izAfBgNVHSMEGDAWgBQKSepRcKTv9hr8
-mmKjYCL7NeG2izAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQC/
-BoItafzvjYPzENY16BIkgRqJVU7IosWxGLczzg19NFu6HPa54alqkawp7RI1ZNVH
-bJjQma5ap0L+Y06/peIU9rvEtfbCkkYJwvIaSRlTlzrNwNEcj3yJMmGTr/wfIzq8
-PN1t0hihnqI8ZguOPC+sV6ARoC+ygkwaLU1oPbVvOGz9WplvSokE1mvtqKAyuDoL
-LZfWwbhxRAgwgCIEz6cPfEcgg3Xzc+L4OzmNhTTc7GNOAtvvW7Zqc2Lohb8nQMNw
-uY65yiHPNmjmc+xLHZk3jQg82tKv792JJRkVXPsIfQV087IzxFFjjvKy82rVfeaN
-F9g2EpUvdjtm8zx7K5tiDv9Es/Up7oOnoB5baLgnMAEVMTZY+4k/6BfVM5CVUu+H
-AO1yh2yeNWbzY8B+zxRef3C2Ax68lJHFyz8J1pfrGpWxML3rDmWiVDMtEk73t3g+
-lcyLYo7OW+iBn6BODRcINO4R640oyMjFz2wPSPAsU0Zj/MbgC6iaS+goS3QnyPQS
-O3hKWfwqQuA7BZ0la1n+plKH5PKxQESAbd37arzCsgQuktl33ONiwYOt6eUyHl/S
-E3ZdldkmGm9z0mcBYG9NczDBSYmtuZOGjEzIRqI5GFD2WixE+dqTzVP/kyBd4BLc
-OTmBynN/8D/qdUZNrT+tgs+mH/I2SsKYW9Zymwf7Qw==
------END CERTIFICATE-----
diff --git a/nearby/halfsheet/apk-certs/key.pem b/nearby/halfsheet/apk-certs/key.pem
deleted file mode 100644
index e9f4288..0000000
--- a/nearby/halfsheet/apk-certs/key.pem
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDtCVtWGeWyh2Ru
-QfXqbrc95BkRJl3Fmfe3YPzgw7N/qKeLpQZoGmHqOcngstvRiEo4MaOBvvXzWexX
-o65Ofyy1qLwKEb1cZ5EcQpE36deY2aSjZ2Y09ok0S18zc9jtOHAtIwfNZrubyfWv
-KbfhEJLksIwzIlIuquEn+iEcbUwneWtcp/Xh39ctgWwplPhMrG7PexyZfT4DAqe4
-T/nWA8RLTUwBEoOSKSdYu7iDYDzZfHdLbyfazhHctCHHXhsLf7dC/XyKEQnj7bR/
-onSiMg43DcqfVrV9v8GSpbBmqfN6xsy3S9jyOA2CdmLZsvFt2vahSTa4Zd0NwWID
-5XAoPakDiCqumSYBRR97J1Qyr7UhXfG/aHyu1Mw+YNkpZU6gwOGL63SuBhE0Lp0o
-xuwAv9j6bzfxXYR5u6QYtH5IMMpDId+YL9IfJpm+qQSEmIK/OfhjAYpTYjSeBuo9
-3HTmu9qb3w1uap+XZ+ZLPHCIFtl9bhfxk/K0ejG9q8IY2PXl5E2j9EcpMs4XjGP+
-3VngPz3Td7jAhCRLe9mWpAyLMIEFTwiMPCkDyH+qZdHeYXC0lUM4agWl7nbHlYNE
-JWfOomqG6Wbwq1tCqkJjFORlYRLQ3NrD3uXvkwplAvx/r6PBfNVGLtzCWgXKXjxZ
-YCLC4VtJ4lE3wqthYjaz8pDiU+1PTQIDAQABAoICAQCt4R5CM+8enlka1IIbvann
-2cpVnUpOaNqhh6EZFBY5gDOfqafgd/H5yvh/P1UnCI5BWJBz3ew33nAT/fsglAPt
-ImEGFetNvJ9jFqXGWWCRPJ6cS35bPbp6RQwKB2JK6grH4ZmYoFLhPi5elwDPNcQ7
-xBKkc/nLSAiwtbjSTI7/qf8K0h752aTUOctpWWEnhZon00ywf4Ic3TbBatF/n/W/
-s20coEMp1cyKN/JrVQ5uD/LGwDyBModB2lWpFSxLrB14I9DWyxbxP28X7ckXLhbl
-ZdWMOyQZoa/S7n5PYT49g1Wq5BW54UpvuH5c6fpWtrgSqk1cyUR2EbTf3NAAhPLU
-PgPK8wbFMcMB3TpQDXl7USA7QX5wSv22OfhivPsHQ9szGM0f84mK0PhXYPWBiNUY
-Y8rrIjOijB4eFGDFnTIMTofAb07NxRThci710BYUqgBVTBG5N+avIesjwkikMjOI
-PwYukKSQSw/Tqxy5Z9l22xksGynBZFjEFs/WT5pDczPAktA4xW3CGxjkMsIYaOBs
-OCEujqc5+mHSywYvy8aN+nA+yPucJP5e5pLZ1qaU0tqyakCx8XeeOyP6Wfm3UAAV
-AYelBRcWcJxM51w4o5UnUnpBD+Uxiz1sRVlqa9bLJjP4M+wJNL+WaIn9D6WhPOvl
-+naDC+p29ou2JzyKFDsOQQKCAQEA+Jalm+xAAPc+t/gCdAqEDo0NMA2/NG8m9BRc
-CVZRRaWVyGPeg5ziT/7caGwy2jpOZEjK0OOTCAqF+sJRDj6DDIw7nDrlxNyaXnCF
-gguQHFIYaHcjKGTs5l0vgL3H7pMFHN2qVynf4xrTuBXyT1GJ4vdWKAJbooa02c8W
-XI2fjwZ7Y8wSWrm1tn3oTTBR3N6o1GyPY6/TrL0mhpWwgx5eJeLl3GuUxOhXY5R9
-y48ziS97Dqdq75MxUOHickofCNcm7p+jA8Hg+SxLMR/kUFsXOxawmvsBqdL1XzU5
-LTS7xAEY9iMuBcO6yIxcxqBx96idjsPXx1lgARo1CpaZYCzgPQKCAQEA9BqKMN/Y
-o+T+ac99St8x3TYkk5lkvLVqlPw+EQhEqrm9EEBPntxWM5FEIpPVmFm7taGTgPfN
-KKaaNxX5XyK9B2v1QqN7XrX0nF4+6x7ao64fdpRUParIuBVctqzQWWthme66eHrf
-L86T/tkt3o/7p+Hd4Z9UT3FaAew1ggWr00xz5PJ/4b3f3mRmtNmgeTYskWMxOpSj
-bEenom4Row7sfLNeXNSWDGlzJ/lf6svvbVM2X5h2uFsxlt/Frq9ooTA3wwhnbd1i
-cFifDQ6cxF5mBpz/V/hnlHVfuXlknEZa9EQXHNo/aC9y+bR+ai05FJyK/WgqleW8
-5PBmoTReWA2MUQKCAQAnnnLkh+GnhcBEN83ESszDOO3KI9a+d5yguAH3Jv+q9voJ
-Rwl2tnFHSJo+NkhgiXxm9UcFxc9wL6Us0v1yJLpkLJFvk9984Z/kv1A36rncGaV0
-ONCspnEvQdjJTvXnax0cfaOhYrYhDuyBYVYOGDO+rabYl4+dNpTqRdwNgjDU7baK
-sEKYnRJ99FEqxDG33vDPckHkJGi7FiZmusK4EwX0SdZSq/6450LORyNJZxhSm/Oj
-4UDkz/PDLU0W5ANQOGInE+A6QBMoA0w0lx2fRPVN4I7jFHAubcXXl7b2InpugbJF
-wFOcbZZ+UgiTS4z+aKw7zbC9P9xSMKgVeO0W6/ANAoIBABe0LA8q7YKczgfAWk5W
-9iShCVQ75QheJYdqJyzIPMLHXpChbhnjE4vWY2NoL6mnrQ6qLgSsC4QTCY6n15th
-aDG8Tgi2j1hXGvXEQR/b0ydp1SxSowuJ9gvKJ0Kl7WWBg+zKvdjNNbcSvFRXCpk+
-KhXXXRB3xFwiibb+FQQXQOQ33FkzIy/snDygS0jsiSS8Gf/UPgeOP4BYRPME9Tl8
-TYKeeF9TVW7HHqOXF7VZMFrRZcpKp9ynHl2kRTH9Xo+oewG5YzHL+a8nK+q8rIR1
-Fjs2K6WDPauw6ia8nwR94H8vzX7Dwrx/Pw74c/4jfhN+UBDjeJ8tu/YPUif9SdwL
-FMECggEALdCGKfQ4vPmqI6UdfVB5hdCPoM6tUsI2yrXFvlHjSGVanC/IG9x2mpRb
-4odamLYx4G4NjP1IJSY08LFT9VhLZtRM1W3fGeboW12LTEVNrI3lRBU84rAQ1ced
-l6/DvTKJjhfwTxb/W7sqmZY5hF3QuNxs67Z8x0pe4b58musa0qFCs4Sa8qTNZKRW
-fIbxIKuvu1HSNOKkZLu6Gq8km+XIlVAaSVA03Tt+EK74MFL6+pcd7/VkS00MAYUC
-gS4ic+QFzCl5P8zl/GoX8iUFsRZQCSJkZ75VwO13pEupVwCAW8WWJO83U4jBsnJs
-ayrX7pbsnW6jsNYBUlck+RYVYkVkxA==
------END PRIVATE KEY-----
diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml
deleted file mode 100644
index 098dccb..0000000
--- a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:interpolator="@android:interpolator/decelerate_quint">
-    <translate android:fromYDelta="100%"
-               android:toYDelta="0"
-               android:duration="900"/>
-</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml
deleted file mode 100644
index 1cf7401..0000000
--- a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:interpolator="@android:interpolator/decelerate_quint">
-    <translate android:fromYDelta="0"
-               android:toYDelta="100%"
-               android:duration="500"/>
-</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml
deleted file mode 100644
index 9a51ddb..0000000
--- a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:targetApi="23"
-    android:duration="@integer/half_sheet_slide_in_duration"
-    android:interpolator="@android:interpolator/fast_out_slow_in">
-  <translate
-      android:fromYDelta="100%p"
-      android:toYDelta="0%p"/>
-
-  <alpha
-      android:fromAlpha="0.0"
-      android:toAlpha="1.0"/>
-</set>
diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml
deleted file mode 100644
index c589482..0000000
--- a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:duration="@integer/half_sheet_fade_out_duration"
-    android:interpolator="@android:interpolator/fast_out_slow_in">
-
-  <translate
-      android:fromYDelta="0%p"
-      android:toYDelta="100%p"/>
-
-  <alpha
-      android:fromAlpha="1.0"
-      android:toAlpha="0.0"/>
-
-</set>
diff --git a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
deleted file mode 100644
index 7d61d1c..0000000
--- a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-  ~ 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.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    android:tint="@color/fast_pair_half_sheet_subtitle_color">
-    <path
-        android:fillColor="@color/fast_pair_half_sheet_subtitle_color"
-        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/drawable/fastpair_outline.xml b/nearby/halfsheet/res/drawable/fastpair_outline.xml
deleted file mode 100644
index 6765e11..0000000
--- a/nearby/halfsheet/res/drawable/fastpair_outline.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-  <stroke
-      android:width="1dp"
-      android:color="@color/fast_pair_notification_image_outline"/>
-</shape>
diff --git a/nearby/halfsheet/res/drawable/half_sheet_bg.xml b/nearby/halfsheet/res/drawable/half_sheet_bg.xml
deleted file mode 100644
index 7e7d8dd..0000000
--- a/nearby/halfsheet/res/drawable/half_sheet_bg.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:targetApi="23">
-  <solid android:color="@color/fast_pair_half_sheet_background" />
-  <corners
-      android:topLeftRadius="16dp"
-      android:topRightRadius="16dp"
-      android:padding="8dp"/>
-</shape>
diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
deleted file mode 100644
index 7fbe229..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:orientation="vertical"
-    tools:ignore="RtlCompat"
-    android:layout_width="match_parent" android:layout_height="match_parent">
-
-  <androidx.constraintlayout.widget.ConstraintLayout
-      android:id="@+id/image_view"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:minHeight="340dp"
-      android:paddingStart="12dp"
-      android:paddingEnd="12dp"
-      android:paddingTop="12dp"
-      android:paddingBottom="12dp">
-    <TextView
-        android:id="@+id/header_subtitle"
-        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
-        android:fontFamily="google-sans"
-        android:textSize="14sp"
-        android:maxLines="3"
-        android:gravity="center"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent" />
-
-    <ImageView
-        android:id="@+id/pairing_pic"
-        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
-        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
-        android:paddingTop="18dp"
-        android:paddingBottom="18dp"
-        android:importantForAccessibility="no"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
-
-    <TextView
-        android:id="@+id/pin_code"
-        android:textColor="@color/fast_pair_half_sheet_subtitle_color"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/fast_pair_half_sheet_image_size"
-        android:paddingTop="18dp"
-        android:paddingBottom="18dp"
-        android:visibility="invisible"
-        android:textSize="50sp"
-        android:letterSpacing="0.2"
-        android:fontFamily="google-sans-medium"
-        android:gravity="center"
-        android:importantForAccessibility="yes"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
-
-    <ProgressBar
-        android:id="@+id/connect_progressbar"
-        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
-        android:layout_height="2dp"
-        android:indeterminate="true"
-        android:indeterminateTint="@color/fast_pair_progress_color"
-        android:indeterminateTintMode="src_in"
-        style="?android:attr/progressBarStyleHorizontal"
-        android:layout_marginBottom="6dp"
-        app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"/>
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent">
-
-      <ImageView
-          android:id="@+id/info_icon"
-          android:layout_width="24dp"
-          android:layout_height="24dp"
-          app:srcCompat="@drawable/fast_pair_ic_info"
-          android:layout_centerInParent="true"
-          android:contentDescription="@null"
-          android:layout_marginEnd="10dp"
-          android:layout_toStartOf="@id/connect_btn"
-          android:visibility="invisible" />
-
-      <com.google.android.material.button.MaterialButton
-          android:id="@+id/connect_btn"
-          android:layout_width="@dimen/fast_pair_half_sheet_image_size"
-          android:layout_height="wrap_content"
-          android:text="@string/paring_action_connect"
-          android:layout_centerInParent="true"
-          style="@style/HalfSheetButton" />
-
-    </RelativeLayout>
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/settings_btn"
-        android:text="@string/paring_action_settings"
-        android:layout_height="wrap_content"
-        android:layout_width="@dimen/fast_pair_half_sheet_image_size"
-        app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        android:visibility="invisible"
-        style="@style/HalfSheetButton" />
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/cancel_btn"
-        android:text="@string/paring_action_done"
-        android:visibility="invisible"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:gravity="start|center_vertical"
-        android:layout_marginTop="6dp"
-        style="@style/HalfSheetButtonBorderless"/>
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/setup_btn"
-        android:text="@string/paring_action_launch"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginTop="6dp"
-        android:layout_marginBottom="16dp"
-        android:background="@color/fast_pair_half_sheet_button_color"
-        android:visibility="invisible"
-        android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
-        android:layout_width="wrap_content"
-        style="@style/HalfSheetButton" />
-
-  </androidx.constraintlayout.widget.ConstraintLayout>
-
-</LinearLayout>
diff --git a/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml
deleted file mode 100644
index 705aa1b..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:ignore="RtlCompat, UselessParent, MergeRootFrame"
-    android:id="@+id/background"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-  <LinearLayout
-      android:id="@+id/card"
-      android:orientation="vertical"
-      android:transitionName="card"
-      android:layout_height="wrap_content"
-      android:layout_width="match_parent"
-      android:layout_gravity= "center|bottom"
-      android:paddingLeft="12dp"
-      android:paddingRight="12dp"
-      android:background="@drawable/half_sheet_bg"
-      android:accessibilityLiveRegion="polite"
-      android:gravity="bottom">
-
-    <RelativeLayout
-        android:id="@+id/toolbar_wrapper"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingLeft="20dp"
-        android:paddingRight="20dp">
-
-      <ImageView
-          android:layout_marginTop="9dp"
-          android:layout_marginBottom="9dp"
-          android:id="@+id/toolbar_image"
-          android:layout_width="42dp"
-          android:layout_height="42dp"
-          android:contentDescription="@null"
-          android:layout_toStartOf="@id/toolbar_title"
-          android:layout_centerHorizontal="true"
-          android:visibility="invisible"/>
-
-      <TextView
-          android:layout_marginTop="18dp"
-          android:layout_marginBottom="18dp"
-          android:layout_centerHorizontal="true"
-          android:id="@+id/toolbar_title"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:fontFamily="google-sans-medium"
-          android:textSize="24sp"
-          android:textColor="@color/fast_pair_half_sheet_text_color"
-          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
-    </RelativeLayout>
-
-    <FrameLayout
-        android:id="@+id/fragment_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-  </LinearLayout>
-
-</FrameLayout>
-
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml
deleted file mode 100644
index 11b8343..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:baselineAligned="false"
-    android:background="@color/fast_pair_notification_background"
-    tools:ignore="ContentDescription,UnusedAttribute,RtlCompat,Overdraw">
-
-  <LinearLayout
-      android:orientation="vertical"
-      android:layout_width="0dp"
-      android:layout_height="wrap_content"
-      android:layout_weight="1"
-      android:layout_marginTop="@dimen/fast_pair_notification_padding"
-      android:layout_marginStart="@dimen/fast_pair_notification_padding"
-      android:layout_marginEnd="@dimen/fast_pair_notification_padding">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-      <TextView
-          android:id="@android:id/title"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:fontFamily="sans-serif-medium"
-          android:textSize="@dimen/fast_pair_notification_text_size"
-          android:textColor="@color/fast_pair_primary_text"
-          android:layout_marginBottom="2dp"
-          android:lines="1"/>
-
-      <TextView
-          android:id="@android:id/text2"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:textSize="@dimen/fast_pair_notification_text_size_small"
-          android:textColor="@color/fast_pair_primary_text"
-          android:layout_marginBottom="2dp"
-          android:layout_marginStart="4dp"
-          android:lines="1"/>
-    </LinearLayout>
-
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="@dimen/fast_pair_notification_text_size"
-        android:textColor="@color/fast_pair_primary_text"
-        android:maxLines="2"
-        android:ellipsize="end"
-        android:breakStrategy="simple" />
-
-    <FrameLayout
-        android:id="@android:id/secondaryProgress"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="32dp"
-        android:orientation="horizontal"
-        android:visibility="gone">
-
-      <ProgressBar
-          android:id="@android:id/progress"
-          style="?android:attr/progressBarStyleHorizontal"
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:layout_gravity="center_vertical"
-          android:indeterminateTint="@color/discovery_activity_accent"/>
-
-    </FrameLayout>
-
-  </LinearLayout>
-
-  <FrameLayout
-      android:id="@android:id/icon1"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"/>
-
-</LinearLayout>
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml
deleted file mode 100644
index dd28947..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@android:id/icon"
-    android:layout_width="@dimen/fast_pair_notification_large_image_size"
-    android:layout_height="@dimen/fast_pair_notification_large_image_size"
-    android:scaleType="fitStart"
-    tools:ignore="ContentDescription"/>
diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml
deleted file mode 100644
index ee1d89f..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@android:id/icon"
-    android:layout_width="@dimen/fast_pair_notification_small_image_size"
-    android:layout_height="@dimen/fast_pair_notification_small_image_size"
-    android:layout_marginTop="@dimen/fast_pair_notification_padding"
-    android:layout_marginBottom="@dimen/fast_pair_notification_padding"
-    android:layout_marginStart="@dimen/fast_pair_notification_padding"
-    android:layout_marginEnd="@dimen/fast_pair_notification_padding"
-    android:scaleType="fitStart"
-    tools:ignore="ContentDescription,RtlCompat"/>
diff --git a/nearby/halfsheet/res/values-af/strings.xml b/nearby/halfsheet/res/values-af/strings.xml
deleted file mode 100644
index 7333e63..0000000
--- a/nearby/halfsheet/res/values-af/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Begin tans opstelling …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Stel toestel op"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Toestel is gekoppel"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kon nie koppel nie"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Klaar"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Stoor"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Koppel"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Stel op"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Instellings"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-am/strings.xml b/nearby/halfsheet/res/values-am/strings.xml
deleted file mode 100644
index da3b144..0000000
--- a/nearby/halfsheet/res/values-am/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ማዋቀርን በመጀመር ላይ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"መሣሪያ አዋቅር"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"መሣሪያ ተገናኝቷል"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"መገናኘት አልተቻለም"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ተጠናቅቋል"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"አስቀምጥ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"አገናኝ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"አዋቅር"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ቅንብሮች"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ar/strings.xml b/nearby/halfsheet/res/values-ar/strings.xml
deleted file mode 100644
index d0bfce4..0000000
--- a/nearby/halfsheet/res/values-ar/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"جارٍ الإعداد…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"إعداد جهاز"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"تمّ إقران الجهاز"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"تعذّر الربط"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"تم"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"حفظ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ربط"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"إعداد"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"الإعدادات"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-as/strings.xml b/nearby/halfsheet/res/values-as/strings.xml
deleted file mode 100644
index 8ff4946..0000000
--- a/nearby/halfsheet/res/values-as/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ছেটআপ আৰম্ভ কৰি থকা হৈছে…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ডিভাইচ ছেট আপ কৰক"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ডিভাইচ সংযোগ কৰা হ’ল"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"সংযোগ কৰিব পৰা নগ’ল"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"হ’ল"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ছেভ কৰক"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"সংযোগ কৰক"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ছেট আপ কৰক"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ছেটিং"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-az/strings.xml b/nearby/halfsheet/res/values-az/strings.xml
deleted file mode 100644
index af499ef..0000000
--- a/nearby/halfsheet/res/values-az/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Ayarlama başladılır…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Cihazı quraşdırın"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Cihaz qoşulub"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Qoşulmaq mümkün olmadı"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Oldu"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Saxlayın"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Qoşun"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Ayarlayın"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ayarlar"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index eea6b64..0000000
--- a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Podešavanje se pokreće…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Podesite uređaj"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspelo"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Sačuvaj"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Podesi"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Podešavanja"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-be/strings.xml b/nearby/halfsheet/res/values-be/strings.xml
deleted file mode 100644
index a5c1ef6..0000000
--- a/nearby/halfsheet/res/values-be/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Пачынаецца наладжванне…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Наладзьце прыладу"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Прылада падключана"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не ўдалося падключыцца"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Гатова"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Захаваць"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Падключыць"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Наладзіць"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Налады"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-bg/strings.xml b/nearby/halfsheet/res/values-bg/strings.xml
deleted file mode 100644
index 0ee7aef..0000000
--- a/nearby/halfsheet/res/values-bg/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Настройването се стартира…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Настройване на устройството"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Устройството е свързано"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Свързването не бе успешно"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Запазване"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Свързване"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Настройване"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Настройки"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-bn/strings.xml b/nearby/halfsheet/res/values-bn/strings.xml
deleted file mode 100644
index 484e35b..0000000
--- a/nearby/halfsheet/res/values-bn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"সেট-আপ করা শুরু হচ্ছে…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ডিভাইস সেট-আপ করুন"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ডিভাইস কানেক্ট হয়েছে"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"কানেক্ট করা যায়নি"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"হয়ে গেছে"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"সেভ করুন"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"কানেক্ট করুন"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"সেট-আপ করুন"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"সেটিংস"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-bs/strings.xml b/nearby/halfsheet/res/values-bs/strings.xml
deleted file mode 100644
index 2fc8644..0000000
--- a/nearby/halfsheet/res/values-bs/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pokretanje postavljanja…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Postavi uređaj"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspjelo"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Sačuvaj"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Postavi"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Postavke"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ca/strings.xml b/nearby/halfsheet/res/values-ca/strings.xml
deleted file mode 100644
index 8912792..0000000
--- a/nearby/halfsheet/res/values-ca/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciant la configuració…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura el dispositiu"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"El dispositiu s\'ha connectat"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"No s\'ha pogut connectar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Fet"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Desa"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connecta"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configura"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Configuració"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-cs/strings.xml b/nearby/halfsheet/res/values-cs/strings.xml
deleted file mode 100644
index 7e7ea3c..0000000
--- a/nearby/halfsheet/res/values-cs/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Zahajování nastavení…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavení zařízení"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Zařízení je připojeno"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nelze se připojit"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Hotovo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Uložit"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Připojit"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Nastavit"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Nastavení"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-da/strings.xml b/nearby/halfsheet/res/values-da/strings.xml
deleted file mode 100644
index 1d937e2..0000000
--- a/nearby/halfsheet/res/values-da/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Begynder konfiguration…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurer enhed"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheden er forbundet"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Forbindelsen kan ikke oprettes"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Luk"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Gem"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Opret forbindelse"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Konfigurer"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Indstillinger"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-de/strings.xml b/nearby/halfsheet/res/values-de/strings.xml
deleted file mode 100644
index 9186a44..0000000
--- a/nearby/halfsheet/res/values-de/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Einrichtung wird gestartet..."</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Gerät einrichten"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Gerät verbunden"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Verbindung nicht möglich"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Fertig"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Speichern"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Verbinden"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Einrichten"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Einstellungen"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-el/strings.xml b/nearby/halfsheet/res/values-el/strings.xml
deleted file mode 100644
index 3e18a93..0000000
--- a/nearby/halfsheet/res/values-el/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Έναρξη ρύθμισης…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Ρύθμιση συσκευής"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Η συσκευή συνδέθηκε"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Αδυναμία σύνδεσης"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Τέλος"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Αποθήκευση"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Σύνδεση"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Ρύθμιση"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ρυθμίσεις"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-en-rAU/strings.xml b/nearby/halfsheet/res/values-en-rAU/strings.xml
deleted file mode 100644
index d4ed675..0000000
--- a/nearby/halfsheet/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Set up"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Settings"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-en-rCA/strings.xml b/nearby/halfsheet/res/values-en-rCA/strings.xml
deleted file mode 100644
index 6094199..0000000
--- a/nearby/halfsheet/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting Setup…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Set up"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Settings"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-en-rGB/strings.xml b/nearby/halfsheet/res/values-en-rGB/strings.xml
deleted file mode 100644
index d4ed675..0000000
--- a/nearby/halfsheet/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Set up"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Settings"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-en-rIN/strings.xml b/nearby/halfsheet/res/values-en-rIN/strings.xml
deleted file mode 100644
index d4ed675..0000000
--- a/nearby/halfsheet/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starting setup…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Set up device"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Device connected"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Couldn\'t connect"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Done"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Save"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connect"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Set up"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Settings"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-en-rXC/strings.xml b/nearby/halfsheet/res/values-en-rXC/strings.xml
deleted file mode 100644
index 460cc1b..0000000
--- a/nearby/halfsheet/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎Starting Setup…‎‏‎‎‏‎"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎Set up device‎‏‎‎‏‎"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎Device connected‎‏‎‎‏‎"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Couldn\'t connect‎‏‎‎‏‎"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎Done‎‏‎‎‏‎"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎Save‎‏‎‎‏‎"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎Connect‎‏‎‎‏‎"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎Set up‎‏‎‎‏‎"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎Settings‎‏‎‎‏‎"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-es-rUS/strings.xml b/nearby/halfsheet/res/values-es-rUS/strings.xml
deleted file mode 100644
index d8fb283..0000000
--- a/nearby/halfsheet/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando la configuración…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configuración del dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Se conectó el dispositivo"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"No se pudo establecer conexión"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Listo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Configuración"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-es/strings.xml b/nearby/halfsheet/res/values-es/strings.xml
deleted file mode 100644
index 4b8340a..0000000
--- a/nearby/halfsheet/res/values-es/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando configuración…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar el dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"No se ha podido conectar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Hecho"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ajustes"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-et/strings.xml b/nearby/halfsheet/res/values-et/strings.xml
deleted file mode 100644
index e6abc64..0000000
--- a/nearby/halfsheet/res/values-et/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Seadistuse käivitamine …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Seadistage seade"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Seade on ühendatud"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ühendamine ebaõnnestus"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Valmis"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salvesta"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Ühenda"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Seadistamine"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Seaded"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-eu/strings.xml b/nearby/halfsheet/res/values-eu/strings.xml
deleted file mode 100644
index 4243fd5..0000000
--- a/nearby/halfsheet/res/values-eu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Konfigurazio-prozesua abiarazten…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfiguratu gailua"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Konektatu da gailua"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ezin izan da konektatu"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Eginda"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Gorde"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Konektatu"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Konfiguratu"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ezarpenak"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-fa/strings.xml b/nearby/halfsheet/res/values-fa/strings.xml
deleted file mode 100644
index 3585f95..0000000
--- a/nearby/halfsheet/res/values-fa/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"درحال شروع راه‌اندازی…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"راه‌اندازی دستگاه"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"دستگاه متصل شد"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"متصل نشد"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"تمام"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ذخیره"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"متصل کردن"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"راه‌اندازی"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"تنظیمات"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-fi/strings.xml b/nearby/halfsheet/res/values-fi/strings.xml
deleted file mode 100644
index e8d47de..0000000
--- a/nearby/halfsheet/res/values-fi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Aloitetaan käyttöönottoa…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Määritä laite"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Laite on yhdistetty"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ei yhteyttä"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Valmis"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Tallenna"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Yhdistä"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Ota käyttöön"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Asetukset"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-fr-rCA/strings.xml b/nearby/halfsheet/res/values-fr-rCA/strings.xml
deleted file mode 100644
index 64dd107..0000000
--- a/nearby/halfsheet/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Démarrage de la configuration…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurer l\'appareil"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Appareil associé"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossible d\'associer"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"OK"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Enregistrer"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Associer"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurer"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Paramètres"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-fr/strings.xml b/nearby/halfsheet/res/values-fr/strings.xml
deleted file mode 100644
index 484c57b..0000000
--- a/nearby/halfsheet/res/values-fr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Début de la configuration…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurer un appareil"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Appareil associé"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossible de se connecter"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"OK"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Enregistrer"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connecter"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurer"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Paramètres"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-gl/strings.xml b/nearby/halfsheet/res/values-gl/strings.xml
deleted file mode 100644
index 30393ff..0000000
--- a/nearby/halfsheet/res/values-gl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando configuración…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura o dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Conectouse o dispositivo"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Non se puido conectar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Feito"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Gardar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Configuración"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-gu/strings.xml b/nearby/halfsheet/res/values-gu/strings.xml
deleted file mode 100644
index 03b057d..0000000
--- a/nearby/halfsheet/res/values-gu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"સેટઅપ શરૂ કરી રહ્યાં છીએ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ડિવાઇસનું સેટઅપ કરો"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ડિવાઇસ કનેક્ટ કર્યું"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"કનેક્ટ કરી શક્યા નથી"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"થઈ ગયું"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"સાચવો"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"કનેક્ટ કરો"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"સેટઅપ કરો"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"સેટિંગ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-hi/strings.xml b/nearby/halfsheet/res/values-hi/strings.xml
deleted file mode 100644
index ecd420e..0000000
--- a/nearby/halfsheet/res/values-hi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेट अप शुरू किया जा रहा है…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिवाइस सेट अप करें"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिवाइस कनेक्ट हो गया"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट नहीं किया जा सका"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"हो गया"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"सेव करें"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट करें"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"सेट अप करें"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"सेटिंग"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-hr/strings.xml b/nearby/halfsheet/res/values-hr/strings.xml
deleted file mode 100644
index 5a3de8f..0000000
--- a/nearby/halfsheet/res/values-hr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pokretanje postavljanja…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Postavi uređaj"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Uređaj je povezan"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezivanje nije uspjelo"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gotovo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Spremi"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Postavi"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Postavke"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-hu/strings.xml b/nearby/halfsheet/res/values-hu/strings.xml
deleted file mode 100644
index ba3d2e0..0000000
--- a/nearby/halfsheet/res/values-hu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Beállítás megkezdése…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Eszköz beállítása"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Eszköz csatlakoztatva"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nem sikerült csatlakozni"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Kész"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Mentés"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Csatlakozás"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Beállítás"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Beállítások"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-hy/strings.xml b/nearby/halfsheet/res/values-hy/strings.xml
deleted file mode 100644
index ecabd16..0000000
--- a/nearby/halfsheet/res/values-hy/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Կարգավորում…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Կարգավորեք սարքը"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Սարքը զուգակցվեց"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Չհաջողվեց միանալ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Պատրաստ է"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Պահել"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Միանալ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Կարգավորել"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Կարգավորումներ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-in/strings.xml b/nearby/halfsheet/res/values-in/strings.xml
deleted file mode 100644
index dc777b2..0000000
--- a/nearby/halfsheet/res/values-in/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Memulai Penyiapan …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Siapkan perangkat"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Perangkat terhubung"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tidak dapat terhubung"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Selesai"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Simpan"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Hubungkan"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Siapkan"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Setelan"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-is/strings.xml b/nearby/halfsheet/res/values-is/strings.xml
deleted file mode 100644
index ee094d9..0000000
--- a/nearby/halfsheet/res/values-is/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Ræsir uppsetningu…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Uppsetning tækis"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Tækið er tengt"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tenging mistókst"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Lokið"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Vista"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Tengja"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Setja upp"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Stillingar"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-it/strings.xml b/nearby/halfsheet/res/values-it/strings.xml
deleted file mode 100644
index 700dd77..0000000
--- a/nearby/halfsheet/res/values-it/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Avvio della configurazione…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configura dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo connesso"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Impossibile connettere"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Fine"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salva"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Connetti"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configura"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Impostazioni"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-iw/strings.xml b/nearby/halfsheet/res/values-iw/strings.xml
deleted file mode 100644
index e6ff9b9..0000000
--- a/nearby/halfsheet/res/values-iw/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ההגדרה מתבצעת…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"הגדרת המכשיר"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"המכשיר מחובר"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"לא ניתן להתחבר"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"סיום"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"שמירה"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"התחברות"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"הגדרה"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"הגדרות"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ja/strings.xml b/nearby/halfsheet/res/values-ja/strings.xml
deleted file mode 100644
index a429b7e..0000000
--- a/nearby/halfsheet/res/values-ja/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"セットアップを開始中…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"デバイスのセットアップ"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"デバイス接続完了"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"接続エラー"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"完了"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"保存"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"接続"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"セットアップ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"設定"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ka/strings.xml b/nearby/halfsheet/res/values-ka/strings.xml
deleted file mode 100644
index 4353ae9..0000000
--- a/nearby/halfsheet/res/values-ka/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"დაყენება იწყება…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"მოწყობილობის დაყენება"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"მოწყობილობა დაკავშირებულია"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"დაკავშირება ვერ მოხერხდა"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"მზადაა"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"შენახვა"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"დაკავშირება"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"დაყენება"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"პარამეტრები"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-kk/strings.xml b/nearby/halfsheet/res/values-kk/strings.xml
deleted file mode 100644
index 98d8073..0000000
--- a/nearby/halfsheet/res/values-kk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Реттеу басталуда…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Құрылғыны реттеу"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Құрылғы байланыстырылды"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Қосылмады"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Дайын"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Сақтау"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Қосу"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Реттеу"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-km/strings.xml b/nearby/halfsheet/res/values-km/strings.xml
deleted file mode 100644
index 85e39db..0000000
--- a/nearby/halfsheet/res/values-km/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"កំពុងចាប់ផ្ដើម​រៀបចំ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"រៀបចំ​ឧបករណ៍"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"បានភ្ជាប់ឧបករណ៍"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"មិន​អាចភ្ជាប់​បានទេ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"រួចរាល់"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"រក្សាទុក"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ភ្ជាប់"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"រៀបចំ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ការកំណត់"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-kn/strings.xml b/nearby/halfsheet/res/values-kn/strings.xml
deleted file mode 100644
index fb62bb1..0000000
--- a/nearby/halfsheet/res/values-kn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ಸೆಟಪ್ ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ಸಾಧನವನ್ನು ಸೆಟಪ್ ಮಾಡಿ"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ಮುಗಿದಿದೆ"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ಉಳಿಸಿ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ಸೆಟಪ್ ಮಾಡಿ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ko/strings.xml b/nearby/halfsheet/res/values-ko/strings.xml
deleted file mode 100644
index c94ff76..0000000
--- a/nearby/halfsheet/res/values-ko/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"설정을 시작하는 중…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"기기 설정"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"기기 연결됨"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"연결할 수 없음"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"완료"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"저장"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"연결"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"설정"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"설정"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml
deleted file mode 100644
index b0dfe20..0000000
--- a/nearby/halfsheet/res/values-ky/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Жөндөлүп баштады…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Түзмөктү жөндөө"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Түзмөк туташты"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Туташпай койду"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Бүттү"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Сактоо"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Туташуу"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Жөндөө"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-lo/strings.xml b/nearby/halfsheet/res/values-lo/strings.xml
deleted file mode 100644
index 9c945b2..0000000
--- a/nearby/halfsheet/res/values-lo/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ກຳລັງເລີ່ມການຕັ້ງຄ່າ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ຕັ້ງຄ່າອຸປະກອນ"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ເຊື່ອມຕໍ່ອຸປະກອນແລ້ວ"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ແລ້ວໆ"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ບັນທຶກ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ເຊື່ອມຕໍ່"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ຕັ້ງຄ່າ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ການຕັ້ງຄ່າ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-lt/strings.xml b/nearby/halfsheet/res/values-lt/strings.xml
deleted file mode 100644
index 5dbad0a..0000000
--- a/nearby/halfsheet/res/values-lt/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Pradedama sąranka…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Įrenginio nustatymas"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Įrenginys prijungtas"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Prisijungti nepavyko"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Atlikta"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Išsaugoti"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Prisijungti"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Nustatyti"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Nustatymai"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-lv/strings.xml b/nearby/halfsheet/res/values-lv/strings.xml
deleted file mode 100644
index a9e1bf9..0000000
--- a/nearby/halfsheet/res/values-lv/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Tiek sākta iestatīšana…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Iestatiet ierīci"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Ierīce ir pievienota"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nevarēja izveidot savienojumu"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gatavs"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Saglabāt"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Izveidot savienojumu"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Iestatīt"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Iestatījumi"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-mk/strings.xml b/nearby/halfsheet/res/values-mk/strings.xml
deleted file mode 100644
index e29dfa1..0000000
--- a/nearby/halfsheet/res/values-mk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Се започнува со поставување…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Поставете го уредот"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Уредот е поврзан"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не може да се поврзе"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Зачувај"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Поврзи"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Поставете"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Поставки"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ml/strings.xml b/nearby/halfsheet/res/values-ml/strings.xml
deleted file mode 100644
index cbc171b..0000000
--- a/nearby/halfsheet/res/values-ml/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"സജ്ജീകരിക്കൽ ആരംഭിക്കുന്നു…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ഉപകരണം സജ്ജീകരിക്കുക"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ഉപകരണം കണക്റ്റ് ചെയ്‌തു"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"കണക്റ്റ് ചെയ്യാനായില്ല"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"പൂർത്തിയായി"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"സംരക്ഷിക്കുക"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"കണക്റ്റ് ചെയ്യുക"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"സജ്ജീകരിക്കുക"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ക്രമീകരണം"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-mn/strings.xml b/nearby/halfsheet/res/values-mn/strings.xml
deleted file mode 100644
index 6d21eff..0000000
--- a/nearby/halfsheet/res/values-mn/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Тохируулгыг эхлүүлж байна…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Төхөөрөмж тохируулах"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Төхөөрөмж холбогдсон"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Холбогдож чадсангүй"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Болсон"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Хадгалах"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Холбох"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Тохируулах"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Тохиргоо"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-mr/strings.xml b/nearby/halfsheet/res/values-mr/strings.xml
deleted file mode 100644
index a3e1d7a..0000000
--- a/nearby/halfsheet/res/values-mr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेटअप सुरू करत आहे…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिव्हाइस सेट करा"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिव्हाइस कनेक्ट केले आहे"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट करता आले नाही"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"पूर्ण झाले"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"सेव्ह करा"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट करा"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"सेट करा"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"सेटिंग्ज"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ms/strings.xml b/nearby/halfsheet/res/values-ms/strings.xml
deleted file mode 100644
index 4835c1b..0000000
--- a/nearby/halfsheet/res/values-ms/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Memulakan Persediaan…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Sediakan peranti"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Peranti disambungkan"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Tidak dapat menyambung"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Selesai"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Simpan"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Sambung"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Sediakan"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Tetapan"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-my/strings.xml b/nearby/halfsheet/res/values-my/strings.xml
deleted file mode 100644
index 32c3105..0000000
--- a/nearby/halfsheet/res/values-my/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"စနစ်ထည့်သွင်းခြင်း စတင်နေသည်…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"စက်ကို စနစ်ထည့်သွင်းရန်"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"စက်ကို ချိတ်ဆက်လိုက်ပြီ"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"ချိတ်ဆက်၍မရပါ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ပြီးပြီ"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"သိမ်းရန်"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ချိတ်ဆက်ရန်"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"စနစ်ထည့်သွင်းရန်"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ဆက်တင်များ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-nb/strings.xml b/nearby/halfsheet/res/values-nb/strings.xml
deleted file mode 100644
index 9d72565..0000000
--- a/nearby/halfsheet/res/values-nb/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Starter konfigureringen …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurer enheten"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheten er tilkoblet"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kunne ikke koble til"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Ferdig"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Lagre"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Koble til"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Konfigurer"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Innstillinger"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ne/strings.xml b/nearby/halfsheet/res/values-ne/strings.xml
deleted file mode 100644
index 1370412..0000000
--- a/nearby/halfsheet/res/values-ne/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"सेटअप प्रक्रिया सुरु गरिँदै छ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"डिभाइस सेटअप गर्नुहोस्"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"डिभाइस कनेक्ट गरियो"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"कनेक्ट गर्न सकिएन"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"सम्पन्न भयो"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"सेभ गर्नुहोस्"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"कनेक्ट गर्नुहोस्"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"सेटअप गर्नुहोस्"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"सेटिङ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-nl/strings.xml b/nearby/halfsheet/res/values-nl/strings.xml
deleted file mode 100644
index 4eb7624..0000000
--- a/nearby/halfsheet/res/values-nl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Instellen starten…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Apparaat instellen"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Apparaat verbonden"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Kan geen verbinding maken"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Klaar"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Opslaan"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Verbinden"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Instellen"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Instellingen"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-or/strings.xml b/nearby/halfsheet/res/values-or/strings.xml
deleted file mode 100644
index c5e8cfc..0000000
--- a/nearby/halfsheet/res/values-or/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ସେଟଅପ ଆରମ୍ଭ କରାଯାଉଛି…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ଡିଭାଇସ ସେଟ ଅପ କରନ୍ତୁ"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ଡିଭାଇସ ସଂଯୁକ୍ତ ହୋଇଛି"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ହୋଇଗଲା"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ସେଭ କରନ୍ତୁ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ସଂଯୋଗ କରନ୍ତୁ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ସେଟ ଅପ କରନ୍ତୁ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ସେଟିଂସ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-pa/strings.xml b/nearby/halfsheet/res/values-pa/strings.xml
deleted file mode 100644
index f0523a3..0000000
--- a/nearby/halfsheet/res/values-pa/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"ਸੈੱਟਅੱਪ ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ਡੀਵਾਈਸ ਸੈੱਟਅੱਪ ਕਰੋ"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ਹੋ ਗਿਆ"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"ਰੱਖਿਅਤ ਕਰੋ"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"ਕਨੈਕਟ ਕਰੋ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ਸੈੱਟਅੱਪ ਕਰੋ"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ਸੈਟਿੰਗਾਂ"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-pl/strings.xml b/nearby/halfsheet/res/values-pl/strings.xml
deleted file mode 100644
index 5abf5fd..0000000
--- a/nearby/halfsheet/res/values-pl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Rozpoczynam konfigurowanie…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Skonfiguruj urządzenie"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Urządzenie połączone"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nie udało się połączyć"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gotowe"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Zapisz"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Połącz"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Skonfiguruj"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ustawienia"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-pt-rBR/strings.xml b/nearby/halfsheet/res/values-pt-rBR/strings.xml
deleted file mode 100644
index b021b39..0000000
--- a/nearby/halfsheet/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando a configuração…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Erro ao conectar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Concluído"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salvar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Configurações"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-pt-rPT/strings.xml b/nearby/halfsheet/res/values-pt-rPT/strings.xml
deleted file mode 100644
index 3285c73..0000000
--- a/nearby/halfsheet/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"A iniciar a configuração…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configure o dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo ligado"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Não foi possível ligar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Concluir"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Guardar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Ligar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Definições"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-pt/strings.xml b/nearby/halfsheet/res/values-pt/strings.xml
deleted file mode 100644
index b021b39..0000000
--- a/nearby/halfsheet/res/values-pt/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iniciando a configuração…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurar dispositivo"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispositivo conectado"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Erro ao conectar"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Concluído"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salvar"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectar"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurar"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Configurações"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ro/strings.xml b/nearby/halfsheet/res/values-ro/strings.xml
deleted file mode 100644
index 189f698..0000000
--- a/nearby/halfsheet/res/values-ro/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Începe configurarea…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurează dispozitivul"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispozitivul s-a conectat"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nu s-a putut conecta"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Gata"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Salvează"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Conectează"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Configurează"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Setări"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ru/strings.xml b/nearby/halfsheet/res/values-ru/strings.xml
deleted file mode 100644
index ee869df..0000000
--- a/nearby/halfsheet/res/values-ru/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Начинаем настройку…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Настройка устройства"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Устройство подключено"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ошибка подключения"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Сохранить"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Подключить"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Настроить"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Открыть настройки"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-si/strings.xml b/nearby/halfsheet/res/values-si/strings.xml
deleted file mode 100644
index f4274c2..0000000
--- a/nearby/halfsheet/res/values-si/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"පිහිටුවීම ආරම්භ කරමින්…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"උපාංගය පිහිටුවන්න"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"උපාංගය සම්බන්ධිතයි"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"සම්බන්ධ කළ නොහැකි විය"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"නිමයි"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"සුරකින්න"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"සම්බන්ධ කරන්න"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"පිහිටුවන්න"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"සැකසීම්"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sk/strings.xml b/nearby/halfsheet/res/values-sk/strings.xml
deleted file mode 100644
index 46c45af..0000000
--- a/nearby/halfsheet/res/values-sk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Spúšťa sa nastavenie…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavte zariadenie"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Zariadenie bolo pripojené"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nepodarilo sa pripojiť"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Hotovo"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Uložiť"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Pripojiť"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Nastaviť"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Nastavenia"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sl/strings.xml b/nearby/halfsheet/res/values-sl/strings.xml
deleted file mode 100644
index e4f3c91..0000000
--- a/nearby/halfsheet/res/values-sl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Začetek nastavitve …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Nastavitev naprave"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Naprava je povezana"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Povezava ni mogoča"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Končano"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Shrani"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Poveži"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Nastavi"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Nastavitve"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sq/strings.xml b/nearby/halfsheet/res/values-sq/strings.xml
deleted file mode 100644
index 9265d1f..0000000
--- a/nearby/halfsheet/res/values-sq/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Po nis konfigurimin…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfiguro pajisjen"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Pajisja u lidh"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Nuk mund të lidhej"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"U krye"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Ruaj"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Lidh"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Konfiguro"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Cilësimet"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sr/strings.xml b/nearby/halfsheet/res/values-sr/strings.xml
deleted file mode 100644
index 094be03..0000000
--- a/nearby/halfsheet/res/values-sr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Подешавање се покреће…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Подесите уређај"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Уређај је повезан"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Повезивање није успело"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Сачувај"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Повежи"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Подеси"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Подешавања"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sv/strings.xml b/nearby/halfsheet/res/values-sv/strings.xml
deleted file mode 100644
index 297b7bc..0000000
--- a/nearby/halfsheet/res/values-sv/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Konfigureringen startas …"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Konfigurera enheten"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Enheten är ansluten"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Det gick inte att ansluta"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Klar"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Spara"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Anslut"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Konfigurera"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Inställningar"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-sw/strings.xml b/nearby/halfsheet/res/values-sw/strings.xml
deleted file mode 100644
index bf0bfeb..0000000
--- a/nearby/halfsheet/res/values-sw/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Inaanza Kuweka Mipangilio…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Weka mipangilio ya kifaa"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Kifaa kimeunganishwa"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Imeshindwa kuunganisha"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Imemaliza"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Hifadhi"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Unganisha"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Weka mipangilio"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Mipangilio"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ta/strings.xml b/nearby/halfsheet/res/values-ta/strings.xml
deleted file mode 100644
index dfd67a6..0000000
--- a/nearby/halfsheet/res/values-ta/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"அமைவைத் தொடங்குகிறது…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"சாதனத்தை அமையுங்கள்"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"சாதனம் இணைக்கப்பட்டது"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"இணைக்க முடியவில்லை"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"முடிந்தது"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"சேமி"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"இணை"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"அமை"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"அமைப்புகள்"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-te/strings.xml b/nearby/halfsheet/res/values-te/strings.xml
deleted file mode 100644
index 87be145..0000000
--- a/nearby/halfsheet/res/values-te/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"సెటప్ ప్రారంభమవుతోంది…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"పరికరాన్ని సెటప్ చేయండి"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"పరికరం కనెక్ట్ చేయబడింది"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"కనెక్ట్ చేయడం సాధ్యపడలేదు"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"పూర్తయింది"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"సేవ్ చేయండి"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"కనెక్ట్ చేయండి"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"సెటప్ చేయండి"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"సెట్టింగ్‌లు"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-th/strings.xml b/nearby/halfsheet/res/values-th/strings.xml
deleted file mode 100644
index bc4296b..0000000
--- a/nearby/halfsheet/res/values-th/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"กำลังเริ่มการตั้งค่า…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"ตั้งค่าอุปกรณ์"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"เชื่อมต่ออุปกรณ์แล้ว"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"เชื่อมต่อไม่ได้"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"เสร็จสิ้น"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"บันทึก"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"เชื่อมต่อ"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"ตั้งค่า"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"การตั้งค่า"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-tl/strings.xml b/nearby/halfsheet/res/values-tl/strings.xml
deleted file mode 100644
index a6de0e8..0000000
--- a/nearby/halfsheet/res/values-tl/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Sinisimulan ang Pag-set Up…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"I-set up ang device"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Naikonekta na ang device"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Hindi makakonekta"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Tapos na"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"I-save"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Kumonekta"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"I-set up"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Mga Setting"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-tr/strings.xml b/nearby/halfsheet/res/values-tr/strings.xml
deleted file mode 100644
index cd5a6ea..0000000
--- a/nearby/halfsheet/res/values-tr/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Kurulum Başlatılıyor…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Cihazı kur"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Cihaz bağlandı"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Bağlanamadı"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Bitti"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Kaydet"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Bağlan"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Kur"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Ayarlar"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-uk/strings.xml b/nearby/halfsheet/res/values-uk/strings.xml
deleted file mode 100644
index 242ca07..0000000
--- a/nearby/halfsheet/res/values-uk/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Запуск налаштування…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Налаштуйте пристрій"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Пристрій підключено"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Не вдалося підключити"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Готово"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Зберегти"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Підключити"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Налаштувати"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Налаштування"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-ur/strings.xml b/nearby/halfsheet/res/values-ur/strings.xml
deleted file mode 100644
index 4a4a59c..0000000
--- a/nearby/halfsheet/res/values-ur/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"سیٹ اپ شروع ہو رہا ہے…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"آلہ سیٹ اپ کریں"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"آلہ منسلک ہے"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"منسلک نہیں ہو سکا"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"ہو گیا"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"محفوظ کریں"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"منسلک کریں"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"سیٹ اپ کریں"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"ترتیبات"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-uz/strings.xml b/nearby/halfsheet/res/values-uz/strings.xml
deleted file mode 100644
index 420512d..0000000
--- a/nearby/halfsheet/res/values-uz/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Sozlash boshlandi…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Qurilmani sozlash"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Qurilma ulandi"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ulanmadi"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Tayyor"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Saqlash"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Ulanish"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Sozlash"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Sozlamalar"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-vi/strings.xml b/nearby/halfsheet/res/values-vi/strings.xml
deleted file mode 100644
index 9c1e052..0000000
--- a/nearby/halfsheet/res/values-vi/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Đang bắt đầu thiết lập…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Thiết lập thiết bị"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Đã kết nối thiết bị"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Không kết nối được"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Xong"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Lưu"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Kết nối"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Thiết lập"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Cài đặt"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-zh-rCN/strings.xml b/nearby/halfsheet/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 482b5c4..0000000
--- a/nearby/halfsheet/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"正在启动设置…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"设置设备"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"设备已连接"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"无法连接"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"保存"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"连接"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"设置"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"设置"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-zh-rHK/strings.xml b/nearby/halfsheet/res/values-zh-rHK/strings.xml
deleted file mode 100644
index 3ca73e6..0000000
--- a/nearby/halfsheet/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"開始設定…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"設定裝置"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"已連接裝置"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"無法連接"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"儲存"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"連接"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"設定"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"設定"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-zh-rTW/strings.xml b/nearby/halfsheet/res/values-zh-rTW/strings.xml
deleted file mode 100644
index b4e680d..0000000
--- a/nearby/halfsheet/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"正在啟動設定程序…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"設定裝置"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"裝置已連線"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"無法連線"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"完成"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"儲存"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"連線"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"設定"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"設定"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values-zu/strings.xml b/nearby/halfsheet/res/values-zu/strings.xml
deleted file mode 100644
index 33fb405..0000000
--- a/nearby/halfsheet/res/values-zu/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  ~ Copyright (C) 2021 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Iqalisa Ukusetha…"</string>
-    <string name="fast_pair_title_setup" msgid="2894360355540593246">"Setha idivayisi"</string>
-    <string name="fast_pair_device_ready" msgid="2903490346082833101">"Idivayisi ixhunyiwe"</string>
-    <string name="fast_pair_title_fail" msgid="5677174346601290232">"Ayikwazanga ukuxhuma"</string>
-    <string name="paring_action_done" msgid="6888875159174470731">"Kwenziwe"</string>
-    <string name="paring_action_save" msgid="6259357442067880136">"Londoloza"</string>
-    <string name="paring_action_connect" msgid="4801102939608129181">"Xhuma"</string>
-    <string name="paring_action_launch" msgid="8940808384126591230">"Setha"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Amasethingi"</string>
-</resources>
diff --git a/nearby/halfsheet/res/values/colors.xml b/nearby/halfsheet/res/values/colors.xml
deleted file mode 100644
index b066665..0000000
--- a/nearby/halfsheet/res/values/colors.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-  <!-- Use original background color -->
-  <color name="fast_pair_notification_background">#00000000</color>
-  <!-- Ignores NewApi as below system colors are available since API 31, and HalfSheet is always
-       running on T+ even though it has min_sdk 30 to match its containing APEX -->
-  <color name="fast_pair_half_sheet_button_color" tools:ignore="NewApi">@android:color/system_accent1_100</color>
-  <color name="fast_pair_half_sheet_button_text" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
-  <color name="fast_pair_half_sheet_button_accent_text" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
-  <color name="fast_pair_progress_color" tools:ignore="NewApi">@android:color/system_accent1_600</color>
-  <color name="fast_pair_half_sheet_subtitle_color" tools:ignore="NewApi">@android:color/system_neutral2_700</color>
-  <color name="fast_pair_half_sheet_text_color" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
-
-  <!-- Nearby Discoverer -->
-  <color name="discovery_activity_accent">#4285F4</color>
-
-  <!-- Fast Pair -->
-  <color name="fast_pair_primary_text">#DE000000</color>
-  <color name="fast_pair_notification_image_outline">#24000000</color>
-  <color name="fast_pair_battery_level_low">#D93025</color>
-  <color name="fast_pair_battery_level_normal">#80868B</color>
-  <color name="fast_pair_half_sheet_background">#FFFFFF</color>
-  <color name="fast_pair_half_sheet_color_accent">#1A73E8</color>
-  <color name="fast_pair_fail_progress_color">#F44336</color>
-  <color name="fast_pair_progress_back_ground">#24000000</color>
-</resources>
diff --git a/nearby/halfsheet/res/values/dimens.xml b/nearby/halfsheet/res/values/dimens.xml
deleted file mode 100644
index f843042..0000000
--- a/nearby/halfsheet/res/values/dimens.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-  <!-- Fast Pair notification values -->
-  <dimen name="fast_pair_halfsheet_mid_image_size">160dp</dimen>
-  <dimen name="fast_pair_notification_text_size">14sp</dimen>
-  <dimen name="fast_pair_notification_text_size_small">11sp</dimen>
-  <dimen name="fast_pair_battery_notification_empty_view_height">4dp</dimen>
-  <dimen name="fast_pair_battery_notification_margin_top">8dp</dimen>
-  <dimen name="fast_pair_battery_notification_margin_bottom">8dp</dimen>
-  <dimen name="fast_pair_battery_notification_content_height">40dp</dimen>
-  <dimen name="fast_pair_battery_notification_content_height_v2">64dp</dimen>
-  <dimen name="fast_pair_battery_notification_image_size">32dp</dimen>
-  <dimen name="fast_pair_battery_notification_image_padding">3dp</dimen>
-  <dimen name="fast_pair_half_sheet_min_height">350dp</dimen>
-  <dimen name="fast_pair_half_sheet_image_size">215dp</dimen>
-  <dimen name="fast_pair_half_sheet_land_image_size">136dp</dimen>
-  <dimen name="fast_pair_connect_button_height">36dp</dimen>
-  <dimen name="accessibility_required_min_touch_target_size">48dp</dimen>
-  <dimen name="fast_pair_half_sheet_battery_case_image_size">152dp</dimen>
-  <dimen name="fast_pair_half_sheet_battery_bud_image_size">100dp</dimen>
-  <integer name="half_sheet_battery_case_width_dp">156</integer>
-  <integer name="half_sheet_battery_case_height_dp">182</integer>
-
-  <!-- Maximum height for SliceView, override on slices/view/src/main/res/values/dimens.xml -->
-  <dimen name="abc_slice_large_height">360dp</dimen>
-
-  <dimen name="action_dialog_content_margin_left">16dp</dimen>
-  <dimen name="action_dialog_content_margin_top">70dp</dimen>
-  <dimen name="action_button_focused_elevation">4dp</dimen>
-  <!-- Subsequent Notification -->
-  <dimen name="fast_pair_notification_padding">4dp</dimen>
-  <dimen name="fast_pair_notification_large_image_size">32dp</dimen>
-  <dimen name="fast_pair_notification_small_image_size">32dp</dimen>
-  <!-- Battery Notification -->
-  <dimen name="fast_pair_battery_notification_main_view_padding">0dp</dimen>
-  <dimen name="fast_pair_battery_notification_title_image_margin_start">0dp</dimen>
-  <dimen name="fast_pair_battery_notification_title_text_margin_start">0dp</dimen>
-  <dimen name="fast_pair_battery_notification_title_text_margin_start_v2">0dp</dimen>
-  <dimen name="fast_pair_battery_notification_image_margin_start">0dp</dimen>
-
-  <dimen name="fast_pair_half_sheet_bottom_button_height">48dp</dimen>
-</resources>
diff --git a/nearby/halfsheet/res/values/ints.xml b/nearby/halfsheet/res/values/ints.xml
deleted file mode 100644
index 07bf9d2..0000000
--- a/nearby/halfsheet/res/values/ints.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-  <integer name="half_sheet_slide_in_duration">250</integer>
-  <integer name="half_sheet_fade_out_duration">250</integer>
-</resources>
diff --git a/nearby/halfsheet/res/values/overlayable.xml b/nearby/halfsheet/res/values/overlayable.xml
deleted file mode 100644
index fffa2e3..0000000
--- a/nearby/halfsheet/res/values/overlayable.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <overlayable name="NearbyHalfSheetResourcesConfig">
-        <policy type="product|system|vendor">
-            <item type="color" name="fast_pair_half_sheet_background"/>
-            <item type="color" name="fast_pair_half_sheet_button_color"/>
-        </policy>
-    </overlayable>
-</resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
deleted file mode 100644
index 01a82e4..0000000
--- a/nearby/halfsheet/res/values/strings.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-
-<resources>
-
-    <!--
-      ============================================================
-      PAIRING FRAGMENT
-      ============================================================
-    -->
-
-    <!--
-      A button shown to remind user setup is in progress. [CHAR LIMIT=30]
-    -->
-    <string name="fast_pair_setup_in_progress">Starting Setup&#x2026;</string>
-    <!--
-      Title text shown to remind user to setup a device through companion app. [CHAR LIMIT=40]
-    -->
-    <string name="fast_pair_title_setup">Set up device</string>
-    <!--
-      Title after we successfully pair with the audio device
-      [CHAR LIMIT=30]
-    -->
-    <string name="fast_pair_device_ready">Device connected</string>
-    <!-- Title text shown when peripheral device fail to connect to phone. [CHAR_LIMIT=30] -->
-    <string name="fast_pair_title_fail">Couldn\'t connect</string>
-
-    <!--
-      ============================================================
-      MISCELLANEOUS
-      ============================================================
-    -->
-
-    <!--
-      A button shown after paring process to dismiss the current activity.
-      [CHAR LIMIT=30]
-    -->
-    <string name="paring_action_done">Done</string>
-    <!--
-      A button shown for retroactive paring.
-      [CHAR LIMIT=30]
-     -->
-    <string name="paring_action_save">Save</string>
-    <!--
-      A button to start connecting process.
-      [CHAR LIMIT=30]
-     -->
-    <string name="paring_action_connect">Connect</string>
-    <!--
-      A button to launch a companion app.
-      [CHAR LIMIT=30]
-    -->
-    <string name="paring_action_launch">Set up</string>
-    <!--
-      A button to launch a bluetooth Settings page.
-      [CHAR LIMIT=20]
-    -->
-    <string name="paring_action_settings">Settings</string>
-</resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/values/styles.xml b/nearby/halfsheet/res/values/styles.xml
deleted file mode 100644
index 917bb63..0000000
--- a/nearby/halfsheet/res/values/styles.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-  <style name="HalfSheetStyle" parent="Theme.Material3.DayNight.NoActionBar">
-    <item name="android:windowFrame">@null</item>
-    <item name="android:windowBackground">@android:color/transparent</item>
-    <item name="android:windowEnterAnimation">@anim/fast_pair_half_sheet_slide_in</item>
-    <item name="android:windowExitAnimation">@anim/fast_pair_half_sheet_slide_out</item>
-    <item name="android:windowIsTranslucent">true</item>
-    <item name="android:windowContentOverlay">@null</item>
-    <item name="android:windowNoTitle">true</item>
-    <item name="android:backgroundDimEnabled">true</item>
-    <item name="android:statusBarColor">@android:color/transparent</item>
-    <item name="android:fitsSystemWindows">true</item>
-    <item name="android:windowTranslucentNavigation">true</item>
-  </style>
-
-  <style name="HalfSheetButton" parent="@style/Widget.Material3.Button.TonalButton">
-    <item name="android:textColor">@color/fast_pair_half_sheet_button_accent_text</item>
-    <item name="android:backgroundTint">@color/fast_pair_half_sheet_button_color</item>
-    <item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
-    <item name="android:fontFamily">google-sans-medium</item>
-    <item name="android:textAlignment">center</item>
-    <item name="android:textAllCaps">false</item>
-  </style>
-
-  <style name="HalfSheetButtonBorderless" parent="@style/Widget.Material3.Button.OutlinedButton">
-    <item name="android:textColor">@color/fast_pair_half_sheet_button_text</item>
-    <item name="android:strokeColor">@color/fast_pair_half_sheet_button_color</item>
-    <item name="android:textAllCaps">false</item>
-    <item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
-    <item name="android:fontFamily">google-sans-medium</item>
-    <item name="android:layout_width">wrap_content</item>
-    <item name="android:layout_height">wrap_content</item>
-    <item name="android:textAlignment">center</item>
-    <item name="android:minHeight">@dimen/accessibility_required_min_touch_target_size</item>
-  </style>
-
-</resources>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java
deleted file mode 100644
index bec0c0a..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.nearby.halfsheet;
-
-import android.content.Context;
-import android.nearby.FastPairDevice;
-import android.nearby.FastPairStatusCallback;
-import android.nearby.PairStatusMetadata;
-import android.nearby.aidl.IFastPairStatusCallback;
-import android.nearby.aidl.IFastPairUiService;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.UiThread;
-
-import java.lang.ref.WeakReference;
-
-/**
- *  A utility class for connecting to the {@link IFastPairUiService} and receive callbacks.
- *
- * @hide
- */
-@UiThread
-public class FastPairUiServiceClient {
-
-    private static final String TAG = "FastPairHalfSheet";
-
-    private final IBinder mBinder;
-    private final WeakReference<Context> mWeakContext;
-    IFastPairUiService mFastPairUiService;
-    PairStatusCallbackIBinder mPairStatusCallbackIBinder;
-
-    /**
-     * The Ibinder instance should be from
-     * {@link com.android.server.nearby.fastpair.halfsheet.FastPairUiServiceImpl} so that the client can
-     * talk with the service.
-     */
-    public FastPairUiServiceClient(Context context, IBinder binder) {
-        mBinder = binder;
-        mFastPairUiService = IFastPairUiService.Stub.asInterface(mBinder);
-        mWeakContext = new WeakReference<>(context);
-    }
-
-    /**
-     * Registers a callback at service to get UI updates.
-     */
-    public void registerHalfSheetStateCallBack(FastPairStatusCallback fastPairStatusCallback) {
-        if (mPairStatusCallbackIBinder != null) {
-            return;
-        }
-        mPairStatusCallbackIBinder = new PairStatusCallbackIBinder(fastPairStatusCallback);
-        try {
-            mFastPairUiService.registerCallback(mPairStatusCallbackIBinder);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to register fastPairStatusCallback", e);
-        }
-    }
-
-    /**
-     * Pairs the device at service.
-     */
-    public void connect(FastPairDevice fastPairDevice) {
-        try {
-            mFastPairUiService.connect(fastPairDevice);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
-        }
-    }
-
-    /**
-     * Cancels Fast Pair connection and dismisses half sheet.
-     */
-    public void cancel(FastPairDevice fastPairDevice) {
-        try {
-            mFastPairUiService.cancel(fastPairDevice);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
-        }
-    }
-
-    private class PairStatusCallbackIBinder extends IFastPairStatusCallback.Stub {
-        private final FastPairStatusCallback mStatusCallback;
-
-        private PairStatusCallbackIBinder(FastPairStatusCallback fastPairStatusCallback) {
-            mStatusCallback = fastPairStatusCallback;
-        }
-
-        @BinderThread
-        @Override
-        public synchronized void onPairUpdate(FastPairDevice fastPairDevice,
-                PairStatusMetadata pairStatusMetadata) {
-            Context context = mWeakContext.get();
-            if (context != null) {
-                Handler handler = new Handler(context.getMainLooper());
-                handler.post(() ->
-                        mStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata));
-            }
-        }
-    }
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
deleted file mode 100644
index 07e5776..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2021 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.nearby.halfsheet;
-
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-
-import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
-import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
-import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
-import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.nearby.halfsheet.fragment.DevicePairingFragment;
-import com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment;
-import com.android.nearby.halfsheet.utils.BroadcastUtils;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.util.Locale;
-
-import service.proto.Cache;
-
-/**
- * A class show Fast Pair related information in Half sheet format.
- */
-public class HalfSheetActivity extends FragmentActivity {
-
-    public static final String TAG = "FastPairHalfSheet";
-
-    public static final String EXTRA_HALF_SHEET_CONTENT =
-            "com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
-    public static final String EXTRA_TITLE =
-            "com.android.nearby.halfsheet.HALF_SHEET_TITLE";
-    public static final String EXTRA_DESCRIPTION =
-            "com.android.nearby.halfsheet.HALF_SHEET_DESCRIPTION";
-    public static final String EXTRA_HALF_SHEET_ID =
-            "com.android.nearby.halfsheet.HALF_SHEET_ID";
-    public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE =
-            "com.android.nearby.halfsheet.HALF_SHEET_IS_RETROACTIVE";
-    public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR =
-            "com.android.nearby.halfsheet.HALF_SHEET_IS_SUBSEQUENT_PAIR";
-    public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
-    public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
-            "com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
-    // Intent extra contains the user gmail name eg. testaccount@gmail.com.
-    public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
-            "com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
-    public static final String EXTRA_HALF_SHEET_FOREGROUND =
-            "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
-    public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
-    @Nullable
-    private HalfSheetModuleFragment mHalfSheetModuleFragment;
-    @Nullable
-    private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        byte[] infoArray = getIntent().getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
-        String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
-        if (infoArray == null || fragmentType == null) {
-            Log.d(
-                    "HalfSheetActivity",
-                    "exit flag off or do not have enough half sheet information.");
-            finish();
-            return;
-        }
-
-        switch (fragmentType) {
-            case DEVICE_PAIRING_FRAGMENT_TYPE:
-                mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(),
-                        savedInstanceState);
-                if (mHalfSheetModuleFragment == null) {
-                    Log.d(TAG, "device pairing fragment has error.");
-                    finish();
-                    return;
-                }
-                break;
-            case APP_LAUNCH_FRAGMENT_TYPE:
-                // currentFragment = AppLaunchFragment.newInstance(getIntent());
-                if (mHalfSheetModuleFragment == null) {
-                    Log.v(TAG, "app launch fragment has error.");
-                    finish();
-                    return;
-                }
-                break;
-            default:
-                Log.w(TAG, "there is no valid type for half sheet");
-                finish();
-                return;
-        }
-        if (mHalfSheetModuleFragment != null) {
-            getSupportFragmentManager()
-                    .beginTransaction()
-                    .replace(R.id.fragment_container, mHalfSheetModuleFragment)
-                    .commit();
-        }
-        setContentView(R.layout.fast_pair_half_sheet);
-
-        // If the user taps on the background, then close the activity.
-        // Unless they tap on the card itself, then ignore the tap.
-        findViewById(R.id.background).setOnClickListener(v -> onCancelClicked());
-        findViewById(R.id.card)
-                .setOnClickListener(
-                        v -> Log.v(TAG, "card view is clicked noop"));
-        try {
-            mScanFastPairStoreItem =
-                    Cache.ScanFastPairStoreItem.parseFrom(infoArray);
-        } catch (InvalidProtocolBufferException e) {
-            Log.w(
-                    TAG, "error happens when pass info to half sheet");
-        }
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-    }
-
-    @Override
-    protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
-        super.onSaveInstanceState(savedInstanceState);
-        if (mHalfSheetModuleFragment != null) {
-            mHalfSheetModuleFragment.onSaveInstanceState(savedInstanceState);
-        }
-    }
-
-    @Override
-    public void onBackPressed() {
-        super.onBackPressed();
-        sendHalfSheetCancelBroadcast();
-    }
-
-    @Override
-    protected void onUserLeaveHint() {
-        super.onUserLeaveHint();
-        sendHalfSheetCancelBroadcast();
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
-        if (fragmentType == null) {
-            return;
-        }
-        if (fragmentType.equals(DEVICE_PAIRING_FRAGMENT_TYPE)
-                && intent.getExtras() != null
-                && intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO) != null) {
-            try {
-                Cache.ScanFastPairStoreItem testScanFastPairStoreItem =
-                        Cache.ScanFastPairStoreItem.parseFrom(
-                                intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO));
-                if (mScanFastPairStoreItem != null
-                        && !testScanFastPairStoreItem.getAddress().equals(
-                        mScanFastPairStoreItem.getAddress())
-                        && testScanFastPairStoreItem.getModelId().equals(
-                        mScanFastPairStoreItem.getModelId())) {
-                    Log.d(TAG, "possible factory reset happens");
-                    halfSheetStateChange();
-                }
-            } catch (InvalidProtocolBufferException | NullPointerException e) {
-                Log.w(TAG, "error happens when pass info to half sheet");
-            }
-        }
-    }
-
-    /** This function should be called when user click empty area and cancel button. */
-    public void onCancelClicked() {
-        Log.d(TAG, "Cancels the half sheet and paring.");
-        sendHalfSheetCancelBroadcast();
-        finish();
-    }
-
-    /** Changes the half sheet foreground state to false. */
-    public void halfSheetStateChange() {
-        BroadcastUtils.sendBroadcast(
-                this,
-                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
-                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
-        finish();
-    }
-
-    private void sendHalfSheetCancelBroadcast() {
-        BroadcastUtils.sendBroadcast(
-                this,
-                new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
-                        .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
-        if (mScanFastPairStoreItem != null) {
-            BroadcastUtils.sendBroadcast(
-                    this,
-                    new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL)
-                            .putExtra(EXTRA_MODEL_ID,
-                                    mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))
-                            .putExtra(EXTRA_HALF_SHEET_TYPE,
-                                    getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE))
-                            .putExtra(
-                                    EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
-                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
-                                            false))
-                            .putExtra(
-                                    EXTRA_HALF_SHEET_IS_RETROACTIVE,
-                                    getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
-                                            false))
-                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()),
-                    ACCESS_FINE_LOCATION);
-        }
-    }
-
-    @Override
-    public void setTitle(CharSequence title) {
-        super.setTitle(title);
-        TextView toolbarTitle = findViewById(R.id.toolbar_title);
-        toolbarTitle.setText(title);
-    }
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
deleted file mode 100644
index 320965b..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2021 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.nearby.halfsheet.fragment;
-
-import static android.text.TextUtils.isEmpty;
-
-import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.nearby.FastPairDevice;
-import android.nearby.FastPairStatusCallback;
-import android.nearby.NearbyDevice;
-import android.nearby.PairStatusMetadata;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.nearby.halfsheet.FastPairUiServiceClient;
-import com.android.nearby.halfsheet.HalfSheetActivity;
-import com.android.nearby.halfsheet.R;
-import com.android.nearby.halfsheet.utils.FastPairUtils;
-import com.android.nearby.halfsheet.utils.IconUtils;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.util.Objects;
-
-import service.proto.Cache.ScanFastPairStoreItem;
-
-/**
- * Modularize half sheet for fast pair this fragment will show when half sheet does device pairing.
- *
- * <p>This fragment will handle initial pairing subsequent pairing and retroactive pairing.
- */
-@SuppressWarnings("nullness")
-public class DevicePairingFragment extends HalfSheetModuleFragment implements
-        FastPairStatusCallback {
-    private TextView mTitleView;
-    private TextView mSubTitleView;
-    private ImageView mImage;
-
-    private Button mConnectButton;
-    private Button mSetupButton;
-    private Button mCancelButton;
-    // Opens Bluetooth Settings.
-    private Button mSettingsButton;
-    private ImageView mInfoIconButton;
-    private ProgressBar mConnectProgressBar;
-
-    private Bundle mBundle;
-
-    private ScanFastPairStoreItem mScanFastPairStoreItem;
-    private FastPairUiServiceClient mFastPairUiServiceClient;
-
-    private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN;
-    // True when there is a companion app to open.
-    private boolean mIsLaunchable;
-    private boolean mIsConnecting;
-    // Indicates that the setup button is clicked before.
-    private boolean mSetupButtonClicked = false;
-
-    // Holds the new text while we transition between the two.
-    private static final int TAG_PENDING_TEXT = R.id.toolbar_title;
-    public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
-
-    private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
-    private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
-
-    /**
-     * Create certain fragment according to the intent.
-     */
-    @Nullable
-    public static HalfSheetModuleFragment newInstance(
-            Intent intent, @Nullable Bundle saveInstanceStates) {
-        Bundle args = new Bundle();
-        byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
-
-        Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
-        String title = intent.getStringExtra(EXTRA_TITLE);
-        String description = intent.getStringExtra(EXTRA_DESCRIPTION);
-        String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME);
-        String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT);
-        int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0);
-
-        args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray);
-        args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName);
-        args.putString(EXTRA_TITLE, title);
-        args.putString(EXTRA_DESCRIPTION, description);
-        args.putInt(EXTRA_HALF_SHEET_ID, halfSheetId);
-        args.putString(EXTRA_HALF_SHEET_CONTENT, result == null ? "" : result);
-        args.putBundle(EXTRA_BUNDLE, bundle);
-        if (saveInstanceStates != null) {
-            if (saveInstanceStates.containsKey(ARG_FRAGMENT_STATE)) {
-                args.putSerializable(
-                        ARG_FRAGMENT_STATE, saveInstanceStates.getSerializable(ARG_FRAGMENT_STATE));
-            }
-            if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_DEVICE)) {
-                args.putParcelable(
-                        BluetoothDevice.EXTRA_DEVICE,
-                        saveInstanceStates.getParcelable(BluetoothDevice.EXTRA_DEVICE));
-            }
-            if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_PAIRING_KEY)) {
-                args.putInt(
-                        BluetoothDevice.EXTRA_PAIRING_KEY,
-                        saveInstanceStates.getInt(BluetoothDevice.EXTRA_PAIRING_KEY));
-            }
-            if (saveInstanceStates.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
-                args.putBoolean(
-                        ARG_SETUP_BUTTON_CLICKED,
-                        saveInstanceStates.getBoolean(ARG_SETUP_BUTTON_CLICKED));
-            }
-            if (saveInstanceStates.containsKey(ARG_PAIRING_RESULT)) {
-                args.putBoolean(ARG_PAIRING_RESULT,
-                        saveInstanceStates.getBoolean(ARG_PAIRING_RESULT));
-            }
-        }
-        DevicePairingFragment fragment = new DevicePairingFragment();
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        /* attachToRoot= */
-        View rootView = inflater.inflate(
-                R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
-                false);
-        if (getContext() == null) {
-            Log.d(TAG, "can't find the attached activity");
-            return rootView;
-        }
-
-        Bundle args = getArguments();
-        byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
-        mBundle = args.getBundle(EXTRA_BUNDLE);
-        if (mBundle != null) {
-            mFastPairUiServiceClient =
-                    new FastPairUiServiceClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
-            mFastPairUiServiceClient.registerHalfSheetStateCallBack(this);
-        }
-        if (args.containsKey(ARG_FRAGMENT_STATE)) {
-            mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
-        }
-        if (args.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
-            mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED);
-        }
-        if (args.containsKey(ARG_PAIRING_RESULT)) {
-            mPairStatus = args.getInt(ARG_PAIRING_RESULT);
-        }
-
-        // Initiate views.
-        mTitleView = Objects.requireNonNull(getActivity()).findViewById(R.id.toolbar_title);
-        mSubTitleView = rootView.findViewById(R.id.header_subtitle);
-        mImage = rootView.findViewById(R.id.pairing_pic);
-        mConnectProgressBar = rootView.findViewById(R.id.connect_progressbar);
-        mConnectButton = rootView.findViewById(R.id.connect_btn);
-        mCancelButton = rootView.findViewById(R.id.cancel_btn);
-        mSettingsButton = rootView.findViewById(R.id.settings_btn);
-        mSetupButton = rootView.findViewById(R.id.setup_btn);
-        mInfoIconButton = rootView.findViewById(R.id.info_icon);
-        mInfoIconButton.setImageResource(R.drawable.fast_pair_ic_info);
-
-        try {
-            setScanFastPairStoreItem(ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray));
-        } catch (InvalidProtocolBufferException e) {
-            Log.w(TAG,
-                    "DevicePairingFragment: error happens when pass info to half sheet");
-            return rootView;
-        }
-
-        // Config for landscape mode
-        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
-        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            rootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
-            rootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
-            mImage.getLayoutParams().height = displayMetrics.heightPixels / 2;
-            mImage.getLayoutParams().width = displayMetrics.heightPixels / 2;
-            mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2;
-            mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2;
-            //TODO(b/213373051): Add cancel button
-        }
-
-        Bitmap icon = IconUtils.getIcon(mScanFastPairStoreItem.getIconPng().toByteArray(),
-                mScanFastPairStoreItem.getIconPng().size());
-        if (icon != null) {
-            mImage.setImageBitmap(icon);
-        }
-        mConnectButton.setOnClickListener(v -> onConnectClick());
-        mCancelButton.setOnClickListener(v ->
-                ((HalfSheetActivity) getActivity()).onCancelClicked());
-        mSettingsButton.setOnClickListener(v -> onSettingsClicked());
-        mSetupButton.setOnClickListener(v -> onSetupClick());
-
-        return rootView;
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        // Get access to the activity's menu
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        Log.v(TAG, "onStart: invalidate states");
-        invalidateState();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle savedInstanceState) {
-        super.onSaveInstanceState(savedInstanceState);
-
-        savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState);
-        savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked);
-        savedInstanceState.putInt(ARG_PAIRING_RESULT, mPairStatus);
-    }
-
-    private void onSettingsClicked() {
-        startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
-    }
-
-    private void onSetupClick() {
-        String companionApp =
-                FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
-        Intent intent =
-                FastPairUtils.createCompanionAppIntent(
-                        Objects.requireNonNull(getContext()),
-                        companionApp,
-                        mScanFastPairStoreItem.getAddress());
-        mSetupButtonClicked = true;
-        if (mFragmentState == PAIRED_LAUNCHABLE) {
-            if (intent != null) {
-                startActivity(intent);
-            }
-        } else {
-            Log.d(TAG, "onSetupClick: State is " + mFragmentState);
-        }
-    }
-
-    private void onConnectClick() {
-        if (mScanFastPairStoreItem == null) {
-            Log.w(TAG, "No pairing related information in half sheet");
-            return;
-        }
-        if (getFragmentState() == PAIRING) {
-            return;
-        }
-        mIsConnecting = true;
-        invalidateState();
-        mFastPairUiServiceClient.connect(
-                new FastPairDevice.Builder()
-                        .addMedium(NearbyDevice.Medium.BLE)
-                        .setBluetoothAddress(mScanFastPairStoreItem.getAddress())
-                        .setData(FastPairUtils.convertFrom(mScanFastPairStoreItem)
-                                .toByteArray())
-                        .build());
-    }
-
-    // Receives callback from service.
-    @Override
-    public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) {
-        @PairStatusMetadata.Status int status = pairStatusMetadata.getStatus();
-        if (status == PairStatusMetadata.Status.DISMISS && getActivity() != null) {
-            getActivity().finish();
-        }
-        mIsConnecting = false;
-        mPairStatus = status;
-        invalidateState();
-    }
-
-    @Override
-    public void invalidateState() {
-        HalfSheetFragmentState newState = NOT_STARTED;
-        if (mIsConnecting) {
-            newState = PAIRING;
-        } else {
-            switch (mPairStatus) {
-                case PairStatusMetadata.Status.SUCCESS:
-                    newState = mIsLaunchable ? PAIRED_LAUNCHABLE : PAIRED_UNLAUNCHABLE;
-                    break;
-                case PairStatusMetadata.Status.FAIL:
-                    newState = FAILED;
-                    break;
-                default:
-                    if (mScanFastPairStoreItem != null) {
-                        newState = FOUND_DEVICE;
-                    }
-            }
-        }
-        if (newState == mFragmentState) {
-            return;
-        }
-        setState(newState);
-    }
-
-    @Override
-    public void setState(HalfSheetFragmentState state) {
-        super.setState(state);
-        invalidateTitles();
-        invalidateButtons();
-    }
-
-    private void setScanFastPairStoreItem(ScanFastPairStoreItem item) {
-        mScanFastPairStoreItem = item;
-        invalidateLaunchable();
-    }
-
-    private void invalidateLaunchable() {
-        String companionApp =
-                FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
-        if (isEmpty(companionApp)) {
-            mIsLaunchable = false;
-            return;
-        }
-        mIsLaunchable =
-                FastPairUtils.isLaunchable(Objects.requireNonNull(getContext()), companionApp);
-    }
-
-    private void invalidateButtons() {
-        mConnectProgressBar.setVisibility(View.INVISIBLE);
-        mConnectButton.setVisibility(View.INVISIBLE);
-        mCancelButton.setVisibility(View.INVISIBLE);
-        mSetupButton.setVisibility(View.INVISIBLE);
-        mSettingsButton.setVisibility(View.INVISIBLE);
-        mInfoIconButton.setVisibility(View.INVISIBLE);
-
-        switch (mFragmentState) {
-            case FOUND_DEVICE:
-                mInfoIconButton.setVisibility(View.VISIBLE);
-                mConnectButton.setVisibility(View.VISIBLE);
-                break;
-            case PAIRING:
-                mConnectProgressBar.setVisibility(View.VISIBLE);
-                mCancelButton.setVisibility(View.VISIBLE);
-                setBackgroundClickable(false);
-                break;
-            case PAIRED_LAUNCHABLE:
-                mCancelButton.setVisibility(View.VISIBLE);
-                mSetupButton.setVisibility(View.VISIBLE);
-                setBackgroundClickable(true);
-                break;
-            case FAILED:
-                mSettingsButton.setVisibility(View.VISIBLE);
-                setBackgroundClickable(true);
-                break;
-            case NOT_STARTED:
-            case PAIRED_UNLAUNCHABLE:
-            default:
-                mCancelButton.setVisibility(View.VISIBLE);
-                setBackgroundClickable(true);
-        }
-    }
-
-    private void setBackgroundClickable(boolean isClickable) {
-        HalfSheetActivity activity = (HalfSheetActivity) getActivity();
-        if (activity == null) {
-            Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
-                    + " because cannot get HalfSheetActivity.");
-            return;
-        }
-        View background = activity.findViewById(R.id.background);
-        if (background == null) {
-            Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
-                    + " cannot find background at HalfSheetActivity.");
-            return;
-        }
-        Log.d(TAG, "setBackgroundClickable to " + isClickable);
-        background.setClickable(isClickable);
-    }
-
-    private void invalidateTitles() {
-        String newTitle = getTitle();
-        invalidateTextView(mTitleView, newTitle);
-        String newSubTitle = getSubTitle();
-        invalidateTextView(mSubTitleView, newSubTitle);
-    }
-
-    private void invalidateTextView(TextView textView, String newText) {
-        CharSequence oldText =
-                textView.getTag(TAG_PENDING_TEXT) != null
-                        ? (CharSequence) textView.getTag(TAG_PENDING_TEXT)
-                        : textView.getText();
-        if (TextUtils.equals(oldText, newText)) {
-            return;
-        }
-        if (TextUtils.isEmpty(oldText)) {
-            // First time run. Don't animate since there's nothing to animate from.
-            textView.setText(newText);
-        } else {
-            textView.setTag(TAG_PENDING_TEXT, newText);
-            textView
-                    .animate()
-                    .alpha(0f)
-                    .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS)
-                    .withEndAction(
-                            () -> {
-                                textView.setText(newText);
-                                textView
-                                        .animate()
-                                        .alpha(1f)
-                                        .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS);
-                            });
-        }
-    }
-
-    private String getTitle() {
-        switch (mFragmentState) {
-            case PAIRED_LAUNCHABLE:
-                return getString(R.string.fast_pair_title_setup);
-            case FAILED:
-                return getString(R.string.fast_pair_title_fail);
-            case FOUND_DEVICE:
-            case NOT_STARTED:
-            case PAIRED_UNLAUNCHABLE:
-            default:
-                return mScanFastPairStoreItem.getDeviceName();
-        }
-    }
-
-    private String getSubTitle() {
-        switch (mFragmentState) {
-            case PAIRED_LAUNCHABLE:
-                return String.format(
-                        mScanFastPairStoreItem
-                                .getFastPairStrings()
-                                .getPairingFinishedCompanionAppInstalled(),
-                        mScanFastPairStoreItem.getDeviceName());
-            case FAILED:
-                return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription();
-            case PAIRED_UNLAUNCHABLE:
-                getString(R.string.fast_pair_device_ready);
-            // fall through
-            case FOUND_DEVICE:
-            case NOT_STARTED:
-                return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription();
-            default:
-                return "";
-        }
-    }
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
deleted file mode 100644
index f1db4d0..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 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.nearby.halfsheet.fragment;
-
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
-
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-
-/** Base class for all of the half sheet fragment. */
-public abstract class HalfSheetModuleFragment extends Fragment {
-
-    static final int TEXT_ANIMATION_DURATION_MILLISECONDS = 200;
-
-    HalfSheetFragmentState mFragmentState = NOT_STARTED;
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-    }
-
-    /** UI states of the half-sheet fragment. */
-    public enum HalfSheetFragmentState {
-        NOT_STARTED, // Initial status
-        FOUND_DEVICE, // When a device is found found from Nearby scan service
-        PAIRING, // When user taps 'Connect' and Fast Pair stars pairing process
-        PAIRED_LAUNCHABLE, // When pair successfully
-        // and we found a launchable companion app installed
-        PAIRED_UNLAUNCHABLE, // When pair successfully
-        // but we cannot find a companion app to launch it
-        FAILED, // When paring was failed
-        FINISHED // When the activity is about to end finished.
-    }
-
-    /**
-     * Returns the {@link HalfSheetFragmentState} to the parent activity.
-     *
-     * <p>Overrides this method if the fragment's state needs to be preserved in the parent
-     * activity.
-     */
-    public HalfSheetFragmentState getFragmentState() {
-        return mFragmentState;
-    }
-
-    void setState(HalfSheetFragmentState state) {
-        Log.v(TAG, "Settings state from " + mFragmentState + " to " + state);
-        mFragmentState = state;
-    }
-
-    /**
-     * Populate data to UI widgets according to the latest {@link HalfSheetFragmentState}.
-     */
-    abstract void invalidateState();
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
deleted file mode 100644
index 2f1e90a..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 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.nearby.halfsheet.utils;
-
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Broadcast util class
- */
-public class BroadcastUtils {
-
-    /**
-     * Helps send broadcast.
-     */
-    public static void sendBroadcast(Context context, Intent intent) {
-        context.sendBroadcast(intent);
-    }
-
-    /**
-     * Helps send a broadcast with specified receiver permission.
-     */
-    public static void sendBroadcast(Context context, Intent intent, String receiverPermission) {
-        context.sendBroadcast(intent, receiverPermission);
-    }
-
-    private BroadcastUtils() {
-    }
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
deleted file mode 100644
index 00a365c..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2021 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.nearby.halfsheet.utils;
-
-import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
-import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.net.URISyntaxException;
-
-import service.proto.Cache;
-
-/**
- * Util class in half sheet apk
- */
-public class FastPairUtils {
-
-    /** FastPair util method check certain app is install on the device or not. */
-    public static boolean isAppInstalled(Context context, String packageName) {
-        try {
-            context.getPackageManager().getPackageInfo(packageName, 0);
-            return true;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    /** FastPair util method to properly format the action url extra. */
-    @Nullable
-    public static String getCompanionAppFromActionUrl(String actionUrl) {
-        try {
-            Intent intent = Intent.parseUri(actionUrl, Intent.URI_INTENT_SCHEME);
-            if (!intent.getAction().equals(ACTION_FAST_PAIR)) {
-                Log.e("FastPairUtils", "Companion app launch attempted from malformed action url");
-                return null;
-            }
-            return intent.getStringExtra(EXTRA_COMPANION_APP);
-        } catch (URISyntaxException e) {
-            Log.e("FastPairUtils", "FastPair: fail to get companion app info from discovery item");
-            return null;
-        }
-    }
-
-    /**
-     * Converts {@link service.proto.Cache.StoredDiscoveryItem} from
-     * {@link service.proto.Cache.ScanFastPairStoreItem}
-     */
-    public static Cache.StoredDiscoveryItem convertFrom(Cache.ScanFastPairStoreItem item) {
-        return convertFrom(item, /* isSubsequentPair= */ false);
-    }
-
-    /**
-     * Converts a {@link service.proto.Cache.ScanFastPairStoreItem}
-     * to a {@link service.proto.Cache.StoredDiscoveryItem}.
-     *
-     * <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the
-     * legacy Fast Pair code.
-     */
-    public static Cache.StoredDiscoveryItem convertFrom(
-            Cache.ScanFastPairStoreItem item, boolean isSubsequentPair) {
-        return Cache.StoredDiscoveryItem.newBuilder()
-                .setId(item.getModelId())
-                .setFirstObservationTimestampMillis(item.getFirstObservationTimestampMillis())
-                .setLastObservationTimestampMillis(item.getLastObservationTimestampMillis())
-                .setActionUrl(item.getActionUrl())
-                .setActionUrlType(Cache.ResolvedUrlType.APP)
-                .setTitle(
-                        isSubsequentPair
-                                ? item.getFastPairStrings().getTapToPairWithoutAccount()
-                                : item.getDeviceName())
-                .setMacAddress(item.getAddress())
-                .setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED)
-                .setTriggerId(item.getModelId())
-                .setIconPng(item.getIconPng())
-                .setIconFifeUrl(item.getIconFifeUrl())
-                .setDescription(
-                        isSubsequentPair
-                                ? item.getDeviceName()
-                                : item.getFastPairStrings().getTapToPairWithoutAccount())
-                .setAuthenticationPublicKeySecp256R1(item.getAntiSpoofingPublicKey())
-                .setCompanionDetail(item.getCompanionDetail())
-                .setFastPairStrings(item.getFastPairStrings())
-                .setFastPairInformation(
-                        Cache.FastPairInformation.newBuilder()
-                                .setDataOnlyConnection(item.getDataOnlyConnection())
-                                .setTrueWirelessImages(item.getTrueWirelessImages())
-                                .setAssistantSupported(item.getAssistantSupported())
-                                .setCompanyName(item.getCompanyName()))
-                .build();
-    }
-
-    /**
-     * Returns true the application is installed and can be opened on device.
-     */
-    public static boolean isLaunchable(@NonNull Context context, String companionApp) {
-        return isAppInstalled(context, companionApp)
-                && createCompanionAppIntent(context, companionApp, null) != null;
-    }
-
-    /**
-     * Returns an intent to launch given the package name and bluetooth address (if provided).
-     * Returns null if no such an intent can be found.
-     */
-    @Nullable
-    public static Intent createCompanionAppIntent(@NonNull Context context, String packageName,
-            @Nullable String address) {
-        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
-        if (intent == null) {
-            return null;
-        }
-        if (address != null) {
-            BluetoothAdapter adapter = getBluetoothAdapter(context);
-            if (adapter != null) {
-                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
-            }
-        }
-        return intent;
-    }
-
-    @Nullable
-    private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) {
-        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
-        return bluetoothManager == null ? null : bluetoothManager.getAdapter();
-    }
-
-    private FastPairUtils() {}
-}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
deleted file mode 100644
index 218c756..0000000
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.nearby.halfsheet.utils;
-
-import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
-
-/**
- * Utility class for icon size verification.
- */
-public class IconUtils {
-
-    private static final float NOTIFICATION_BACKGROUND_PADDING_PERCENT = 0.125f;
-    private static final float NOTIFICATION_BACKGROUND_ALPHA = 0.7f;
-    private static final int MIN_ICON_SIZE = 16;
-    private static final int DESIRED_ICON_SIZE = 32;
-
-    /**
-     * Verify that the icon is non null and falls in the small bucket. Just because an icon isn't
-     * small doesn't guarantee it is large or exists.
-     */
-    public static boolean isIconSizedSmall(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        return bitmap.getWidth() >= MIN_ICON_SIZE
-                && bitmap.getWidth() < DESIRED_ICON_SIZE
-                && bitmap.getHeight() >= MIN_ICON_SIZE
-                && bitmap.getHeight() < DESIRED_ICON_SIZE;
-    }
-
-    /**
-     * Verify that the icon is non null and falls in the regular / default size bucket. Doesn't
-     * guarantee if not regular then it is small.
-     */
-    static boolean isIconSizedRegular(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        return bitmap.getWidth() >= DESIRED_ICON_SIZE && bitmap.getHeight() >= DESIRED_ICON_SIZE;
-    }
-
-    /**
-     * All icons that are sized correctly (larger than the MIN_ICON_SIZE icon size)
-     * are resize on the server to the DESIRED_ICON_SIZE icon size so that
-     * they appear correct.
-     */
-    public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap);
-    }
-
-    /**
-     * Returns the bitmap from the byte array. Returns null if cannot decode or not in correct size.
-     */
-    @Nullable
-    public static Bitmap getIcon(byte[] imageData, int size) {
-        try {
-            Bitmap icon =
-                    BitmapFactory.decodeByteArray(imageData, /* offset= */ 0, size);
-            if (IconUtils.isIconSizeCorrect(icon)) {
-                // Do not add background for Half Sheet.
-                return IconUtils.addWhiteCircleBackground(icon);
-            }
-        } catch (OutOfMemoryError e) {
-            Log.w(TAG, "getIcon: Failed to decode icon, returning null.", e);
-        }
-        return null;
-    }
-
-    /** Adds a circular, white background to the bitmap. */
-    @Nullable
-    public static Bitmap addWhiteCircleBackground(Bitmap bitmap) {
-        if (bitmap == null) {
-            Log.w(TAG, "addWhiteCircleBackground: Bitmap is null, not adding background.");
-            return null;
-        }
-
-        if (bitmap.getWidth() != bitmap.getHeight()) {
-            Log.w(TAG, "addWhiteCircleBackground: Bitmap dimensions not square. Skipping"
-                    + "adding background.");
-            return bitmap;
-        }
-
-        int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENT);
-        Bitmap bitmapWithBackground =
-                Bitmap.createBitmap(
-                        bitmap.getWidth() + (2 * padding),
-                        bitmap.getHeight() + (2 * padding),
-                        bitmap.getConfig());
-        Canvas canvas = new Canvas(bitmapWithBackground);
-        Paint paint = new Paint();
-        paint.setColor(
-                ColorUtils.setAlphaComponent(
-                        Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA)));
-        paint.setStyle(Paint.Style.FILL);
-        paint.setAntiAlias(true);
-        canvas.drawCircle(
-                bitmapWithBackground.getWidth() / 2,
-                bitmapWithBackground.getHeight() / 2,
-                bitmapWithBackground.getWidth() / 2,
-                paint);
-        canvas.drawBitmap(bitmap, padding, padding, null);
-
-        return bitmapWithBackground;
-    }
-}
-
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 684b133..4630902 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -24,57 +24,6 @@
     ],
 }
 
-filegroup {
-    name: "nearby-service-string-res",
-    srcs: [
-        "java/**/Constant.java",
-        "java/**/UserActionHandlerBase.java",
-        "java/**/UserActionHandler.java",
-        "java/**/FastPairConstants.java",
-    ],
-}
-
-java_library {
-    name: "nearby-service-string",
-    srcs: [":nearby-service-string-res"],
-    libs: ["framework-bluetooth"],
-    sdk_version: "module_current",
-}
-
-// Common lib for nearby end-to-end testing.
-java_library {
-    name: "nearby-common-lib",
-    srcs: [
-        "java/com/android/server/nearby/common/bloomfilter/*.java",
-        "java/com/android/server/nearby/common/bluetooth/*.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java",
-        "java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java",
-        "java/com/android/server/nearby/common/bluetooth/testability/**/*.java",
-        "java/com/android/server/nearby/common/bluetooth/gatt/*.java",
-        "java/com/android/server/nearby/common/bluetooth/util/*.java",
-    ],
-    libs: [
-        "androidx.annotation_annotation",
-        "androidx.core_core",
-        "error_prone_annotations",
-        "framework-bluetooth",
-        "guava",
-    ],
-    sdk_version: "module_current",
-    visibility: [
-        "//packages/modules/Connectivity/nearby/tests/multidevices/clients/test_support/fastpair_provider",
-    ],
-}
-
 // Main lib for nearby services.
 java_library {
     name: "service-nearby-pre-jarjar",
@@ -95,7 +44,6 @@
         "androidx.core_core",
         "guava",
         "libprotobuf-java-lite",
-        "fast-pair-lite-protos",
         "modules-utils-build",
         "modules-utils-handlerexecutor",
         "modules-utils-preconditions",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 5ebf1e5..1220104 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START;
 
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -39,13 +38,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.FastPairManager;
 import com.android.server.nearby.injector.ContextHubManagerAdapter;
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.provider.BroadcastProviderManager;
 import com.android.server.nearby.provider.DiscoveryProviderManager;
-import com.android.server.nearby.provider.FastPairDataProvider;
 import com.android.server.nearby.util.identity.CallerIdentity;
 import com.android.server.nearby.util.permissions.BroadcastPermissions;
 import com.android.server.nearby.util.permissions.DiscoveryPermissions;
@@ -56,7 +52,6 @@
 
     private final Context mContext;
     private Injector mInjector;
-    private final FastPairManager mFastPairManager;
     private final BroadcastReceiver mBluetoothReceiver =
             new BroadcastReceiver() {
                 @Override
@@ -82,8 +77,6 @@
         mInjector = new SystemInjector(context);
         mProviderManager = new DiscoveryProviderManager(context, mInjector);
         mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector);
-        final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
-        mFastPairManager = new FastPairManager(lcw);
     }
 
     @VisibleForTesting
@@ -152,10 +145,6 @@
                     ((SystemInjector) mInjector).initializeAppOpsManager();
                 }
                 break;
-            case PHASE_THIRD_PARTY_APPS_CAN_START:
-                // Ensures that a fast pair data provider exists which will work in direct boot.
-                FastPairDataProvider.init(mContext);
-                break;
             case PHASE_BOOT_COMPLETED:
                 if (mInjector instanceof SystemInjector) {
                     // The nearby service must be functioning after this boot phase.
@@ -166,7 +155,6 @@
                 mContext.registerReceiver(
                         mBluetoothReceiver,
                         new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-                mFastPairManager.initiate();
                 break;
         }
     }
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
deleted file mode 100644
index 23d5170..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
+++ /dev/null
@@ -1,746 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.ScanFilter;
-import android.os.Parcel;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.UUID;
-
-/**
- * Criteria for filtering BLE devices. A {@link BleFilter} allows clients to restrict BLE devices to
- * only those that are of interest to them.
- *
- *
- * <p>Current filtering on the following fields are supported:
- * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
- * <li>Name of remote Bluetooth LE device.
- * <li>Mac address of the remote device.
- * <li>Service data which is the data associated with a service.
- * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
- *
- * @see BleSighting
- */
-public final class BleFilter implements Parcelable {
-
-    @Nullable
-    private String mDeviceName;
-
-    @Nullable
-    private String mDeviceAddress;
-
-    @Nullable
-    private ParcelUuid mServiceUuid;
-
-    @Nullable
-    private ParcelUuid mServiceUuidMask;
-
-    @Nullable
-    private ParcelUuid mServiceDataUuid;
-
-    @Nullable
-    private byte[] mServiceData;
-
-    @Nullable
-    private byte[] mServiceDataMask;
-
-    private int mManufacturerId;
-
-    @Nullable
-    private byte[] mManufacturerData;
-
-    @Nullable
-    private byte[] mManufacturerDataMask;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    BleFilter() {
-    }
-
-    BleFilter(
-            @Nullable String deviceName,
-            @Nullable String deviceAddress,
-            @Nullable ParcelUuid serviceUuid,
-            @Nullable ParcelUuid serviceUuidMask,
-            @Nullable ParcelUuid serviceDataUuid,
-            @Nullable byte[] serviceData,
-            @Nullable byte[] serviceDataMask,
-            int manufacturerId,
-            @Nullable byte[] manufacturerData,
-            @Nullable byte[] manufacturerDataMask) {
-        this.mDeviceName = deviceName;
-        this.mDeviceAddress = deviceAddress;
-        this.mServiceUuid = serviceUuid;
-        this.mServiceUuidMask = serviceUuidMask;
-        this.mServiceDataUuid = serviceDataUuid;
-        this.mServiceData = serviceData;
-        this.mServiceDataMask = serviceDataMask;
-        this.mManufacturerId = manufacturerId;
-        this.mManufacturerData = manufacturerData;
-        this.mManufacturerDataMask = manufacturerDataMask;
-    }
-
-    public static final Parcelable.Creator<BleFilter> CREATOR = new Creator<BleFilter>() {
-        @Override
-        public BleFilter createFromParcel(Parcel source) {
-            BleFilter nBleFilter = new BleFilter();
-            nBleFilter.mDeviceName = source.readString();
-            nBleFilter.mDeviceAddress = source.readString();
-            nBleFilter.mManufacturerId = source.readInt();
-            nBleFilter.mManufacturerData = source.marshall();
-            nBleFilter.mManufacturerDataMask = source.marshall();
-            nBleFilter.mServiceDataUuid = source.readParcelable(null);
-            nBleFilter.mServiceData = source.marshall();
-            nBleFilter.mServiceDataMask = source.marshall();
-            nBleFilter.mServiceUuid = source.readParcelable(null);
-            nBleFilter.mServiceUuidMask = source.readParcelable(null);
-            return nBleFilter;
-        }
-
-        @Override
-        public BleFilter[] newArray(int size) {
-            return new BleFilter[size];
-        }
-    };
-
-
-    /** Returns the filter set on the device name field of Bluetooth advertisement data. */
-    @Nullable
-    public String getDeviceName() {
-        return mDeviceName;
-    }
-
-    /** Returns the filter set on the service uuid. */
-    @Nullable
-    public ParcelUuid getServiceUuid() {
-        return mServiceUuid;
-    }
-
-    /** Returns the mask for the service uuid. */
-    @Nullable
-    public ParcelUuid getServiceUuidMask() {
-        return mServiceUuidMask;
-    }
-
-    /** Returns the filter set on the device address. */
-    @Nullable
-    public String getDeviceAddress() {
-        return mDeviceAddress;
-    }
-
-    /** Returns the filter set on the service data. */
-    @Nullable
-    public byte[] getServiceData() {
-        return mServiceData;
-    }
-
-    /** Returns the mask for the service data. */
-    @Nullable
-    public byte[] getServiceDataMask() {
-        return mServiceDataMask;
-    }
-
-    /** Returns the filter set on the service data uuid. */
-    @Nullable
-    public ParcelUuid getServiceDataUuid() {
-        return mServiceDataUuid;
-    }
-
-    /** Returns the manufacturer id. -1 if the manufacturer filter is not set. */
-    public int getManufacturerId() {
-        return mManufacturerId;
-    }
-
-    /** Returns the filter set on the manufacturer data. */
-    @Nullable
-    public byte[] getManufacturerData() {
-        return mManufacturerData;
-    }
-
-    /** Returns the mask for the manufacturer data. */
-    @Nullable
-    public byte[] getManufacturerDataMask() {
-        return mManufacturerDataMask;
-    }
-
-    /**
-     * Check if the filter matches a {@code BleSighting}. A BLE sighting is considered as a match if
-     * it matches all the field filters.
-     */
-    public boolean matches(@Nullable BleSighting bleSighting) {
-        if (bleSighting == null) {
-            return false;
-        }
-        BluetoothDevice device = bleSighting.getDevice();
-        // Device match.
-        if (mDeviceAddress != null && (device == null || !mDeviceAddress.equals(
-                device.getAddress()))) {
-            return false;
-        }
-
-        BleRecord bleRecord = bleSighting.getBleRecord();
-
-        // Scan record is null but there exist filters on it.
-        if (bleRecord == null
-                && (mDeviceName != null
-                || mServiceUuid != null
-                || mManufacturerData != null
-                || mServiceData != null)) {
-            return false;
-        }
-
-        // Local name match.
-        if (mDeviceName != null && !mDeviceName.equals(bleRecord.getDeviceName())) {
-            return false;
-        }
-
-        // UUID match.
-        if (mServiceUuid != null
-                && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
-                bleRecord.getServiceUuids())) {
-            return false;
-        }
-
-        // Service data match
-        if (mServiceDataUuid != null
-                && !matchesPartialData(
-                mServiceData, mServiceDataMask, bleRecord.getServiceData(mServiceDataUuid))) {
-            return false;
-        }
-
-        // Manufacturer data match.
-        if (mManufacturerId >= 0
-                && !matchesPartialData(
-                mManufacturerData,
-                mManufacturerDataMask,
-                bleRecord.getManufacturerSpecificData(mManufacturerId))) {
-            return false;
-        }
-
-        // All filters match.
-        return true;
-    }
-
-    /**
-     * Determines if the characteristics of this filter are a superset of the characteristics of the
-     * given filter.
-     */
-    public boolean isSuperset(@Nullable BleFilter bleFilter) {
-        if (bleFilter == null) {
-            return false;
-        }
-
-        if (equals(bleFilter)) {
-            return true;
-        }
-
-        // Verify device address matches.
-        if (mDeviceAddress != null && !mDeviceAddress.equals(bleFilter.getDeviceAddress())) {
-            return false;
-        }
-
-        // Verify device name matches.
-        if (mDeviceName != null && !mDeviceName.equals(bleFilter.getDeviceName())) {
-            return false;
-        }
-
-        // Verify UUID is a superset.
-        if (mServiceUuid != null
-                && !serviceUuidIsSuperset(
-                mServiceUuid,
-                mServiceUuidMask,
-                bleFilter.getServiceUuid(),
-                bleFilter.getServiceUuidMask())) {
-            return false;
-        }
-
-        // Verify service data is a superset.
-        if (mServiceDataUuid != null
-                && (!mServiceDataUuid.equals(bleFilter.getServiceDataUuid())
-                || !partialDataIsSuperset(
-                mServiceData,
-                mServiceDataMask,
-                bleFilter.getServiceData(),
-                bleFilter.getServiceDataMask()))) {
-            return false;
-        }
-
-        // Verify manufacturer data is a superset.
-        if (mManufacturerId >= 0
-                && (mManufacturerId != bleFilter.getManufacturerId()
-                || !partialDataIsSuperset(
-                mManufacturerData,
-                mManufacturerDataMask,
-                bleFilter.getManufacturerData(),
-                bleFilter.getManufacturerDataMask()))) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Determines if the first uuid and mask are a superset of the second uuid and mask. */
-    private static boolean serviceUuidIsSuperset(
-            @Nullable ParcelUuid uuid1,
-            @Nullable ParcelUuid uuidMask1,
-            @Nullable ParcelUuid uuid2,
-            @Nullable ParcelUuid uuidMask2) {
-        // First uuid1 is null so it can match any service UUID.
-        if (uuid1 == null) {
-            return true;
-        }
-
-        // uuid2 is a superset of uuid1, but not the other way around.
-        if (uuid2 == null) {
-            return false;
-        }
-
-        // Without a mask, the uuids must match.
-        if (uuidMask1 == null) {
-            return uuid1.equals(uuid2);
-        }
-
-        // Mask2 should be at least as specific as mask1.
-        if (uuidMask2 != null) {
-            long uuid1MostSig = uuidMask1.getUuid().getMostSignificantBits();
-            long uuid1LeastSig = uuidMask1.getUuid().getLeastSignificantBits();
-            long uuid2MostSig = uuidMask2.getUuid().getMostSignificantBits();
-            long uuid2LeastSig = uuidMask2.getUuid().getLeastSignificantBits();
-            if (((uuid1MostSig & uuid2MostSig) != uuid1MostSig)
-                    || ((uuid1LeastSig & uuid2LeastSig) != uuid1LeastSig)) {
-                return false;
-            }
-        }
-
-        if (!matchesServiceUuids(uuid1, uuidMask1, Arrays.asList(uuid2))) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Determines if the first data and mask are the superset of the second data and mask. */
-    private static boolean partialDataIsSuperset(
-            @Nullable byte[] data1,
-            @Nullable byte[] dataMask1,
-            @Nullable byte[] data2,
-            @Nullable byte[] dataMask2) {
-        if (Arrays.equals(data1, data2) && Arrays.equals(dataMask1, dataMask2)) {
-            return true;
-        }
-
-        if (data1 == null) {
-            return true;
-        }
-
-        if (data2 == null) {
-            return false;
-        }
-
-        // Mask2 should be at least as specific as mask1.
-        if (dataMask1 != null && dataMask2 != null) {
-            for (int i = 0, j = 0; i < dataMask1.length && j < dataMask2.length; i++, j++) {
-                if ((dataMask1[i] & dataMask2[j]) != dataMask1[i]) {
-                    return false;
-                }
-            }
-        }
-
-        if (!matchesPartialData(data1, dataMask1, data2)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Check if the uuid pattern is contained in a list of parcel uuids. */
-    private static boolean matchesServiceUuids(
-            @Nullable ParcelUuid uuid, @Nullable ParcelUuid parcelUuidMask,
-            List<ParcelUuid> uuids) {
-        if (uuid == null) {
-            // No service uuid filter has been set, so there's a match.
-            return true;
-        }
-
-        UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
-        for (ParcelUuid parcelUuid : uuids) {
-            if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Check if the uuid pattern matches the particular service uuid. */
-    private static boolean matchesServiceUuid(UUID uuid, @Nullable UUID mask, UUID data) {
-        if (mask == null) {
-            return uuid.equals(data);
-        }
-        if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
-                != (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
-            return false;
-        }
-        return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits())
-                == (data.getMostSignificantBits() & mask.getMostSignificantBits()));
-    }
-
-    /**
-     * Check whether the data pattern matches the parsed data. Assumes that {@code data} and {@code
-     * dataMask} have the same length.
-     */
-    /* package */
-    static boolean matchesPartialData(
-            @Nullable byte[] data, @Nullable byte[] dataMask, @Nullable byte[] parsedData) {
-        if (data == null || parsedData == null || parsedData.length < data.length) {
-            return false;
-        }
-        if (dataMask == null) {
-            for (int i = 0; i < data.length; ++i) {
-                if (parsedData[i] != data[i]) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        for (int i = 0; i < data.length; ++i) {
-            if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return "BleFilter [deviceName="
-                + mDeviceName
-                + ", deviceAddress="
-                + mDeviceAddress
-                + ", uuid="
-                + mServiceUuid
-                + ", uuidMask="
-                + mServiceUuidMask
-                + ", serviceDataUuid="
-                + mServiceDataUuid
-                + ", serviceData="
-                + Arrays.toString(mServiceData)
-                + ", serviceDataMask="
-                + Arrays.toString(mServiceDataMask)
-                + ", manufacturerId="
-                + mManufacturerId
-                + ", manufacturerData="
-                + Arrays.toString(mManufacturerData)
-                + ", manufacturerDataMask="
-                + Arrays.toString(mManufacturerDataMask)
-                + "]";
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mDeviceName);
-        out.writeString(mDeviceAddress);
-        out.writeInt(mManufacturerId);
-        out.writeByteArray(mManufacturerData);
-        out.writeByteArray(mManufacturerDataMask);
-        out.writeParcelable(mServiceDataUuid, flags);
-        out.writeByteArray(mServiceData);
-        out.writeByteArray(mServiceDataMask);
-        out.writeParcelable(mServiceUuid, flags);
-        out.writeParcelable(mServiceUuidMask, flags);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                mDeviceName,
-                mDeviceAddress,
-                mManufacturerId,
-                Arrays.hashCode(mManufacturerData),
-                Arrays.hashCode(mManufacturerDataMask),
-                mServiceDataUuid,
-                Arrays.hashCode(mServiceData),
-                Arrays.hashCode(mServiceDataMask),
-                mServiceUuid,
-                mServiceUuidMask);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        BleFilter other = (BleFilter) obj;
-        return mDeviceName.equals(other.mDeviceName)
-                && mDeviceAddress.equals(other.mDeviceAddress)
-                && mManufacturerId == other.mManufacturerId
-                && Arrays.equals(mManufacturerData, other.mManufacturerData)
-                && Arrays.equals(mManufacturerDataMask, other.mManufacturerDataMask)
-                && mServiceDataUuid.equals(other.mServiceDataUuid)
-                && Arrays.equals(mServiceData, other.mServiceData)
-                && Arrays.equals(mServiceDataMask, other.mServiceDataMask)
-                && mServiceUuid.equals(other.mServiceUuid)
-                && mServiceUuidMask.equals(other.mServiceUuidMask);
-    }
-
-    /** Builder class for {@link BleFilter}. */
-    public static final class Builder {
-
-        private String mDeviceName;
-        private String mDeviceAddress;
-
-        @Nullable
-        private ParcelUuid mServiceUuid;
-        @Nullable
-        private ParcelUuid mUuidMask;
-
-        private ParcelUuid mServiceDataUuid;
-        @Nullable
-        private byte[] mServiceData;
-        @Nullable
-        private byte[] mServiceDataMask;
-
-        private int mManufacturerId = -1;
-        private byte[] mManufacturerData;
-        @Nullable
-        private byte[] mManufacturerDataMask;
-
-        /** Set filter on device name. */
-        public Builder setDeviceName(String deviceName) {
-            this.mDeviceName = deviceName;
-            return this;
-        }
-
-        /**
-         * Set filter on device address.
-         *
-         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
-         *                      format of "01:02:03:AB:CD:EF". The device address can be validated
-         *                      using {@link
-         *                      BluetoothAdapter#checkBluetoothAddress}.
-         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
-         */
-        public Builder setDeviceAddress(String deviceAddress) {
-            if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
-                throw new IllegalArgumentException("invalid device address " + deviceAddress);
-            }
-            this.mDeviceAddress = deviceAddress;
-            return this;
-        }
-
-        /** Set filter on service uuid. */
-        public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid) {
-            this.mServiceUuid = serviceUuid;
-            mUuidMask = null; // clear uuid mask
-            return this;
-        }
-
-        /**
-         * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the {@code
-         * serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the bit in
-         * {@code serviceUuid}, and 0 to ignore that bit.
-         *
-         * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
-         *                                  uuidMask}
-         *                                  is not {@code null}.
-         */
-        public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid,
-                @Nullable ParcelUuid uuidMask) {
-            if (uuidMask != null && serviceUuid == null) {
-                throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
-            }
-            this.mServiceUuid = serviceUuid;
-            this.mUuidMask = uuidMask;
-            return this;
-        }
-
-        /**
-         * Set filtering on service data.
-         */
-        public Builder setServiceData(ParcelUuid serviceDataUuid, @Nullable byte[] serviceData) {
-            this.mServiceDataUuid = serviceDataUuid;
-            this.mServiceData = serviceData;
-            mServiceDataMask = null; // clear service data mask
-            return this;
-        }
-
-        /**
-         * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
-         * match
-         * the one in service data, otherwise set it to 0 to ignore that bit.
-         *
-         * <p>The {@code serviceDataMask} must have the same length of the {@code serviceData}.
-         *
-         * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while {@code
-         *                                  serviceData} is not or {@code serviceDataMask} and
-         *                                  {@code serviceData} has different
-         *                                  length.
-         */
-        public Builder setServiceData(
-                ParcelUuid serviceDataUuid,
-                @Nullable byte[] serviceData,
-                @Nullable byte[] serviceDataMask) {
-            if (serviceDataMask != null) {
-                if (serviceData == null) {
-                    throw new IllegalArgumentException(
-                            "serviceData is null while serviceDataMask is not null");
-                }
-                // Since the serviceDataMask is a bit mask for serviceData, the lengths of the two
-                // byte array need to be the same.
-                if (serviceData.length != serviceDataMask.length) {
-                    throw new IllegalArgumentException(
-                            "size mismatch for service data and service data mask");
-                }
-            }
-            this.mServiceDataUuid = serviceDataUuid;
-            this.mServiceData = serviceData;
-            this.mServiceDataMask = serviceDataMask;
-            return this;
-        }
-
-        /**
-         * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
-         *
-         * <p>Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
-         *
-         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
-         */
-        public Builder setManufacturerData(int manufacturerId, @Nullable byte[] manufacturerData) {
-            return setManufacturerData(manufacturerId, manufacturerData, null /* mask */);
-        }
-
-        /**
-         * Set filter on partial manufacture data. For any bit in the mask, set it to 1 if it needs
-         * to
-         * match the one in manufacturer data, otherwise set it to 0.
-         *
-         * <p>The {@code manufacturerDataMask} must have the same length of {@code
-         * manufacturerData}.
-         *
-         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
-         *                                  manufacturerData} is null while {@code
-         *                                  manufacturerDataMask} is not, or {@code
-         *                                  manufacturerData} and {@code manufacturerDataMask} have
-         *                                  different length.
-         */
-        public Builder setManufacturerData(
-                int manufacturerId,
-                @Nullable byte[] manufacturerData,
-                @Nullable byte[] manufacturerDataMask) {
-            if (manufacturerData != null && manufacturerId < 0) {
-                throw new IllegalArgumentException("invalid manufacture id");
-            }
-            if (manufacturerDataMask != null) {
-                if (manufacturerData == null) {
-                    throw new IllegalArgumentException(
-                            "manufacturerData is null while manufacturerDataMask is not null");
-                }
-                // Since the manufacturerDataMask is a bit mask for manufacturerData, the lengths
-                // of the two byte array need to be the same.
-                if (manufacturerData.length != manufacturerDataMask.length) {
-                    throw new IllegalArgumentException(
-                            "size mismatch for manufacturerData and manufacturerDataMask");
-                }
-            }
-            this.mManufacturerId = manufacturerId;
-            this.mManufacturerData = manufacturerData == null ? new byte[0] : manufacturerData;
-            this.mManufacturerDataMask = manufacturerDataMask;
-            return this;
-        }
-
-
-        /**
-         * Builds the filter.
-         *
-         * @throws IllegalArgumentException If the filter cannot be built.
-         */
-        public BleFilter build() {
-            return new BleFilter(
-                    mDeviceName,
-                    mDeviceAddress,
-                    mServiceUuid,
-                    mUuidMask,
-                    mServiceDataUuid,
-                    mServiceData,
-                    mServiceDataMask,
-                    mManufacturerId,
-                    mManufacturerData,
-                    mManufacturerDataMask);
-        }
-    }
-
-    /**
-     * Changes ble filter to os filter
-     */
-    public ScanFilter toOsFilter() {
-        ScanFilter.Builder osFilterBuilder = new ScanFilter.Builder();
-        if (!TextUtils.isEmpty(getDeviceAddress())) {
-            osFilterBuilder.setDeviceAddress(getDeviceAddress());
-        }
-        if (!TextUtils.isEmpty(getDeviceName())) {
-            osFilterBuilder.setDeviceName(getDeviceName());
-        }
-
-        byte[] manufacturerData = getManufacturerData();
-        if (getManufacturerId() != -1 && manufacturerData != null) {
-            byte[] manufacturerDataMask = getManufacturerDataMask();
-            if (manufacturerDataMask != null) {
-                osFilterBuilder.setManufacturerData(
-                        getManufacturerId(), manufacturerData, manufacturerDataMask);
-            } else {
-                osFilterBuilder.setManufacturerData(getManufacturerId(), manufacturerData);
-            }
-        }
-
-        ParcelUuid serviceDataUuid = getServiceDataUuid();
-        byte[] serviceData = getServiceData();
-        if (serviceDataUuid != null && serviceData != null) {
-            byte[] serviceDataMask = getServiceDataMask();
-            if (serviceDataMask != null) {
-                osFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask);
-            } else {
-                osFilterBuilder.setServiceData(serviceDataUuid, serviceData);
-            }
-        }
-
-        ParcelUuid serviceUuid = getServiceUuid();
-        if (serviceUuid != null) {
-            ParcelUuid serviceUuidMask = getServiceUuidMask();
-            if (serviceUuidMask != null) {
-                osFilterBuilder.setServiceUuid(serviceUuid, serviceUuidMask);
-            } else {
-                osFilterBuilder.setServiceUuid(serviceUuid);
-            }
-        }
-        return osFilterBuilder.build();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java b/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java
deleted file mode 100644
index 103a27f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble;
-
-import android.os.ParcelUuid;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.ble.util.StringUtils;
-
-import com.google.common.collect.ImmutableList;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * Represents a BLE record from Bluetooth LE scan.
- */
-public final class BleRecord {
-
-    // The following data type values are assigned by Bluetooth SIG.
-    // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
-    private static final int DATA_TYPE_FLAGS = 0x01;
-    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
-    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
-    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
-    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
-    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
-    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
-    private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
-    private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
-    private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
-    private static final int DATA_TYPE_SERVICE_DATA = 0x16;
-    private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
-
-    /** The base 128-bit UUID representation of a 16-bit UUID. */
-    private static final ParcelUuid BASE_UUID =
-            ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
-    /** Length of bytes for 16 bit UUID. */
-    private static final int UUID_BYTES_16_BIT = 2;
-    /** Length of bytes for 32 bit UUID. */
-    private static final int UUID_BYTES_32_BIT = 4;
-    /** Length of bytes for 128 bit UUID. */
-    private static final int UUID_BYTES_128_BIT = 16;
-
-    // Flags of the advertising data.
-    // -1 when the scan record is not valid.
-    private final int mAdvertiseFlags;
-
-    private final ImmutableList<ParcelUuid> mServiceUuids;
-
-    // null when the scan record is not valid.
-    @Nullable
-    private final SparseArray<byte[]> mManufacturerSpecificData;
-
-    // null when the scan record is not valid.
-    @Nullable
-    private final Map<ParcelUuid, byte[]> mServiceData;
-
-    // Transmission power level(in dB).
-    // Integer.MIN_VALUE when the scan record is not valid.
-    private final int mTxPowerLevel;
-
-    // Local name of the Bluetooth LE device.
-    // null when the scan record is not valid.
-    @Nullable
-    private final String mDeviceName;
-
-    // Raw bytes of scan record.
-    // Never null, whether valid or not.
-    private final byte[] mBytes;
-
-    // If the raw scan record byte[] cannot be parsed, all non-primitive args here other than the
-    // raw scan record byte[] and serviceUudis will be null. See parsefromBytes().
-    private BleRecord(
-            List<ParcelUuid> serviceUuids,
-            @Nullable SparseArray<byte[]> manufacturerData,
-            @Nullable Map<ParcelUuid, byte[]> serviceData,
-            int advertiseFlags,
-            int txPowerLevel,
-            @Nullable String deviceName,
-            byte[] bytes) {
-        this.mServiceUuids = ImmutableList.copyOf(serviceUuids);
-        mManufacturerSpecificData = manufacturerData;
-        this.mServiceData = serviceData;
-        this.mDeviceName = deviceName;
-        this.mAdvertiseFlags = advertiseFlags;
-        this.mTxPowerLevel = txPowerLevel;
-        this.mBytes = bytes;
-    }
-
-    /**
-     * Returns a list of service UUIDs within the advertisement that are used to identify the
-     * bluetooth GATT services.
-     */
-    public ImmutableList<ParcelUuid> getServiceUuids() {
-        return mServiceUuids;
-    }
-
-    /**
-     * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
-     * data.
-     */
-    @Nullable
-    public SparseArray<byte[]> getManufacturerSpecificData() {
-        return mManufacturerSpecificData;
-    }
-
-    /**
-     * Returns the manufacturer specific data associated with the manufacturer id. Returns {@code
-     * null} if the {@code manufacturerId} is not found.
-     */
-    @Nullable
-    public byte[] getManufacturerSpecificData(int manufacturerId) {
-        if (mManufacturerSpecificData == null) {
-            return null;
-        }
-        return mManufacturerSpecificData.get(manufacturerId);
-    }
-
-    /** Returns a map of service UUID and its corresponding service data. */
-    @Nullable
-    public Map<ParcelUuid, byte[]> getServiceData() {
-        return mServiceData;
-    }
-
-    /**
-     * Returns the service data byte array associated with the {@code serviceUuid}. Returns {@code
-     * null} if the {@code serviceDataUuid} is not found.
-     */
-    @Nullable
-    public byte[] getServiceData(ParcelUuid serviceDataUuid) {
-        if (serviceDataUuid == null || mServiceData == null) {
-            return null;
-        }
-        return mServiceData.get(serviceDataUuid);
-    }
-
-    /**
-     * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
-     * if
-     * the field is not set. This value can be used to calculate the path loss of a received packet
-     * using the following equation:
-     *
-     * <p><code>pathloss = txPowerLevel - rssi</code>
-     */
-    public int getTxPowerLevel() {
-        return mTxPowerLevel;
-    }
-
-    /** Returns the local name of the BLE device. The is a UTF-8 encoded string. */
-    @Nullable
-    public String getDeviceName() {
-        return mDeviceName;
-    }
-
-    /** Returns raw bytes of scan record. */
-    public byte[] getBytes() {
-        return mBytes;
-    }
-
-    /**
-     * Parse scan record bytes to {@link BleRecord}.
-     *
-     * <p>The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
-     *
-     * <p>All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
-     * order.
-     *
-     * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
-     */
-    public static BleRecord parseFromBytes(byte[] scanRecord) {
-        int currentPos = 0;
-        int advertiseFlag = -1;
-        List<ParcelUuid> serviceUuids = new ArrayList<>();
-        String localName = null;
-        int txPowerLevel = Integer.MIN_VALUE;
-
-        SparseArray<byte[]> manufacturerData = new SparseArray<>();
-        Map<ParcelUuid, byte[]> serviceData = new HashMap<>();
-
-        try {
-            while (currentPos < scanRecord.length) {
-                // length is unsigned int.
-                int length = scanRecord[currentPos++] & 0xFF;
-                if (length == 0) {
-                    break;
-                }
-                // Note the length includes the length of the field type itself.
-                int dataLength = length - 1;
-                // fieldType is unsigned int.
-                int fieldType = scanRecord[currentPos++] & 0xFF;
-                switch (fieldType) {
-                    case DATA_TYPE_FLAGS:
-                        advertiseFlag = scanRecord[currentPos] & 0xFF;
-                        break;
-                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
-                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
-                        parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_16_BIT,
-                                serviceUuids);
-                        break;
-                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
-                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
-                        parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_32_BIT,
-                                serviceUuids);
-                        break;
-                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
-                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
-                        parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_128_BIT,
-                                serviceUuids);
-                        break;
-                    case DATA_TYPE_LOCAL_NAME_SHORT:
-                    case DATA_TYPE_LOCAL_NAME_COMPLETE:
-                        localName = new String(extractBytes(scanRecord, currentPos, dataLength));
-                        break;
-                    case DATA_TYPE_TX_POWER_LEVEL:
-                        txPowerLevel = scanRecord[currentPos];
-                        break;
-                    case DATA_TYPE_SERVICE_DATA:
-                        // The first two bytes of the service data are service data UUID in little
-                        // endian. The rest bytes are service data.
-                        int serviceUuidLength = UUID_BYTES_16_BIT;
-                        byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
-                                serviceUuidLength);
-                        ParcelUuid serviceDataUuid = parseUuidFrom(serviceDataUuidBytes);
-                        byte[] serviceDataArray =
-                                extractBytes(
-                                        scanRecord, currentPos + serviceUuidLength,
-                                        dataLength - serviceUuidLength);
-                        serviceData.put(serviceDataUuid, serviceDataArray);
-                        break;
-                    case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
-                        // The first two bytes of the manufacturer specific data are
-                        // manufacturer ids in little endian.
-                        int manufacturerId =
-                                ((scanRecord[currentPos + 1] & 0xFF) << 8) + (scanRecord[currentPos]
-                                        & 0xFF);
-                        byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
-                                dataLength - 2);
-                        manufacturerData.put(manufacturerId, manufacturerDataBytes);
-                        break;
-                    default:
-                        // Just ignore, we don't handle such data type.
-                        break;
-                }
-                currentPos += dataLength;
-            }
-
-            return new BleRecord(
-                    serviceUuids,
-                    manufacturerData,
-                    serviceData,
-                    advertiseFlag,
-                    txPowerLevel,
-                    localName,
-                    scanRecord);
-        } catch (Exception e) {
-            Log.w("BleRecord", "Unable to parse scan record: " + Arrays.toString(scanRecord), e);
-            // As the record is invalid, ignore all the parsed results for this packet
-            // and return an empty record with raw scanRecord bytes in results
-            // check at the top of this method does? Maybe we expect callers to use the
-            // scanRecord part in
-            // some fallback. But if that's the reason, it would seem we still can return null.
-            // They still
-            // have the raw scanRecord in hand, 'cause they passed it to us. It seems too easy for a
-            // caller to misuse this "empty" BleRecord (as in b/22693067).
-            return new BleRecord(ImmutableList.of(), null, null, -1, Integer.MIN_VALUE, null,
-                    scanRecord);
-        }
-    }
-
-    // Parse service UUIDs.
-    private static int parseServiceUuid(
-            byte[] scanRecord,
-            int currentPos,
-            int dataLength,
-            int uuidLength,
-            List<ParcelUuid> serviceUuids) {
-        while (dataLength > 0) {
-            byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
-            serviceUuids.add(parseUuidFrom(uuidBytes));
-            dataLength -= uuidLength;
-            currentPos += uuidLength;
-        }
-        return currentPos;
-    }
-
-    // Helper method to extract bytes from byte array.
-    private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
-        byte[] bytes = new byte[length];
-        System.arraycopy(scanRecord, start, bytes, 0, length);
-        return bytes;
-    }
-
-    @Override
-    public String toString() {
-        return "BleRecord [advertiseFlags="
-                + mAdvertiseFlags
-                + ", serviceUuids="
-                + mServiceUuids
-                + ", manufacturerSpecificData="
-                + StringUtils.toString(mManufacturerSpecificData)
-                + ", serviceData="
-                + StringUtils.toString(mServiceData)
-                + ", txPowerLevel="
-                + mTxPowerLevel
-                + ", deviceName="
-                + mDeviceName
-                + "]";
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof BleRecord)) {
-            return false;
-        }
-        BleRecord record = (BleRecord) obj;
-        // BleRecord objects are built from bytes, so we only need that field.
-        return Arrays.equals(mBytes, record.mBytes);
-    }
-
-    @Override
-    public int hashCode() {
-        // BleRecord objects are built from bytes, so we only need that field.
-        return Arrays.hashCode(mBytes);
-    }
-
-    /**
-     * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
-     * but the returned UUID is always in 128-bit format. Note UUID is little endian in Bluetooth.
-     *
-     * @param uuidBytes Byte representation of uuid.
-     * @return {@link ParcelUuid} parsed from bytes.
-     * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed.
-     */
-    private static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
-        if (uuidBytes == null) {
-            throw new IllegalArgumentException("uuidBytes cannot be null");
-        }
-        int length = uuidBytes.length;
-        if (length != UUID_BYTES_16_BIT
-                && length != UUID_BYTES_32_BIT
-                && length != UUID_BYTES_128_BIT) {
-            throw new IllegalArgumentException("uuidBytes length invalid - " + length);
-        }
-        // Construct a 128 bit UUID.
-        if (length == UUID_BYTES_128_BIT) {
-            ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
-            long msb = buf.getLong(8);
-            long lsb = buf.getLong(0);
-            return new ParcelUuid(new UUID(msb, lsb));
-        }
-        // For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
-        // 128_bit_value = uuid * 2^96 + BASE_UUID
-        long shortUuid;
-        if (length == UUID_BYTES_16_BIT) {
-            shortUuid = uuidBytes[0] & 0xFF;
-            shortUuid += (uuidBytes[1] & 0xFF) << 8;
-        } else {
-            shortUuid = uuidBytes[0] & 0xFF;
-            shortUuid += (uuidBytes[1] & 0xFF) << 8;
-            shortUuid += (uuidBytes[2] & 0xFF) << 16;
-            shortUuid += (uuidBytes[3] & 0xFF) << 24;
-        }
-        long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
-        long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
-        return new ParcelUuid(new UUID(msb, lsb));
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java b/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java
deleted file mode 100644
index 71ec10c..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.ScanRecord;
-import android.bluetooth.le.ScanResult;
-import android.os.Build.VERSION_CODES;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-
-import java.util.Arrays;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A sighting of a BLE device found in a Bluetooth LE scan.
- */
-
-public class BleSighting implements Parcelable {
-
-    public static final Parcelable.Creator<BleSighting> CREATOR = new Creator<BleSighting>() {
-        @Override
-        public BleSighting createFromParcel(Parcel source) {
-            BleSighting nBleSighting = new BleSighting(source.readParcelable(null),
-                    source.marshall(), source.readInt(), source.readLong());
-            return null;
-        }
-
-        @Override
-        public BleSighting[] newArray(int size) {
-            return new BleSighting[size];
-        }
-    };
-
-    // Max and min rssi value which is from {@link android.bluetooth.le.ScanResult#getRssi()}.
-    @VisibleForTesting
-    public static final int MAX_RSSI_VALUE = 126;
-    @VisibleForTesting
-    public static final int MIN_RSSI_VALUE = -127;
-
-    /** Remote bluetooth device. */
-    private final BluetoothDevice mDevice;
-
-    /**
-     * BLE record, including advertising data and response data. BleRecord is not parcelable, so
-     * this
-     * is created from bleRecordBytes.
-     */
-    private final BleRecord mBleRecord;
-
-    /** The bytes of a BLE record. */
-    private final byte[] mBleRecordBytes;
-
-    /** Received signal strength. */
-    private final int mRssi;
-
-    /** Nanos timestamp when the ble device was observed (epoch time). */
-    private final long mTimestampEpochNanos;
-
-    /**
-     * Constructor of a BLE sighting.
-     *
-     * @param device              Remote bluetooth device that is found.
-     * @param bleRecordBytes      The bytes that will create a BleRecord.
-     * @param rssi                Received signal strength.
-     * @param timestampEpochNanos Nanos timestamp when the BLE device was observed (epoch time).
-     */
-    public BleSighting(BluetoothDevice device, byte[] bleRecordBytes, int rssi,
-            long timestampEpochNanos) {
-        this.mDevice = device;
-        this.mBleRecordBytes = bleRecordBytes;
-        this.mRssi = rssi;
-        this.mTimestampEpochNanos = timestampEpochNanos;
-        mBleRecord = BleRecord.parseFromBytes(bleRecordBytes);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Returns the remote bluetooth device identified by the bluetooth device address. */
-    public BluetoothDevice getDevice() {
-        return mDevice;
-    }
-
-    /** Returns the BLE record, which is a combination of advertisement and scan response. */
-    public BleRecord getBleRecord() {
-        return mBleRecord;
-    }
-
-    /** Returns the bytes of the BLE record. */
-    public byte[] getBleRecordBytes() {
-        return mBleRecordBytes;
-    }
-
-    /** Returns the received signal strength in dBm. The valid range is [-127, 127]. */
-    public int getRssi() {
-        return mRssi;
-    }
-
-    /**
-     * Returns the received signal strength normalized with the offset specific to the given device.
-     * 3 is the rssi offset to calculate fast init distance.
-     * <p>This method utilized the rssi offset maintained by Nearby Sharing.
-     *
-     * @return normalized rssi which is between [-127, 126] according to {@link
-     * android.bluetooth.le.ScanResult#getRssi()}.
-     */
-    public int getNormalizedRSSI() {
-        int adjustedRssi = mRssi + 3;
-        if (adjustedRssi < MIN_RSSI_VALUE) {
-            return MIN_RSSI_VALUE;
-        } else if (adjustedRssi > MAX_RSSI_VALUE) {
-            return MAX_RSSI_VALUE;
-        } else {
-            return adjustedRssi;
-        }
-    }
-
-    /** Returns timestamp in epoch time when the scan record was observed. */
-    public long getTimestampNanos() {
-        return mTimestampEpochNanos;
-    }
-
-    /** Returns timestamp in epoch time when the scan record was observed, in millis. */
-    public long getTimestampMillis() {
-        return TimeUnit.NANOSECONDS.toMillis(mTimestampEpochNanos);
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(mDevice, flags);
-        dest.writeByteArray(mBleRecordBytes);
-        dest.writeInt(mRssi);
-        dest.writeLong(mTimestampEpochNanos);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mDevice, mRssi, mTimestampEpochNanos, Arrays.hashCode(mBleRecordBytes));
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof BleSighting)) {
-            return false;
-        }
-        BleSighting other = (BleSighting) obj;
-        return Objects.equals(mDevice, other.mDevice)
-                && mRssi == other.mRssi
-                && Arrays.equals(mBleRecordBytes, other.mBleRecordBytes)
-                && mTimestampEpochNanos == other.mTimestampEpochNanos;
-    }
-
-    @Override
-    public String toString() {
-        return "BleSighting{"
-                + "device="
-                + mDevice
-                + ", bleRecord="
-                + mBleRecord
-                + ", rssi="
-                + mRssi
-                + ", timestampNanos="
-                + mTimestampEpochNanos
-                + "}";
-    }
-
-    /** Creates {@link BleSighting} using the {@link ScanResult}. */
-    @RequiresApi(api = VERSION_CODES.LOLLIPOP)
-    @Nullable
-    public static BleSighting createFromOsScanResult(ScanResult osResult) {
-        ScanRecord osScanRecord = osResult.getScanRecord();
-        if (osScanRecord == null) {
-            return null;
-        }
-
-        return new BleSighting(
-                osResult.getDevice(),
-                osScanRecord.getBytes(),
-                osResult.getRssi(),
-                // The timestamp from ScanResult is 'nanos since boot', Beacon lib will change it
-                // as 'nanos
-                // since epoch', but Nearby never reference this field, just pass it as 'nanos
-                // since boot'.
-                // ref to beacon/scan/impl/LBluetoothLeScannerCompat.fromOs for beacon design
-                // about how to
-                // convert nanos since boot to epoch.
-                osResult.getTimestampNanos());
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java b/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java
deleted file mode 100644
index 9e795ac..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble.decode;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.ble.BleRecord;
-
-/**
- * This class encapsulates the logic specific to each manufacturer for parsing formats for beacons,
- * and presents a common API to access important ADV/EIR packet fields such as:
- *
- * <ul>
- *   <li><b>UUID (universally unique identifier)</b>, a value uniquely identifying a group of one or
- *       more beacons as belonging to an organization or of a certain type, up to 128 bits.
- *   <li><b>Instance</b> a 32-bit unsigned integer that can be used to group related beacons that
- *       have the same UUID.
- *   <li>the mathematics of <b>TX signal strength</b>, used for proximity calculations.
- * </ul>
- *
- * ...and others.
- *
- * @see <a href="http://go/ble-glossary">BLE Glossary</a>
- * @see <a href="https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=245130">Bluetooth
- * Data Types Specification</a>
- */
-public abstract class BeaconDecoder {
-    /**
-     * Returns true if the bleRecord corresponds to a beacon format that contains sufficient
-     * information to construct a BeaconId and contains the Tx power.
-     */
-    public boolean supportsBeaconIdAndTxPower(@SuppressWarnings("unused") BleRecord bleRecord) {
-        return true;
-    }
-
-    /**
-     * Returns true if this decoder supports returning TxPower via {@link
-     * #getCalibratedBeaconTxPower(BleRecord)}.
-     */
-    public boolean supportsTxPower() {
-        return true;
-    }
-
-    /**
-     * Reads the calibrated transmitted power at 1 meter of the beacon in dBm. This value is
-     * contained
-     * in the scan record, as set by the transmitting beacon. Suitable for use in computing path
-     * loss,
-     * distance, and related derived values.
-     *
-     * @param bleRecord the parsed payload contained in the beacon packet
-     * @return integer value of the calibrated Tx power in dBm or null if the bleRecord doesn't
-     * contain sufficient information to calculate the Tx power.
-     */
-    @Nullable
-    public abstract Integer getCalibratedBeaconTxPower(BleRecord bleRecord);
-
-    /**
-     * Extract telemetry information from the beacon. Byte 0 of the returned telemetry block should
-     * encode the telemetry format.
-     *
-     * @return telemetry block for this beacon, or null if no telemetry data is found in the scan
-     * record.
-     */
-    @Nullable
-    public byte[] getTelemetry(@SuppressWarnings("unused") BleRecord bleRecord) {
-        return null;
-    }
-
-    /** Returns the appropriate type for this scan record. */
-    public abstract int getBeaconIdType();
-
-    /**
-     * Returns an array of bytes which uniquely identify this beacon, for beacons from any of the
-     * supported beacon types. This unique identifier is the indexing key for various internal
-     * services. Returns null if the bleRecord doesn't contain sufficient information to construct
-     * the
-     * ID.
-     */
-    @Nullable
-    public abstract byte[] getBeaconIdBytes(BleRecord bleRecord);
-
-    /**
-     * Returns the URL of the beacon. Returns null if the bleRecord doesn't contain a URL or
-     * contains
-     * a malformed URL.
-     */
-    @Nullable
-    public String getUrl(BleRecord bleRecord) {
-        return null;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java
deleted file mode 100644
index c1ff9fd..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble.decode;
-
-import android.bluetooth.le.ScanRecord;
-import android.os.ParcelUuid;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.ble.BleFilter;
-import com.android.server.nearby.common.ble.BleRecord;
-
-import java.util.Arrays;
-
-/**
- * Parses Fast Pair information out of {@link BleRecord}s.
- *
- * <p>There are 2 different packet formats that are supported, which is used can be determined by
- * packet length:
- *
- * <p>For 3-byte packets, the full packet is the model ID.
- *
- * <p>For all other packets, the first byte is the header, followed by the model ID, followed by
- * zero or more extra fields. Each field has its own header byte followed by the field value. The
- * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and
- * each extra field header is 0bLLLLTTTT (L = field length, T = field type).
- *
- * @see <a href="http://go/fast-pair-2-service-data">go/fast-pair-2-service-data</a>
- */
-public class FastPairDecoder extends BeaconDecoder {
-
-    private static final int FIELD_TYPE_BLOOM_FILTER = 0;
-    private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1;
-    private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2;
-    private static final int FIELD_TYPE_BATTERY = 3;
-    private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4;
-    public static final int FIELD_TYPE_CONNECTION_STATE = 5;
-    private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6;
-
-    /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */
-    private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID =
-            ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB");
-
-    /** The filter you use to scan for Fast Pair BLE advertisements. */
-    public static final BleFilter FILTER =
-            new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID,
-                    new byte[0]).build();
-
-    // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly
-    // without needing worry about signing errors.
-    private static final int HEADER_VERSION_BITMASK = 0b11100000;
-    private static final int HEADER_LENGTH_BITMASK = 0b00011110;
-    private static final int HEADER_VERSION_OFFSET = 5;
-    private static final int HEADER_LENGTH_OFFSET = 1;
-
-    private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000;
-    private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111;
-    private static final int EXTRA_FIELD_LENGTH_OFFSET = 4;
-    private static final int EXTRA_FIELD_TYPE_OFFSET = 0;
-
-    private static final int MIN_ID_LENGTH = 3;
-    private static final int MAX_ID_LENGTH = 14;
-    private static final int HEADER_INDEX = 0;
-    private static final int HEADER_LENGTH = 1;
-    private static final int FIELD_HEADER_LENGTH = 1;
-
-    // Not using java.util.IllegalFormatException because it is unchecked.
-    private static class IllegalFormatException extends Exception {
-        private IllegalFormatException(String message) {
-            super(message);
-        }
-    }
-
-    @Nullable
-    @Override
-    public Integer getCalibratedBeaconTxPower(BleRecord bleRecord) {
-        return null;
-    }
-
-    // TODO(b/205320613) create beacon type
-    @Override
-    public int getBeaconIdType() {
-        return 1;
-    }
-
-    /** Returns the Model ID from our service data, if present. */
-    @Nullable
-    @Override
-    public byte[] getBeaconIdBytes(BleRecord bleRecord) {
-        return getModelId(bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID));
-    }
-
-    /** Returns the Model ID from our service data, if present. */
-    @Nullable
-    public static byte[] getModelId(@Nullable byte[] serviceData) {
-        if (serviceData == null) {
-            return null;
-        }
-
-        if (serviceData.length >= MIN_ID_LENGTH) {
-            if (serviceData.length == MIN_ID_LENGTH) {
-                // If the length == 3, all bytes are the ID. See flag docs for more about
-                // endianness.
-                return serviceData;
-            } else {
-                // Otherwise, the first byte is a header which contains the length of the
-                // big-endian model
-                // ID that follows. The model ID will be trimmed if it contains leading zeros.
-                int idIndex = 1;
-                int end = idIndex + getIdLength(serviceData);
-                while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) {
-                    idIndex++;
-                }
-                return Arrays.copyOfRange(serviceData, idIndex, end);
-            }
-        }
-        return null;
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(BleRecord bleRecord) {
-        return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(ScanRecord scanRecord) {
-        return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the bloom filter from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilter(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER);
-    }
-
-    /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilterSalt(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT);
-    }
-
-    /**
-     * Gets the suppress notification with bloom filter from the extra fields if available,
-     * otherwise
-     * returns null.
-     */
-    @Nullable
-    public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
-    }
-
-    /** Gets the battery level from extra fields if available, otherwise return null. */
-    @Nullable
-    public static byte[] getBatteryLevel(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BATTERY);
-    }
-
-    /**
-     * Gets the suppress notification with battery level from extra fields if available, otherwise
-     * return null.
-     */
-    @Nullable
-    public static byte[] getBatteryLevelNoNotification(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BATTERY_NO_NOTIFICATION);
-    }
-
-    /**
-     * Gets the random resolvable data from extra fields if available, otherwise
-     * return null.
-     */
-    @Nullable
-    public static byte[] getRandomResolvableData(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA);
-    }
-
-    @Nullable
-    private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) {
-        if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) {
-            return null;
-        }
-        try {
-            return getExtraFields(serviceData).get(fieldId);
-        } catch (IllegalFormatException e) {
-            Log.v("FastPairDecode", "Extra fields incorrectly formatted.");
-            return null;
-        }
-    }
-
-    /** Gets extra field data at the end of the packet, defined by the extra field header. */
-    private static SparseArray<byte[]> getExtraFields(byte[] serviceData)
-            throws IllegalFormatException {
-        SparseArray<byte[]> extraFields = new SparseArray<>();
-        if (getVersion(serviceData) != 0) {
-            return extraFields;
-        }
-        int headerIndex = getFirstExtraFieldHeaderIndex(serviceData);
-        while (headerIndex < serviceData.length) {
-            int length = getExtraFieldLength(serviceData, headerIndex);
-            int index = headerIndex + FIELD_HEADER_LENGTH;
-            int type = getExtraFieldType(serviceData, headerIndex);
-            int end = index + length;
-            if (extraFields.get(type) == null) {
-                if (end <= serviceData.length) {
-                    extraFields.put(type, Arrays.copyOfRange(serviceData, index, end));
-                } else {
-                    throw new IllegalFormatException(
-                            "Invalid length, " + end + " is longer than service data size "
-                                    + serviceData.length);
-                }
-            }
-            headerIndex = end;
-        }
-        return extraFields;
-    }
-
-    /** Checks whether or not a valid ID is included in the service data packet. */
-    public static boolean hasBeaconIdBytes(BleRecord bleRecord) {
-        byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-        return checkModelId(serviceData);
-    }
-
-    /** Check whether byte array is FastPair model id or not. */
-    public static boolean checkModelId(@Nullable byte[] scanResult) {
-        return scanResult != null
-                // The 3-byte format has no header byte (all bytes are the ID).
-                && (scanResult.length == MIN_ID_LENGTH
-                // Header byte exists. We support only format version 0. (A different version
-                // indicates
-                // a breaking change in the format.)
-                || (scanResult.length > MIN_ID_LENGTH
-                && getVersion(scanResult) == 0
-                && isIdLengthValid(scanResult)));
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(BleRecord bleRecord) {
-        return (getBloomFilter(getServiceDataArray(bleRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null);
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(ScanRecord scanRecord) {
-        return (getBloomFilter(getServiceDataArray(scanRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null);
-    }
-
-    private static int getVersion(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? 0
-                : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET;
-    }
-
-    private static int getIdLength(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? MIN_ID_LENGTH
-                : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET;
-    }
-
-    private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) {
-        return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData);
-    }
-
-    private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK)
-                >> EXTRA_FIELD_LENGTH_OFFSET;
-    }
-
-    private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET;
-    }
-
-    private static boolean isIdLengthValid(byte[] serviceData) {
-        int idLength = getIdLength(serviceData);
-        return MIN_ID_LENGTH <= idLength
-                && idLength <= MAX_ID_LENGTH
-                && idLength + HEADER_LENGTH <= serviceData.length;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
deleted file mode 100644
index f27899f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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.server.nearby.common.ble.testing;
-
-import com.android.server.nearby.util.ArrayUtils;
-import com.android.server.nearby.util.Hex;
-
-/**
- * Test class to provide example to unit test.
- */
-public class FastPairTestData {
-    private static final byte[] FAST_PAIR_RECORD_BIG_ENDIAN =
-            Hex.stringToBytes("02011E020AF006162CFEAABBCC");
-
-    /**
-     * A Fast Pair frame, Note: The service UUID is FE2C, but in the
-     * packet it's 2CFE, since the core Bluetooth data types are little-endian.
-     *
-     * <p>However, the model ID is big-endian (multi-byte values in our spec are now big-endian, aka
-     * network byte order).
-     *
-     * @see {http://go/fast-pair-service-data}
-     */
-    public static byte[] getFastPairRecord() {
-        return FAST_PAIR_RECORD_BIG_ENDIAN;
-    }
-
-    /** A Fast Pair frame, with a shared account key. */
-    public static final byte[] FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD =
-            Hex.stringToBytes("02011E020AF00C162CFE007011223344556677");
-
-    /** Model ID in {@link #getFastPairRecord()}. */
-    public static final byte[] FAST_PAIR_MODEL_ID = Hex.stringToBytes("AABBCC");
-
-    /** @see #getFastPairRecord() */
-    public static byte[] newFastPairRecord(byte header, byte[] modelId) {
-        return newFastPairRecord(
-                modelId.length == 3 ? modelId : ArrayUtils.concatByteArrays(new byte[] {header},
-                        modelId));
-    }
-
-    /** @see #getFastPairRecord() */
-    public static byte[] newFastPairRecord(byte[] serviceData) {
-        int length = /* length of type and service UUID = */ 3 + serviceData.length;
-        return Hex.stringToBytes(
-                String.format("02011E020AF0%02X162CFE%s", length,
-                        Hex.bytesToStringUppercase(serviceData)));
-    }
-
-    // This is an example of advertising data with AD types
-    public static byte[] adv_1 = {
-            0x02, // Length of this Data
-            0x01, // <<Flags>>
-            0x01, // LE Limited Discoverable Mode
-            0x0A, // Length of this Data
-            0x09, // <<Complete local name>>
-            'P', 'e', 'd', 'o', 'm', 'e', 't', 'e', 'r'
-    };
-
-    // This is an example of advertising data with positive TX Power
-    // Level.
-    public static byte[] adv_2 = {
-            0x02, // Length of this Data
-            0x0a, // <<TX Power Level>>
-            127 // Level = 127
-    };
-
-    // Example data including a service data block
-    public static byte[] sd1 = {
-            0x02, // Length of this Data
-            0x01, // <<Flags>>
-            0x04, // BR/EDR Not Supported.
-            0x03, // Length of this Data
-            0x02, // <<Incomplete List of 16-bit Service UUIDs>>
-            0x04,
-            0x18, // TX Power Service UUID
-            0x1e, // Length of this Data
-            (byte) 0x16, // <<Service Specific Data>>
-            // Service UUID
-            (byte) 0xe0,
-            0x00,
-            // gBeacon Header
-            0x15,
-            // Running time ENCRYPT
-            (byte) 0xd2,
-            0x77,
-            0x01,
-            0x00,
-            // Scan Freq ENCRYPT
-            0x32,
-            0x05,
-            // Time in slow mode
-            0x00,
-            0x00,
-            // Time in fast mode
-            0x7f,
-            0x17,
-            // Subset of UID
-            0x56,
-            0x00,
-            // ID Mask
-            (byte) 0xd4,
-            0x7c,
-            0x18,
-            // RFU (reserved)
-            0x00,
-            // GUID = decimal 1297482358
-            0x76,
-            0x02,
-            0x56,
-            0x4d,
-            0x00,
-            // Ranging Payload Header
-            0x24,
-            // MAC of scanning address
-            (byte) 0xa4,
-            (byte) 0xbb,
-            // NORM RX RSSI -67dBm
-            (byte) 0xb0,
-            // NORM TX POWER -77dBm, so actual TX POWER = -36dBm
-            (byte) 0xb3,
-            // Note based on the values aboves PATH LOSS = (-36) - (-67) = 31dBm
-            // Below zero padding added to test it is handled correctly
-            0x00
-    };
-
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java b/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
deleted file mode 100644
index eec52ad..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble.util;
-
-
-/**
- * Ranging utilities embody the physics of converting RF path loss to distance. The free space path
- * loss is proportional to the square of the distance from transmitter to receiver, and to the
- * square of the frequency of the propagation signal.
- */
-public final class RangingUtils {
-    private static final int MAX_RSSI_VALUE = 126;
-    private static final int MIN_RSSI_VALUE = -127;
-
-    private RangingUtils() {
-    }
-
-    /* This was original derived in {@link com.google.android.gms.beacon.util.RangingUtils} from
-     * <a href="http://en.wikipedia.org/wiki/Free-space_path_loss">Free-space_path_loss</a>.
-     * Duplicated here for easy reference.
-     *
-     * c   = speed of light (2.9979 x 10^8 m/s);
-     * f   = frequency (Bluetooth center frequency is 2.44175GHz = 2.44175x10^9 Hz);
-     * l   = wavelength (in meters);
-     * d   = distance (from transmitter to receiver in meters);
-     * dB  = decibels
-     * dBm = decibel milliwatts
-     *
-     *
-     * Free-space path loss (FSPL) is proportional to the square of the distance between the
-     * transmitter and the receiver, and also proportional to the square of the frequency of the
-     * radio signal.
-     *
-     * FSPL      = (4 * pi * d / l)^2 = (4 * pi * d * f / c)^2
-     *
-     * FSPL (dB) = 10 * log10((4 * pi * d  * f / c)^2)
-     *           = 20 * log10(4 * pi * d * f / c)
-     *           = (20 * log10(d)) + (20 * log10(f)) + (20 * log10(4 * pi/c))
-     *
-     * Calculating constants:
-     *
-     * FSPL_FREQ        = 20 * log10(f)
-     *                  = 20 * log10(2.44175 * 10^9)
-     *                  = 187.75
-     *
-     * FSPL_LIGHT       = 20 * log10(4 * pi/c)
-     *                  = 20 * log10(4 * pi/(2.9979 * 10^8))
-     *                  = 20 * log10(4 * pi/(2.9979 * 10^8))
-     *                  = 20 * log10(41.9172441s * 10^-9)
-     *                  = -147.55
-     *
-     * FSPL_DISTANCE_1M = 20 * log10(1)
-     *                  = 0
-     *
-     * PATH_LOSS_AT_1M  = FSPL_DISTANCE_1M + FSPL_FREQ + FSPL_LIGHT
-     *                  =       0          + 187.75    + (-147.55)
-     *                  = 40.20db [round to 41db]
-     *
-     * Note: Rounding up makes us "closer" and makes us more aggressive at showing notifications.
-     */
-    private static final int RSSI_DROP_OFF_AT_1_M = 41;
-
-    /**
-     * Convert target distance and txPower to a RSSI value using the Log-distance path loss model
-     * with Path Loss at 1m of 41db.
-     *
-     * @return RSSI expected at distanceInMeters with device broadcasting at txPower.
-     */
-    public static int rssiFromTargetDistance(double distanceInMeters, int txPower) {
-        /*
-         * See <a href="https://en.wikipedia.org/wiki/Log-distance_path_loss_model">
-         * Log-distance path loss model</a>.
-         *
-         * PL      = total path loss in db
-         * txPower = TxPower in dbm
-         * rssi    = Received signal strength in dbm
-         * PL_0    = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm
-         * d       = length of path
-         * d_0     = reference distance  (1 m)
-         * gamma   = path loss exponent (2 in free space)
-         *
-         * Log-distance path loss (LDPL) formula:
-         *
-         * PL = txPower - rssi =                   PL_0          + 10 * gamma  * log_10(d / d_0)
-         *      txPower - rssi =            RSSI_DROP_OFF_AT_1_M + 10 * 2 * log_10
-         * (distanceInMeters / 1)
-         *              - rssi = -txPower + RSSI_DROP_OFF_AT_1_M + 20 * log_10(distanceInMeters)
-         *                rssi =  txPower - RSSI_DROP_OFF_AT_1_M - 20 * log_10(distanceInMeters)
-         */
-        txPower = adjustPower(txPower);
-        return distanceInMeters == 0
-                ? txPower
-                : (int) Math.floor((txPower - RSSI_DROP_OFF_AT_1_M)
-                        - 20 * Math.log10(distanceInMeters));
-    }
-
-    /**
-     * Convert RSSI and txPower to a distance value using the Log-distance path loss model with Path
-     * Loss at 1m of 41db.
-     *
-     * @return distance in meters with device broadcasting at txPower and given RSSI.
-     */
-    public static double distanceFromRssiAndTxPower(int rssi, int txPower) {
-        /*
-         * See <a href="https://en.wikipedia.org/wiki/Log-distance_path_loss_model">Log-distance
-         * path
-         * loss model</a>.
-         *
-         * PL      = total path loss in db
-         * txPower = TxPower in dbm
-         * rssi    = Received signal strength in dbm
-         * PL_0    = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm
-         * d       = length of path
-         * d_0     = reference distance  (1 m)
-         * gamma   = path loss exponent (2 in free space)
-         *
-         * Log-distance path loss (LDPL) formula:
-         *
-         * PL =    txPower - rssi                               = PL_0 + 10 * gamma  * log_10(d /
-         *  d_0)
-         *         txPower - rssi               = RSSI_DROP_OFF_AT_1_M + 10 * gamma  * log_10(d /
-         *  d_0)
-         *         txPower - rssi - RSSI_DROP_OFF_AT_1_M        = 10 * 2 * log_10
-         * (distanceInMeters / 1)
-         *         txPower - rssi - RSSI_DROP_OFF_AT_1_M        = 20 * log_10(distanceInMeters / 1)
-         *        (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20  = log_10(distanceInMeters)
-         *  10 ^ ((txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20) = distanceInMeters
-         */
-        txPower = adjustPower(txPower);
-        rssi = adjustPower(rssi);
-        return Math.pow(10, (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20.0);
-    }
-
-    /**
-     * Prevents the power from becoming too large or too small.
-     */
-    private static int adjustPower(int power) {
-        if (power > MAX_RSSI_VALUE) {
-            return MAX_RSSI_VALUE;
-        }
-        if (power < MIN_RSSI_VALUE) {
-            return MIN_RSSI_VALUE;
-        }
-        return power;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java b/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java
deleted file mode 100644
index 4d90b6d..0000000
--- a/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble.util;
-
-import android.annotation.Nullable;
-import android.util.SparseArray;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Map;
-
-/** Helper class for Bluetooth LE utils. */
-public final class StringUtils {
-    private StringUtils() {
-    }
-
-    /** Returns a string composed from a {@link SparseArray}. */
-    public static String toString(@Nullable SparseArray<byte[]> array) {
-        if (array == null) {
-            return "null";
-        }
-        if (array.size() == 0) {
-            return "{}";
-        }
-        StringBuilder buffer = new StringBuilder();
-        buffer.append('{');
-        for (int i = 0; i < array.size(); ++i) {
-            buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i)));
-        }
-        buffer.append('}');
-        return buffer.toString();
-    }
-
-    /** Returns a string composed from a {@link Map}. */
-    public static <T> String toString(@Nullable Map<T, byte[]> map) {
-        if (map == null) {
-            return "null";
-        }
-        if (map.isEmpty()) {
-            return "{}";
-        }
-        StringBuilder buffer = new StringBuilder();
-        buffer.append('{');
-        Iterator<Map.Entry<T, byte[]>> it = map.entrySet().iterator();
-        while (it.hasNext()) {
-            Map.Entry<T, byte[]> entry = it.next();
-            Object key = entry.getKey();
-            buffer.append(key).append("=").append(Arrays.toString(map.get(key)));
-            if (it.hasNext()) {
-                buffer.append(", ");
-            }
-        }
-        buffer.append('}');
-        return buffer.toString();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java b/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java
deleted file mode 100644
index 6d4275f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.server.nearby.common.bloomfilter;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.primitives.UnsignedInts;
-
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.BitSet;
-
-/**
- * A bloom filter that gives access to the underlying BitSet.
- */
-public class BloomFilter {
-    private static final Charset CHARSET = UTF_8;
-
-    /**
-     * Receives a value and converts it into an array of ints that will be converted to indexes for
-     * the filter.
-     */
-    public interface Hasher {
-        /**
-         * Generate hash value.
-         */
-        int[] getHashes(byte[] value);
-    }
-
-    // The backing data for this bloom filter. As additions are made, they're OR'd until it
-    // eventually reaches 0xFF.
-    private final BitSet mBits;
-    // The max length of bits.
-    private final int mBitLength;
-    // The hasher to use for converting a value into an array of hashes.
-    private final Hasher mHasher;
-
-    public BloomFilter(byte[] bytes, Hasher hasher) {
-        this.mBits = BitSet.valueOf(bytes);
-        this.mBitLength = bytes.length * 8;
-        this.mHasher = hasher;
-    }
-
-    /**
-     * Return the bloom filter check bit set as byte array.
-     */
-    public byte[] asBytes() {
-        // BitSet.toByteArray() truncates all the unset bits after the last set bit (eg. [0,0,1,0]
-        // becomes [0,0,1]) so we re-add those bytes if needed with Arrays.copy().
-        byte[] b = mBits.toByteArray();
-        if (b.length == mBitLength / 8) {
-            return b;
-        }
-        return Arrays.copyOf(b, mBitLength / 8);
-    }
-
-    /**
-     * Add string value to bloom filter hash.
-     */
-    public void add(String s) {
-        add(s.getBytes(CHARSET));
-    }
-
-    /**
-     * Adds value to bloom filter hash.
-     */
-    public void add(byte[] value) {
-        int[] hashes = mHasher.getHashes(value);
-        for (int hash : hashes) {
-            mBits.set(UnsignedInts.remainder(hash, mBitLength));
-        }
-    }
-
-    /**
-     * Check if the string format has collision.
-     */
-    public boolean possiblyContains(String s) {
-        return possiblyContains(s.getBytes(CHARSET));
-    }
-
-    /**
-     * Checks if value after hash will have collision.
-     */
-    public boolean possiblyContains(byte[] value) {
-        int[] hashes = mHasher.getHashes(value);
-        for (int hash : hashes) {
-            if (!mBits.get(UnsignedInts.remainder(hash, mBitLength))) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java b/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java
deleted file mode 100644
index 0ccee97..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.server.nearby.common.bloomfilter;
-
-import com.google.common.hash.Hashing;
-
-import java.nio.ByteBuffer;
-
-/**
- * Hasher which hashes a value using SHA-256 and splits it into parts, each of which can be
- * converted to an index.
- */
-public class FastPairBloomFilterHasher implements BloomFilter.Hasher {
-
-    private static final int NUM_INDEXES = 8;
-
-    @Override
-    public int[] getHashes(byte[] value) {
-        byte[] hash = Hashing.sha256().hashBytes(value).asBytes();
-        ByteBuffer buffer = ByteBuffer.wrap(hash);
-        int[] hashes = new int[NUM_INDEXES];
-        for (int i = 0; i < NUM_INDEXES; i++) {
-            hashes[i] = buffer.getInt();
-        }
-        return hashes;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java
deleted file mode 100644
index 3a02b18..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth;
-
-import java.util.UUID;
-
-/**
- * Bluetooth constants.
- */
-public class BluetoothConsts {
-
-    /**
-     * Default MTU when value is unknown.
-     */
-    public static final int DEFAULT_MTU = 23;
-
-    // The following random uuids are used to indicate that the device has dynamic services.
-    /**
-     * UUID of dynamic service.
-     */
-    public static final UUID SERVICE_DYNAMIC_SERVICE =
-            UUID.fromString("00000100-0af3-11e5-a6c0-1697f925ec7b");
-
-    /**
-     * UUID of dynamic characteristic.
-     */
-    public static final UUID SERVICE_DYNAMIC_CHARACTERISTIC =
-            UUID.fromString("00002A05-0af3-11e5-a6c0-1697f925ec7b");
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothException.java
deleted file mode 100644
index db2e1cc..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth;
-
-/**
- * {@link Exception} thrown during a Bluetooth operation.
- */
-public class BluetoothException extends Exception {
-    /** Constructor. */
-    public BluetoothException(String message) {
-        super(message);
-    }
-
-    /** Constructor. */
-    public BluetoothException(String message, Throwable throwable) {
-        super(message, throwable);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java
deleted file mode 100644
index 5ac4882..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth;
-
-/**
- * Exception for Bluetooth GATT operations.
- */
-public class BluetoothGattException extends BluetoothException {
-    private final int mErrorCode;
-
-    /** Constructor. */
-    public BluetoothGattException(String message, int errorCode) {
-        super(message);
-        mErrorCode = errorCode;
-    }
-
-    /** Constructor. */
-    public BluetoothGattException(String message, int errorCode, Throwable cause) {
-        super(message, cause);
-        mErrorCode = errorCode;
-    }
-
-    /** Returns Gatt error code. */
-    public int getGattErrorCode() {
-        return mErrorCode;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothTimeoutException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothTimeoutException.java
deleted file mode 100644
index 30fd188..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothTimeoutException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth;
-
-/**
- * {@link Exception} thrown during a Bluetooth operation when a timeout occurs.
- */
-public class BluetoothTimeoutException extends BluetoothException {
-
-    /** Constructor. */
-    public BluetoothTimeoutException(String message) {
-        super(message);
-    }
-
-    /** Constructor. */
-    public BluetoothTimeoutException(String message, Throwable throwable) {
-        super(message, throwable);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java
deleted file mode 100644
index 249011a..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth;
-
-import java.util.UUID;
-
-/**
- * Reserved UUIDS by BT SIG.
- * <p>
- * See https://developer.bluetooth.org for more details.
- */
-public class ReservedUuids {
-    /** UUIDs reserved for services. */
-    public static class Services {
-        /**
-         * The Device Information Service exposes manufacturer and/or vendor info about a device.
-         * <p>
-         * See reserved UUID org.bluetooth.service.device_information.
-         */
-        public static final UUID DEVICE_INFORMATION = fromShortUuid((short) 0x180A);
-
-        /**
-         * Generic attribute service.
-         * <p>
-         * See reserved UUID org.bluetooth.service.generic_attribute.
-         */
-        public static final UUID GENERIC_ATTRIBUTE = fromShortUuid((short) 0x1801);
-    }
-
-    /** UUIDs reserved for characteristics. */
-    public static class Characteristics {
-        /**
-         * The value of this characteristic is a UTF-8 string representing the firmware revision for
-         * the firmware within the device.
-         * <p>
-         * See reserved UUID org.bluetooth.characteristic.firmware_revision_string.
-         */
-        public static final UUID FIRMWARE_REVISION_STRING = fromShortUuid((short) 0x2A26);
-
-        /**
-         * Service change characteristic.
-         * <p>
-         * See reserved UUID org.bluetooth.characteristic.gatt.service_changed.
-         */
-        public static final UUID SERVICE_CHANGE = fromShortUuid((short) 0x2A05);
-    }
-
-    /** UUIDs reserved for descriptors. */
-    public static class Descriptors {
-        /**
-         * This descriptor shall be persistent across connections for bonded devices. The Client
-         * Characteristic Configuration descriptor is unique for each client. A client may read and
-         * write this descriptor to determine and set the configuration for that client.
-         * Authentication and authorization may be required by the server to write this descriptor.
-         * The default value for the Client Characteristic Configuration descriptor is 0x00. Upon
-         * connection of non-binded clients, this descriptor is set to the default value.
-         * <p>
-         * See reserved UUID org.bluetooth.descriptor.gatt.client_characteristic_configuration.
-         */
-        public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION =
-                fromShortUuid((short) 0x2902);
-    }
-
-    /** The base 128-bit UUID representation of a 16-bit UUID */
-    public static final UUID BASE_16_BIT_UUID =
-            UUID.fromString("00000000-0000-1000-8000-00805F9B34FB");
-
-    /** Converts from short UUId to UUID. */
-    public static UUID fromShortUuid(short shortUuid) {
-        return new UUID(((((long) shortUuid) << 32) & 0x0000FFFF00000000L)
-                | ReservedUuids.BASE_16_BIT_UUID.getMostSignificantBits(),
-                ReservedUuids.BASE_16_BIT_UUID.getLeastSignificantBits());
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java
deleted file mode 100644
index 28a9c33..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.generateKey;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
-
-import java.security.NoSuchAlgorithmException;
-
-/**
- * This is to generate account key with fast-pair style.
- */
-public final class AccountKeyGenerator {
-
-    // Generate a key where the first byte is always defined as the type, 0x04. This maintains 15
-    // bytes of entropy in the key while also allowing providers to verify that they have received
-    // a properly formatted key and decrypted it correctly, minimizing the risk of replay attacks.
-
-    /**
-     * Creates account key.
-     */
-    public static byte[] createAccountKey() throws NoSuchAlgorithmException {
-        byte[] accountKey = generateKey();
-        accountKey[0] = AccountKeyCharacteristic.TYPE;
-        return accountKey;
-    }
-
-    private AccountKeyGenerator() {
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java
deleted file mode 100644
index c9ccfd5..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-/**
- * Utilities for encoding/decoding the additional data packet and verifying both the data integrity
- * and the authentication.
- *
- * <p>Additional Data packet is:
- *
- * <ol>
- *   <li>AdditionalData_Packet[0 - 7]: the first 8-byte of HMAC.
- *   <li>AdditionalData_Packet[8 - var]: the encrypted message by AES-CTR, with 8-byte nonce
- *       appended to the front.
- * </ol>
- *
- * See https://developers.google.com/nearby/fast-pair/spec#AdditionalData.
- */
-public final class AdditionalDataEncoder {
-
-    static final int EXTRACT_HMAC_SIZE = 8;
-    static final int MAX_LENGTH_OF_DATA = 64;
-
-    /**
-     * Encodes the given data to additional data packet by the given secret.
-     */
-    static byte[] encodeAdditionalDataPacket(byte[] secret, byte[] additionalData)
-            throws GeneralSecurityException {
-        if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
-            throw new GeneralSecurityException(
-                    "Incorrect secret for encoding additional data packet, secret.length = "
-                            + (secret == null ? "NULL" : secret.length));
-        }
-
-        if ((additionalData == null)
-                || (additionalData.length == 0)
-                || (additionalData.length > MAX_LENGTH_OF_DATA)) {
-            throw new GeneralSecurityException(
-                    "Invalid data for encoding additional data packet, data = "
-                            + (additionalData == null ? "NULL" : additionalData.length));
-        }
-
-        byte[] encryptedData = AesCtrMultipleBlockEncryption.encrypt(secret, additionalData);
-        byte[] extractedHmac =
-                Arrays.copyOf(HmacSha256.build(secret, encryptedData), EXTRACT_HMAC_SIZE);
-
-        return concat(extractedHmac, encryptedData);
-    }
-
-    /**
-     * Decodes additional data packet by the given secret.
-     *
-     * @param secret AES-128 key used in the encryption to decrypt data
-     * @param additionalDataPacket additional data packet which is encoded by the given secret
-     * @return the data byte array decoded from the given packet
-     * @throws GeneralSecurityException if the given key or additional data packet is invalid for
-     * decoding
-     */
-    static byte[] decodeAdditionalDataPacket(byte[] secret, byte[] additionalDataPacket)
-            throws GeneralSecurityException {
-        if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
-            throw new GeneralSecurityException(
-                    "Incorrect secret for decoding additional data packet, secret.length = "
-                            + (secret == null ? "NULL" : secret.length));
-        }
-        if (additionalDataPacket == null
-                || additionalDataPacket.length <= EXTRACT_HMAC_SIZE
-                || additionalDataPacket.length
-                > (MAX_LENGTH_OF_DATA + EXTRACT_HMAC_SIZE + NONCE_SIZE)) {
-            throw new GeneralSecurityException(
-                    "Additional data packet size is incorrect, additionalDataPacket.length is "
-                            + (additionalDataPacket == null ? "NULL"
-                            : additionalDataPacket.length));
-        }
-
-        if (!verifyHmac(secret, additionalDataPacket)) {
-            throw new GeneralSecurityException(
-                    "Verify HMAC failed, could be incorrect key or packet.");
-        }
-        byte[] encryptedData =
-                Arrays.copyOfRange(
-                        additionalDataPacket, EXTRACT_HMAC_SIZE, additionalDataPacket.length);
-        return AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData);
-    }
-
-    // Computes the HMAC of the given key and additional data, and compares the first 8-byte of the
-    // HMAC result with the one from additional data packet.
-    // Must call constant-time comparison to prevent a possible timing attack, e.g. time the same
-    // MAC with all different first byte for a given ciphertext, the right one will take longer as
-    // it will fail on the second byte's verification.
-    private static boolean verifyHmac(byte[] key, byte[] additionalDataPacket)
-            throws GeneralSecurityException {
-        byte[] packetHmac =
-                Arrays.copyOfRange(additionalDataPacket, /* from= */ 0, EXTRACT_HMAC_SIZE);
-        byte[] encryptedData =
-                Arrays.copyOfRange(
-                        additionalDataPacket, EXTRACT_HMAC_SIZE, additionalDataPacket.length);
-        byte[] computedHmac = Arrays.copyOf(
-                HmacSha256.build(key, encryptedData), EXTRACT_HMAC_SIZE);
-
-        return HmacSha256.compareTwoHMACs(packetHmac, computedHmac);
-    }
-
-    private AdditionalDataEncoder() {
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java
deleted file mode 100644
index 50a818b..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-/**
- * AES-CTR utilities used for encrypting and decrypting Fast Pair packets that contain multiple
- * blocks. Encrypts input data by:
- *
- * <ol>
- *   <li>encryptedBlock[i] = clearBlock[i] ^ AES(counter), and
- *   <li>concat(encryptedBlock[0], encryptedBlock[1],...) to create the encrypted result, where
- *   <li>counter: the 16-byte input of AES. counter = iv + block_index.
- *   <li>iv: extend 8-byte nonce to 16 bytes with zero padding. i.e. concat(0x0000000000000000,
- *       nonce).
- *   <li>nonce: the cryptographically random 8 bytes, must never be reused with the same key.
- * </ol>
- */
-final class AesCtrMultipleBlockEncryption {
-
-    /** Length for AES-128 key. */
-    static final int KEY_LENGTH = AesEcbSingleBlockEncryption.KEY_LENGTH;
-
-    @VisibleForTesting
-    static final int AES_BLOCK_LENGTH = AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
-
-    /** Length of the nonce, a byte array of cryptographically random bytes. */
-    static final int NONCE_SIZE = 8;
-
-    private static final int IV_SIZE = AES_BLOCK_LENGTH;
-    private static final int MAX_NUMBER_OF_BLOCKS = 4;
-
-    private AesCtrMultipleBlockEncryption() {}
-
-    /** Generates a 16-byte AES key. */
-    static byte[] generateKey() throws NoSuchAlgorithmException {
-        return AesEcbSingleBlockEncryption.generateKey();
-    }
-
-    /**
-     * Encrypts data using AES-CTR by the given secret.
-     *
-     * @param secret AES-128 key.
-     * @param data the plaintext to be encrypted.
-     * @return the encrypted data with the 8-byte nonce appended to the front.
-     */
-    static byte[] encrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
-        byte[] nonce = generateNonce();
-        return concat(nonce, doAesCtr(secret, data, nonce));
-    }
-
-    /**
-     * Decrypts data using AES-CTR by the given secret and nonce.
-     *
-     * @param secret AES-128 key.
-     * @param data the first 8 bytes is the nonce, and the remaining is the encrypted data to be
-     *     decrypted.
-     * @return the decrypted data.
-     */
-    static byte[] decrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
-        if (data == null || data.length <= NONCE_SIZE) {
-            throw new GeneralSecurityException(
-                    "Incorrect data length "
-                            + (data == null ? "NULL" : data.length)
-                            + " to decrypt, the data should contain nonce.");
-        }
-        byte[] nonce = Arrays.copyOf(data, NONCE_SIZE);
-        byte[] encryptedData = Arrays.copyOfRange(data, NONCE_SIZE, data.length);
-        return doAesCtr(secret, encryptedData, nonce);
-    }
-
-    /**
-     * Generates cryptographically random NONCE_SIZE bytes nonce. This nonce can be used only once.
-     * Always call this function to generate a new nonce before a new encryption.
-     */
-    // Suppression for a warning for potentially insecure random numbers on Android 4.3 and older.
-    // Fast Pair service is only for Android 6.0+ devices.
-    static byte[] generateNonce() {
-        SecureRandom random = new SecureRandom();
-        byte[] nonce = new byte[NONCE_SIZE];
-        random.nextBytes(nonce);
-
-        return nonce;
-    }
-
-    // AES-CTR implementation.
-    @VisibleForTesting
-    static byte[] doAesCtr(byte[] secret, byte[] data, byte[] nonce)
-            throws GeneralSecurityException {
-        if (secret.length != KEY_LENGTH) {
-            throw new IllegalArgumentException(
-                    "Incorrect key length for encryption, only supports 16-byte AES Key.");
-        }
-        if (nonce.length != NONCE_SIZE) {
-            throw new IllegalArgumentException(
-                    "Incorrect nonce length for encryption, "
-                            + "Fast Pair naming scheme only supports 8-byte nonce.");
-        }
-
-        // Keeps the following operations on this byte[], returns it as the final AES-CTR result.
-        byte[] aesCtrResult = new byte[data.length];
-        System.arraycopy(data, /*srcPos=*/ 0, aesCtrResult, /*destPos=*/ 0, data.length);
-
-        // Initializes counter as IV.
-        byte[] counter = createIv(nonce);
-        // The length of the given data is permitted to non-align block size.
-        int numberOfBlocks =
-                (data.length / AES_BLOCK_LENGTH) + ((data.length % AES_BLOCK_LENGTH == 0) ? 0 : 1);
-
-        if (numberOfBlocks > MAX_NUMBER_OF_BLOCKS) {
-            throw new IllegalArgumentException(
-                    "Incorrect data size, Fast Pair naming scheme only supports 4 blocks.");
-        }
-
-        for (int i = 0; i < numberOfBlocks; i++) {
-            // Performs the operation: encryptedBlock[i] = clearBlock[i] ^ AES(counter).
-            counter[0] = (byte) (i & 0xFF);
-            byte[] aesOfCounter = doAesSingleBlock(secret, counter);
-            int start = i * AES_BLOCK_LENGTH;
-            // The size of the last block of data may not be 16 bytes. If not, still do xor to the
-            // last byte of data.
-            int end = Math.min(start + AES_BLOCK_LENGTH, data.length);
-            for (int j = 0; start < end; j++, start++) {
-                aesCtrResult[start] ^= aesOfCounter[j];
-            }
-        }
-        return aesCtrResult;
-    }
-
-    private static byte[] doAesSingleBlock(byte[] secret, byte[] counter)
-            throws GeneralSecurityException {
-        return AesEcbSingleBlockEncryption.encrypt(secret, counter);
-    }
-
-    /** Extends 8-byte nonce to 16 bytes with zero padding to create IV. */
-    private static byte[] createIv(byte[] nonce) {
-        return concat(new byte[IV_SIZE - NONCE_SIZE], nonce);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java
deleted file mode 100644
index 547931e..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.annotation.SuppressLint;
-
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Utilities used for encrypting and decrypting Fast Pair packets.
- */
-// SuppressLint for ""ecb encryption mode should not be used".
-// Reasons:
-//    1. FastPair data is guaranteed to be only 1 AES block in size, ECB is secure.
-//    2. In each case, the encrypted data is less than 16-bytes and is
-//       padded up to 16-bytes using random data to fill the rest of the byte array,
-//       so the plaintext will never be the same.
-@SuppressLint("GetInstance")
-public final class AesEcbSingleBlockEncryption {
-
-    public static final int AES_BLOCK_LENGTH = 16;
-    public static final int KEY_LENGTH = 16;
-
-    private AesEcbSingleBlockEncryption() {
-    }
-
-    /**
-     * Generates a 16-byte AES key.
-     */
-    public static byte[] generateKey() throws NoSuchAlgorithmException {
-        KeyGenerator generator = KeyGenerator.getInstance("AES");
-        generator.init(KEY_LENGTH * 8); // Ensure a 16-byte key is always used.
-        return generator.generateKey().getEncoded();
-    }
-
-    /**
-     * Encrypts data with the provided secret.
-     */
-    public static byte[] encrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
-        return doEncryption(Cipher.ENCRYPT_MODE, secret, data);
-    }
-
-    /**
-     * Decrypts data with the provided secret.
-     */
-    public static byte[] decrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
-        return doEncryption(Cipher.DECRYPT_MODE, secret, data);
-    }
-
-    private static byte[] doEncryption(int mode, byte[] secret, byte[] data)
-            throws GeneralSecurityException {
-        if (data.length != AES_BLOCK_LENGTH) {
-            throw new IllegalArgumentException("This encrypter only supports 16-byte inputs.");
-        }
-        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
-        cipher.init(mode, new SecretKeySpec(secret, "AES"));
-        return cipher.doFinal(data);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java
deleted file mode 100644
index 9bb5a86..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.io.BaseEncoding.base16;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.google.common.base.Ascii;
-import com.google.common.io.BaseEncoding;
-
-import java.util.Locale;
-
-/** Utils for dealing with Bluetooth addresses. */
-public final class BluetoothAddress {
-
-    private static final BaseEncoding ENCODING = base16().upperCase().withSeparator(":", 2);
-
-    @VisibleForTesting
-    static final String SECURE_SETTINGS_KEY_BLUETOOTH_ADDRESS = "bluetooth_address";
-
-    /**
-     * @return The string format used by e.g. {@link android.bluetooth.BluetoothDevice}. Upper case.
-     *     Example: "AA:BB:CC:11:22:33"
-     */
-    public static String encode(byte[] address) {
-        return ENCODING.encode(address);
-    }
-
-    /**
-     * @param address The string format used by e.g. {@link android.bluetooth.BluetoothDevice}.
-     *     Case-insensitive. Example: "AA:BB:CC:11:22:33"
-     */
-    public static byte[] decode(String address) {
-        return ENCODING.decode(address.toUpperCase(Locale.US));
-    }
-
-    /**
-     * Get public bluetooth address.
-     *
-     * @param context a valid {@link Context} instance.
-     */
-    public static @Nullable byte[] getPublicAddress(Context context) {
-        String publicAddress =
-                Settings.Secure.getString(
-                        context.getContentResolver(), SECURE_SETTINGS_KEY_BLUETOOTH_ADDRESS);
-        return publicAddress != null && BluetoothAdapter.checkBluetoothAddress(publicAddress)
-                ? decode(publicAddress)
-                : null;
-    }
-
-    /**
-     * Hides partial information of Bluetooth address.
-     * ex1: input is null, output should be empty string
-     * ex2: input is String(AA:BB:CC), output should be AA:BB:CC
-     * ex3: input is String(AA:BB:CC:DD:EE:FF), output should be XX:XX:XX:XX:EE:FF
-     * ex4: input is String(Aa:Bb:Cc:Dd:Ee:Ff), output should be XX:XX:XX:XX:EE:FF
-     * ex5: input is BluetoothDevice(AA:BB:CC:DD:EE:FF), output should be XX:XX:XX:XX:EE:FF
-     */
-    public static String maskBluetoothAddress(@Nullable Object address) {
-        if (address == null) {
-            return "";
-        }
-
-        if (address instanceof String) {
-            String originalAddress = (String) address;
-            String upperCasedAddress = Ascii.toUpperCase(originalAddress);
-            if (!BluetoothAdapter.checkBluetoothAddress(upperCasedAddress)) {
-                return originalAddress;
-            }
-            return convert(upperCasedAddress);
-        } else if (address instanceof BluetoothDevice) {
-            return convert(((BluetoothDevice) address).getAddress());
-        }
-
-        // For others, returns toString().
-        return address.toString();
-    }
-
-    private static String convert(String address) {
-        return "XX:XX:XX:XX:" + address.substring(12);
-    }
-
-    private BluetoothAddress() {}
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java
deleted file mode 100644
index 07306c1..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java
+++ /dev/null
@@ -1,774 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static android.bluetooth.BluetoothDevice.BOND_BONDED;
-import static android.bluetooth.BluetoothDevice.BOND_BONDING;
-import static android.bluetooth.BluetoothDevice.BOND_NONE;
-import static android.bluetooth.BluetoothDevice.ERROR;
-import static android.bluetooth.BluetoothProfile.A2DP;
-import static android.bluetooth.BluetoothProfile.HEADSET;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
-
-import android.Manifest.permission;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.core.content.ContextCompat;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.PasskeyCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.Profile;
-import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Pairs to Bluetooth audio devices.
- */
-public class BluetoothAudioPairer {
-
-    private static final String TAG = BluetoothAudioPairer.class.getSimpleName();
-
-    /**
-     * Hidden, see {@link BluetoothDevice}.
-     */
-    // TODO(b/202549655): remove Hidden usage.
-    private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
-
-    /**
-     * Hidden, see {@link BluetoothDevice}.
-     */
-    // TODO(b/202549655): remove Hidden usage.
-    private static final int PAIRING_VARIANT_CONSENT = 3;
-
-    /**
-     * Hidden, see {@link BluetoothDevice}.
-     */
-    // TODO(b/202549655): remove Hidden usage.
-    public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
-
-    private static final int DISCOVERY_STATE_CHANGE_TIMEOUT_MS = 3000;
-
-    private final Context mContext;
-    private final Preferences mPreferences;
-    private final EventLoggerWrapper mEventLogger;
-    private final BluetoothDevice mDevice;
-    @Nullable
-    private final KeyBasedPairingInfo mKeyBasedPairingInfo;
-    @Nullable
-    private final PasskeyConfirmationHandler mPasskeyConfirmationHandler;
-    private final TimingLogger mTimingLogger;
-
-    private static boolean sTestMode = false;
-
-    static void enableTestMode() {
-        sTestMode = true;
-    }
-
-    static class KeyBasedPairingInfo {
-
-        private final byte[] mSecret;
-        private final GattConnectionManager mGattConnectionManager;
-        private final boolean mProviderInitiatesBonding;
-
-        /**
-         * @param secret The secret negotiated during the initial BLE handshake for Key-based
-         * Pairing. See {@link FastPairConnection#handshake}.
-         * @param gattConnectionManager A manager that knows how to get and create Gatt connections
-         * to the remote device.
-         */
-        KeyBasedPairingInfo(
-                byte[] secret,
-                GattConnectionManager gattConnectionManager,
-                boolean providerInitiatesBonding) {
-            this.mSecret = secret;
-            this.mGattConnectionManager = gattConnectionManager;
-            this.mProviderInitiatesBonding = providerInitiatesBonding;
-        }
-    }
-
-    public BluetoothAudioPairer(
-            Context context,
-            BluetoothDevice device,
-            Preferences preferences,
-            EventLoggerWrapper eventLogger,
-            @Nullable KeyBasedPairingInfo keyBasedPairingInfo,
-            @Nullable PasskeyConfirmationHandler passkeyConfirmationHandler,
-            TimingLogger timingLogger)
-            throws PairingException {
-        this.mContext = context;
-        this.mDevice = device;
-        this.mPreferences = preferences;
-        this.mEventLogger = eventLogger;
-        this.mKeyBasedPairingInfo = keyBasedPairingInfo;
-        this.mPasskeyConfirmationHandler = passkeyConfirmationHandler;
-        this.mTimingLogger = timingLogger;
-
-        // TODO(b/203455314): follow up with the following comments.
-        // The OS should give the user some UI to choose if they want to allow access, but there
-        // seems to be a bug where if we don't reject access, it's auto-granted in some cases
-        // (Plantronics headset gets contacts access when pairing with my Taimen via Bluetooth
-        // Settings, without me seeing any UI about it). b/64066631
-        //
-        // If that OS bug doesn't get fixed, we can flip these flags to force-reject the
-        // permissions.
-        if (preferences.getRejectPhonebookAccess() && (sTestMode ? false :
-                !device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED))) {
-            throw new PairingException("Failed to deny contacts (phonebook) access.");
-        }
-        if (preferences.getRejectMessageAccess()
-                && (sTestMode ? false :
-                !device.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED))) {
-            throw new PairingException("Failed to deny message access.");
-        }
-        if (preferences.getRejectSimAccess()
-                && (sTestMode ? false :
-                !device.setSimAccessPermission(BluetoothDevice.ACCESS_REJECTED))) {
-            throw new PairingException("Failed to deny SIM access.");
-        }
-    }
-
-    boolean isPaired() {
-        return (sTestMode ? false : mDevice.getBondState() == BOND_BONDED);
-    }
-
-    /**
-     * Unpairs from the device. Throws an exception if any error occurs.
-     */
-    @WorkerThread
-    void unpair()
-            throws InterruptedException, ExecutionException, TimeoutException, PairingException {
-        int bondState =  sTestMode ? BOND_NONE : mDevice.getBondState();
-        try (UnbondedReceiver unbondedReceiver = new UnbondedReceiver();
-                ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                        "Unpair for state: " + bondState)) {
-            // We'll only get a state change broadcast if we're actually unbonding (method returns
-            // true).
-            if (bondState == BluetoothDevice.BOND_BONDED) {
-                mEventLogger.setCurrentEvent(EventCode.REMOVE_BOND);
-                Log.i(TAG,  "removeBond with " + maskBluetoothAddress(mDevice));
-                mDevice.removeBond();
-                unbondedReceiver.await(
-                        mPreferences.getRemoveBondTimeoutSeconds(), TimeUnit.SECONDS);
-            } else if (bondState == BluetoothDevice.BOND_BONDING) {
-                mEventLogger.setCurrentEvent(EventCode.CANCEL_BOND);
-                Log.i(TAG,  "cancelBondProcess with " + maskBluetoothAddress(mDevice));
-                mDevice.cancelBondProcess();
-                unbondedReceiver.await(
-                        mPreferences.getRemoveBondTimeoutSeconds(), TimeUnit.SECONDS);
-            } else {
-                // The OS may have beaten us in a race, unbonding before we called the method. So if
-                // we're (somehow) in the desired state then we're happy, if not then bail.
-                if (bondState != BluetoothDevice.BOND_NONE) {
-                    throw new PairingException("returned false, state=%s", bondState);
-                }
-            }
-        }
-
-        // This seems to improve the probability that createBond will succeed after removeBond.
-        SystemClock.sleep(mPreferences.getRemoveBondSleepMillis());
-        mEventLogger.logCurrentEventSucceeded();
-    }
-
-    /**
-     * Pairs with the device. Throws an exception if any error occurs.
-     */
-    @WorkerThread
-    void pair()
-            throws InterruptedException, ExecutionException, TimeoutException, PairingException {
-        // Unpair first, because if we have a bond, but the other device has forgotten its bond,
-        // it can send us a pairing request that we're not ready for (which can pop up a dialog).
-        // Or, if we're in the middle of a (too-long) bonding attempt, we want to cancel.
-        unpair();
-
-        mEventLogger.setCurrentEvent(EventCode.CREATE_BOND);
-        try (BondedReceiver bondedReceiver = new BondedReceiver();
-                ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Create bond")) {
-            // If the provider's initiating the bond, we do nothing but wait for broadcasts.
-            if (mKeyBasedPairingInfo == null || !mKeyBasedPairingInfo.mProviderInitiatesBonding) {
-                if (!sTestMode) {
-                    Log.i(TAG, "createBond with " + maskBluetoothAddress(mDevice) + ", type="
-                        + mDevice.getType());
-                    if (mPreferences.getSpecifyCreateBondTransportType()) {
-                        mDevice.createBond(mPreferences.getCreateBondTransportType());
-                    } else {
-                        mDevice.createBond();
-                    }
-                }
-            }
-            try {
-                bondedReceiver.await(mPreferences.getCreateBondTimeoutSeconds(), TimeUnit.SECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "bondedReceiver time out after " + mPreferences
-                        .getCreateBondTimeoutSeconds() + " seconds");
-                if (mPreferences.getIgnoreUuidTimeoutAfterBonded() && isPaired()) {
-                    Log.w(TAG, "Created bond but never received UUIDs, attempting to continue.");
-                } else {
-                    // Rethrow e to cause the pairing to fail and be retried if necessary.
-                    throw e;
-                }
-            }
-        }
-        mEventLogger.logCurrentEventSucceeded();
-    }
-
-    /**
-     * Connects to the given profile. Throws an exception if any error occurs.
-     *
-     * <p>If remote device clears the link key, the BOND_BONDED state would transit to BOND_BONDING
-     * (and go through the pairing process again) when directly connecting the profile. By enabling
-     * enablePairingBehavior, we provide both pairing and connecting behaviors at the same time. See
-     * b/145699390 for more details.
-     */
-    // Suppression for possible null from ImmutableMap#get. See go/lsc-get-nullable
-    @SuppressWarnings("nullness:argument")
-    @WorkerThread
-    public void connect(short profileUuid, boolean enablePairingBehavior)
-            throws InterruptedException, ReflectionException, TimeoutException, ExecutionException,
-            ConnectException {
-        if (!mPreferences.isSupportedProfile(profileUuid)) {
-            throw new ConnectException(
-                    ConnectErrorCode.UNSUPPORTED_PROFILE, "Unsupported profile=%s", profileUuid);
-        }
-        Profile profile = Constants.PROFILES.get(profileUuid);
-        Log.i(TAG,
-                "Connecting to profile=" + profile + " on device=" + maskBluetoothAddress(mDevice));
-        try (BondedReceiver bondedReceiver = enablePairingBehavior ? new BondedReceiver() : null;
-                ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                        "Connect: " + profile)) {
-            connectByProfileProxy(profile);
-        }
-    }
-
-    private void connectByProfileProxy(Profile profile)
-            throws ReflectionException, InterruptedException, ExecutionException, TimeoutException,
-            ConnectException {
-        try (BluetoothProfileWrapper autoClosingProxy = new BluetoothProfileWrapper(profile);
-                ConnectedReceiver connectedReceiver = new ConnectedReceiver(profile)) {
-            BluetoothProfile proxy = autoClosingProxy.mProxy;
-
-            // Try to connect via reflection
-            Log.v(TAG, "Connect to proxy=" + proxy);
-
-            if (!sTestMode) {
-                if (!(Boolean) Reflect.on(proxy).withMethod("connect", BluetoothDevice.class)
-                        .get(mDevice)) {
-                    // If we're already connecting, connect() may return false. :/
-                    Log.w(TAG, "connect returned false, expected if connecting, state="
-                            + proxy.getConnectionState(mDevice));
-                }
-            }
-
-            // If we're already connected, the OS may not send the connection state broadcast, so
-            // return immediately for that case.
-            if (!sTestMode) {
-                if (proxy.getConnectionState(mDevice) == BluetoothProfile.STATE_CONNECTED) {
-                    Log.v(TAG, "connectByProfileProxy: already connected to device="
-                            + maskBluetoothAddress(mDevice));
-                    return;
-                }
-            }
-
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Wait connection")) {
-                // Wait for connecting to succeed or fail (via event or timeout).
-                connectedReceiver
-                        .await(mPreferences.getCreateBondTimeoutSeconds(), TimeUnit.SECONDS);
-            }
-        }
-    }
-
-    private class BluetoothProfileWrapper implements AutoCloseable {
-
-        // incompatible types in assignment.
-        @SuppressWarnings("nullness:assignment")
-        private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        private final Profile mProfile;
-        private final BluetoothProfile mProxy;
-
-        /**
-         * Blocks until we get the proxy. Throws on error.
-         */
-        private BluetoothProfileWrapper(Profile profile)
-                throws InterruptedException, ExecutionException, TimeoutException,
-                ConnectException {
-            this.mProfile = profile;
-            mProxy = getProfileProxy(profile);
-        }
-
-        @Override
-        public void close() {
-            try (ScopedTiming scopedTiming =
-                    new ScopedTiming(mTimingLogger, "Close profile: " + mProfile)) {
-                if (!sTestMode) {
-                    mBluetoothAdapter.closeProfileProxy(mProfile.type, mProxy);
-                }
-            }
-        }
-
-        private BluetoothProfile getProfileProxy(BluetoothProfileWrapper this, Profile profile)
-                throws InterruptedException, ExecutionException, TimeoutException,
-                ConnectException {
-            if (profile.type != A2DP && profile.type != HEADSET) {
-                throw new IllegalArgumentException("Unsupported profile type=" + profile.type);
-            }
-
-            SettableFuture<BluetoothProfile> proxyFuture = SettableFuture.create();
-            BluetoothProfile.ServiceListener listener =
-                    new BluetoothProfile.ServiceListener() {
-                        @UiThread
-                        @Override
-                        public void onServiceConnected(int profileType, BluetoothProfile proxy) {
-                            proxyFuture.set(proxy);
-                        }
-
-                        @Override
-                        public void onServiceDisconnected(int profileType) {
-                            Log.v(TAG, "proxy disconnected for profile=" + profile);
-                        }
-                    };
-
-            if (!mBluetoothAdapter.getProfileProxy(mContext, listener, profile.type)) {
-                throw new ConnectException(
-                        ConnectErrorCode.GET_PROFILE_PROXY_FAILED,
-                        "getProfileProxy failed immediately");
-            }
-
-            return proxyFuture.get(mPreferences.getProxyTimeoutSeconds(), TimeUnit.SECONDS);
-        }
-    }
-
-    private class UnbondedReceiver extends DeviceIntentReceiver {
-
-        private UnbondedReceiver() {
-            super(mContext, mPreferences, mDevice, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        }
-
-        @Override
-        protected void onReceiveDeviceIntent(Intent intent) throws Exception {
-            if (mDevice.getBondState() == BOND_NONE) {
-                try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                        "Close UnbondedReceiver")) {
-                    close();
-                }
-            }
-        }
-    }
-
-    /**
-     * Receiver that closes after bonding has completed.
-     */
-    class BondedReceiver extends DeviceIntentReceiver {
-
-        private boolean mReceivedUuids = false;
-        private boolean mReceivedPasskey = false;
-
-        private BondedReceiver() {
-            super(
-                    mContext,
-                    mPreferences,
-                    mDevice,
-                    BluetoothDevice.ACTION_PAIRING_REQUEST,
-                    BluetoothDevice.ACTION_BOND_STATE_CHANGED,
-                    BluetoothDevice.ACTION_UUID);
-        }
-
-        // switching on a possibly-null value (intent.getAction())
-        // incompatible types in argument.
-        @SuppressWarnings({"nullness:switching.nullable", "nullness:argument"})
-        @Override
-        protected void onReceiveDeviceIntent(Intent intent)
-                throws PairingException, InterruptedException, ExecutionException, TimeoutException,
-                BluetoothException, GeneralSecurityException {
-            switch (intent.getAction()) {
-                case BluetoothDevice.ACTION_PAIRING_REQUEST:
-                    int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR);
-                    int passkey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
-                    handlePairingRequest(variant, passkey);
-                    break;
-                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
-                    // Use the state in the intent, not device.getBondState(), to avoid a race where
-                    // we log the wrong failure reason during a rapid transition.
-                    int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, ERROR);
-                    int reason = intent.getIntExtra(EXTRA_REASON, ERROR);
-                    handleBondStateChanged(bondState, reason);
-                    break;
-                case BluetoothDevice.ACTION_UUID:
-                    // According to eisenbach@ and pavlin@, there's always a UUID broadcast when
-                    // pairing (it can happen either before or after the transition to BONDED).
-                    if (mPreferences.getWaitForUuidsAfterBonding()) {
-                        Parcelable[] uuids = intent
-                                .getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
-                        handleUuids(uuids);
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        private void handlePairingRequest(int variant, int passkey) {
-            Log.i(TAG, "Pairing request, variant=" + variant + ", passkey=" + (passkey == ERROR
-                    ? "(none)" : String.valueOf(passkey)));
-            if (mPreferences.getMoreEventLogForQuality()) {
-                mEventLogger.setCurrentEvent(EventCode.HANDLE_PAIRING_REQUEST);
-            }
-
-            if (mPreferences.getSupportHidDevice() && variant == PAIRING_VARIANT_DISPLAY_PASSKEY) {
-                mReceivedPasskey = true;
-                extendAwaitSecond(
-                        mPreferences.getHidCreateBondTimeoutSeconds()
-                                - mPreferences.getCreateBondTimeoutSeconds());
-                triggerDiscoverStateChange();
-                if (mPreferences.getMoreEventLogForQuality()) {
-                    mEventLogger.logCurrentEventSucceeded();
-                }
-                return;
-
-            } else {
-                // Prevent Bluetooth Settings from getting the pairing request and showing its own
-                // UI.
-                abortBroadcast();
-
-                if (variant == PAIRING_VARIANT_CONSENT
-                        && mKeyBasedPairingInfo == null // Fast Pair 1.0 device
-                        && mPreferences.getAcceptConsentForFastPairOne()) {
-                    // Previously, if Bluetooth decided to use the Just Works variant (e.g. Fast
-                    // Pair 1.0), we don't get a pairing request broadcast at all.
-                    // However, after CVE-2019-2225, Bluetooth will decide to ask consent from
-                    // users. Details:
-                    // https://source.android.com/security/bulletin/2019-12-01#system
-                    // Since we've certified the Fast Pair 1.0 devices, and user taps to pair it
-                    // (with the device's image), we could help user to accept the consent.
-                    if (!sTestMode) {
-                        mDevice.setPairingConfirmation(true);
-                    }
-                    if (mPreferences.getMoreEventLogForQuality()) {
-                        mEventLogger.logCurrentEventSucceeded();
-                    }
-                    return;
-                } else if (variant != BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
-                    if (!sTestMode) {
-                        mDevice.setPairingConfirmation(false);
-                    }
-                    if (mPreferences.getMoreEventLogForQuality()) {
-                        mEventLogger.logCurrentEventFailed(
-                                new CreateBondException(
-                                        CreateBondErrorCode.INCORRECT_VARIANT, 0,
-                                        "Incorrect variant for FastPair"));
-                    }
-                    return;
-                }
-                mReceivedPasskey = true;
-
-                if (mKeyBasedPairingInfo == null) {
-                    if (mPreferences.getAcceptPasskey()) {
-                        // Must be the simulator using FP 1.0 (no Key-based Pairing). Real
-                        // headphones using FP 1.0 use Just Works instead (and maybe we should
-                        // disable this flag for them).
-                        if (!sTestMode) {
-                            mDevice.setPairingConfirmation(true);
-                        }
-                    }
-                    if (mPreferences.getMoreEventLogForQuality()) {
-                        if (!sTestMode) {
-                            mEventLogger.logCurrentEventSucceeded();
-                        }
-                    }
-                    return;
-                }
-            }
-
-            if (mPreferences.getMoreEventLogForQuality()) {
-                mEventLogger.logCurrentEventSucceeded();
-            }
-
-            newSingleThreadExecutor()
-                    .execute(
-                            () -> {
-                                try (ScopedTiming scopedTiming1 =
-                                        new ScopedTiming(mTimingLogger, "Exchange passkey")) {
-                                    mEventLogger.setCurrentEvent(EventCode.PASSKEY_EXCHANGE);
-
-                                    // We already check above, but the static analyzer's not
-                                    // convinced without this.
-                                    Preconditions.checkNotNull(mKeyBasedPairingInfo);
-                                    BluetoothGattConnection connection =
-                                            mKeyBasedPairingInfo.mGattConnectionManager
-                                                    .getConnection();
-                                    UUID characteristicUuid =
-                                            PasskeyCharacteristic.getId(connection);
-                                    ChangeObserver remotePasskeyObserver =
-                                            connection.enableNotification(FastPairService.ID,
-                                                    characteristicUuid);
-                                    Log.i(TAG, "Sending local passkey.");
-                                    byte[] encryptedData;
-                                    try (ScopedTiming scopedTiming2 =
-                                            new ScopedTiming(mTimingLogger, "Encrypt passkey")) {
-                                        encryptedData =
-                                                PasskeyCharacteristic.encrypt(
-                                                        PasskeyCharacteristic.Type.SEEKER,
-                                                        mKeyBasedPairingInfo.mSecret, passkey);
-                                    }
-                                    try (ScopedTiming scopedTiming3 =
-                                            new ScopedTiming(mTimingLogger,
-                                                    "Send passkey to remote")) {
-                                        connection.writeCharacteristic(
-                                                FastPairService.ID, characteristicUuid,
-                                                encryptedData);
-                                    }
-                                    Log.i(TAG, "Waiting for remote passkey.");
-                                    byte[] encryptedRemotePasskey;
-                                    try (ScopedTiming scopedTiming4 =
-                                            new ScopedTiming(mTimingLogger,
-                                                    "Wait for remote passkey")) {
-                                        encryptedRemotePasskey =
-                                                remotePasskeyObserver.waitForUpdate(
-                                                        TimeUnit.SECONDS.toMillis(mPreferences
-                                                                .getGattOperationTimeoutSeconds()));
-                                    }
-                                    int remotePasskey;
-                                    try (ScopedTiming scopedTiming5 =
-                                            new ScopedTiming(mTimingLogger, "Decrypt passkey")) {
-                                        remotePasskey =
-                                                PasskeyCharacteristic.decrypt(
-                                                        PasskeyCharacteristic.Type.PROVIDER,
-                                                        mKeyBasedPairingInfo.mSecret,
-                                                        encryptedRemotePasskey);
-                                    }
-
-                                    // We log success if we made it through with no exceptions.
-                                    // If the passkey was wrong, pairing will fail and we'll log
-                                    // BOND_BROKEN with reason = AUTH_FAILED.
-                                    mEventLogger.logCurrentEventSucceeded();
-
-                                    boolean isPasskeyCorrect = passkey == remotePasskey;
-                                    if (isPasskeyCorrect) {
-                                        Log.i(TAG, "Passkey correct.");
-                                    } else {
-                                        Log.e(TAG, "Passkey incorrect, local= " + passkey
-                                                + ", remote=" + remotePasskey);
-                                    }
-
-                                    // Don't estimate the {@code ScopedTiming} because the
-                                    // passkey confirmation is done by UI.
-                                    if (isPasskeyCorrect
-                                            && mPreferences.getHandlePasskeyConfirmationByUi()
-                                            && mPasskeyConfirmationHandler != null) {
-                                        Log.i(TAG, "Callback the passkey to UI for confirmation.");
-                                        mPasskeyConfirmationHandler
-                                                .onPasskeyConfirmation(mDevice, passkey);
-                                    } else {
-                                        try (ScopedTiming scopedTiming6 =
-                                                new ScopedTiming(
-                                                        mTimingLogger, "Confirm the pairing: "
-                                                        + isPasskeyCorrect)) {
-                                            mDevice.setPairingConfirmation(isPasskeyCorrect);
-                                        }
-                                    }
-                                } catch (BluetoothException
-                                        | GeneralSecurityException
-                                        | InterruptedException
-                                        | ExecutionException
-                                        | TimeoutException e) {
-                                    mEventLogger.logCurrentEventFailed(e);
-                                    closeWithError(e);
-                                }
-                            });
-        }
-
-        /**
-         * Workaround to let Settings popup a pairing dialog instead of notification. When pairing
-         * request intent passed to Settings, it'll check several conditions to decide that it
-         * should show a dialog or a notification. One of those conditions is to check if the device
-         * is in discovery mode recently, which can be fulfilled by calling {@link
-         * BluetoothAdapter#startDiscovery()}. This method aims to fulfill the condition, and block
-         * the pairing broadcast for at most
-         * {@link BluetoothAudioPairer#DISCOVERY_STATE_CHANGE_TIMEOUT_MS}
-         * to make sure that we fulfill the condition first and successful.
-         */
-        // dereference of possibly-null reference bluetoothAdapter
-        @SuppressWarnings("nullness:dereference.of.nullable")
-        private void triggerDiscoverStateChange() {
-            BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
-            if (bluetoothAdapter.isDiscovering()) {
-                return;
-            }
-
-            HandlerThread backgroundThread = new HandlerThread("TriggerDiscoverStateChangeThread");
-            backgroundThread.start();
-
-            AtomicBoolean result = new AtomicBoolean(false);
-            SimpleBroadcastReceiver receiver =
-                    new SimpleBroadcastReceiver(
-                            mContext,
-                            mPreferences,
-                            new Handler(backgroundThread.getLooper()),
-                            BluetoothAdapter.ACTION_DISCOVERY_STARTED,
-                            BluetoothAdapter.ACTION_DISCOVERY_FINISHED) {
-
-                        @Override
-                        protected void onReceive(Intent intent) throws Exception {
-                            result.set(true);
-                            close();
-                        }
-                    };
-
-            Log.i(TAG, "triggerDiscoverStateChange call startDiscovery.");
-            // Uses startDiscovery to trigger Settings show pairing dialog instead of notification.
-            if (!sTestMode) {
-                bluetoothAdapter.startDiscovery();
-                bluetoothAdapter.cancelDiscovery();
-            }
-            try {
-                receiver.await(DISCOVERY_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                Log.w(TAG, "triggerDiscoverStateChange failed!");
-            }
-
-            backgroundThread.quitSafely();
-            try {
-                backgroundThread.join();
-            } catch (InterruptedException e) {
-                Log.i(TAG, "triggerDiscoverStateChange backgroundThread.join meet exception!", e);
-            }
-
-            if (result.get()) {
-                Log.i(TAG, "triggerDiscoverStateChange successful.");
-            }
-        }
-
-        private void handleBondStateChanged(int bondState, int reason)
-                throws PairingException, InterruptedException, ExecutionException,
-                TimeoutException {
-            Log.i(TAG, "Bond state changed to " + bondState + ", reason=" + reason);
-            switch (bondState) {
-                case BOND_BONDED:
-                    if (mKeyBasedPairingInfo != null && !mReceivedPasskey) {
-                        // The device bonded with Just Works, although we did the Key-based Pairing
-                        // GATT handshake and agreed on a pairing secret. It might be a Person In
-                        // The Middle Attack!
-                        try (ScopedTiming scopedTiming =
-                                new ScopedTiming(mTimingLogger,
-                                        "Close BondedReceiver: POSSIBLE_MITM")) {
-                            closeWithError(
-                                    new CreateBondException(
-                                            CreateBondErrorCode.POSSIBLE_MITM,
-                                            reason,
-                                            "Unexpectedly bonded without a passkey. It might be a "
-                                                    + "Person In The Middle Attack! Unbonding!"));
-                        }
-                        unpair();
-                    } else if (!mPreferences.getWaitForUuidsAfterBonding()
-                            || (mPreferences.getReceiveUuidsAndBondedEventBeforeClose()
-                            && mReceivedUuids)) {
-                        try (ScopedTiming scopedTiming =
-                                new ScopedTiming(mTimingLogger, "Close BondedReceiver")) {
-                            close();
-                        }
-                    }
-                    break;
-                case BOND_NONE:
-                    throw new CreateBondException(
-                            CreateBondErrorCode.BOND_BROKEN, reason, "Bond broken, reason=%d",
-                            reason);
-                case BOND_BONDING:
-                default:
-                    break;
-            }
-        }
-
-        private void handleUuids(Parcelable[] uuids) {
-            Log.i(TAG, "Got UUIDs for " + maskBluetoothAddress(mDevice) + ": "
-                    + Arrays.toString(uuids));
-            mReceivedUuids = true;
-            if (!mPreferences.getReceiveUuidsAndBondedEventBeforeClose() || isPaired()) {
-                try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                        "Close BondedReceiver")) {
-                    close();
-                }
-            }
-        }
-    }
-
-    private class ConnectedReceiver extends DeviceIntentReceiver {
-
-        private ConnectedReceiver(Profile profile) throws ConnectException {
-            super(mContext, mPreferences, mDevice, profile.connectionStateAction);
-        }
-
-        @Override
-        public void onReceiveDeviceIntent(Intent intent) throws PairingException {
-            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, ERROR);
-            Log.i(TAG, "Connection state changed to " + state);
-            switch (state) {
-                case BluetoothAdapter.STATE_CONNECTED:
-                    try (ScopedTiming scopedTiming =
-                            new ScopedTiming(mTimingLogger, "Close ConnectedReceiver")) {
-                        close();
-                    }
-                    break;
-                case BluetoothAdapter.STATE_DISCONNECTED:
-                    throw new ConnectException(ConnectErrorCode.DISCONNECTED, "Disconnected");
-                case BluetoothAdapter.STATE_CONNECTING:
-                case BluetoothAdapter.STATE_DISCONNECTING:
-                default:
-                    break;
-            }
-        }
-    }
-
-    private boolean hasPermission(String permission) {
-        return ContextCompat.checkSelfPermission(mContext, permission) == PERMISSION_GRANTED;
-    }
-
-    public BluetoothDevice getDevice() {
-        return mDevice;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java
deleted file mode 100644
index 6c467d3..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static android.bluetooth.BluetoothDevice.BOND_BONDED;
-import static android.bluetooth.BluetoothDevice.BOND_BONDING;
-import static android.bluetooth.BluetoothDevice.BOND_NONE;
-import static android.bluetooth.BluetoothDevice.ERROR;
-import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import android.annotation.SuppressLint;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.google.common.base.Strings;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Pairs to Bluetooth classic devices with passkey confirmation.
- */
-// TODO(b/202524672): Add class unit test.
-public class BluetoothClassicPairer {
-
-    private static final String TAG = BluetoothClassicPairer.class.getSimpleName();
-    /**
-     * Hidden, see {@link BluetoothDevice}.
-     */
-    private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
-
-    private final Context mContext;
-    private final BluetoothDevice mDevice;
-    private final Preferences mPreferences;
-    private final PasskeyConfirmationHandler mPasskeyConfirmationHandler;
-
-    public BluetoothClassicPairer(
-            Context context,
-            BluetoothDevice device,
-            Preferences preferences,
-            PasskeyConfirmationHandler passkeyConfirmationHandler) {
-        this.mContext = context;
-        this.mDevice = device;
-        this.mPreferences = preferences;
-        this.mPasskeyConfirmationHandler = passkeyConfirmationHandler;
-    }
-
-    /**
-     * Pairs with the device. Throws a {@link PairingException} if any error occurs.
-     */
-    @WorkerThread
-    public void pair() throws PairingException {
-        Log.i(TAG, "BluetoothClassicPairer, createBond with " + maskBluetoothAddress(mDevice)
-                + ", type=" + mDevice.getType());
-        try (BondedReceiver bondedReceiver = new BondedReceiver()) {
-            if (mDevice.createBond()) {
-                bondedReceiver.await(mPreferences.getCreateBondTimeoutSeconds(), SECONDS);
-            } else {
-                throw new PairingException(
-                        "BluetoothClassicPairer, createBond got immediate error");
-            }
-        } catch (TimeoutException | InterruptedException | ExecutionException e) {
-            throw new PairingException("BluetoothClassicPairer, createBond failed", e);
-        }
-    }
-
-    protected boolean isPaired() {
-        return mDevice.getBondState() == BOND_BONDED;
-    }
-
-    /**
-     * Receiver that closes after bonding has completed.
-     */
-    private class BondedReceiver extends DeviceIntentReceiver {
-
-        private BondedReceiver() {
-            super(
-                    mContext,
-                    mPreferences,
-                    mDevice,
-                    BluetoothDevice.ACTION_PAIRING_REQUEST,
-                    BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        }
-
-        /**
-         * Called with ACTION_PAIRING_REQUEST and ACTION_BOND_STATE_CHANGED about the interesting
-         * device (see {@link DeviceIntentReceiver}).
-         *
-         * <p>The ACTION_PAIRING_REQUEST intent provides the passkey which will be sent to the
-         * {@link PasskeyConfirmationHandler} for showing the UI, and the ACTION_BOND_STATE_CHANGED
-         * will provide the result of the bonding.
-         */
-        @Override
-        protected void onReceiveDeviceIntent(Intent intent) {
-            String intentAction = intent.getAction();
-            BluetoothDevice remoteDevice = intent.getParcelableExtra(EXTRA_DEVICE);
-            if (Strings.isNullOrEmpty(intentAction)
-                    || remoteDevice == null
-                    || !remoteDevice.getAddress().equals(mDevice.getAddress())) {
-                Log.w(TAG,
-                        "BluetoothClassicPairer, receives " + intentAction
-                                + " from unexpected device " + maskBluetoothAddress(remoteDevice));
-                return;
-            }
-            switch (intentAction) {
-                case BluetoothDevice.ACTION_PAIRING_REQUEST:
-                    handlePairingRequest(
-                            remoteDevice,
-                            intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR),
-                            intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR));
-                    break;
-                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
-                    handleBondStateChanged(
-                            intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, ERROR),
-                            intent.getIntExtra(EXTRA_REASON, ERROR));
-                    break;
-                default:
-                    break;
-            }
-        }
-
-        private void handlePairingRequest(BluetoothDevice device, int variant, int passkey) {
-            Log.i(TAG,
-                    "BluetoothClassicPairer, pairing request, " + device + ", " + variant + ", "
-                            + passkey);
-            // Prevent Bluetooth Settings from getting the pairing request and showing its own UI.
-            abortBroadcast();
-            mPasskeyConfirmationHandler.onPasskeyConfirmation(device, passkey);
-        }
-
-        private void handleBondStateChanged(int bondState, int reason) {
-            Log.i(TAG,
-                    "BluetoothClassicPairer, bond state changed to " + bondState + ", reason="
-                            + reason);
-            switch (bondState) {
-                case BOND_BONDING:
-                    // Don't close!
-                    return;
-                case BOND_BONDED:
-                    close();
-                    return;
-                case BOND_NONE:
-                default:
-                    closeWithError(
-                            new PairingException(
-                                    "BluetoothClassicPairer, createBond failed, reason:" + reason));
-            }
-        }
-    }
-
-    // Applies UsesPermission annotation will create circular dependency.
-    @SuppressLint("MissingPermission")
-    static void setPairingConfirmation(BluetoothDevice device, boolean confirm) {
-        Log.i(TAG, "BluetoothClassicPairer: setPairingConfirmation " + maskBluetoothAddress(device)
-                + ", confirm: " + confirm);
-        device.setPairingConfirmation(confirm);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java
deleted file mode 100644
index c5475a6..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import java.util.UUID;
-
-/**
- * Utilities for dealing with UUIDs assigned by the Bluetooth SIG. Has a lot in common with
- * com.android.BluetoothUuid, but that class is hidden.
- */
-public class BluetoothUuids {
-
-    /**
-     * The Base UUID is used for calculating 128-bit UUIDs from "short UUIDs" (16- and 32-bit).
-     *
-     * @see {https://www.bluetooth.com/specifications/assigned-numbers/service-discovery}
-     */
-    private static final UUID BASE_UUID = UUID.fromString("00000000-0000-1000-8000-00805F9B34FB");
-
-    /**
-     * Fast Pair custom GATT characteristics 128-bit UUIDs base.
-     *
-     * <p>Notes: The 16-bit value locates at the 3rd and 4th bytes.
-     *
-     * @see {go/fastpair-128bit-gatt}
-     */
-    private static final UUID FAST_PAIR_BASE_UUID =
-            UUID.fromString("FE2C0000-8366-4814-8EB0-01DE32100BEA");
-
-    private static final int BIT_INDEX_OF_16_BIT_UUID = 32;
-
-    private BluetoothUuids() {}
-
-    /**
-     * Returns the 16-bit version of the UUID. If this is not a 16-bit UUID, throws
-     * IllegalArgumentException.
-     */
-    public static short get16BitUuid(UUID uuid) {
-        if (!is16BitUuid(uuid)) {
-            throw new IllegalArgumentException("Not a 16-bit Bluetooth UUID: " + uuid);
-        }
-        return (short) (uuid.getMostSignificantBits() >> BIT_INDEX_OF_16_BIT_UUID);
-    }
-
-    /** Checks whether the UUID is 16 bit */
-    public static boolean is16BitUuid(UUID uuid) {
-        // See Service Discovery Protocol in the Bluetooth Core Specification. Bits at index 32-48
-        // are the 16-bit UUID, and the rest must match the Base UUID.
-        return uuid.getLeastSignificantBits() == BASE_UUID.getLeastSignificantBits()
-                && (uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL)
-                == BASE_UUID.getMostSignificantBits();
-    }
-
-    /** Converts short UUID to 128 bit UUID */
-    public static UUID to128BitUuid(short shortUuid) {
-        return new UUID(
-                ((shortUuid & 0xFFFFL) << BIT_INDEX_OF_16_BIT_UUID)
-                        | BASE_UUID.getMostSignificantBits(), BASE_UUID.getLeastSignificantBits());
-    }
-
-    /** Transfers the 16-bit Fast Pair custom GATT characteristics to 128-bit. */
-    public static UUID toFastPair128BitUuid(short shortUuid) {
-        return new UUID(
-                ((shortUuid & 0xFFFFL) << BIT_INDEX_OF_16_BIT_UUID)
-                        | FAST_PAIR_BASE_UUID.getMostSignificantBits(),
-                FAST_PAIR_BASE_UUID.getLeastSignificantBits());
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java
deleted file mode 100644
index c26c6ad..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/**
- * Constants to share with the cloud syncing process.
- */
-public class BroadcastConstants {
-
-    // TODO: Set right value for AOSP.
-    /** Package name of the cloud syncing logic. */
-    public static final String PACKAGE_NAME = "PACKAGE_NAME";
-    /** Service name of the cloud syncing instance. */
-    public static final String SERVICE_NAME = PACKAGE_NAME + ".SERVICE_NAME";
-    private static final String PREFIX = PACKAGE_NAME + ".PREFIX_NAME.";
-
-    /** Action when a fast pair device is added. */
-    public static final String ACTION_FAST_PAIR_DEVICE_ADDED =
-            PREFIX + "ACTION_FAST_PAIR_DEVICE_ADDED";
-    /**
-     * The BLE address of a device. BLE is used here instead of public because the caller of the
-     * library never knows what the device's public address is.
-     */
-    public static final String EXTRA_ADDRESS = PREFIX + "BLE_ADDRESS";
-    /** The public address of a device. */
-    public static final String EXTRA_PUBLIC_ADDRESS = PREFIX + "PUBLIC_ADDRESS";
-    /** Account key. */
-    public static final String EXTRA_ACCOUNT_KEY = PREFIX + "ACCOUNT_KEY";
-    /** Whether a paring is retroactive. */
-    public static final String EXTRA_RETROACTIVE_PAIR = PREFIX + "EXTRA_RETROACTIVE_PAIR";
-
-    private BroadcastConstants() {
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java
deleted file mode 100644
index 637cd03..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import androidx.annotation.Nullable;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.util.Arrays;
-
-/** Represents a block of bytes, with hashCode and equals. */
-public abstract class Bytes {
-    private static final char[] sHexDigits = "0123456789abcdef".toCharArray();
-    private final byte[] mBytes;
-
-    /**
-     * A logical value consisting of one or more bytes in the given order (little-endian, i.e.
-     * LSO...MSO, or big-endian, i.e. MSO...LSO). E.g. the Fast Pair Model ID is a 3-byte value,
-     * and a Bluetooth device address is a 6-byte value.
-     */
-    public static class Value extends Bytes {
-        private final ByteOrder mByteOrder;
-
-        /**
-         * Constructor.
-         */
-        public Value(byte[] bytes, ByteOrder byteOrder) {
-            super(bytes);
-            this.mByteOrder = byteOrder;
-        }
-
-        /**
-         * Gets bytes.
-         */
-        public byte[] getBytes(ByteOrder byteOrder) {
-            return this.mByteOrder.equals(byteOrder) ? getBytes() : reverse(getBytes());
-        }
-
-        private static byte[] reverse(byte[] bytes) {
-            byte[] reversedBytes = new byte[bytes.length];
-            for (int i = 0; i < bytes.length; i++) {
-                reversedBytes[i] = bytes[bytes.length - i - 1];
-            }
-            return reversedBytes;
-        }
-    }
-
-    Bytes(byte[] bytes) {
-        mBytes = bytes;
-    }
-
-    private static String toHexString(byte[] bytes) {
-        StringBuilder sb = new StringBuilder(2 * bytes.length);
-        for (byte b : bytes) {
-            sb.append(sHexDigits[(b >> 4) & 0xf]).append(sHexDigits[b & 0xf]);
-        }
-        return sb.toString();
-    }
-
-    /** Returns 2-byte values in the same order, each using the given byte order. */
-    public static byte[] toBytes(ByteOrder byteOrder, short... shorts) {
-        ByteBuffer byteBuffer = ByteBuffer.allocate(shorts.length * 2).order(byteOrder);
-        for (short s : shorts) {
-            byteBuffer.putShort(s);
-        }
-        return byteBuffer.array();
-    }
-
-    /** Returns the shorts in the same order, each converted using the given byte order. */
-    static short[] toShorts(ByteOrder byteOrder, byte[] bytes) {
-        ShortBuffer shortBuffer = ByteBuffer.wrap(bytes).order(byteOrder).asShortBuffer();
-        short[] shorts = new short[shortBuffer.remaining()];
-        shortBuffer.get(shorts);
-        return shorts;
-    }
-
-    /** @return The bytes. */
-    public byte[] getBytes() {
-        return mBytes;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof Bytes)) {
-            return false;
-        }
-        Bytes that = (Bytes) o;
-        return Arrays.equals(mBytes, that.mBytes);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mBytes);
-    }
-
-    @Override
-    public String toString() {
-        return toHexString(mBytes);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java
deleted file mode 100644
index 9c8d292..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode;
-
-
-/** Thrown when connecting to a bluetooth device fails. */
-public class ConnectException extends PairingException {
-    final @ConnectErrorCode int mErrorCode;
-
-    ConnectException(@ConnectErrorCode int errorCode, String format, Object... objects) {
-        super(format, objects);
-        this.mErrorCode = errorCode;
-    }
-
-    /** Returns error code. */
-    public @ConnectErrorCode int getErrorCode() {
-        return mErrorCode;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java
deleted file mode 100644
index cfecd2f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java
+++ /dev/null
@@ -1,703 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static android.bluetooth.BluetoothProfile.A2DP;
-import static android.bluetooth.BluetoothProfile.HEADSET;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.toFastPair128BitUuid;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothHeadset;
-import android.util.Log;
-
-import androidx.annotation.IntDef;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.primitives.Shorts;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.util.Random;
-import java.util.UUID;
-
-/**
- * Fast Pair and Transport Discovery Service constants.
- *
- * <p>Unless otherwise specified, these numbers come from
- * {https://www.bluetooth.com/specifications/gatt}.
- */
-public final class Constants {
-
-    /** A2DP sink service uuid. */
-    public static final short A2DP_SINK_SERVICE_UUID = 0x110B;
-
-    /** Headset service uuid. */
-    public static final short HEADSET_SERVICE_UUID = 0x1108;
-
-    /** Hands free sink service uuid. */
-    public static final short HANDS_FREE_SERVICE_UUID = 0x111E;
-
-    /** Bluetooth address length. */
-    public static final int BLUETOOTH_ADDRESS_LENGTH = 6;
-
-    private static final String TAG = Constants.class.getSimpleName();
-
-    /**
-     * Defined by https://developers.google.com/nearby/fast-pair/spec.
-     */
-    public static final class FastPairService {
-
-        /** Fast Pair service UUID. */
-        public static final UUID ID = to128BitUuid((short) 0xFE2C);
-
-        /**
-         * Characteristic to write verification bytes to during the key handshake.
-         */
-        public static final class KeyBasedPairingCharacteristic {
-
-            private static final short SHORT_UUID = 0x1234;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * KeyBasedPairingCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore needs the {@link BluetoothGattConnection} parameter to check the supported
-             * status of the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            /**
-             * Constants related to the decrypted request written to this characteristic.
-             */
-            public static final class Request {
-
-                /**
-                 * The size of this message.
-                 */
-                public static final int SIZE = 16;
-
-                /**
-                 * The index of this message for indicating the type byte.
-                 */
-                public static final int TYPE_INDEX = 0;
-
-                /**
-                 * The index of this message for indicating the flags byte.
-                 */
-                public static final int FLAGS_INDEX = 1;
-
-                /**
-                 * The index of this message for indicating the verification data start from.
-                 */
-                public static final int VERIFICATION_DATA_INDEX = 2;
-
-                /**
-                 * The length of verification data, it is Provider’s current BLE address or public
-                 * address.
-                 */
-                public static final int VERIFICATION_DATA_LENGTH = BLUETOOTH_ADDRESS_LENGTH;
-
-                /**
-                 * The index of this message for indicating the seeker's public address start from.
-                 */
-                public static final int SEEKER_PUBLIC_ADDRESS_INDEX = 8;
-
-                /**
-                 * The index of this message for indicating event group.
-                 */
-                public static final int EVENT_GROUP_INDEX = 8;
-
-                /**
-                 * The index of this message for indicating event code.
-                 */
-                public static final int EVENT_CODE_INDEX = 9;
-
-                /**
-                 * The index of this message for indicating the length of additional data of the
-                 * event.
-                 */
-                public static final int EVENT_ADDITIONAL_DATA_LENGTH_INDEX = 10;
-
-                /**
-                 * The index of this message for indicating the event additional data start from.
-                 */
-                public static final int EVENT_ADDITIONAL_DATA_INDEX = 11;
-
-                /**
-                 * The index of this message for indicating the additional data type used in the
-                 * following Additional Data characteristic.
-                 */
-                public static final int ADDITIONAL_DATA_TYPE_INDEX = 10;
-
-                /**
-                 * The type of this message for Key-based Pairing Request.
-                 */
-                public static final byte TYPE_KEY_BASED_PAIRING_REQUEST = 0x00;
-
-                /**
-                 * The bit indicating that the Fast Pair device should temporarily become
-                 * discoverable.
-                 */
-                public static final byte REQUEST_DISCOVERABLE = (byte) (1 << 7);
-
-                /**
-                 * The bit indicating that the requester (Seeker) has included their public address
-                 * in bytes [7,12] of the request, and the Provider should initiate bonding to that
-                 * address.
-                 */
-                public static final byte PROVIDER_INITIATES_BONDING = (byte) (1 << 6);
-
-                /**
-                 * The bit indicating that Seeker requests Provider shall return the existing name.
-                 */
-                public static final byte REQUEST_DEVICE_NAME = (byte) (1 << 5);
-
-                /**
-                 * The bit to request retroactive pairing.
-                 */
-                public static final byte REQUEST_RETROACTIVE_PAIR = (byte) (1 << 4);
-
-                /**
-                 * The type of this message for action over BLE.
-                 */
-                public static final byte TYPE_ACTION_OVER_BLE = 0x10;
-
-                private Request() {
-                }
-            }
-
-            /**
-             * Enumerates all flags of key-based pairing request.
-             */
-            @Retention(RetentionPolicy.SOURCE)
-            @IntDef(
-                    value = {
-                            KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE,
-                            KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING,
-                            KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME,
-                            KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR,
-                    })
-            public @interface KeyBasedPairingRequestFlag {
-                /**
-                 * The bit indicating that the Fast Pair device should temporarily become
-                 * discoverable.
-                 */
-                int REQUEST_DISCOVERABLE = (byte) (1 << 7);
-                /**
-                 * The bit indicating that the requester (Seeker) has included their public address
-                 * in bytes [7,12] of the request, and the Provider should initiate bonding to that
-                 * address.
-                 */
-                int PROVIDER_INITIATES_BONDING = (byte) (1 << 6);
-                /**
-                 * The bit indicating that Seeker requests Provider shall return the existing name.
-                 */
-                int REQUEST_DEVICE_NAME = (byte) (1 << 5);
-                /**
-                 * The bit indicating that the Seeker request retroactive pairing.
-                 */
-                int REQUEST_RETROACTIVE_PAIR = (byte) (1 << 4);
-            }
-
-            /**
-             * Enumerates all flags of action over BLE request, see Fast Pair spec for details.
-             */
-            @IntDef(
-                    value = {
-                            ActionOverBleFlag.DEVICE_ACTION,
-                            ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC,
-                    })
-            public @interface ActionOverBleFlag {
-                /**
-                 * The bit indicating that the handshaking is for Device Action.
-                 */
-                int DEVICE_ACTION = (byte) (1 << 7);
-                /**
-                 * The bit indicating that this handshake will be followed by Additional Data
-                 * characteristic.
-                 */
-                int ADDITIONAL_DATA_CHARACTERISTIC = (byte) (1 << 6);
-            }
-
-
-            /**
-             * Constants related to the decrypted response sent back in a notify.
-             */
-            public static final class Response {
-
-                /**
-                 * The type of this message = Key-based Pairing Response.
-                 */
-                public static final byte TYPE = 0x01;
-
-                private Response() {
-                }
-            }
-
-            private KeyBasedPairingCharacteristic() {
-            }
-        }
-
-        /**
-         * Characteristic used during Key-based Pairing, to exchange the encrypted passkey.
-         */
-        public static final class PasskeyCharacteristic {
-
-            private static final short SHORT_UUID = 0x1235;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * PasskeyCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore
-             * needs the {@link BluetoothGattConnection} parameter to check the supported status of
-             * the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            /**
-             * The type of the Passkey Block message.
-             */
-            @IntDef(
-                    value = {
-                            Type.SEEKER,
-                            Type.PROVIDER,
-                    })
-            public @interface Type {
-                /**
-                 * Seeker's Passkey.
-                 */
-                int SEEKER = (byte) 0x02;
-                /**
-                 * Provider's Passkey.
-                 */
-                int PROVIDER = (byte) 0x03;
-            }
-
-            /**
-             * Constructs the encrypted value to write to the characteristic.
-             */
-            public static byte[] encrypt(@Type int type, byte[] secret, int passkey)
-                    throws GeneralSecurityException {
-                Preconditions.checkArgument(
-                        0 < passkey && passkey < /*2^24=*/ 16777216,
-                        "Passkey %s must be positive and fit in 3 bytes",
-                        passkey);
-                byte[] passkeyBytes =
-                        new byte[]{(byte) (passkey >>> 16), (byte) (passkey >>> 8), (byte) passkey};
-                byte[] salt =
-                        new byte[AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH - 1
-                                - passkeyBytes.length];
-                new Random().nextBytes(salt);
-                return AesEcbSingleBlockEncryption.encrypt(
-                        secret, concat(new byte[]{(byte) type}, passkeyBytes, salt));
-            }
-
-            /**
-             * Extracts the passkey from the encrypted characteristic value.
-             */
-            public static int decrypt(@Type int type, byte[] secret,
-                    byte[] passkeyCharacteristicValue)
-                    throws GeneralSecurityException {
-                byte[] decrypted = AesEcbSingleBlockEncryption
-                        .decrypt(secret, passkeyCharacteristicValue);
-                if (decrypted[0] != (byte) type) {
-                    throw new GeneralSecurityException(
-                            "Wrong Passkey Block type (expected " + type + ", got "
-                                    + decrypted[0] + ")");
-                }
-                return ByteBuffer.allocate(4)
-                        .put((byte) 0)
-                        .put(decrypted, /*offset=*/ 1, /*length=*/ 3)
-                        .getInt(0);
-            }
-
-            private PasskeyCharacteristic() {
-            }
-        }
-
-        /**
-         * Characteristic to write to during the key exchange.
-         */
-        public static final class AccountKeyCharacteristic {
-
-            private static final short SHORT_UUID = 0x1236;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * AccountKeyCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore
-             * needs the {@link BluetoothGattConnection} parameter to check the supported status of
-             * the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            /**
-             * The type for this message, account key request.
-             */
-            public static final byte TYPE = 0x04;
-
-            private AccountKeyCharacteristic() {
-            }
-        }
-
-        /**
-         * Characteristic to write to and notify on for handling personalized name, see {@link
-         * NamingEncoder}.
-         */
-        public static final class NameCharacteristic {
-
-            private static final short SHORT_UUID = 0x1237;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * NameCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore
-             * needs the {@link BluetoothGattConnection} parameter to check the supported status of
-             * the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            private NameCharacteristic() {
-            }
-        }
-
-        /**
-         * Characteristic to write to and notify on for handling additional data, see
-         * https://developers.google.com/nearby/fast-pair/early-access/spec#AdditionalData
-         */
-        public static final class AdditionalDataCharacteristic {
-
-            private static final short SHORT_UUID = 0x1237;
-
-            public static final int DATA_ID_INDEX = 0;
-            public static final int DATA_LENGTH_INDEX = 1;
-            public static final int DATA_START_INDEX = 2;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * AdditionalDataCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore
-             * needs the {@link BluetoothGattConnection} parameter to check the supported status of
-             * the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            /**
-             * Enumerates all types of additional data.
-             */
-            @Retention(RetentionPolicy.SOURCE)
-            @IntDef(
-                    value = {
-                            AdditionalDataType.PERSONALIZED_NAME,
-                            AdditionalDataType.UNKNOWN,
-                    })
-            public @interface AdditionalDataType {
-                /**
-                 * The value indicating that the type is for personalized name.
-                 */
-                int PERSONALIZED_NAME = (byte) 0x01;
-                int UNKNOWN = (byte) 0x00; // and all others.
-            }
-        }
-
-        /**
-         * Characteristic to control the beaconing feature (FastPair+Eddystone).
-         */
-        public static final class BeaconActionsCharacteristic {
-
-            private static final short SHORT_UUID = 0x1238;
-
-            /**
-             * Gets the new 128-bit UUID of this characteristic.
-             *
-             * <p>Note: For GATT server only. GATT client should use {@link
-             * BeaconActionsCharacteristic#getId(BluetoothGattConnection)}.
-             */
-            public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID);
-
-            /**
-             * Gets the {@link UUID} of this characteristic.
-             *
-             * <p>This method is designed for being backward compatible with old version of UUID
-             * therefore
-             * needs the {@link BluetoothGattConnection} parameter to check the supported status of
-             * the Fast Pair provider.
-             */
-            public static UUID getId(BluetoothGattConnection gattConnection) {
-                return getSupportedUuid(gattConnection, SHORT_UUID);
-            }
-
-            /**
-             * Enumerates all types of beacon actions.
-             */
-            /** Fast Pair Bond State. */
-            @Retention(RetentionPolicy.SOURCE)
-            @IntDef(
-                    value = {
-                            BeaconActionType.READ_BEACON_PARAMETERS,
-                            BeaconActionType.READ_PROVISIONING_STATE,
-                            BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY,
-                            BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY,
-                            BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY,
-                            BeaconActionType.RING,
-                            BeaconActionType.READ_RINGING_STATE,
-                            BeaconActionType.UNKNOWN,
-                    })
-            public @interface BeaconActionType {
-                int READ_BEACON_PARAMETERS = (byte) 0x00;
-                int READ_PROVISIONING_STATE = (byte) 0x01;
-                int SET_EPHEMERAL_IDENTITY_KEY = (byte) 0x02;
-                int CLEAR_EPHEMERAL_IDENTITY_KEY = (byte) 0x03;
-                int READ_EPHEMERAL_IDENTITY_KEY = (byte) 0x04;
-                int RING = (byte) 0x05;
-                int READ_RINGING_STATE = (byte) 0x06;
-                int UNKNOWN = (byte) 0xFF; // and all others
-            }
-
-            /** Converts value to enum. */
-            public static @BeaconActionType int valueOf(byte value) {
-                switch(value) {
-                    case BeaconActionType.READ_BEACON_PARAMETERS:
-                    case BeaconActionType.READ_PROVISIONING_STATE:
-                    case BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY:
-                    case BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY:
-                    case BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY:
-                    case BeaconActionType.RING:
-                    case BeaconActionType.READ_RINGING_STATE:
-                    case BeaconActionType.UNKNOWN:
-                        return value;
-                    default:
-                        return BeaconActionType.UNKNOWN;
-                }
-            }
-        }
-
-
-        /**
-         * Characteristic to read for checking firmware version. 0X2A26 is assigned number from
-         * bluetooth SIG website.
-         */
-        public static final class FirmwareVersionCharacteristic {
-
-            /** UUID for firmware version. */
-            public static final UUID ID = to128BitUuid((short) 0x2A26);
-
-            private FirmwareVersionCharacteristic() {
-            }
-        }
-
-        private FastPairService() {
-        }
-    }
-
-    /**
-     * Defined by the BR/EDR Handover Profile. Pre-release version here:
-     * {https://jfarfel.users.x20web.corp.google.com/Bluetooth%20Handover%20d09.pdf}
-     */
-    public interface TransportDiscoveryService {
-
-        UUID ID = to128BitUuid((short) 0x1824);
-
-        byte BLUETOOTH_SIG_ORGANIZATION_ID = 0x01;
-        byte SERVICE_UUIDS_16_BIT_LIST_TYPE = 0x01;
-        byte SERVICE_UUIDS_32_BIT_LIST_TYPE = 0x02;
-        byte SERVICE_UUIDS_128_BIT_LIST_TYPE = 0x03;
-
-        /**
-         * Writing to this allows you to activate the BR/EDR transport.
-         */
-        interface ControlPointCharacteristic {
-
-            UUID ID = to128BitUuid((short) 0x2ABC);
-            byte ACTIVATE_TRANSPORT_OP_CODE = 0x01;
-        }
-
-        /**
-         * Info necessary to pair (mostly the Bluetooth Address).
-         */
-        interface BrHandoverDataCharacteristic {
-
-            UUID ID = to128BitUuid((short) 0x2C01);
-
-            /**
-             * All bits are reserved for future use.
-             */
-            byte BR_EDR_FEATURES = 0x00;
-        }
-
-        /**
-         * This characteristic exists only to wrap the descriptor.
-         */
-        interface BluetoothSigDataCharacteristic {
-
-            UUID ID = to128BitUuid((short) 0x2C02);
-
-            /**
-             * The entire Transport Block data (e.g. supported Bluetooth services).
-             */
-            interface BrTransportBlockDataDescriptor {
-
-                UUID ID = to128BitUuid((short) 0x2C03);
-            }
-        }
-    }
-
-    public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID =
-            to128BitUuid((short) 0x2902);
-
-    /**
-     * Wrapper for Bluetooth profile
-     */
-    public static class Profile {
-
-        public final int type;
-        public final String name;
-        public final String connectionStateAction;
-
-        private Profile(int type, String name, String connectionStateAction) {
-            this.type = type;
-            this.name = name;
-            this.connectionStateAction = connectionStateAction;
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-    }
-
-    /**
-     * {@link BluetoothHeadset} is used for both Headset and HandsFree (HFP).
-     */
-    private static final Profile HEADSET_AND_HANDS_FREE_PROFILE =
-            new Profile(
-                    HEADSET, "HEADSET_AND_HANDS_FREE",
-                    BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-
-    /** Fast Pair supported profiles. */
-    public static final ImmutableMap<Short, Profile> PROFILES =
-            ImmutableMap.<Short, Profile>builder()
-                    .put(
-                            Constants.A2DP_SINK_SERVICE_UUID,
-                            new Profile(A2DP, "A2DP",
-                                    BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
-                    .put(Constants.HEADSET_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE)
-                    .put(Constants.HANDS_FREE_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE)
-                    .build();
-
-    static short[] getSupportedProfiles() {
-        return Shorts.toArray(PROFILES.keySet());
-    }
-
-    /**
-     * Helper method of getting 128-bit UUID for Fast Pair custom GATT characteristics.
-     *
-     * <p>This method is designed for being backward compatible with old version of UUID therefore
-     * needs the {@link BluetoothGattConnection} parameter to check the supported status of the Fast
-     * Pair provider.
-     *
-     * <p>Note: For new custom GATT characteristics, don't need to use this helper and please just
-     * call {@code toFastPair128BitUuid(shortUuid)} to get the UUID. Which also implies that callers
-     * don't need to provide {@link BluetoothGattConnection} to get the UUID anymore.
-     */
-    private static UUID getSupportedUuid(BluetoothGattConnection gattConnection, short shortUuid) {
-        // In worst case (new characteristic not found), this method's performance impact is about
-        // 6ms
-        // by using Pixel2 + JBL LIVE220. And the impact should be less and less along with more and
-        // more devices adopt the new characteristics.
-        try {
-            // Checks the new UUID first.
-            if (gattConnection
-                    .getCharacteristic(FastPairService.ID, toFastPair128BitUuid(shortUuid))
-                    != null) {
-                Log.d(TAG, "Uses new KeyBasedPairingCharacteristic.ID");
-                return toFastPair128BitUuid(shortUuid);
-            }
-        } catch (BluetoothException e) {
-            Log.d(TAG, "Uses old KeyBasedPairingCharacteristic.ID");
-        }
-        // Returns the old UUID for default.
-        return to128BitUuid(shortUuid);
-    }
-
-    private Constants() {
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java
deleted file mode 100644
index d6aa3b2..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode;
-
-/** Thrown when binding (pairing) with a bluetooth device fails. */
-public class CreateBondException extends PairingException {
-    final @CreateBondErrorCode int mErrorCode;
-    int mReason;
-
-    CreateBondException(@CreateBondErrorCode int errorCode, int reason, String format,
-            Object... objects) {
-        super(format, objects);
-        this.mErrorCode = errorCode;
-        this.mReason = reason;
-    }
-
-    /** Returns error code. */
-    public @CreateBondErrorCode int getErrorCode() {
-        return mErrorCode;
-    }
-
-    /** Returns reason. */
-    public int getReason() {
-        return mReason;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java
deleted file mode 100644
index 5bcf10a..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * Like {@link SimpleBroadcastReceiver}, but for intents about a certain {@link BluetoothDevice}.
- */
-abstract class DeviceIntentReceiver extends SimpleBroadcastReceiver {
-
-    private static final String TAG = DeviceIntentReceiver.class.getSimpleName();
-
-    private final BluetoothDevice mDevice;
-
-    static DeviceIntentReceiver oneShotReceiver(
-            Context context, Preferences preferences, BluetoothDevice device, String... actions) {
-        return new DeviceIntentReceiver(context, preferences, device, actions) {
-            @Override
-            protected void onReceiveDeviceIntent(Intent intent) throws Exception {
-                close();
-            }
-        };
-    }
-
-    /**
-     * @param context The context to use to register / unregister the receiver.
-     * @param device The interesting device. We ignore intents about other devices.
-     * @param actions The actions to include in our intent filter.
-     */
-    protected DeviceIntentReceiver(
-            Context context, Preferences preferences, BluetoothDevice device, String... actions) {
-        super(context, preferences, actions);
-        this.mDevice = device;
-    }
-
-    /**
-     * Called with intents about the interesting device (see {@link #DeviceIntentReceiver}). Any
-     * exception thrown by this method will be delivered via {@link #await}.
-     */
-    protected abstract void onReceiveDeviceIntent(Intent intent) throws Exception;
-
-    // incompatible types in argument.
-    @Override
-    protected void onReceive(Intent intent) throws Exception {
-        BluetoothDevice intentDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        if (mDevice == null || mDevice.equals(intentDevice)) {
-            onReceiveDeviceIntent(intent);
-        } else {
-            Log.v(TAG,
-                    "Ignoring intent for device=" + maskBluetoothAddress(intentDevice)
-                            + "(expected "
-                            + maskBluetoothAddress(mDevice) + ")");
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java
deleted file mode 100644
index dbcdf07..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import androidx.annotation.Nullable;
-
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECGenParameterSpec;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPrivateKeySpec;
-import java.security.spec.ECPublicKeySpec;
-import java.util.Arrays;
-
-import javax.crypto.KeyAgreement;
-
-/**
- * Helper for generating keys based off of the Elliptic-Curve Diffie-Hellman algorithm (ECDH).
- */
-public final class EllipticCurveDiffieHellmanExchange {
-
-    public static final int PUBLIC_KEY_LENGTH = 64;
-    static final int PRIVATE_KEY_LENGTH = 32;
-
-    private static final String[] PROVIDERS = {"GmsCore_OpenSSL", "AndroidOpenSSL", "SC", "BC"};
-
-    private static final String EC_ALGORITHM = "EC";
-
-    /**
-     * Also known as prime256v1 or NIST P-256.
-     */
-    private static final ECGenParameterSpec EC_GEN_PARAMS = new ECGenParameterSpec("secp256r1");
-
-    @Nullable
-    private final ECPublicKey mPublicKey;
-    private final ECPrivateKey mPrivateKey;
-
-    /**
-     * Creates a new EllipticCurveDiffieHellmanExchange object.
-     */
-    public static EllipticCurveDiffieHellmanExchange create() throws GeneralSecurityException {
-        KeyPair keyPair = generateKeyPair();
-        return new EllipticCurveDiffieHellmanExchange(
-                (ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate());
-    }
-
-    /**
-     * Creates a new EllipticCurveDiffieHellmanExchange object.
-     */
-    public static EllipticCurveDiffieHellmanExchange create(byte[] privateKey)
-            throws GeneralSecurityException {
-        ECPrivateKey ecPrivateKey = (ECPrivateKey) generatePrivateKey(privateKey);
-        return new EllipticCurveDiffieHellmanExchange(/*publicKey=*/ null, ecPrivateKey);
-    }
-
-    private EllipticCurveDiffieHellmanExchange(
-            @Nullable ECPublicKey publicKey, ECPrivateKey privateKey) {
-        this.mPublicKey = publicKey;
-        this.mPrivateKey = privateKey;
-    }
-
-    /**
-     * @param otherPublicKey Another party's public key. See {@link #getPublicKey()} for format.
-     * @return The shared secret. Given our public key (and its private key), the other party can
-     * generate the same secret. This is a key meant for symmetric encryption.
-     */
-    public byte[] generateSecret(byte[] otherPublicKey) throws GeneralSecurityException {
-        KeyAgreement agreement = keyAgreement();
-        agreement.init(mPrivateKey);
-        agreement.doPhase(generatePublicKey(otherPublicKey), /*lastPhase=*/ true);
-        byte[] secret = agreement.generateSecret();
-        // Headsets only support AES with 128-bit keys. So, hash the secret so that the entropy is
-        // high and then take only the first 128-bits.
-        secret = MessageDigest.getInstance("SHA-256").digest(secret);
-        return Arrays.copyOf(secret, 16);
-    }
-
-    /**
-     * Returns a public point W on the NIST P-256 elliptic curve. First 32 bytes are the X
-     * coordinate, next 32 bytes are the Y coordinate. Each coordinate is an unsigned big-endian
-     * integer.
-     */
-    public @Nullable byte[] getPublicKey() {
-        if (mPublicKey == null) {
-            return null;
-        }
-        ECPoint w = mPublicKey.getW();
-        // See getPrivateKey for why we're resizing.
-        byte[] x = resizeWithLeadingZeros(w.getAffineX().toByteArray(), 32);
-        byte[] y = resizeWithLeadingZeros(w.getAffineY().toByteArray(), 32);
-        return concat(x, y);
-    }
-
-    /**
-     * Returns a private value S, an unsigned big-endian integer.
-     */
-    public byte[] getPrivateKey() {
-        // Note that BigInteger.toByteArray() returns a signed representation, so it will add an
-        // extra zero byte to the front if the first bit is 1.
-        // We must remove that leading zero (we know the number is unsigned). We must also add
-        // leading zeros if the number is too small.
-        return resizeWithLeadingZeros(mPrivateKey.getS().toByteArray(), 32);
-    }
-
-    /**
-     * Removes or adds leading zeros until we have an array of size {@code n}.
-     */
-    private static byte[] resizeWithLeadingZeros(byte[] x, int n) {
-        if (n < x.length) {
-            int start = x.length - n;
-            for (int i = 0; i < start; i++) {
-                if (x[i] != 0) {
-                    throw new IllegalArgumentException(
-                            "More than " + n + " non-zero bytes in " + Arrays.toString(x));
-                }
-            }
-            return Arrays.copyOfRange(x, start, x.length);
-        }
-        return concat(new byte[n - x.length], x);
-    }
-
-    /**
-     * @param publicKey See {@link #getPublicKey()} for format.
-     */
-    private static PublicKey generatePublicKey(byte[] publicKey) throws GeneralSecurityException {
-        if (publicKey.length != PUBLIC_KEY_LENGTH) {
-            throw new GeneralSecurityException("Public key length incorrect: " + publicKey.length);
-        }
-        byte[] x = Arrays.copyOf(publicKey, publicKey.length / 2);
-        byte[] y = Arrays.copyOfRange(publicKey, publicKey.length / 2, publicKey.length);
-        return keyFactory()
-                .generatePublic(
-                        new ECPublicKeySpec(
-                                new ECPoint(new BigInteger(/*signum=*/ 1, x),
-                                        new BigInteger(/*signum=*/ 1, y)),
-                                ecParameterSpec()));
-    }
-
-    /**
-     * @param privateKey See {@link #getPrivateKey()} for format.
-     */
-    private static PrivateKey generatePrivateKey(byte[] privateKey)
-            throws GeneralSecurityException {
-        if (privateKey.length != PRIVATE_KEY_LENGTH) {
-            throw new GeneralSecurityException("Private key length incorrect: "
-                    + privateKey.length);
-        }
-        return keyFactory()
-                .generatePrivate(
-                        new ECPrivateKeySpec(new BigInteger(/*signum=*/ 1, privateKey),
-                                ecParameterSpec()));
-    }
-
-    private static ECParameterSpec ecParameterSpec() throws GeneralSecurityException {
-        // This seems to be the simplest way to get the curve's ECParameterSpec. Verified that it's
-        // the same whether you get it from the public or private key, and that it's the same as the
-        // raw params in SecAggEcUtil.getNistP256Params().
-        return ((ECPublicKey) generateKeyPair().getPublic()).getParams();
-    }
-
-    private static KeyPair generateKeyPair() throws GeneralSecurityException {
-        KeyPairGenerator generator = findProvider(p -> KeyPairGenerator.getInstance(EC_ALGORITHM,
-                p));
-        generator.initialize(EC_GEN_PARAMS);
-        return generator.generateKeyPair();
-    }
-
-    private static KeyAgreement keyAgreement() throws NoSuchProviderException {
-        return findProvider(p -> KeyAgreement.getInstance("ECDH", p));
-    }
-
-    private static KeyFactory keyFactory() throws NoSuchProviderException {
-        return findProvider(p -> KeyFactory.getInstance(EC_ALGORITHM, p));
-    }
-
-    private interface ProviderConsumer<T> {
-
-        T tryProvider(String provider) throws NoSuchAlgorithmException, NoSuchProviderException;
-    }
-
-    private static <T> T findProvider(ProviderConsumer<T> providerConsumer)
-            throws NoSuchProviderException {
-        for (String provider : PROVIDERS) {
-            try {
-                return providerConsumer.tryProvider(provider);
-            } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
-                // No-op
-            }
-        }
-        throw new NoSuchProviderException();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
deleted file mode 100644
index 0b50dfd..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.bluetooth.BluetoothDevice;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import java.util.Objects;
-
-import javax.annotation.Nullable;
-
-/**
- * Describes events that are happening during fast pairing. EventCode is required, everything else
- * is optional.
- */
-public class Event implements Parcelable {
-
-    private final @EventCode int mEventCode;
-    private final long mTimestamp;
-    private final Short mProfile;
-    private final BluetoothDevice mBluetoothDevice;
-    private final Exception mException;
-
-    private Event(@EventCode int eventCode, long timestamp, @Nullable Short profile,
-            @Nullable BluetoothDevice bluetoothDevice, @Nullable Exception exception) {
-        mEventCode = eventCode;
-        mTimestamp = timestamp;
-        mProfile = profile;
-        mBluetoothDevice = bluetoothDevice;
-        mException = exception;
-    }
-
-    /**
-     * Returns event code.
-     */
-    public @EventCode int getEventCode() {
-        return mEventCode;
-    }
-
-    /**
-     * Returns timestamp.
-     */
-    public long getTimestamp() {
-        return mTimestamp;
-    }
-
-    /**
-     * Returns profile.
-     */
-    @Nullable
-    public Short getProfile() {
-        return mProfile;
-    }
-
-    /**
-     * Returns Bluetooth device.
-     */
-    @Nullable
-    public BluetoothDevice getBluetoothDevice() {
-        return mBluetoothDevice;
-    }
-
-    /**
-     * Returns exception.
-     */
-    @Nullable
-    public Exception getException() {
-        return mException;
-    }
-
-    /**
-     * Returns whether profile is not null.
-     */
-    public boolean hasProfile() {
-        return getProfile() != null;
-    }
-
-    /**
-     * Returns whether Bluetooth device is not null.
-     */
-    public boolean hasBluetoothDevice() {
-        return getBluetoothDevice() != null;
-    }
-
-    /**
-     * Returns a builder.
-     */
-    public static Builder builder() {
-        return new Event.Builder();
-    }
-
-    /**
-     * Returns whether it fails.
-     */
-    public boolean isFailure() {
-        return getException() != null;
-    }
-
-    @Override
-    public String toString() {
-        return "Event{"
-                + "eventCode=" + mEventCode + ", "
-                + "timestamp=" + mTimestamp + ", "
-                + "profile=" + mProfile + ", "
-                + "bluetoothDevice=" + mBluetoothDevice + ", "
-                + "exception=" + mException
-                + "}";
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o instanceof Event) {
-            Event that = (Event) o;
-            return this.mEventCode == that.getEventCode()
-                    && this.mTimestamp == that.getTimestamp()
-                    && (this.mProfile == null
-                        ? that.getProfile() == null : this.mProfile.equals(that.getProfile()))
-                    && (this.mBluetoothDevice == null
-                        ? that.getBluetoothDevice() == null :
-                            this.mBluetoothDevice.equals(that.getBluetoothDevice()))
-                    && (this.mException == null
-                        ?  that.getException() == null :
-                            this.mException.equals(that.getException()));
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException);
-    }
-
-
-    /**
-     * Builder
-     */
-    public static class Builder {
-        private @EventCode int mEventCode;
-        private long mTimestamp;
-        private Short mProfile;
-        private BluetoothDevice mBluetoothDevice;
-        private Exception mException;
-
-        /**
-         * Set event code.
-         */
-        public Builder setEventCode(@EventCode int eventCode) {
-            this.mEventCode = eventCode;
-            return this;
-        }
-
-        /**
-         * Set timestamp.
-         */
-        public Builder setTimestamp(long timestamp) {
-            this.mTimestamp = timestamp;
-            return this;
-        }
-
-        /**
-         * Set profile.
-         */
-        public Builder setProfile(@Nullable Short profile) {
-            this.mProfile = profile;
-            return this;
-        }
-
-        /**
-         * Set Bluetooth device.
-         */
-        public Builder setBluetoothDevice(@Nullable BluetoothDevice device) {
-            this.mBluetoothDevice = device;
-            return this;
-        }
-
-        /**
-         * Set exception.
-         */
-        public Builder setException(@Nullable Exception exception) {
-            this.mException = exception;
-            return this;
-        }
-
-        /**
-         * Builds event.
-         */
-        public Event build() {
-            return new Event(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException);
-        }
-    }
-
-    @Override
-    public final void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(getEventCode());
-        dest.writeLong(getTimestamp());
-        dest.writeValue(getProfile());
-        dest.writeParcelable(getBluetoothDevice(), 0);
-        dest.writeSerializable(getException());
-    }
-
-    @Override
-    public final int describeContents() {
-        return 0;
-    }
-
-    /**
-     * Event Creator instance.
-     */
-    public static final Creator<Event> CREATOR =
-            new Creator<Event>() {
-                @Override
-                /** Creates Event from Parcel. */
-                public Event createFromParcel(Parcel in) {
-                    return Event.builder()
-                            .setEventCode(in.readInt())
-                            .setTimestamp(in.readLong())
-                            .setProfile((Short) in.readValue(Short.class.getClassLoader()))
-                            .setBluetoothDevice(
-                                    in.readParcelable(BluetoothDevice.class.getClassLoader()))
-                            .setException((Exception) in.readSerializable())
-                            .build();
-                }
-
-                @Override
-                /** Returns Event array. */
-                public Event[] newArray(int size) {
-                    return new Event[size];
-                }
-            };
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLogger.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLogger.java
deleted file mode 100644
index 4fc1917..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLogger.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/** Logs events triggered during Fast Pairing. */
-public interface EventLogger {
-
-    /** Log successful event. */
-    void logEventSucceeded(Event event);
-
-    /** Log failed event. */
-    void logEventFailed(Event event, Exception e);
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java
deleted file mode 100644
index 024bfde..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Preferences.ExtraLoggingInformation;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import javax.annotation.Nullable;
-
-/**
- * Convenience wrapper around EventLogger.
- */
-// TODO(b/202559985): cleanup EventLoggerWrapper.
-class EventLoggerWrapper {
-
-    EventLoggerWrapper(@Nullable EventLogger eventLogger) {
-    }
-
-    /**
-     * Binds to the logging service. This operation blocks until binding has completed or timed
-     * out.
-     */
-    void bind(
-            Context context, String address,
-            @Nullable ExtraLoggingInformation extraLoggingInformation) {
-    }
-
-    boolean isBound() {
-        return false;
-    }
-
-    void unbind(Context context) {
-    }
-
-    void setCurrentEvent(@EventCode int code) {
-    }
-
-    void setCurrentProfile(short profile) {
-    }
-
-    void logCurrentEventFailed(Exception e) {
-    }
-
-    void logCurrentEventSucceeded() {
-    }
-
-    void setDevice(@Nullable BluetoothDevice device) {
-    }
-
-    boolean isCurrentEvent() {
-        return false;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
deleted file mode 100644
index c963aa6..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.annotation.WorkerThread;
-import android.bluetooth.BluetoothDevice;
-
-import androidx.annotation.Nullable;
-import androidx.core.util.Consumer;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-/** Abstract class for pairing or connecting via FastPair. */
-public abstract class FastPairConnection {
-    @Nullable protected OnPairedCallback mPairedCallback;
-    @Nullable protected OnGetBluetoothAddressCallback mOnGetBluetoothAddressCallback;
-    @Nullable protected PasskeyConfirmationHandler mPasskeyConfirmationHandler;
-    @Nullable protected FastPairSignalChecker mFastPairSignalChecker;
-    @Nullable protected Consumer<Integer> mRescueFromError;
-    @Nullable protected Runnable mPrepareCreateBondCallback;
-    protected boolean mPasskeyIsGotten;
-
-    /** Sets a callback to be invoked once the device is paired. */
-    public void setOnPairedCallback(OnPairedCallback callback) {
-        this.mPairedCallback = callback;
-    }
-
-    /** Sets a callback to be invoked while the target bluetooth address is decided. */
-    public void setOnGetBluetoothAddressCallback(OnGetBluetoothAddressCallback callback) {
-        this.mOnGetBluetoothAddressCallback = callback;
-    }
-
-    /** Sets a callback to be invoked while handling the passkey confirmation. */
-    public void setPasskeyConfirmationHandler(
-            PasskeyConfirmationHandler passkeyConfirmationHandler) {
-        this.mPasskeyConfirmationHandler = passkeyConfirmationHandler;
-    }
-
-    public void setFastPairSignalChecker(FastPairSignalChecker fastPairSignalChecker) {
-        this.mFastPairSignalChecker = fastPairSignalChecker;
-    }
-
-    public void setRescueFromError(Consumer<Integer> rescueFromError) {
-        this.mRescueFromError = rescueFromError;
-    }
-
-    public void setPrepareCreateBondCallback(Runnable runnable) {
-        this.mPrepareCreateBondCallback = runnable;
-    }
-
-    @VisibleForTesting
-    @Nullable
-    public Runnable getPrepareCreateBondCallback() {
-        return mPrepareCreateBondCallback;
-    }
-
-    /**
-     * Sets the fast pair history for identifying whether or not the provider has paired with the
-     * primary account on other phones before.
-     */
-    @WorkerThread
-    public abstract void setFastPairHistory(List<FastPairHistoryItem> fastPairHistoryItem);
-
-    /** Sets the device name to the Provider. */
-    public abstract void setProviderDeviceName(String deviceName);
-
-    /** Gets the device name from the Provider. */
-    @Nullable
-    public abstract String getProviderDeviceName();
-
-    /**
-     * Gets the existing account key of the Provider.
-     *
-     * @return the existing account key if the Provider has paired with the account, null otherwise
-     */
-    @WorkerThread
-    @Nullable
-    public abstract byte[] getExistingAccountKey();
-
-    /**
-     * Pairs with Provider. Synchronous: Blocks until paired and connected. Throws on any error.
-     *
-     * @return the secret key for the user's account, if written
-     */
-    @WorkerThread
-    @Nullable
-    public abstract SharedSecret pair()
-            throws BluetoothException, InterruptedException, TimeoutException, ExecutionException,
-            PairingException, ReflectionException;
-
-    /**
-     * Pairs with Provider. Synchronous: Blocks until paired and connected. Throws on any error.
-     *
-     * @param key can be in two different formats. If it is 16 bytes long, then it is an AES account
-     *    key. Otherwise, it's a public key generated by {@link EllipticCurveDiffieHellmanExchange}.
-     *    See go/fast-pair-2-spec for how each of these keys are used.
-     * @return the secret key for the user's account, if written
-     */
-    @WorkerThread
-    @Nullable
-    public abstract SharedSecret pair(@Nullable byte[] key)
-            throws BluetoothException, InterruptedException, TimeoutException, ExecutionException,
-            PairingException, GeneralSecurityException, ReflectionException;
-
-    /** Unpairs with Provider. Synchronous: Blocks until unpaired. Throws on any error. */
-    @WorkerThread
-    public abstract void unpair(BluetoothDevice device)
-            throws InterruptedException, TimeoutException, ExecutionException, PairingException,
-            ReflectionException;
-
-    /** Gets the public address of the Provider. */
-    @Nullable
-    public abstract String getPublicAddress();
-
-
-    /** Callback for getting notifications when pairing has completed. */
-    public interface OnPairedCallback {
-        /** Called when the device at address has finished pairing. */
-        void onPaired(String address);
-    }
-
-    /** Callback for getting bluetooth address Bisto oobe need this information */
-    public interface OnGetBluetoothAddressCallback {
-        /** Called when the device has received bluetooth address. */
-        void onGetBluetoothAddress(String address);
-    }
-
-    /** Holds the exchanged secret key and the public mac address of the device. */
-    public static class SharedSecret {
-        private final byte[] mKey;
-        private final String mAddress;
-        private SharedSecret(byte[] key, String address) {
-            mKey = key;
-            mAddress = address;
-        }
-
-        /** Creates Shared Secret. */
-        public static SharedSecret create(byte[] key, String address) {
-            return new SharedSecret(key, address);
-        }
-
-        /** Gets Shared Secret Key. */
-        public byte[] getKey() {
-            return mKey;
-        }
-
-        /** Gets Shared Secret Address. */
-        public String getAddress() {
-            return mAddress;
-        }
-
-        @Override
-        public String toString() {
-            return "SharedSecret{"
-                    + "key=" + Arrays.toString(mKey) + ", "
-                    + "address=" + mAddress
-                    + "}";
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (o instanceof SharedSecret) {
-                SharedSecret that = (SharedSecret) o;
-                return Arrays.equals(this.mKey, that.getKey())
-                        && this.mAddress.equals(that.getAddress());
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(Arrays.hashCode(mKey), mAddress);
-        }
-    }
-
-    /** Invokes if gotten the passkey. */
-    public void setPasskeyIsGotten() {
-        mPasskeyIsGotten = true;
-    }
-
-    /** Returns the value of passkeyIsGotten. */
-    public boolean getPasskeyIsGotten() {
-        return mPasskeyIsGotten;
-    }
-
-    /** Interface to get latest address of ModelId. */
-    public interface FastPairSignalChecker {
-        /** Gets address of ModelId. */
-        String getValidAddressForModelId(String currentDevice);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java
deleted file mode 100644
index 0ff1bf2..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.bluetooth.BluetoothDevice;
-
-/** Constants to share with other team. */
-public class FastPairConstants {
-    private static final String PACKAGE_NAME = "com.android.server.nearby";
-    private static final String PREFIX = PACKAGE_NAME + ".common.bluetooth.fastpair.";
-
-    /** MODEL_ID item name for extended intent field. */
-    public static final String EXTRA_MODEL_ID = PREFIX + "MODEL_ID";
-    /** CONNECTION_ID item name for extended intent field. */
-    public static final String EXTRA_CONNECTION_ID = PREFIX + "CONNECTION_ID";
-    /** BLUETOOTH_MAC_ADDRESS item name for extended intent field. */
-    public static final String EXTRA_BLUETOOTH_MAC_ADDRESS = PREFIX + "BLUETOOTH_MAC_ADDRESS";
-    /** COMPANION_SCAN_ITEM item name for extended intent field. */
-    public static final String EXTRA_SCAN_ITEM = PREFIX + "COMPANION_SCAN_ITEM";
-    /** BOND_RESULT item name for extended intent field. */
-    public static final String EXTRA_BOND_RESULT = PREFIX + "EXTRA_BOND_RESULT";
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means device is BONDED but the pairing process is not triggered by FastPair.
-     */
-    public static final int BOND_RESULT_SUCCESS_WITHOUT_FP = 0;
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means device is BONDED and the pairing process is triggered by FastPair.
-     */
-    public static final int BOND_RESULT_SUCCESS_WITH_FP = 1;
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means the pairing process triggered by FastPair is failed due to the lack of PIN code.
-     */
-    public static final int BOND_RESULT_FAIL_WITH_FP_WITHOUT_PIN = 2;
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means the pairing process triggered by FastPair is failed due to the PIN code is not
-     * confirmed by the user.
-     */
-    public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_NOT_CONFIRMED = 3;
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means the pairing process triggered by FastPair is failed due to the user thinks the PIN is
-     * wrong.
-     */
-    public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_WRONG = 4;
-
-    /**
-     * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it
-     * means the pairing process triggered by FastPair is failed even after the user confirmed the
-     * PIN code is correct.
-     */
-    public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_CORRECT = 5;
-
-    private FastPairConstants() {}
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
deleted file mode 100644
index 789ef59..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ /dev/null
@@ -1,2127 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static android.bluetooth.BluetoothDevice.BOND_BONDED;
-import static android.bluetooth.BluetoothDevice.BOND_BONDING;
-import static android.bluetooth.BluetoothDevice.BOND_NONE;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
-import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes;
-import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Verify.verifyNotNull;
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.primitives.Bytes.concat;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.ParcelUuid;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.annotation.WorkerThread;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException;
-import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAudioPairer.KeyBasedPairingInfo;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService;
-import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.ActionOverBle;
-import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeException;
-import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeMessage;
-import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.KeyBasedPairingRequest;
-import com.android.server.nearby.common.bluetooth.fastpair.Ltv.ParseException;
-import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.FastPairController;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.BrEdrHandoverErrorCode;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import com.google.common.base.Ascii;
-import com.google.common.base.Preconditions;
-import com.google.common.primitives.Shorts;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteOrder;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Supports Fast Pair pairing with certain Bluetooth headphones, Auto, etc.
- *
- * <p>Based on https://developers.google.com/nearby/fast-pair/spec, the pairing is constructed by
- * both BLE and BREDR connections. Example state transitions for Fast Pair 2, ie a pairing key is
- * included in the request (note: timeouts and retries are governed by flags, may change):
- *
- * <pre>
- * {@code
- *   Connect GATT
- *     A) Success -> Handshake
- *     B) Failure (3s timeout) -> Retry 2x -> end
- *
- *   Handshake
- *     A) Generate a shared secret with the headset (either using anti-spoofing key or account key)
- *       1) Account key is used directly as the key
- *       2) Anti-spoofing key is used by combining out private key with the headset's public and
- *          sending our public to the headset to combine with their private to generate a shared
- *          key. Sending our public key to headset takes ~3s.
- *     B) Write an encrypted packet to the headset containing their BLE address for verification
- *        that both sides have the same key (headset decodes this packet and checks it against their
- *        own address) (~250ms).
- *     C) Receive a response from the headset containing their public address (~250ms).
- *
- *   Discovery (for devices < Oreo)
- *     A) Success -> Create Bond
- *     B) Failure (10s timeout) -> Sleep 1s, Retry 3x -> end
- *
- *   Connect to device
- *     A) If already bonded
- *       1) Attempt directly connecting to supported profiles (A2DP, etc)
- *         a) Success -> Write Account Key
- *         b) Failure (15s timeout, usually fails within a ~2s) -> Remove bond (~1s) -> Create bond
- *     B) If not already bonded
- *       1) Create bond
- *         a) Success -> Connect profile
- *         b) Failure (15s timeout) -> Retry 2x -> end
- *       2) Connect profile
- *         a) Success -> Write account key
- *         b) Failure -> Retry -> end
- *
- *   Write account key
- *     A) Callback that pairing succeeded
- *     B) Disconnect GATT
- *     C) Reconnect GATT for secure connection
- *     D) Write account key (~3s)
- * }
- * </pre>
- *
- * The performance profiling result by {@link TimingLogger}:
- *
- * <pre>
- *   FastPairDualConnection [Exclusive time] / [Total time] ([Timestamp])
- *     Connect GATT #1 3054ms (0)
- *     Handshake 32ms / 740ms (3054)
- *       Generate key via ECDH 10ms (3054)
- *       Add salt 1ms (3067)
- *       Encrypt request 3ms (3068)
- *       Write data to GATT 692ms (3097)
- *       Wait response from GATT 0ms (3789)
- *       Decrypt response 2ms (3789)
- *     Get BR/EDR handover information via SDP 1ms (3795)
- *     Pair device #1 6ms / 4887ms (3805)
- *       Create bond 3965ms / 4881ms (3809)
- *         Exchange passkey 587ms / 915ms (7124)
- *           Encrypt passkey 6ms (7694)
- *           Send passkey to remote 290ms (7700)
- *           Wait for remote passkey 0ms (7993)
- *           Decrypt passkey 18ms (7994)
- *           Confirm the pairing: true 14ms (8025)
- *         Close BondedReceiver 1ms (8688)
- *     Connect: A2DP 19ms / 370ms (8701)
- *       Wait connection 348ms / 349ms (8720)
- *         Close ConnectedReceiver 1ms (9068)
- *       Close profile: A2DP 2ms (9069)
- *     Write account key 2ms / 789ms (9163)
- *       Encrypt key 0ms (9164)
- *       Write key via GATT #1 777ms / 783ms (9164)
- *         Close GATT 6ms (9941)
- *       Start CloudSyncing 2ms (9947)
- *       Broadcast Validator 2ms (9949)
- *   FastPairDualConnection end, 9952ms
- * </pre>
- */
-// TODO(b/203441105): break down FastPairDualConnection into smaller classes.
-public class FastPairDualConnection extends FastPairConnection {
-
-    private static final String TAG = FastPairDualConnection.class.getSimpleName();
-
-    @VisibleForTesting
-    static final int GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST = 10000;
-    @VisibleForTesting
-    static final int GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED = 20000;
-    @VisibleForTesting
-    static final int GATT_ERROR_CODE_USER_RETRY = 30000;
-    @VisibleForTesting
-    static final int GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT = 40000;
-    @VisibleForTesting
-    static final int GATT_ERROR_CODE_TIMEOUT = 1000;
-
-    @Nullable
-    private static String sInitialConnectionFirmwareVersion;
-    private static final byte[] REQUESTED_SERVICES_LTV =
-            new Ltv(
-                    TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE,
-                    toBytes(
-                            ByteOrder.LITTLE_ENDIAN,
-                            Constants.A2DP_SINK_SERVICE_UUID,
-                            Constants.HANDS_FREE_SERVICE_UUID,
-                            Constants.HEADSET_SERVICE_UUID))
-                    .getBytes();
-    private static final byte[] TDS_CONTROL_POINT_REQUEST =
-            concat(
-                    new byte[]{
-                            TransportDiscoveryService.ControlPointCharacteristic
-                                    .ACTIVATE_TRANSPORT_OP_CODE,
-                            TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID
-                    },
-                    REQUESTED_SERVICES_LTV);
-
-    private static boolean sTestMode = false;
-
-    static void enableTestMode() {
-        sTestMode = true;
-    }
-
-    /**
-     * Operation Result Code.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    ResultCode.UNKNOWN,
-                    ResultCode.SUCCESS,
-                    ResultCode.OP_CODE_NOT_SUPPORTED,
-                    ResultCode.INVALID_PARAMETER,
-                    ResultCode.UNSUPPORTED_ORGANIZATION_ID,
-                    ResultCode.OPERATION_FAILED,
-            })
-
-    public @interface ResultCode {
-
-        int UNKNOWN = (byte) 0xFF;
-        int SUCCESS = (byte) 0x00;
-        int OP_CODE_NOT_SUPPORTED = (byte) 0x01;
-        int INVALID_PARAMETER = (byte) 0x02;
-        int UNSUPPORTED_ORGANIZATION_ID = (byte) 0x03;
-        int OPERATION_FAILED = (byte) 0x04;
-    }
-
-
-    private static @ResultCode int fromTdsControlPointIndication(byte[] response) {
-        return response == null || response.length < 2 ? ResultCode.UNKNOWN : from(response[1]);
-    }
-
-    private static @ResultCode int from(byte byteValue) {
-        switch (byteValue) {
-            case ResultCode.UNKNOWN:
-            case ResultCode.SUCCESS:
-            case ResultCode.OP_CODE_NOT_SUPPORTED:
-            case ResultCode.INVALID_PARAMETER:
-            case ResultCode.UNSUPPORTED_ORGANIZATION_ID:
-            case ResultCode.OPERATION_FAILED:
-                return byteValue;
-            default:
-                return ResultCode.UNKNOWN;
-        }
-    }
-
-    private static class BrEdrHandoverInformation {
-
-        private final byte[] mBluetoothAddress;
-        private final short[] mProfiles;
-
-        private BrEdrHandoverInformation(byte[] bluetoothAddress, short[] profiles) {
-            this.mBluetoothAddress = bluetoothAddress;
-
-            // For now, since we only connect to one profile, prefer A2DP Sink over headset/HFP.
-            // TODO(b/37167120): Connect to more than one profile.
-            Set<Short> profileSet = new HashSet<>(Shorts.asList(profiles));
-            if (profileSet.contains(Constants.A2DP_SINK_SERVICE_UUID)) {
-                profileSet.remove(Constants.HEADSET_SERVICE_UUID);
-                profileSet.remove(Constants.HANDS_FREE_SERVICE_UUID);
-            }
-            this.mProfiles = Shorts.toArray(profileSet);
-        }
-
-        @Override
-        public String toString() {
-            return "BrEdrHandoverInformation{"
-                    + maskBluetoothAddress(BluetoothAddress.encode(mBluetoothAddress))
-                    + ", profiles="
-                    + (mProfiles.length > 0 ? Shorts.join(",", mProfiles) : "(none)")
-                    + "}";
-        }
-    }
-
-    private final Context mContext;
-    private final Preferences mPreferences;
-    private final EventLoggerWrapper mEventLogger;
-    private final BluetoothAdapter mBluetoothAdapter =
-            checkNotNull(BluetoothAdapter.getDefaultAdapter());
-    private String mBleAddress;
-
-    private final TimingLogger mTimingLogger;
-    private GattConnectionManager mGattConnectionManager;
-    private boolean mProviderInitiatesBonding;
-    private @Nullable
-    byte[] mPairingSecret;
-    private @Nullable
-    byte[] mPairingKey;
-    @Nullable
-    private String mPublicAddress;
-    @VisibleForTesting
-    @Nullable
-    FastPairHistoryFinder mPairedHistoryFinder;
-    @Nullable
-    private String mProviderDeviceName = null;
-    private boolean mNeedUpdateProviderName = false;
-    @Nullable
-    DeviceNameReceiver mDeviceNameReceiver;
-    @Nullable
-    private HandshakeHandler mHandshakeHandlerForTest;
-    @Nullable
-    private Runnable mBeforeDirectlyConnectProfileFromCacheForTest;
-
-    public FastPairDualConnection(
-            Context context,
-            String bleAddress,
-            Preferences preferences,
-            @Nullable EventLogger eventLogger) {
-        this(context, bleAddress, preferences, eventLogger,
-                new TimingLogger("FastPairDualConnection", preferences));
-    }
-
-    @VisibleForTesting
-    FastPairDualConnection(
-            Context context,
-            String bleAddress,
-            Preferences preferences,
-            @Nullable EventLogger eventLogger,
-            TimingLogger timingLogger) {
-        this.mContext = context;
-        this.mPreferences = preferences;
-        this.mEventLogger = new EventLoggerWrapper(eventLogger);
-        this.mBleAddress = bleAddress;
-        this.mTimingLogger = timingLogger;
-    }
-
-    /**
-     * Unpairs with headphones. Synchronous: Blocks until unpaired. Throws on any error.
-     */
-    @WorkerThread
-    public void unpair(BluetoothDevice device)
-            throws ReflectionException, InterruptedException, ExecutionException, TimeoutException,
-            PairingException {
-        if (mPreferences.getExtraLoggingInformation() != null) {
-            mEventLogger
-                    .bind(mContext, device.getAddress(), mPreferences.getExtraLoggingInformation());
-        }
-        new BluetoothAudioPairer(
-                mContext,
-                device,
-                mPreferences,
-                mEventLogger,
-                /* keyBasedPairingInfo= */ null,
-                /* passkeyConfirmationHandler= */ null,
-                mTimingLogger)
-                .unpair();
-        if (mEventLogger.isBound()) {
-            mEventLogger.unbind(mContext);
-        }
-    }
-
-    /**
-     * Sets the fast pair history for identifying the provider which has paired (without being
-     * forgotten) with the primary account on the device, i.e. the history is not limited on this
-     * phone, can be on other phones with the same account. If they have already paired, Fast Pair
-     * should not generate new account key and default personalized name for it after initial pair.
-     */
-    @WorkerThread
-    public void setFastPairHistory(List<FastPairHistoryItem> fastPairHistoryItem) {
-        Log.i(TAG, "Paired history has been set.");
-        this.mPairedHistoryFinder = new FastPairHistoryFinder(fastPairHistoryItem);
-    }
-
-    /**
-     * Update the provider device name when we take provider default name and account based name
-     * into consideration.
-     */
-    public void setProviderDeviceName(String deviceName) {
-        Log.i(TAG, "Update provider device name = " + deviceName);
-        mProviderDeviceName = deviceName;
-        mNeedUpdateProviderName = true;
-    }
-
-    /**
-     * Gets the device name from the Provider (via GATT notify).
-     */
-    @Nullable
-    public String getProviderDeviceName() {
-        if (mDeviceNameReceiver == null) {
-            Log.i(TAG, "getProviderDeviceName failed, deviceNameReceiver == null.");
-            return null;
-        }
-        if (mPairingSecret == null) {
-            Log.i(TAG, "getProviderDeviceName failed, pairingSecret == null.");
-            return null;
-        }
-        String deviceName = mDeviceNameReceiver.getParsedResult(mPairingSecret);
-        Log.i(TAG, "getProviderDeviceName = " + deviceName);
-
-        return deviceName;
-    }
-
-    /**
-     * Get the existing account key of the provider, this API can be called after handshake.
-     *
-     * @return the existing account key if the provider has paired with the account before.
-     * Otherwise, return null, i.e. it is a real initial pairing.
-     */
-    @WorkerThread
-    @Nullable
-    public byte[] getExistingAccountKey() {
-        return mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey();
-    }
-
-    /**
-     * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error.
-     *
-     * @return the secret key for the user's account, if written.
-     */
-    @WorkerThread
-    @Nullable
-    public SharedSecret pair()
-            throws BluetoothException, InterruptedException, ReflectionException, TimeoutException,
-            ExecutionException, PairingException {
-        try {
-            return pair(/*key=*/ null);
-        } catch (GeneralSecurityException e) {
-            throw new RuntimeException("Should never happen, no security key!", e);
-        }
-    }
-
-    /**
-     * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error.
-     *
-     * @param key can be in two different formats. If it is 16 bytes long, then it is an AES account
-     * key. Otherwise, it's a public key generated by {@link EllipticCurveDiffieHellmanExchange}.
-     * See go/fast-pair-2-spec for how each of these keys are used.
-     * @return the secret key for the user's account, if written
-     */
-    @WorkerThread
-    @Nullable
-    public SharedSecret pair(@Nullable byte[] key)
-            throws BluetoothException, InterruptedException, ReflectionException, TimeoutException,
-            ExecutionException, PairingException, GeneralSecurityException {
-        mPairingKey = key;
-        if (key != null) {
-            Log.i(TAG, "Starting to pair " + maskBluetoothAddress(mBleAddress) + ": key["
-                    + key.length + "], " + mPreferences);
-        } else {
-            Log.i(TAG, "Pairing " + maskBluetoothAddress(mBleAddress) + ": " + mPreferences);
-        }
-        if (mPreferences.getExtraLoggingInformation() != null) {
-            this.mEventLogger.bind(
-                    mContext, mBleAddress, mPreferences.getExtraLoggingInformation());
-        }
-        // Provider never initiates if key is null (Fast Pair 1.0).
-        if (key != null && mPreferences.getProviderInitiatesBondingIfSupported()) {
-            // Provider can't initiate if we can't get our own public address, so check.
-            this.mEventLogger.setCurrentEvent(EventCode.GET_LOCAL_PUBLIC_ADDRESS);
-            if (BluetoothAddress.getPublicAddress(mContext) != null) {
-                this.mEventLogger.logCurrentEventSucceeded();
-                mProviderInitiatesBonding = true;
-            } else {
-                this.mEventLogger
-                        .logCurrentEventFailed(new IllegalStateException("null bluetooth_address"));
-                Log.e(TAG,
-                        "Want provider to initiate bonding, but cannot access Bluetooth public "
-                                + "address. Falling back to initiating bonding ourselves.");
-            }
-        }
-
-        // User might be pairing with a bonded device. In this case, we just connect profile
-        // directly and finish pairing.
-        if (directConnectProfileWithCachedAddress()) {
-            callbackOnPaired();
-            mTimingLogger.dump();
-            if (mEventLogger.isBound()) {
-                mEventLogger.unbind(mContext);
-            }
-            return null;
-        }
-
-        // Lazily initialize a new connection manager for each pairing request.
-        initGattConnectionManager();
-        boolean isSecretHandshakeCompleted = true;
-
-        try {
-            if (key != null && key.length > 0) {
-                // GATT_CONNECTION_AND_SECRET_HANDSHAKE start.
-                mEventLogger.setCurrentEvent(EventCode.GATT_CONNECTION_AND_SECRET_HANDSHAKE);
-                isSecretHandshakeCompleted = false;
-                Exception lastException = null;
-                boolean lastExceptionFromHandshake = false;
-                long startTime = SystemClock.elapsedRealtime();
-                // We communicate over this connection twice for Key-based Pairing: once before
-                // bonding begins, and once during (to transfer the passkey). Empirically, keeping
-                // it alive throughout is far more reliable than disconnecting and reconnecting for
-                // each step. The while loop is for retry of GATT connection and handshake only.
-                do {
-                    boolean isHandshaking = false;
-                    try (BluetoothGattConnection connection =
-                            mGattConnectionManager
-                                    .getConnectionWithSignalLostCheck(mRescueFromError)) {
-                        mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE);
-                        if (lastException != null && !lastExceptionFromHandshake) {
-                            logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_GATT, lastException,
-                                    mEventLogger);
-                            lastException = null;
-                        }
-                        try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                                "Handshake")) {
-                            isHandshaking = true;
-                            handshakeForKeyBasedPairing(key);
-                            // After handshake, Fast Pair has the public address of the provider, so
-                            // we can check if it has paired with the account.
-                            if (mPublicAddress != null && mPairedHistoryFinder != null) {
-                                if (mPairedHistoryFinder.isInPairedHistory(mPublicAddress)) {
-                                    Log.i(TAG, "The provider is found in paired history.");
-                                } else {
-                                    Log.i(TAG, "The provider is not found in paired history.");
-                                }
-                            }
-                        }
-                        isHandshaking = false;
-                        // SECRET_HANDSHAKE end.
-                        mEventLogger.logCurrentEventSucceeded();
-                        isSecretHandshakeCompleted = true;
-                        if (mPrepareCreateBondCallback != null) {
-                            mPrepareCreateBondCallback.run();
-                        }
-                        if (lastException != null && lastExceptionFromHandshake) {
-                            logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE_RECONNECT,
-                                    lastException, mEventLogger);
-                        }
-                        logManualRetryCounts(/* success= */ true);
-                        // GATT_CONNECTION_AND_SECRET_HANDSHAKE end.
-                        mEventLogger.logCurrentEventSucceeded();
-                        return pair(mPreferences.getEnableBrEdrHandover());
-                    } catch (SignalLostException e) {
-                        long spentTime = SystemClock.elapsedRealtime() - startTime;
-                        if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) {
-                            Log.w(TAG, "Signal lost but already spend too much time " + spentTime
-                                    + "ms");
-                            throw e;
-                        }
-
-                        logCurrentEventFailedBySignalLost(e);
-                        lastException = (Exception) e.getCause();
-                        lastExceptionFromHandshake = isHandshaking;
-                        if (mRescueFromError != null && isHandshaking) {
-                            mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT);
-                        }
-                        Log.i(TAG, "Signal lost, retry");
-                        // In case we meet some GATT error which is not recoverable and fail very
-                        // quick.
-                        SystemClock.sleep(mPreferences.getPairingRetryDelayMs());
-                    } catch (SignalRotatedException e) {
-                        long spentTime = SystemClock.elapsedRealtime() - startTime;
-                        if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) {
-                            Log.w(TAG, "Address rotated but already spend too much time "
-                                    + spentTime + "ms");
-                            throw e;
-                        }
-
-                        logCurrentEventFailedBySignalRotated(e);
-                        setBleAddress(e.getNewAddress());
-                        lastException = (Exception) e.getCause();
-                        lastExceptionFromHandshake = isHandshaking;
-                        if (mRescueFromError != null) {
-                            mRescueFromError.accept(ErrorCode.SUCCESS_ADDRESS_ROTATE);
-                        }
-                        Log.i(TAG, "Address rotated, retry");
-                    } catch (HandshakeException e) {
-                        long spentTime = SystemClock.elapsedRealtime() - startTime;
-                        if (spentTime > mPreferences
-                                .getSecretHandshakeRetryGattConnectionMaxSpentTimeMs()) {
-                            Log.w(TAG, "Secret handshake failed but already spend too much time "
-                                    + spentTime + "ms");
-                            throw e.getOriginalException();
-                        }
-                        if (mEventLogger.isCurrentEvent()) {
-                            mEventLogger.logCurrentEventFailed(e.getOriginalException());
-                        }
-                        initGattConnectionManager();
-                        lastException = e.getOriginalException();
-                        lastExceptionFromHandshake = true;
-                        if (mRescueFromError != null) {
-                            mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT);
-                        }
-                        Log.i(TAG, "Handshake failed, retry GATT connection");
-                    }
-                } while (mPreferences.getRetryGattConnectionAndSecretHandshake());
-            }
-            if (mPrepareCreateBondCallback != null) {
-                mPrepareCreateBondCallback.run();
-            }
-            return pair(mPreferences.getEnableBrEdrHandover());
-        } catch (SignalLostException e) {
-            logCurrentEventFailedBySignalLost(e);
-            // GATT_CONNECTION_AND_SECRET_HANDSHAKE end.
-            if (!isSecretHandshakeCompleted) {
-                logManualRetryCounts(/* success= */ false);
-                logCurrentEventFailedBySignalLost(e);
-            }
-            throw e;
-        } catch (SignalRotatedException e) {
-            logCurrentEventFailedBySignalRotated(e);
-            // GATT_CONNECTION_AND_SECRET_HANDSHAKE end.
-            if (!isSecretHandshakeCompleted) {
-                logManualRetryCounts(/* success= */ false);
-                logCurrentEventFailedBySignalRotated(e);
-            }
-            throw e;
-        } catch (BluetoothException
-                | InterruptedException
-                | ReflectionException
-                | TimeoutException
-                | ExecutionException
-                | PairingException
-                | GeneralSecurityException e) {
-            if (mEventLogger.isCurrentEvent()) {
-                mEventLogger.logCurrentEventFailed(e);
-            }
-            // GATT_CONNECTION_AND_SECRET_HANDSHAKE end.
-            if (!isSecretHandshakeCompleted) {
-                logManualRetryCounts(/* success= */ false);
-                if (mEventLogger.isCurrentEvent()) {
-                    mEventLogger.logCurrentEventFailed(e);
-                }
-            }
-            throw e;
-        } finally {
-            mTimingLogger.dump();
-            if (mEventLogger.isBound()) {
-                mEventLogger.unbind(mContext);
-            }
-        }
-    }
-
-    private boolean directConnectProfileWithCachedAddress() throws ReflectionException {
-        if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress())
-                || !mPreferences.getDirectConnectProfileIfModelIdInCache()
-                || mPreferences.getSkipConnectingProfiles()) {
-            return false;
-        }
-        Log.i(TAG, "Try to direct connect profile with cached address "
-                + maskBluetoothAddress(mPreferences.getCachedDeviceAddress()));
-        mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS);
-        BluetoothDevice device =
-                mBluetoothAdapter.getRemoteDevice(mPreferences.getCachedDeviceAddress()).unwrap();
-        AtomicBoolean interruptConnection = new AtomicBoolean(false);
-        BroadcastReceiver receiver =
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (intent == null
-                                || !BluetoothDevice.ACTION_PAIRING_REQUEST
-                                .equals(intent.getAction())) {
-                            return;
-                        }
-                        BluetoothDevice pairingDevice = intent
-                                .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                        if (pairingDevice == null || !device.getAddress()
-                                .equals(pairingDevice.getAddress())) {
-                            return;
-                        }
-                        abortBroadcast();
-                        // Should be the clear link key case, make it fail directly to go back to
-                        // initial pairing process.
-                        pairingDevice.setPairingConfirmation(/* confirm= */ false);
-                        Log.w(TAG, "Get pairing request broadcast for device "
-                                + maskBluetoothAddress(device.getAddress())
-                                + " while try to direct connect profile with cached address, reject"
-                                + " and to go back to initial pairing process");
-                        interruptConnection.set(true);
-                    }
-                };
-        mContext.registerReceiver(receiver,
-                new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST));
-        try (ScopedTiming scopedTiming =
-                new ScopedTiming(mTimingLogger,
-                        "Connect to profile with cached address directly")) {
-            if (mBeforeDirectlyConnectProfileFromCacheForTest != null) {
-                mBeforeDirectlyConnectProfileFromCacheForTest.run();
-            }
-            attemptConnectProfiles(
-                    new BluetoothAudioPairer(
-                            mContext,
-                            device,
-                            mPreferences,
-                            mEventLogger,
-                            /* keyBasedPairingInfo= */ null,
-                            /* passkeyConfirmationHandler= */ null,
-                            mTimingLogger),
-                    maskBluetoothAddress(device),
-                    getSupportedProfiles(device),
-                    /* numConnectionAttempts= */ 1,
-                    /* enablePairingBehavior= */ false,
-                    interruptConnection);
-            Log.i(TAG,
-                    "Directly connected to " + maskBluetoothAddress(device)
-                            + "with cached address.");
-            mEventLogger.logCurrentEventSucceeded();
-            mEventLogger.setDevice(device);
-            logPairWithPossibleCachedAddress(device.getAddress());
-            return true;
-        } catch (PairingException e) {
-            if (interruptConnection.get()) {
-                Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device)
-                        + " with cached address due to link key is cleared.", e);
-                mEventLogger.logCurrentEventFailed(
-                        new ConnectException(ConnectErrorCode.LINK_KEY_CLEARED,
-                                "Link key is cleared"));
-            } else {
-                Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device)
-                        + " with cached address.", e);
-                mEventLogger.logCurrentEventFailed(e);
-            }
-            return false;
-        } finally {
-            mContext.unregisterReceiver(receiver);
-        }
-    }
-
-    /**
-     * Logs for user retry, check go/fastpairquality21q3 for more details.
-     */
-    private void logManualRetryCounts(boolean success) {
-        if (!mPreferences.getLogUserManualRetry()) {
-            return;
-        }
-
-        // We don't want to be the final event on analytics.
-        if (!mEventLogger.isCurrentEvent()) {
-            return;
-        }
-
-        mEventLogger.setCurrentEvent(EventCode.GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS);
-        if (mPreferences.getPairFailureCounts() <= 0 && success) {
-            mEventLogger.logCurrentEventSucceeded();
-        } else {
-            int errorCode = mPreferences.getPairFailureCounts();
-            if (errorCode > 99) {
-                errorCode = 99;
-            }
-            errorCode += success ? 0 : 100;
-            // To not conflict with current error codes.
-            errorCode += GATT_ERROR_CODE_USER_RETRY;
-            mEventLogger.logCurrentEventFailed(
-                    new BluetoothGattException("Error for manual retry", errorCode));
-        }
-    }
-
-    static void logRetrySuccessEvent(
-            @EventCode int eventCode,
-            @Nullable Exception recoverFromException,
-            EventLoggerWrapper eventLogger) {
-        if (recoverFromException == null) {
-            return;
-        }
-        eventLogger.setCurrentEvent(eventCode);
-        eventLogger.logCurrentEventFailed(recoverFromException);
-    }
-
-    private void initGattConnectionManager() {
-        mGattConnectionManager =
-                new GattConnectionManager(
-                        mContext,
-                        mPreferences,
-                        mEventLogger,
-                        mBluetoothAdapter,
-                        this::toggleBluetooth,
-                        mBleAddress,
-                        mTimingLogger,
-                        mFastPairSignalChecker,
-                        isPairingWithAntiSpoofingPublicKey());
-    }
-
-    private void logCurrentEventFailedBySignalRotated(SignalRotatedException e) {
-        if (!mEventLogger.isCurrentEvent()) {
-            return;
-        }
-
-        Log.w(TAG, "BLE Address for pairing device might rotated!");
-        mEventLogger.logCurrentEventFailed(
-                new BluetoothGattException(
-                        "BLE Address for pairing device might rotated",
-                        appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
-                                e.getCause()),
-                        e));
-    }
-
-    private void logCurrentEventFailedBySignalLost(SignalLostException e) {
-        if (!mEventLogger.isCurrentEvent()) {
-            return;
-        }
-
-        Log.w(TAG, "BLE signal for pairing device might lost!");
-        mEventLogger.logCurrentEventFailed(
-                new BluetoothGattException(
-                        "BLE signal for pairing device might lost",
-                        appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST, e.getCause()),
-                        e));
-    }
-
-    @VisibleForTesting
-    static int appendMoreErrorCode(int masterErrorCode, @Nullable Throwable cause) {
-        if (cause instanceof BluetoothGattException) {
-            return masterErrorCode + ((BluetoothGattException) cause).getGattErrorCode();
-        } else if (cause instanceof TimeoutException
-                || cause instanceof BluetoothTimeoutException
-                || cause instanceof BluetoothOperationTimeoutException) {
-            return masterErrorCode + GATT_ERROR_CODE_TIMEOUT;
-        } else {
-            return masterErrorCode;
-        }
-    }
-
-    private void setBleAddress(String newAddress) {
-        if (TextUtils.isEmpty(newAddress) || Ascii.equalsIgnoreCase(newAddress, mBleAddress)) {
-            return;
-        }
-
-        mBleAddress = newAddress;
-
-        // Recreates a GattConnectionManager with the new address for establishing a new GATT
-        // connection later.
-        initGattConnectionManager();
-
-        mEventLogger.setDevice(mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap());
-    }
-
-    /**
-     * Gets the public address of the headset used in the connection. Before the handshake, this
-     * could be null.
-     */
-    @Nullable
-    public String getPublicAddress() {
-        return mPublicAddress;
-    }
-
-    /**
-     * Pairs with a Bluetooth device. In general, this process goes through the following steps:
-     *
-     * <ol>
-     *   <li>Get BrEdr handover information if requested
-     *   <li>Discover the device (on Android N and lower to work around a bug)
-     *   <li>Connect to the device
-     *       <ul>
-     *         <li>Attempt a direct connection to a supported profile if we're already bonded
-     *         <li>Create a new bond with the not bonded device and then connect to a supported
-     *             profile
-     *       </ul>
-     *   <li>Write the account secret
-     * </ol>
-     *
-     * <p>Blocks until paired. May take 10+ seconds, so run on a background thread.
-     */
-    @Nullable
-    private SharedSecret pair(boolean enableBrEdrHandover)
-            throws BluetoothException, InterruptedException, ReflectionException, TimeoutException,
-            ExecutionException, PairingException, GeneralSecurityException {
-        BrEdrHandoverInformation brEdrHandoverInformation = null;
-        if (enableBrEdrHandover) {
-            try (ScopedTiming scopedTiming =
-                    new ScopedTiming(mTimingLogger, "Get BR/EDR handover information via GATT")) {
-                brEdrHandoverInformation =
-                        getBrEdrHandoverInformation(mGattConnectionManager.getConnection());
-            } catch (BluetoothException | TdsException e) {
-                Log.w(TAG,
-                        "Couldn't get BR/EDR Handover info via TDS. Trying direct connect.", e);
-                mEventLogger.logCurrentEventFailed(e);
-            }
-        }
-
-        if (brEdrHandoverInformation == null) {
-            // Pair directly to the BLE address. Works if the BLE and Bluetooth Classic addresses
-            // are the same, or if we can do BLE cross-key transport.
-            brEdrHandoverInformation =
-                    new BrEdrHandoverInformation(
-                            BluetoothAddress
-                                    .decode(mPublicAddress != null ? mPublicAddress : mBleAddress),
-                            attemptGetBluetoothClassicProfiles(
-                                    mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(),
-                                    mPreferences.getNumSdpAttempts()));
-        }
-
-        BluetoothDevice device =
-                mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress)
-                        .unwrap();
-        callbackOnGetAddress(device.getAddress());
-        mEventLogger.setDevice(device);
-
-        Log.i(TAG, "Pairing with " + brEdrHandoverInformation);
-        KeyBasedPairingInfo keyBasedPairingInfo =
-                mPairingSecret == null
-                        ? null
-                        : new KeyBasedPairingInfo(
-                                mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding);
-
-        BluetoothAudioPairer pairer =
-                new BluetoothAudioPairer(
-                        mContext,
-                        device,
-                        mPreferences,
-                        mEventLogger,
-                        keyBasedPairingInfo,
-                        mPasskeyConfirmationHandler,
-                        mTimingLogger);
-
-        logPairWithPossibleCachedAddress(device.getAddress());
-        logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(device);
-
-        // In the case where we are already bonded, we should first just try connecting to supported
-        // profiles. If successful, then this will be much faster than recreating the bond like we
-        // normally do and we can finish early. It is also more reliable than tearing down the bond
-        // and recreating it.
-        try {
-            if (!sTestMode) {
-                attemptDirectConnectionIfBonded(device, pairer);
-            }
-            callbackOnPaired();
-            return maybeWriteAccountKey(device);
-        } catch (PairingException e) {
-            Log.i(TAG, "Failed to directly connect to supported profiles: " + e.getMessage());
-            // Catches exception when we fail to connect support profile. And makes the flow to go
-            // through step to write account key when device is bonded.
-            if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()
-                    && device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                if (mPreferences.getSkipConnectingProfiles()
-                        && !mPreferences.getCheckBondStateWhenSkipConnectingProfiles()) {
-                    Log.i(TAG, "For notCheckBondStateWhenSkipConnectingProfiles case should do "
-                            + "re-bond");
-                } else {
-                    Log.i(TAG, "Fail to connect profile when device is bonded, still call back on"
-                            + "pair callback to show ui");
-                    callbackOnPaired();
-                    return maybeWriteAccountKey(device);
-                }
-            }
-        }
-
-        if (mPreferences.getMoreEventLogForQuality()) {
-            switch (device.getBondState()) {
-                case BOND_BONDED:
-                    mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDED);
-                    break;
-                case BOND_BONDING:
-                    mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDING);
-                    break;
-                case BOND_NONE:
-                default:
-                    mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND);
-            }
-        }
-
-        for (int i = 1; i <= mPreferences.getNumCreateBondAttempts(); i++) {
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Pair device #" + i)) {
-                pairer.pair();
-                if (mPreferences.getMoreEventLogForQuality()) {
-                    // For EventCode.BEFORE_CREATE_BOND
-                    mEventLogger.logCurrentEventSucceeded();
-                }
-                break;
-            } catch (Exception e) {
-                mEventLogger.logCurrentEventFailed(e);
-                if (mPasskeyIsGotten) {
-                    Log.w(TAG,
-                            "createBond() failed because of " + e.getMessage()
-                                    + " after getting the passkey. Skip retry.");
-                    if (mPreferences.getMoreEventLogForQuality()) {
-                        // For EventCode.BEFORE_CREATE_BOND
-                        mEventLogger.logCurrentEventFailed(
-                                new CreateBondException(
-                                        CreateBondErrorCode.FAILED_BUT_ALREADY_RECEIVE_PASS_KEY,
-                                        0,
-                                        "Already get the passkey"));
-                    }
-                    break;
-                }
-                Log.e(TAG,
-                        "removeBond() or createBond() failed, attempt " + i + " of " + mPreferences
-                                .getNumCreateBondAttempts() + ". Bond state "
-                                + device.getBondState(), e);
-                if (i < mPreferences.getNumCreateBondAttempts()) {
-                    toggleBluetooth();
-
-                    // We've seen 3 createBond() failures within 100ms (!). And then success again
-                    // later (even without turning on/off bluetooth). So create some minimum break
-                    // time.
-                    Log.i(TAG, "Sleeping 1 sec after createBond() failure.");
-                    SystemClock.sleep(1000);
-                } else if (mPreferences.getMoreEventLogForQuality()) {
-                    // For EventCode.BEFORE_CREATE_BOND
-                    mEventLogger.logCurrentEventFailed(e);
-                }
-            }
-        }
-        boolean deviceCreateBondFailWithNullSecret = false;
-        if (!pairer.isPaired()) {
-            if (mPairingSecret != null) {
-                // Bonding could fail for a few different reasons here. It could be an error, an
-                // attacker may have tried to bond, or the device may not be up to spec.
-                throw new PairingException("createBond() failed, exiting connection process.");
-            } else if (mPreferences.getSkipConnectingProfiles()) {
-                throw new PairingException(
-                        "createBond() failed and skipping connecting to a profile.");
-            } else {
-                // When bond creation has failed, connecting a profile will still work most of the
-                // time for Fast Pair 1.0 devices (ie, pairing secret is null), so continue on with
-                // the spec anyways and attempt to connect supported profiles.
-                Log.w(TAG, "createBond() failed, will try connecting profiles anyway.");
-                deviceCreateBondFailWithNullSecret = true;
-            }
-        } else if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) {
-            Log.i(TAG, "new flow to call on paired callback for ui when pairing step is finished");
-            callbackOnPaired();
-        }
-
-        if (!mPreferences.getSkipConnectingProfiles()) {
-            if (mPreferences.getWaitForUuidsAfterBonding()
-                    && brEdrHandoverInformation.mProfiles.length == 0) {
-                short[] supportedProfiles = getCachedUuids(device);
-                if (supportedProfiles.length == 0
-                        && mPreferences.getNumSdpAttemptsAfterBonded() > 0) {
-                    Log.i(TAG, "Found no supported profiles in UUID cache, manually trigger SDP.");
-                    attemptGetBluetoothClassicProfiles(device,
-                            mPreferences.getNumSdpAttemptsAfterBonded());
-                }
-                brEdrHandoverInformation =
-                        new BrEdrHandoverInformation(
-                                brEdrHandoverInformation.mBluetoothAddress, supportedProfiles);
-            }
-            short[] profiles = brEdrHandoverInformation.mProfiles;
-            if (profiles.length == 0) {
-                profiles = Constants.getSupportedProfiles();
-                Log.w(TAG,
-                        "Attempting to connect constants profiles, " + Arrays.toString(profiles));
-            } else {
-                Log.i(TAG, "Attempting to connect device profiles, " + Arrays.toString(profiles));
-            }
-
-            try {
-                attemptConnectProfiles(
-                        pairer,
-                        maskBluetoothAddress(device),
-                        profiles,
-                        mPreferences.getNumConnectAttempts(),
-                        /* enablePairingBehavior= */ false);
-            } catch (PairingException e) {
-                // For new pair flow to show ui, we already show success ui when finishing the
-                // createBond step. So we should catch the exception from connecting profile to
-                // avoid showing fail ui for user.
-                if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()
-                        && !deviceCreateBondFailWithNullSecret) {
-                    Log.i(TAG, "Fail to connect profile when device is bonded");
-                } else {
-                    throw e;
-                }
-            }
-        }
-        if (!mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) {
-            Log.i(TAG, "original flow to call on paired callback for ui");
-            callbackOnPaired();
-        } else if (deviceCreateBondFailWithNullSecret) {
-            // This paired callback is called for device which create bond fail with null secret
-            // such as FastPair 1.0 device when directly connecting to any supported profile.
-            Log.i(TAG, "call on paired callback for ui for device with null secret without bonded "
-                    + "state");
-            callbackOnPaired();
-        }
-        if (mPreferences.getEnableFirmwareVersionCharacteristic()
-                && validateBluetoothGattCharacteristic(
-                mGattConnectionManager.getConnection(), FirmwareVersionCharacteristic.ID)) {
-            try {
-                sInitialConnectionFirmwareVersion = readFirmwareVersion();
-            } catch (BluetoothException e) {
-                Log.i(TAG, "Fast Pair: head phone does not support firmware read", e);
-            }
-        }
-
-        // Catch exception when writing account key or name fail to avoid showing pairing failure
-        // notice for user. Because device is already paired successfully based on paring step.
-        SharedSecret secret = null;
-        try {
-            secret = maybeWriteAccountKey(device);
-        } catch (InterruptedException
-                | ExecutionException
-                | TimeoutException
-                | NoSuchAlgorithmException
-                | BluetoothException e) {
-            Log.w(TAG, "Fast Pair: Got exception when writing account key or name to provider", e);
-        }
-
-        return secret;
-    }
-
-    private void logPairWithPossibleCachedAddress(String brEdrAddressForBonding) {
-        if (TextUtils.isEmpty(mPreferences.getPossibleCachedDeviceAddress())
-                || !mPreferences.getLogPairWithCachedModelId()) {
-            return;
-        }
-        mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_CACHED_MODEL_ID);
-        if (Ascii.equalsIgnoreCase(
-                mPreferences.getPossibleCachedDeviceAddress(), brEdrAddressForBonding)) {
-            mEventLogger.logCurrentEventSucceeded();
-            Log.i(TAG, "Repair with possible cached device "
-                    + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress()));
-        } else {
-            mEventLogger.logCurrentEventFailed(
-                    new PairingException("Pairing with 2nd device with same model ID"));
-            Log.i(TAG, "Pair with a new device " + maskBluetoothAddress(brEdrAddressForBonding)
-                    + " with model ID in cache "
-                    + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress()));
-        }
-    }
-
-    /**
-     * Logs two type of events. First, why cachedAddress mechanism doesn't work if it's repair with
-     * bonded device case. Second, if it's not the case, log how many devices with the same model Id
-     * is already paired.
-     */
-    private void logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(BluetoothDevice device) {
-        if (!mPreferences.getLogPairWithCachedModelId()) {
-            return;
-        }
-
-        if (device.getBondState() == BOND_BONDED) {
-            if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) {
-                Log.i(TAG, "Device is bonded but we don't have this model Id in cache.");
-            } else if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress())
-                    && mPreferences.getDirectConnectProfileIfModelIdInCache()
-                    && !mPreferences.getSkipConnectingProfiles()) {
-                // Pair with bonded device case. Log why the cached address is not found.
-                mEventLogger.setCurrentEvent(
-                        EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS);
-                mEventLogger.logCurrentEventFailed(
-                        mPreferences.getIsDeviceFinishCheckAddressFromCache()
-                                ? new ConnectException(ConnectErrorCode.FAIL_TO_DISCOVERY,
-                                "Failed to discovery")
-                                : new ConnectException(
-                                        ConnectErrorCode.DISCOVERY_NOT_FINISHED,
-                                        "Discovery not finished"));
-                Log.i(TAG, "Failed to get cached address due to "
-                        + (mPreferences.getIsDeviceFinishCheckAddressFromCache()
-                        ? "Failed to discovery"
-                        : "Discovery not finished"));
-            }
-        } else if (device.getBondState() == BOND_NONE) {
-            // Pair with new device case, log how many devices with the same model id is in FastPair
-            // cache already.
-            mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_NEW_MODEL);
-            if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) {
-                mEventLogger.logCurrentEventSucceeded();
-            } else {
-                mEventLogger.logCurrentEventFailed(
-                        new BluetoothGattException(
-                                "Already have this model ID in cache",
-                                GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT
-                                        + mPreferences.getSameModelIdPairedDeviceCount()));
-            }
-            Log.i(TAG, "This device already has " + mPreferences.getSameModelIdPairedDeviceCount()
-                    + " peripheral with the same model Id");
-        }
-    }
-
-    /**
-     * Attempts to directly connect to any supported profile if we're already bonded, this will save
-     * time over tearing down the bond and recreating it.
-     */
-    private void attemptDirectConnectionIfBonded(BluetoothDevice device,
-            BluetoothAudioPairer pairer)
-            throws PairingException {
-        if (mPreferences.getSkipConnectingProfiles()) {
-            if (mPreferences.getCheckBondStateWhenSkipConnectingProfiles()
-                    && device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                Log.i(TAG, "Skipping connecting to profiles by preferences.");
-                return;
-            }
-            throw new PairingException(
-                    "Skipping connecting to profiles, no direct connection possible.");
-        } else if (!mPreferences.getAttemptDirectConnectionWhenPreviouslyBonded()
-                || device.getBondState() != BluetoothDevice.BOND_BONDED) {
-            throw new PairingException(
-                    "Not previously bonded skipping direct connection, %s", device.getBondState());
-        }
-        short[] supportedProfiles = getSupportedProfiles(device);
-        mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECTED_TO_PROFILE);
-        try (ScopedTiming scopedTiming =
-                new ScopedTiming(mTimingLogger, "Connect to profile directly")) {
-            attemptConnectProfiles(
-                    pairer,
-                    maskBluetoothAddress(device),
-                    supportedProfiles,
-                    mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()
-                            ? mPreferences.getNumConnectAttempts()
-                            : 1,
-                    mPreferences.getEnablePairingWhileDirectlyConnecting());
-            Log.i(TAG, "Directly connected to " + maskBluetoothAddress(device));
-            mEventLogger.logCurrentEventSucceeded();
-        } catch (PairingException e) {
-            mEventLogger.logCurrentEventFailed(e);
-            // Rethrow e so that the exception bubbles up and we continue the normal pairing
-            // process.
-            throw e;
-        }
-    }
-
-    @VisibleForTesting
-    void attemptConnectProfiles(
-            BluetoothAudioPairer pairer,
-            String deviceMaskedBluetoothAddress,
-            short[] profiles,
-            int numConnectionAttempts,
-            boolean enablePairingBehavior)
-            throws PairingException {
-        attemptConnectProfiles(
-                pairer,
-                deviceMaskedBluetoothAddress,
-                profiles,
-                numConnectionAttempts,
-                enablePairingBehavior,
-                new AtomicBoolean(false));
-    }
-
-    private void attemptConnectProfiles(
-            BluetoothAudioPairer pairer,
-            String deviceMaskedBluetoothAddress,
-            short[] profiles,
-            int numConnectionAttempts,
-            boolean enablePairingBehavior,
-            AtomicBoolean interruptConnection)
-            throws PairingException {
-        if (mPreferences.getMoreEventLogForQuality()) {
-            mEventLogger.setCurrentEvent(EventCode.BEFORE_CONNECT_PROFILE);
-        }
-        Exception lastException = null;
-        for (short profile : profiles) {
-            if (interruptConnection.get()) {
-                Log.w(TAG, "attemptConnectProfiles interrupted");
-                break;
-            }
-            if (!mPreferences.isSupportedProfile(profile)) {
-                Log.w(TAG, "Ignoring unsupported profile=" + profile);
-                continue;
-            }
-            for (int i = 1; i <= numConnectionAttempts; i++) {
-                if (interruptConnection.get()) {
-                    Log.w(TAG, "attemptConnectProfiles interrupted");
-                    break;
-                }
-                mEventLogger.setCurrentEvent(EventCode.CONNECT_PROFILE);
-                mEventLogger.setCurrentProfile(profile);
-                try {
-                    pairer.connect(profile, enablePairingBehavior);
-                    mEventLogger.logCurrentEventSucceeded();
-                    if (mPreferences.getMoreEventLogForQuality()) {
-                        // For EventCode.BEFORE_CONNECT_PROFILE
-                        mEventLogger.logCurrentEventSucceeded();
-                    }
-                    // If successful, we're done.
-                    // TODO(b/37167120): Connect to more than one profile.
-                    return;
-                } catch (InterruptedException
-                        | ReflectionException
-                        | TimeoutException
-                        | ExecutionException
-                        | ConnectException e) {
-                    Log.w(TAG,
-                            "Error connecting to profile=" + profile
-                                    + " for device=" + deviceMaskedBluetoothAddress
-                                    + " (attempt " + i + " of " + mPreferences
-                                    .getNumConnectAttempts(), e);
-                    mEventLogger.logCurrentEventFailed(e);
-                    lastException = e;
-                }
-            }
-        }
-        if (mPreferences.getMoreEventLogForQuality()) {
-            // For EventCode.BEFORE_CONNECT_PROFILE
-            if (lastException != null) {
-                mEventLogger.logCurrentEventFailed(lastException);
-            } else {
-                mEventLogger.logCurrentEventSucceeded();
-            }
-        }
-        throw new PairingException(
-                "Unable to connect to any profiles in: %s", Arrays.toString(profiles));
-    }
-
-    /**
-     * Checks whether or not an account key should be written to the device and writes it if so.
-     * This is called after handle notifying the pairedCallback that we've finished pairing, because
-     * at this point the headset is ready to use.
-     */
-    @Nullable
-    private SharedSecret maybeWriteAccountKey(BluetoothDevice device)
-            throws InterruptedException, ExecutionException, TimeoutException,
-            NoSuchAlgorithmException,
-            BluetoothException {
-        if (!sTestMode) {
-            Locator.get(mContext, FastPairController.class).setShouldUpload(false);
-        }
-        if (!shouldWriteAccountKey()) {
-            // For FastPair 2.0, here should be a subsequent pairing case.
-            return null;
-        }
-
-        // Check if it should be a subsequent pairing but go through initial pairing. If there is an
-        // existed paired history found, use the same account key instead of creating a new one.
-        byte[] accountKey =
-                mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey();
-        if (accountKey == null) {
-            // It is a real initial pairing, generate a new account key for the headset.
-            try (ScopedTiming scopedTiming1 =
-                    new ScopedTiming(mTimingLogger, "Write account key")) {
-                accountKey = doWriteAccountKey(createAccountKey(), device.getAddress());
-                if (accountKey == null) {
-                    // Without writing account key back to provider, close the connection.
-                    mGattConnectionManager.closeConnection();
-                    return null;
-                }
-                if (!mPreferences.getIsRetroactivePairing()) {
-                    try (ScopedTiming scopedTiming2 = new ScopedTiming(mTimingLogger,
-                            "Start CloudSyncing")) {
-                        // Start to sync to the footprint
-                        Locator.get(mContext, FastPairController.class).setShouldUpload(true);
-                        //mContext.startService(createCloudSyncingIntent(accountKey));
-                    } catch (SecurityException e) {
-                        Log.w(TAG, "Error adding device.", e);
-                    }
-                }
-            }
-        } else if (shouldWriteAccountKeyForExistingCase(accountKey)) {
-            // There is an existing account key, but go through initial pairing, and still write the
-            // existing account key.
-            doWriteAccountKey(accountKey, device.getAddress());
-        }
-
-        // When finish writing account key in initial pairing, write new device name back to
-        // provider.
-        UUID characteristicUuid = NameCharacteristic.getId(mGattConnectionManager.getConnection());
-        if (mPreferences.getEnableNamingCharacteristic()
-                && mNeedUpdateProviderName
-                && validateBluetoothGattCharacteristic(
-                mGattConnectionManager.getConnection(), characteristicUuid)) {
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                    "WriteNameToProvider")) {
-                writeNameToProvider(this.mProviderDeviceName, device.getAddress());
-            }
-        }
-
-        // When finish writing account key and name back to provider, close the connection.
-        mGattConnectionManager.closeConnection();
-        return SharedSecret.create(accountKey, device.getAddress());
-    }
-
-    private boolean shouldWriteAccountKey() {
-        return isWritingAccountKeyEnabled() && isPairingWithAntiSpoofingPublicKey();
-    }
-
-    private boolean isWritingAccountKeyEnabled() {
-        return mPreferences.getNumWriteAccountKeyAttempts() > 0;
-    }
-
-    private boolean isPairingWithAntiSpoofingPublicKey() {
-        return isPairingWithAntiSpoofingPublicKey(mPairingKey);
-    }
-
-    private boolean isPairingWithAntiSpoofingPublicKey(@Nullable byte[] key) {
-        return key != null && key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH;
-    }
-
-    /**
-     * Creates and writes an account key to the provided mac address.
-     */
-    @Nullable
-    private byte[] doWriteAccountKey(byte[] accountKey, String macAddress)
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException {
-        byte[] localPairingSecret = mPairingSecret;
-        if (localPairingSecret == null) {
-            Log.w(TAG, "Pairing secret was null, account key couldn't be encrypted or written.");
-            return null;
-        }
-        if (!mPreferences.getSkipDisconnectingGattBeforeWritingAccountKey()) {
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                    "Close GATT and sleep")) {
-                // Make a new connection instead of reusing gattConnection, because this is
-                // post-pairing and we need an encrypted connection.
-                mGattConnectionManager.closeConnection();
-                // Sleep before re-connecting to gatt, for writing account key, could increase
-                // stability.
-                Thread.sleep(mPreferences.getWriteAccountKeySleepMillis());
-            }
-        }
-
-        byte[] encryptedKey;
-        try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encrypt key")) {
-            encryptedKey = AesEcbSingleBlockEncryption.encrypt(localPairingSecret, accountKey);
-        } catch (GeneralSecurityException e) {
-            Log.w("Failed to encrypt key.", e);
-            return null;
-        }
-
-        for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) {
-            mEventLogger.setCurrentEvent(EventCode.WRITE_ACCOUNT_KEY);
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
-                    "Write key via GATT #" + i)) {
-                writeAccountKey(encryptedKey, macAddress);
-                mEventLogger.logCurrentEventSucceeded();
-                return accountKey;
-            } catch (BluetoothException e) {
-                Log.w("Error writing account key attempt " + i + " of " + mPreferences
-                        .getNumWriteAccountKeyAttempts(), e);
-                mEventLogger.logCurrentEventFailed(e);
-                // Retry with a while for stability.
-                Thread.sleep(mPreferences.getWriteAccountKeySleepMillis());
-            }
-        }
-        return null;
-    }
-
-    private byte[] createAccountKey() throws NoSuchAlgorithmException {
-        return AccountKeyGenerator.createAccountKey();
-    }
-
-    @VisibleForTesting
-    boolean shouldWriteAccountKeyForExistingCase(byte[] existingAccountKey) {
-        if (!mPreferences.getKeepSameAccountKeyWrite()) {
-            Log.i(TAG,
-                    "The provider has already paired with the account, skip writing account key.");
-            return false;
-        }
-        if (existingAccountKey[0] != AccountKeyCharacteristic.TYPE) {
-            Log.i(TAG,
-                    "The provider has already paired with the account, but accountKey[0] != 0x04."
-                            + " Forget the device from the account and re-try");
-
-            return false;
-        }
-        Log.i(TAG, "The provider has already paired with the account, still write the same account "
-                + "key.");
-        return true;
-    }
-
-    /**
-     * Performs a key-based pairing request handshake to authenticate and get the remote device's
-     * public address.
-     *
-     * @param key is described in {@link #pair(byte[])}
-     */
-    @VisibleForTesting
-    SharedSecret handshakeForKeyBasedPairing(byte[] key)
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException,
-            GeneralSecurityException, PairingException {
-        // We may also initialize gattConnectionManager of prepareForHandshake() that will be used
-        // in registerNotificationForNamePacket(), so we need to call it here.
-        HandshakeHandler handshakeHandler = prepareForHandshake();
-        KeyBasedPairingRequest.Builder keyBasedPairingRequestBuilder =
-                new KeyBasedPairingRequest.Builder()
-                        .setVerificationData(BluetoothAddress.decode(mBleAddress));
-        if (mProviderInitiatesBonding) {
-            keyBasedPairingRequestBuilder
-                    .addFlag(KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING);
-        }
-        // Seeker only request provider device name in initial pairing.
-        if (mPreferences.getEnableNamingCharacteristic() && isPairingWithAntiSpoofingPublicKey(
-                key)) {
-            keyBasedPairingRequestBuilder.addFlag(KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME);
-            // Register listener to receive name characteristic response from provider.
-            registerNotificationForNamePacket();
-        }
-        if (mPreferences.getIsRetroactivePairing()) {
-            keyBasedPairingRequestBuilder
-                    .addFlag(KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR);
-            keyBasedPairingRequestBuilder.setSeekerPublicAddress(
-                    Preconditions.checkNotNull(BluetoothAddress.getPublicAddress(mContext)));
-        }
-
-        return performHandshakeWithRetryAndSignalLostCheck(
-                handshakeHandler, key, keyBasedPairingRequestBuilder.build(), /* withRetry= */
-                true);
-    }
-
-    /**
-     * Performs an action-over-BLE request handshake for authentication, i.e. to identify the shared
-     * secret. The given key should be the account key.
-     */
-    private SharedSecret handshakeForActionOverBle(byte[] key,
-            @AdditionalDataType int additionalDataType)
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException,
-            GeneralSecurityException, PairingException {
-        HandshakeHandler handshakeHandler = prepareForHandshake();
-        return performHandshakeWithRetryAndSignalLostCheck(
-                handshakeHandler,
-                key,
-                new ActionOverBle.Builder()
-                        .setVerificationData(BluetoothAddress.decode(mBleAddress))
-                        .setAdditionalDataType(additionalDataType)
-                        .build(),
-                /* withRetry= */ false);
-    }
-
-    private HandshakeHandler prepareForHandshake() {
-        if (mGattConnectionManager == null) {
-            mGattConnectionManager =
-                    new GattConnectionManager(
-                            mContext,
-                            mPreferences,
-                            mEventLogger,
-                            mBluetoothAdapter,
-                            this::toggleBluetooth,
-                            mBleAddress,
-                            mTimingLogger,
-                            mFastPairSignalChecker,
-                            isPairingWithAntiSpoofingPublicKey());
-        }
-        if (mHandshakeHandlerForTest != null) {
-            Log.w(TAG, "Use handshakeHandlerForTest!");
-            return verifyNotNull(mHandshakeHandlerForTest);
-        }
-        return new HandshakeHandler(
-                mGattConnectionManager, mBleAddress, mPreferences, mEventLogger,
-                mFastPairSignalChecker);
-    }
-
-    @VisibleForTesting
-    void setHandshakeHandlerForTest(@Nullable HandshakeHandler handshakeHandlerForTest) {
-        this.mHandshakeHandlerForTest = handshakeHandlerForTest;
-    }
-
-    private SharedSecret performHandshakeWithRetryAndSignalLostCheck(
-            HandshakeHandler handshakeHandler,
-            byte[] key,
-            HandshakeMessage handshakeMessage,
-            boolean withRetry)
-            throws GeneralSecurityException, ExecutionException, BluetoothException,
-            InterruptedException, TimeoutException, PairingException {
-        SharedSecret handshakeResult =
-                withRetry
-                        ? handshakeHandler.doHandshakeWithRetryAndSignalLostCheck(
-                        key, handshakeMessage, mRescueFromError)
-                        : handshakeHandler.doHandshake(key, handshakeMessage);
-        // TODO: Try to remove these two global variables, publicAddress and pairingSecret.
-        mPublicAddress = handshakeResult.getAddress();
-        mPairingSecret = handshakeResult.getKey();
-        return handshakeResult;
-    }
-
-    private void toggleBluetooth()
-            throws InterruptedException, ExecutionException, TimeoutException {
-        if (!mPreferences.getToggleBluetoothOnFailure()) {
-            return;
-        }
-
-        Log.i(TAG, "Turning Bluetooth off.");
-        mEventLogger.setCurrentEvent(EventCode.DISABLE_BLUETOOTH);
-        mBluetoothAdapter.unwrap().disable();
-        disableBle(mBluetoothAdapter.unwrap());
-        try {
-            waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_OFF);
-            mEventLogger.logCurrentEventSucceeded();
-        } catch (TimeoutException e) {
-            mEventLogger.logCurrentEventFailed(e);
-            // Soldier on despite failing to turn off Bluetooth. We can't control whether other
-            // clients (even inside GCore) kept it enabled in BLE-only mode.
-            Log.w(TAG, "Bluetooth still on. BluetoothAdapter state="
-                    + getBleState(mBluetoothAdapter.unwrap()), e);
-        }
-
-        // Note: Intentionally don't re-enable BLE-only mode, because we don't know which app
-        // enabled it. The client app should listen to Bluetooth events and enable as necessary
-        // (because the user can toggle at any time; e.g. via Airplane mode).
-        Log.i(TAG, "Turning Bluetooth on.");
-        mEventLogger.setCurrentEvent(EventCode.ENABLE_BLUETOOTH);
-        mBluetoothAdapter.unwrap().enable();
-        waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_ON);
-        mEventLogger.logCurrentEventSucceeded();
-    }
-
-    private void waitForBluetoothState(int state)
-            throws TimeoutException, ExecutionException, InterruptedException {
-        waitForBluetoothStateUsingPolling(state);
-    }
-
-    private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException {
-        // There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF.
-        // So poll instead.
-        long start = SystemClock.elapsedRealtime();
-        long timeoutMillis = mPreferences.getBluetoothToggleTimeoutSeconds() * 1000L;
-        while (SystemClock.elapsedRealtime() - start < timeoutMillis) {
-            if (state == getBleState(mBluetoothAdapter.unwrap())) {
-                break;
-            }
-            SystemClock.sleep(mPreferences.getBluetoothStatePollingMillis());
-        }
-
-        if (state != getBleState(mBluetoothAdapter.unwrap())) {
-            throw new TimeoutException(
-                    String.format(
-                            Locale.getDefault(),
-                            "Timed out waiting for state %d, current state is %d",
-                            state,
-                            getBleState(mBluetoothAdapter.unwrap())));
-        }
-    }
-
-    private BrEdrHandoverInformation getBrEdrHandoverInformation(BluetoothGattConnection connection)
-            throws BluetoothException, TdsException, InterruptedException, ExecutionException,
-            TimeoutException {
-        Log.i(TAG, "Connecting GATT server to BLE address=" + maskBluetoothAddress(mBleAddress));
-        Log.i(TAG, "Telling device to become discoverable");
-        mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST);
-        ChangeObserver changeObserver =
-                connection.enableNotification(
-                        TransportDiscoveryService.ID,
-                        TransportDiscoveryService.ControlPointCharacteristic.ID);
-        connection.writeCharacteristic(
-                TransportDiscoveryService.ID,
-                TransportDiscoveryService.ControlPointCharacteristic.ID,
-                TDS_CONTROL_POINT_REQUEST);
-
-        byte[] response =
-                changeObserver.waitForUpdate(
-                        TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-        @ResultCode int resultCode = fromTdsControlPointIndication(response);
-        if (resultCode != ResultCode.SUCCESS) {
-            throw new TdsException(
-                    BrEdrHandoverErrorCode.CONTROL_POINT_RESULT_CODE_NOT_SUCCESS,
-                    "TDS Control Point result code (%s) was not success in response %s",
-                    resultCode,
-                    base16().lowerCase().encode(response));
-        }
-        mEventLogger.logCurrentEventSucceeded();
-        return new BrEdrHandoverInformation(
-                getAddressFromBrEdrConnection(connection),
-                getProfilesFromBrEdrConnection(connection));
-    }
-
-    private byte[] getAddressFromBrEdrConnection(BluetoothGattConnection connection)
-            throws BluetoothException, TdsException {
-        Log.i(TAG, "Getting Bluetooth MAC");
-        mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_BLUETOOTH_MAC);
-        byte[] brHandoverData =
-                connection.readCharacteristic(
-                        TransportDiscoveryService.ID,
-                        to128BitUuid(mPreferences.getBrHandoverDataCharacteristicId()));
-        if (brHandoverData == null || brHandoverData.length < 7) {
-            throw new TdsException(
-                    BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID,
-                    "Bluetooth MAC not contained in BR handover data: %s",
-                    brHandoverData != null ? base16().lowerCase().encode(brHandoverData)
-                            : "(none)");
-        }
-        byte[] bluetoothAddress =
-                new Bytes.Value(Arrays.copyOfRange(brHandoverData, 1, 7), ByteOrder.LITTLE_ENDIAN)
-                        .getBytes(ByteOrder.BIG_ENDIAN);
-        mEventLogger.logCurrentEventSucceeded();
-        return bluetoothAddress;
-    }
-
-    private short[] getProfilesFromBrEdrConnection(BluetoothGattConnection connection) {
-        mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK);
-        try {
-            byte[] transportBlock =
-                    connection.readDescriptor(
-                            TransportDiscoveryService.ID,
-                            to128BitUuid(mPreferences.getBluetoothSigDataCharacteristicId()),
-                            to128BitUuid(mPreferences.getBrTransportBlockDataDescriptorId()));
-            Log.i(TAG, "Got transport block: " + base16().lowerCase().encode(transportBlock));
-            short[] profiles = getSupportedProfiles(transportBlock);
-            mEventLogger.logCurrentEventSucceeded();
-            return profiles;
-        } catch (BluetoothException | TdsException | ParseException e) {
-            Log.w(TAG, "Failed to get supported profiles from transport block.", e);
-            mEventLogger.logCurrentEventFailed(e);
-        }
-        return new short[0];
-    }
-
-    @VisibleForTesting
-    boolean writeNameToProvider(@Nullable String deviceName, @Nullable String address)
-            throws InterruptedException, TimeoutException, ExecutionException {
-        if (deviceName == null || address == null) {
-            Log.i(TAG, "writeNameToProvider fail because provider name or address is null.");
-            return false;
-        }
-        if (mPairingSecret == null) {
-            Log.i(TAG, "writeNameToProvider fail because no pairingSecret.");
-            return false;
-        }
-        byte[] encryptedDeviceNamePacket;
-        try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encode device name")) {
-            encryptedDeviceNamePacket =
-                    NamingEncoder.encodeNamingPacket(mPairingSecret, deviceName);
-        } catch (GeneralSecurityException e) {
-            Log.w(TAG, "Failed to encrypt device name.", e);
-            return false;
-        }
-
-        for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) {
-            mEventLogger.setCurrentEvent(EventCode.WRITE_DEVICE_NAME);
-            try {
-                writeDeviceName(encryptedDeviceNamePacket, address);
-                mEventLogger.logCurrentEventSucceeded();
-                return true;
-            } catch (BluetoothException e) {
-                Log.w(TAG, "Error writing name attempt " + i + " of "
-                        + mPreferences.getNumWriteAccountKeyAttempts());
-                mEventLogger.logCurrentEventFailed(e);
-                // Reuses the existing preference because the same usage.
-                Thread.sleep(mPreferences.getWriteAccountKeySleepMillis());
-            }
-        }
-        return false;
-    }
-
-    private void writeAccountKey(byte[] encryptedAccountKey, String address)
-            throws BluetoothException, InterruptedException, ExecutionException, TimeoutException {
-        Log.i(TAG, "Writing account key to address=" + maskBluetoothAddress(address));
-        BluetoothGattConnection connection = mGattConnectionManager.getConnection();
-        connection.setOperationTimeout(
-                TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-        UUID characteristicUuid = AccountKeyCharacteristic.getId(connection);
-        connection.writeCharacteristic(FastPairService.ID, characteristicUuid, encryptedAccountKey);
-        Log.i(TAG,
-                "Finished writing encrypted account key=" + base16().encode(encryptedAccountKey));
-    }
-
-    private void writeDeviceName(byte[] naming, String address)
-            throws BluetoothException, InterruptedException, ExecutionException, TimeoutException {
-        Log.i(TAG, "Writing new device name to address=" + maskBluetoothAddress(address));
-        BluetoothGattConnection connection = mGattConnectionManager.getConnection();
-        connection.setOperationTimeout(
-                TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-        UUID characteristicUuid = NameCharacteristic.getId(connection);
-        connection.writeCharacteristic(FastPairService.ID, characteristicUuid, naming);
-        Log.i(TAG, "Finished writing new device name=" + base16().encode(naming));
-    }
-
-    /**
-     * Reads firmware version after write account key to provider since simulator is more stable to
-     * read firmware version in initial gatt connection. This function will also read firmware when
-     * detect bloomfilter. Need to verify this after real device come out. TODO(b/130592473)
-     */
-    @Nullable
-    public String readFirmwareVersion()
-            throws BluetoothException, InterruptedException, ExecutionException, TimeoutException {
-        if (!TextUtils.isEmpty(sInitialConnectionFirmwareVersion)) {
-            String result = sInitialConnectionFirmwareVersion;
-            sInitialConnectionFirmwareVersion = null;
-            return result;
-        }
-        if (mGattConnectionManager == null) {
-            mGattConnectionManager =
-                    new GattConnectionManager(
-                            mContext,
-                            mPreferences,
-                            mEventLogger,
-                            mBluetoothAdapter,
-                            this::toggleBluetooth,
-                            mBleAddress,
-                            mTimingLogger,
-                            mFastPairSignalChecker,
-                            /* setMtu= */ true);
-            mGattConnectionManager.closeConnection();
-        }
-        if (sTestMode) {
-            return null;
-        }
-        BluetoothGattConnection connection = mGattConnectionManager.getConnection();
-        connection.setOperationTimeout(
-                TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-
-        try {
-            String firmwareVersion =
-                    new String(
-                            connection.readCharacteristic(
-                                    FastPairService.ID,
-                                    to128BitUuid(
-                                            mPreferences.getFirmwareVersionCharacteristicId())));
-            Log.i(TAG, "FastPair: Got the firmware info version number = " + firmwareVersion);
-            mGattConnectionManager.closeConnection();
-            return firmwareVersion;
-        } catch (BluetoothException e) {
-            Log.i(TAG, "FastPair: can't read firmware characteristic.", e);
-            mGattConnectionManager.closeConnection();
-            return null;
-        }
-    }
-
-    @VisibleForTesting
-    @Nullable
-    String getInitialConnectionFirmware() {
-        return sInitialConnectionFirmwareVersion;
-    }
-
-    private void registerNotificationForNamePacket()
-            throws BluetoothException, InterruptedException, ExecutionException, TimeoutException {
-        Log.i(TAG,
-                "register for the device name response from address=" + maskBluetoothAddress(
-                        mBleAddress));
-
-        BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection();
-        gattConnection.setOperationTimeout(
-                TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-        try {
-            mDeviceNameReceiver = new DeviceNameReceiver(gattConnection);
-        } catch (BluetoothException e) {
-            Log.i(TAG, "Can't register for device name response, no naming characteristic.");
-            return;
-        }
-    }
-
-    private short[] getSupportedProfiles(BluetoothDevice device) {
-        short[] supportedProfiles = getCachedUuids(device);
-        if (supportedProfiles.length == 0 && mPreferences.getNumSdpAttemptsAfterBonded() > 0) {
-            supportedProfiles =
-                    attemptGetBluetoothClassicProfiles(device,
-                            mPreferences.getNumSdpAttemptsAfterBonded());
-        }
-        if (supportedProfiles.length == 0) {
-            supportedProfiles = Constants.getSupportedProfiles();
-            Log.w(TAG, "Attempting to connect constants profiles, "
-                    + Arrays.toString(supportedProfiles));
-        } else {
-            Log.i(TAG,
-                    "Attempting to connect device profiles, " + Arrays.toString(supportedProfiles));
-        }
-        return supportedProfiles;
-    }
-
-    private static short[] getSupportedProfiles(byte[] transportBlock)
-            throws TdsException, ParseException {
-        if (transportBlock == null || transportBlock.length < 4) {
-            throw new TdsException(
-                    BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID,
-                    "Transport Block null or too short: %s",
-                    base16().lowerCase().encode(transportBlock));
-        }
-        int transportDataLength = transportBlock[2];
-        if (transportBlock.length < 3 + transportDataLength) {
-            throw new TdsException(
-                    BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID,
-                    "Transport Block has wrong length byte: %s",
-                    base16().lowerCase().encode(transportBlock));
-        }
-        byte[] transportData = Arrays.copyOfRange(transportBlock, 3, 3 + transportDataLength);
-        for (Ltv ltv : Ltv.parse(transportData)) {
-            int uuidLength = uuidLength(ltv.mType);
-            // We currently only support a single list of 2-byte UUIDs.
-            // TODO(b/37539535): Support multiple lists, and longer (32-bit, 128-bit) IDs?
-            if (uuidLength == 2) {
-                return toShorts(ByteOrder.LITTLE_ENDIAN, ltv.mValue);
-            }
-        }
-        return new short[0];
-    }
-
-    /**
-     * Returns 0 if the type is not one of the UUID list types; otherwise returns length in bytes.
-     */
-    private static int uuidLength(byte dataType) {
-        switch (dataType) {
-            case TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE:
-                return 2;
-            case TransportDiscoveryService.SERVICE_UUIDS_32_BIT_LIST_TYPE:
-                return 4;
-            case TransportDiscoveryService.SERVICE_UUIDS_128_BIT_LIST_TYPE:
-                return 16;
-            default:
-                return 0;
-        }
-    }
-
-    private short[] attemptGetBluetoothClassicProfiles(BluetoothDevice device, int numSdpAttempts) {
-        // The docs say that if fetchUuidsWithSdp() has an error or "takes a long time", we get an
-        // intent containing only the stuff in the cache (i.e. nothing). Retry a few times.
-        short[] supportedProfiles = null;
-        for (int i = 1; i <= numSdpAttempts; i++) {
-            mEventLogger.setCurrentEvent(EventCode.GET_PROFILES_VIA_SDP);
-            try (ScopedTiming scopedTiming =
-                    new ScopedTiming(mTimingLogger,
-                            "Get BR/EDR handover information via SDP #" + i)) {
-                supportedProfiles = getSupportedProfilesViaBluetoothClassic(device);
-            } catch (ExecutionException | InterruptedException | TimeoutException e) {
-                // Ignores and retries if needed.
-            }
-            if (supportedProfiles != null && supportedProfiles.length != 0) {
-                mEventLogger.logCurrentEventSucceeded();
-                break;
-            } else {
-                mEventLogger.logCurrentEventFailed(new TimeoutException());
-                Log.w(TAG, "SDP returned no UUIDs from " + maskBluetoothAddress(device.getAddress())
-                        + ", assuming timeout (attempt " + i + " of " + numSdpAttempts + ").");
-            }
-        }
-        return (supportedProfiles == null) ? new short[0] : supportedProfiles;
-    }
-
-    private short[] getSupportedProfilesViaBluetoothClassic(BluetoothDevice device)
-            throws ExecutionException, InterruptedException, TimeoutException {
-        Log.i(TAG, "Getting supported profiles via SDP (Bluetooth Classic) for "
-                + maskBluetoothAddress(device.getAddress()));
-        try (DeviceIntentReceiver supportedProfilesReceiver =
-                DeviceIntentReceiver.oneShotReceiver(
-                        mContext, mPreferences, device, BluetoothDevice.ACTION_UUID)) {
-            device.fetchUuidsWithSdp();
-            supportedProfilesReceiver.await(mPreferences.getSdpTimeoutSeconds(), TimeUnit.SECONDS);
-        }
-        return getCachedUuids(device);
-    }
-
-    private static short[] getCachedUuids(BluetoothDevice device) {
-        ParcelUuid[] parcelUuids = device.getUuids();
-        Log.i(TAG, "Got supported UUIDs: " + Arrays.toString(parcelUuids));
-        if (parcelUuids == null) {
-            // The OS can return null.
-            parcelUuids = new ParcelUuid[0];
-        }
-
-        List<Short> shortUuids = new ArrayList<>(parcelUuids.length);
-        for (ParcelUuid parcelUuid : parcelUuids) {
-            UUID uuid = parcelUuid.getUuid();
-            if (BluetoothUuids.is16BitUuid(uuid)) {
-                shortUuids.add(get16BitUuid(uuid));
-            }
-        }
-        return Shorts.toArray(shortUuids);
-    }
-
-    private void callbackOnPaired() {
-        if (mPairedCallback != null) {
-            mPairedCallback.onPaired(mPublicAddress != null ? mPublicAddress : mBleAddress);
-        }
-    }
-
-    private void callbackOnGetAddress(String address) {
-        if (mOnGetBluetoothAddressCallback != null) {
-            mOnGetBluetoothAddressCallback.onGetBluetoothAddress(address);
-        }
-    }
-
-    private boolean validateBluetoothGattCharacteristic(
-            BluetoothGattConnection connection, UUID characteristicUUID) {
-        try (ScopedTiming scopedTiming =
-                new ScopedTiming(mTimingLogger, "Get service characteristic list")) {
-            List<BluetoothGattCharacteristic> serviceCharacteristicList =
-                    connection.getService(FastPairService.ID).getCharacteristics();
-            for (BluetoothGattCharacteristic characteristic : serviceCharacteristicList) {
-                if (characteristicUUID.equals(characteristic.getUuid())) {
-                    Log.i(TAG, "characteristic is exists, uuid = " + characteristicUUID);
-                    return true;
-                }
-            }
-        } catch (BluetoothException e) {
-            Log.w(TAG, "Can't get service characteristic list.", e);
-        }
-        Log.i(TAG, "can't find characteristic, uuid = " + characteristicUUID);
-        return false;
-    }
-
-    // This method is only for testing to make test method block until get name response or time
-    // out.
-    /**
-     * Set name response countdown latch.
-     */
-    public void setNameResponseCountDownLatch(CountDownLatch countDownLatch) {
-        if (mDeviceNameReceiver != null) {
-            mDeviceNameReceiver.setCountDown(countDownLatch);
-            Log.v(TAG, "set up nameResponseCountDown");
-        }
-    }
-
-    private static int getBleState(android.bluetooth.BluetoothAdapter bluetoothAdapter) {
-        // Can't use the public isLeEnabled() API, because it returns false for
-        // STATE_BLE_TURNING_(ON|OFF). So if we assume false == STATE_OFF, that can be
-        // very wrong.
-        return getLeState(bluetoothAdapter);
-    }
-
-    private static int getLeState(android.bluetooth.BluetoothAdapter adapter) {
-        try {
-            return (Integer) Reflect.on(adapter).withMethod("getLeState").get();
-        } catch (ReflectionException e) {
-            Log.i(TAG, "Can't call getLeState", e);
-        }
-        return adapter.getState();
-    }
-
-    private static void disableBle(android.bluetooth.BluetoothAdapter adapter) {
-        adapter.disableBLE();
-    }
-
-    /**
-     * Handle the searching of Fast Pair history. Since there is only one public address using
-     * during Fast Pair connection, {@link #isInPairedHistory(String)} only needs to be called once,
-     * then the result is kept, and call {@link #getExistingAccountKey()} to get the result.
-     */
-    @VisibleForTesting
-    static final class FastPairHistoryFinder {
-
-        private @Nullable
-        byte[] mExistingAccountKey;
-        @Nullable
-        private final List<FastPairHistoryItem> mHistoryItems;
-
-        FastPairHistoryFinder(List<FastPairHistoryItem> historyItems) {
-            this.mHistoryItems = historyItems;
-        }
-
-        @WorkerThread
-        @VisibleForTesting
-        boolean isInPairedHistory(String publicAddress) {
-            if (mHistoryItems == null || mHistoryItems.isEmpty()) {
-                return false;
-            }
-            for (FastPairHistoryItem item : mHistoryItems) {
-                if (item.isMatched(BluetoothAddress.decode(publicAddress))) {
-                    mExistingAccountKey = item.accountKey().toByteArray();
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        // This function should be called after isInPairedHistory(). Or it will just return null.
-        @WorkerThread
-        @VisibleForTesting
-        @Nullable
-        byte[] getExistingAccountKey() {
-            return mExistingAccountKey;
-        }
-    }
-
-    private static final class DeviceNameReceiver {
-
-        @GuardedBy("this")
-        private @Nullable
-        byte[] mEncryptedResponse;
-
-        @GuardedBy("this")
-        @Nullable
-        private String mDecryptedDeviceName;
-
-        @Nullable
-        private CountDownLatch mResponseCountDown;
-
-        DeviceNameReceiver(BluetoothGattConnection gattConnection) throws BluetoothException {
-            UUID characteristicUuid = NameCharacteristic.getId(gattConnection);
-            ChangeObserver observer =
-                    gattConnection.enableNotification(FastPairService.ID, characteristicUuid);
-            observer.setListener(
-                    (byte[] value) -> {
-                        synchronized (DeviceNameReceiver.this) {
-                            Log.i(TAG, "DeviceNameReceiver: device name response size = "
-                                    + value.length);
-                            // We don't decrypt it here because we may not finish handshaking and
-                            // the pairing
-                            // secret is not available.
-                            mEncryptedResponse = value;
-                        }
-                        // For testing to know we get the device name from provider.
-                        if (mResponseCountDown != null) {
-                            mResponseCountDown.countDown();
-                            Log.v(TAG, "Finish nameResponseCountDown.");
-                        }
-                    });
-        }
-
-        void setCountDown(CountDownLatch countDownLatch) {
-            this.mResponseCountDown = countDownLatch;
-        }
-
-        synchronized @Nullable String getParsedResult(byte[] secret) {
-            if (mDecryptedDeviceName != null) {
-                return mDecryptedDeviceName;
-            }
-            if (mEncryptedResponse == null) {
-                Log.i(TAG, "DeviceNameReceiver: no device name sent from the Provider.");
-                return null;
-            }
-            try {
-                mDecryptedDeviceName = NamingEncoder.decodeNamingPacket(secret, mEncryptedResponse);
-                Log.i(TAG, "DeviceNameReceiver: decrypted provider's name from naming response, "
-                        + "name = " + mDecryptedDeviceName);
-            } catch (GeneralSecurityException e) {
-                Log.w(TAG, "DeviceNameReceiver: fail to parse the NameCharacteristic from provider"
-                        + ".", e);
-                return null;
-            }
-            return mDecryptedDeviceName;
-        }
-    }
-
-    static void checkFastPairSignal(
-            FastPairSignalChecker fastPairSignalChecker,
-            String currentAddress,
-            Exception originalException)
-            throws SignalLostException, SignalRotatedException {
-        String newAddress = fastPairSignalChecker.getValidAddressForModelId(currentAddress);
-        if (TextUtils.isEmpty(newAddress)) {
-            throw new SignalLostException("Signal lost", originalException);
-        } else if (!Ascii.equalsIgnoreCase(currentAddress, newAddress)) {
-            throw new SignalRotatedException("Address rotated", newAddress, originalException);
-        }
-    }
-
-    @VisibleForTesting
-    public Preferences getPreferences() {
-        return mPreferences;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java
deleted file mode 100644
index e774886..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import com.google.common.hash.Hashing;
-import com.google.protobuf.ByteString;
-
-import java.util.Arrays;
-
-/**
- * It contains the sha256 of "account key + headset's public address" to identify the headset which
- * has paired with the account. Previously, account key is the only information for Fast Pair to
- * identify the headset, but Fast Pair can't identify the headset in initial pairing, there is no
- * account key data advertising from headset.
- */
-public class FastPairHistoryItem {
-
-    private final ByteString mAccountKey;
-    private final ByteString mSha256AccountKeyPublicAddress;
-
-    FastPairHistoryItem(ByteString accountkey, ByteString sha256AccountKeyPublicAddress) {
-        mAccountKey = accountkey;
-        mSha256AccountKeyPublicAddress = sha256AccountKeyPublicAddress;
-    }
-
-    /**
-     * Creates an instance of {@link FastPairHistoryItem}.
-     *
-     * @param accountKey key of an account that has paired with the headset.
-     * @param sha256AccountKeyPublicAddress hash value of account key and headset's public address.
-     */
-    public static FastPairHistoryItem create(
-            ByteString accountKey, ByteString sha256AccountKeyPublicAddress) {
-        return new FastPairHistoryItem(accountKey, sha256AccountKeyPublicAddress);
-    }
-
-    ByteString accountKey() {
-        return mAccountKey;
-    }
-
-    ByteString sha256AccountKeyPublicAddress() {
-        return mSha256AccountKeyPublicAddress;
-    }
-
-    // Return true if the input public address is considered the same as this history item. Because
-    // of privacy concern, Fast Pair does not really store the public address, it is identified by
-    // the SHA256 of the account key and the public key.
-    final boolean isMatched(byte[] publicAddress) {
-        return Arrays.equals(
-                sha256AccountKeyPublicAddress().toByteArray(),
-                Hashing.sha256().hashBytes(concat(accountKey().toByteArray(), publicAddress))
-                        .asBytes());
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java
deleted file mode 100644
index e7ce4bf..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.logRetrySuccessEvent;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.util.Consumer;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException;
-import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.ConnectionOptions;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Manager for working with Gatt connections.
- *
- * <p>This helper class allows for opening and closing GATT connections to a provided address.
- * Optionally, it can also support automatically reopening a connection in the case that it has been
- * closed when it's next needed through {@link Preferences#getAutomaticallyReconnectGattWhenNeeded}.
- */
-// TODO(b/202524672): Add class unit test.
-final class GattConnectionManager {
-
-    private static final String TAG = GattConnectionManager.class.getSimpleName();
-
-    private final Context mContext;
-    private final Preferences mPreferences;
-    private final EventLoggerWrapper mEventLogger;
-    private final BluetoothAdapter mBluetoothAdapter;
-    private final ToggleBluetoothTask mToggleBluetooth;
-    private final String mAddress;
-    private final TimingLogger mTimingLogger;
-    private final boolean mSetMtu;
-    @Nullable
-    private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker;
-    @Nullable
-    private BluetoothGattConnection mGattConnection;
-    private static boolean sTestMode = false;
-
-    static void enableTestMode() {
-        sTestMode = true;
-    }
-
-    GattConnectionManager(
-            Context context,
-            Preferences preferences,
-            EventLoggerWrapper eventLogger,
-            BluetoothAdapter bluetoothAdapter,
-            ToggleBluetoothTask toggleBluetooth,
-            String address,
-            TimingLogger timingLogger,
-            @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker,
-            boolean setMtu) {
-        this.mContext = context;
-        this.mPreferences = preferences;
-        this.mEventLogger = eventLogger;
-        this.mBluetoothAdapter = bluetoothAdapter;
-        this.mToggleBluetooth = toggleBluetooth;
-        this.mAddress = address;
-        this.mTimingLogger = timingLogger;
-        this.mFastPairSignalChecker = fastPairSignalChecker;
-        this.mSetMtu = setMtu;
-    }
-
-    /**
-     * Gets a gatt connection to address. If this connection does not exist, it creates one.
-     */
-    BluetoothGattConnection getConnection()
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException {
-        if (mGattConnection == null) {
-            try {
-                mGattConnection =
-                        connect(mAddress, /* checkSignalWhenFail= */ false,
-                                /* rescueFromError= */ null);
-            } catch (SignalLostException | SignalRotatedException e) {
-                // Impossible to happen here because we didn't do signal check.
-                throw new ExecutionException("getConnection throws SignalLostException", e);
-            }
-        }
-        return mGattConnection;
-    }
-
-    BluetoothGattConnection getConnectionWithSignalLostCheck(
-            @Nullable Consumer<Integer> rescueFromError)
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException,
-            SignalLostException, SignalRotatedException {
-        if (mGattConnection == null) {
-            mGattConnection = connect(mAddress, /* checkSignalWhenFail= */ true,
-                    rescueFromError);
-        }
-        return mGattConnection;
-    }
-
-    /**
-     * Closes the gatt connection when it is open.
-     */
-    void closeConnection() throws BluetoothException {
-        if (mGattConnection != null) {
-            try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Close GATT")) {
-                mGattConnection.close();
-                mGattConnection = null;
-            }
-        }
-    }
-
-    private BluetoothGattConnection connect(
-            String address, boolean checkSignalWhenFail,
-            @Nullable Consumer<Integer> rescueFromError)
-            throws InterruptedException, ExecutionException, TimeoutException, BluetoothException,
-            SignalLostException, SignalRotatedException {
-        int i = 1;
-        boolean isRecoverable = true;
-        long startElapsedRealtime = SystemClock.elapsedRealtime();
-        BluetoothException lastException = null;
-        mEventLogger.setCurrentEvent(EventCode.GATT_CONNECT);
-        while (isRecoverable) {
-            try (ScopedTiming scopedTiming =
-                    new ScopedTiming(mTimingLogger, "Connect GATT #" + i)) {
-                Log.i(TAG, "Connecting to GATT server at " + maskBluetoothAddress(address));
-                if (sTestMode) {
-                    return null;
-                }
-                BluetoothGattConnection connection =
-                        new BluetoothGattHelper(mContext, mBluetoothAdapter)
-                                .connect(
-                                        mBluetoothAdapter.getRemoteDevice(address),
-                                        getConnectionOptions(startElapsedRealtime));
-                connection.setOperationTimeout(
-                        TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-                if (mPreferences.getAutomaticallyReconnectGattWhenNeeded()) {
-                    connection.addCloseListener(
-                            () -> {
-                                Log.i(TAG, "Gatt connection with " + maskBluetoothAddress(address)
-                                        + " closed.");
-                                mGattConnection = null;
-                            });
-                }
-                mEventLogger.logCurrentEventSucceeded();
-                if (lastException != null) {
-                    logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_GATT, lastException,
-                            mEventLogger);
-                }
-                return connection;
-            } catch (BluetoothException e) {
-                lastException = e;
-
-                boolean ableToRetry;
-                if (mPreferences.getGattConnectRetryTimeoutMillis() > 0) {
-                    ableToRetry =
-                            (SystemClock.elapsedRealtime() - startElapsedRealtime)
-                                    < mPreferences.getGattConnectRetryTimeoutMillis();
-                    Log.i(TAG, "Retry connecting GATT by timeout: " + ableToRetry);
-                } else {
-                    ableToRetry = i < mPreferences.getNumAttempts();
-                }
-
-                if (mPreferences.getRetryGattConnectionAndSecretHandshake()) {
-                    if (isNoRetryError(mPreferences, e)) {
-                        ableToRetry = false;
-                    }
-
-                    if (ableToRetry) {
-                        if (rescueFromError != null) {
-                            rescueFromError.accept(
-                                    e instanceof BluetoothOperationTimeoutException
-                                            ? ErrorCode.SUCCESS_RETRY_GATT_TIMEOUT
-                                            : ErrorCode.SUCCESS_RETRY_GATT_ERROR);
-                        }
-                        if (mFastPairSignalChecker != null && checkSignalWhenFail) {
-                            FastPairDualConnection
-                                    .checkFastPairSignal(mFastPairSignalChecker, address, e);
-                        }
-                    }
-                    isRecoverable = ableToRetry;
-                    if (ableToRetry && mPreferences.getPairingRetryDelayMs() > 0) {
-                        SystemClock.sleep(mPreferences.getPairingRetryDelayMs());
-                    }
-                } else {
-                    isRecoverable =
-                            ableToRetry
-                                    && (e instanceof BluetoothOperationTimeoutException
-                                    || e instanceof BluetoothTimeoutException
-                                    || (e instanceof BluetoothGattException
-                                    && ((BluetoothGattException) e).getGattErrorCode() == 133));
-                }
-                Log.w(TAG, "GATT connect attempt " + i + "of " + mPreferences.getNumAttempts()
-                        + " failed, " + (isRecoverable ? "recovering" : "permanently"), e);
-                if (isRecoverable) {
-                    // If we're going to retry, log failure here. If we throw, an upper level will
-                    // log it.
-                    mToggleBluetooth.toggleBluetooth();
-                    i++;
-                    mEventLogger.logCurrentEventFailed(e);
-                    mEventLogger.setCurrentEvent(EventCode.GATT_CONNECT);
-                }
-            }
-        }
-        throw checkNotNull(lastException);
-    }
-
-    static boolean isNoRetryError(Preferences preferences, BluetoothException e) {
-        return e instanceof BluetoothGattException
-                && preferences
-                .getGattConnectionAndSecretHandshakeNoRetryGattError()
-                .contains(((BluetoothGattException) e).getGattErrorCode());
-    }
-
-    @VisibleForTesting
-    long getTimeoutMs(long spentTime) {
-        long timeoutInMs;
-        if (mPreferences.getRetryGattConnectionAndSecretHandshake()) {
-            timeoutInMs =
-                    spentTime < mPreferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs()
-                            ? mPreferences.getGattConnectShortTimeoutMs()
-                            : mPreferences.getGattConnectLongTimeoutMs();
-        } else {
-            timeoutInMs = TimeUnit.SECONDS.toMillis(mPreferences.getGattConnectionTimeoutSeconds());
-        }
-        return timeoutInMs;
-    }
-
-    private ConnectionOptions getConnectionOptions(long startElapsedRealtime) {
-        return createConnectionOptions(
-                mSetMtu,
-                getTimeoutMs(SystemClock.elapsedRealtime() - startElapsedRealtime));
-    }
-
-    public static ConnectionOptions createConnectionOptions(boolean setMtu, long timeoutInMs) {
-        ConnectionOptions.Builder builder = ConnectionOptions.builder();
-        if (setMtu) {
-            // There are 3 overhead bytes added to BLE packets.
-            builder.setMtu(
-                    AES_BLOCK_LENGTH + EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH + 3);
-        }
-        builder.setConnectionTimeoutMillis(timeoutInMs);
-        return builder.build();
-    }
-
-    @VisibleForTesting
-    void setGattConnection(BluetoothGattConnection gattConnection) {
-        this.mGattConnection = gattConnection;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java
deleted file mode 100644
index 984133b..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.decrypt;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_LENGTH_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_CODE_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_GROUP_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.FLAGS_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.SEEKER_PUBLIC_ADDRESS_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_ACTION_OVER_BLE;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_KEY_BASED_PAIRING_REQUEST;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_INDEX;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.logRetrySuccessEvent;
-import static com.android.server.nearby.common.bluetooth.fastpair.GattConnectionManager.isNoRetryError;
-
-import static com.google.common.base.Verify.verifyNotNull;
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.primitives.Bytes.concat;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.util.Consumer;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request;
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection.SharedSecret;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Handles the handshake step of Fast Pair, the Provider's public address and the shared secret will
- * be disclosed during this step. It is the first step of all key-based operations, e.g. key-based
- * pairing and action over BLE.
- *
- * @see <a href="https://developers.google.com/nearby/fast-pair/spec#procedure">
- *     Fastpair Spec Procedure</a>
- */
-public class HandshakeHandler {
-
-    private static final String TAG = HandshakeHandler.class.getSimpleName();
-    private final GattConnectionManager mGattConnectionManager;
-    private final String mProviderBleAddress;
-    private final Preferences mPreferences;
-    private final EventLoggerWrapper mEventLogger;
-    @Nullable
-    private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker;
-
-    /**
-     * Keeps the keys used during handshaking, generated by {@link #createKey(byte[])}.
-     */
-    private static final class Keys {
-
-        private final byte[] mSharedSecret;
-        private final byte[] mPublicKey;
-
-        private Keys(byte[] sharedSecret, byte[] publicKey) {
-            this.mSharedSecret = sharedSecret;
-            this.mPublicKey = publicKey;
-        }
-    }
-
-    public HandshakeHandler(
-            GattConnectionManager gattConnectionManager,
-            String bleAddress,
-            Preferences preferences,
-            EventLoggerWrapper eventLogger,
-            @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
-        this.mGattConnectionManager = gattConnectionManager;
-        this.mProviderBleAddress = bleAddress;
-        this.mPreferences = preferences;
-        this.mEventLogger = eventLogger;
-        this.mFastPairSignalChecker = fastPairSignalChecker;
-    }
-
-    /**
-     * Performs a handshake to authenticate and get the remote device's public address. Returns the
-     * AES-128 key as the shared secret for this pairing session.
-     */
-    public SharedSecret doHandshake(byte[] key, HandshakeMessage message)
-            throws GeneralSecurityException, InterruptedException, ExecutionException,
-            TimeoutException, BluetoothException, PairingException {
-        Keys keys = createKey(key);
-        Log.i(TAG,
-                "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags "
-                        + message.mFlags);
-        byte[] handshakeResponse =
-                processGattCommunication(
-                        createPacket(keys, message),
-                        SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
-        String providerPublicAddress = decodeResponse(keys.mSharedSecret, handshakeResponse);
-
-        return SharedSecret.create(keys.mSharedSecret, providerPublicAddress);
-    }
-
-    /**
-     * Performs a handshake to authenticate and get the remote device's public address. Returns the
-     * AES-128 key as the shared secret for this pairing session. Will retry and also performs
-     * FastPair signal check if fails.
-     */
-    public SharedSecret doHandshakeWithRetryAndSignalLostCheck(
-            byte[] key, HandshakeMessage message, @Nullable Consumer<Integer> rescueFromError)
-            throws GeneralSecurityException, InterruptedException, ExecutionException,
-            TimeoutException, BluetoothException, PairingException {
-        Keys keys = createKey(key);
-        Log.i(TAG,
-                "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags "
-                        + message.mFlags);
-        int retryCount = 0;
-        byte[] handshakeResponse = null;
-        long startTime = SystemClock.elapsedRealtime();
-        BluetoothException lastException = null;
-        do {
-            try {
-                mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
-                handshakeResponse =
-                        processGattCommunication(
-                                createPacket(keys, message),
-                                getTimeoutMs(SystemClock.elapsedRealtime() - startTime));
-                mEventLogger.logCurrentEventSucceeded();
-                if (lastException != null) {
-                    logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE, lastException,
-                            mEventLogger);
-                }
-            } catch (BluetoothException e) {
-                lastException = e;
-                long spentTime = SystemClock.elapsedRealtime() - startTime;
-                Log.w(TAG, "Secret handshake failed, address="
-                        + maskBluetoothAddress(mProviderBleAddress)
-                        + ", spent time=" + spentTime + "ms, retryCount=" + retryCount);
-                mEventLogger.logCurrentEventFailed(e);
-
-                if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) {
-                    throw e;
-                }
-
-                if (spentTime > mPreferences.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs()) {
-                    Log.w(TAG, "Spent too long time for handshake, timeInMs=" + spentTime);
-                    throw e;
-                }
-                if (isNoRetryError(mPreferences, e)) {
-                    throw e;
-                }
-
-                if (mFastPairSignalChecker != null) {
-                    FastPairDualConnection
-                            .checkFastPairSignal(mFastPairSignalChecker, mProviderBleAddress, e);
-                }
-                retryCount++;
-                if (retryCount > mPreferences.getSecretHandshakeRetryAttempts()
-                        || ((e instanceof BluetoothOperationTimeoutException)
-                        && !mPreferences.getRetrySecretHandshakeTimeout())) {
-                    throw new HandshakeException("Fail on handshake!", e);
-                }
-                if (rescueFromError != null) {
-                    rescueFromError.accept(
-                            (e instanceof BluetoothTimeoutException
-                                    || e instanceof BluetoothOperationTimeoutException)
-                                    ? ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT
-                                    : ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR);
-                }
-            }
-        } while (mPreferences.getRetryGattConnectionAndSecretHandshake()
-                && handshakeResponse == null);
-        if (retryCount > 0) {
-            Log.i(TAG, "Secret handshake failed but restored by retry, retry count=" + retryCount);
-        }
-        String providerPublicAddress =
-                decodeResponse(keys.mSharedSecret, verifyNotNull(handshakeResponse));
-
-        return SharedSecret.create(keys.mSharedSecret, providerPublicAddress);
-    }
-
-    @VisibleForTesting
-    long getTimeoutMs(long spentTime) {
-        if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) {
-            return SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds());
-        } else {
-            return spentTime < mPreferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
-                    ? mPreferences.getSecretHandshakeShortTimeoutMs()
-                    : mPreferences.getSecretHandshakeLongTimeoutMs();
-        }
-    }
-
-    /**
-     * If the given key is an ecc-256 public key (currently, we are using secp256r1), the shared
-     * secret is generated by ECDH; if the input key is AES-128 key (should be the account key),
-     * then it is the shared secret.
-     */
-    private Keys createKey(byte[] key) throws GeneralSecurityException {
-        if (key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH) {
-            EllipticCurveDiffieHellmanExchange exchange = EllipticCurveDiffieHellmanExchange
-                    .create();
-            byte[] publicKey = exchange.getPublicKey();
-            if (publicKey != null) {
-                Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress)
-                        + ", generates key by ECDH.");
-            } else {
-                throw new GeneralSecurityException("Failed to do ECDH.");
-            }
-            return new Keys(exchange.generateSecret(key), publicKey);
-        } else if (key.length == AesEcbSingleBlockEncryption.KEY_LENGTH) {
-            Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress)
-                    + ", using the given secret.");
-            return new Keys(key, new byte[0]);
-        } else {
-            throw new GeneralSecurityException("Key length is not correct: " + key.length);
-        }
-    }
-
-    private static byte[] createPacket(Keys keys, HandshakeMessage message)
-            throws GeneralSecurityException {
-        byte[] encryptedMessage = encrypt(keys.mSharedSecret, message.getBytes());
-        return concat(encryptedMessage, keys.mPublicKey);
-    }
-
-    private byte[] processGattCommunication(byte[] packet, long gattOperationTimeoutMS)
-            throws BluetoothException, InterruptedException, ExecutionException, TimeoutException {
-        BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection();
-        gattConnection.setOperationTimeout(gattOperationTimeoutMS);
-        UUID characteristicUuid = KeyBasedPairingCharacteristic.getId(gattConnection);
-        ChangeObserver changeObserver =
-                gattConnection.enableNotification(FastPairService.ID, characteristicUuid);
-
-        Log.i(TAG,
-                "Writing handshake packet to address=" + maskBluetoothAddress(mProviderBleAddress));
-        gattConnection.writeCharacteristic(FastPairService.ID, characteristicUuid, packet);
-        Log.i(TAG, "Waiting handshake packet from address=" + maskBluetoothAddress(
-                mProviderBleAddress));
-        return changeObserver.waitForUpdate(gattOperationTimeoutMS);
-    }
-
-    private String decodeResponse(byte[] sharedSecret, byte[] response)
-            throws PairingException, GeneralSecurityException {
-        if (response.length != AES_BLOCK_LENGTH) {
-            throw new PairingException(
-                    "Handshake failed because of incorrect response: " + base16().encode(response));
-        }
-        // 1 byte type, 6 bytes public address, remainder random salt.
-        byte[] decryptedResponse = decrypt(sharedSecret, response);
-        if (decryptedResponse[0] != KeyBasedPairingCharacteristic.Response.TYPE) {
-            throw new PairingException(
-                    "Handshake response type incorrect: " + decryptedResponse[0]);
-        }
-        String address = BluetoothAddress.encode(Arrays.copyOfRange(decryptedResponse, 1, 7));
-        Log.i(TAG, "Handshake success with public " + maskBluetoothAddress(address) + ", ble "
-                + maskBluetoothAddress(mProviderBleAddress));
-        return address;
-    }
-
-    /**
-     * The base class for handshake message that contains the common data: message type, flags and
-     * verification data.
-     */
-    abstract static class HandshakeMessage {
-
-        final byte mType;
-        final byte mFlags;
-        private final byte[] mVerificationData;
-
-        HandshakeMessage(Builder<?> builder) {
-            this.mType = builder.mType;
-            this.mVerificationData = builder.mVerificationData;
-            this.mFlags = builder.mFlags;
-        }
-
-        abstract static class Builder<T extends Builder<T>> {
-
-            byte mType;
-            byte mFlags;
-            private byte[] mVerificationData;
-
-            abstract T getThis();
-
-            T setVerificationData(byte[] verificationData) {
-                if (verificationData.length != BLUETOOTH_ADDRESS_LENGTH) {
-                    throw new IllegalArgumentException(
-                            "Incorrect verification data length: " + verificationData.length + ".");
-                }
-                this.mVerificationData = verificationData;
-                return getThis();
-            }
-        }
-
-        /**
-         * Constructs the base handshake message according to the format of Fast Pair spec.
-         */
-        byte[] constructBaseBytes() {
-            byte[] rawMessage = new byte[Request.SIZE];
-            new SecureRandom().nextBytes(rawMessage);
-            rawMessage[TYPE_INDEX] = mType;
-            rawMessage[FLAGS_INDEX] = mFlags;
-
-            System.arraycopy(
-                    mVerificationData,
-                    /* srcPos= */ 0,
-                    rawMessage,
-                    VERIFICATION_DATA_INDEX,
-                    VERIFICATION_DATA_LENGTH);
-            return rawMessage;
-        }
-
-        /**
-         * Returns the raw handshake message.
-         */
-        abstract byte[] getBytes();
-    }
-
-    /**
-     * Extends {@link HandshakeMessage} and contains the required data for key-based pairing
-     * request.
-     */
-    public static class KeyBasedPairingRequest extends HandshakeMessage {
-
-        @Nullable
-        private final byte[] mSeekerPublicAddress;
-
-        private KeyBasedPairingRequest(Builder builder) {
-            super(builder);
-            this.mSeekerPublicAddress = builder.mSeekerPublicAddress;
-        }
-
-        @Override
-        byte[] getBytes() {
-            byte[] rawMessage = constructBaseBytes();
-            if (mSeekerPublicAddress != null) {
-                System.arraycopy(
-                        mSeekerPublicAddress,
-                        /* srcPos= */ 0,
-                        rawMessage,
-                        SEEKER_PUBLIC_ADDRESS_INDEX,
-                        BLUETOOTH_ADDRESS_LENGTH);
-            }
-            Log.i(TAG,
-                    "Handshake Message: type (" + rawMessage[TYPE_INDEX] + "), flag ("
-                            + rawMessage[FLAGS_INDEX] + ").");
-            return rawMessage;
-        }
-
-        /**
-         * Builder class for key-based pairing request.
-         */
-        public static class Builder extends HandshakeMessage.Builder<Builder> {
-
-            @Nullable
-            private byte[] mSeekerPublicAddress;
-
-            /**
-             * Adds flags without changing other flags.
-             */
-            public Builder addFlag(@KeyBasedPairingRequestFlag int flag) {
-                this.mFlags |= (byte) flag;
-                return this;
-            }
-
-            /**
-             * Set seeker's public address.
-             */
-            public Builder setSeekerPublicAddress(byte[] seekerPublicAddress) {
-                this.mSeekerPublicAddress = seekerPublicAddress;
-                return this;
-            }
-
-            /**
-             * Buulds KeyBasedPairigRequest.
-             */
-            public KeyBasedPairingRequest build() {
-                mType = TYPE_KEY_BASED_PAIRING_REQUEST;
-                return new KeyBasedPairingRequest(this);
-            }
-
-            @Override
-            Builder getThis() {
-                return this;
-            }
-        }
-    }
-
-    /**
-     * Extends {@link HandshakeMessage} and contains the required data for action over BLE request.
-     */
-    public static class ActionOverBle extends HandshakeMessage {
-
-        private final byte mEventGroup;
-        private final byte mEventCode;
-        @Nullable
-        private final byte[] mEventData;
-        private final byte mAdditionalDataType;
-
-        private ActionOverBle(Builder builder) {
-            super(builder);
-            this.mEventGroup = builder.mEventGroup;
-            this.mEventCode = builder.mEventCode;
-            this.mEventData = builder.mEventData;
-            this.mAdditionalDataType = builder.mAdditionalDataType;
-        }
-
-        @Override
-        byte[] getBytes() {
-            byte[] rawMessage = constructBaseBytes();
-            StringBuilder stringBuilder =
-                    new StringBuilder(
-                            String.format(
-                                    "type (%02X), flag (%02X)", rawMessage[TYPE_INDEX],
-                                    rawMessage[FLAGS_INDEX]));
-            if ((mFlags & (byte) DEVICE_ACTION) != 0) {
-                rawMessage[EVENT_GROUP_INDEX] = mEventGroup;
-                rawMessage[EVENT_CODE_INDEX] = mEventCode;
-
-                if (mEventData != null) {
-                    rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) mEventData.length;
-                    System.arraycopy(
-                            mEventData,
-                            /* srcPos= */ 0,
-                            rawMessage,
-                            EVENT_ADDITIONAL_DATA_INDEX,
-                            mEventData.length);
-                } else {
-                    rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) 0;
-                }
-                stringBuilder.append(
-                        String.format(
-                                ", group(%02X), code(%02X), length(%02X)",
-                                rawMessage[EVENT_GROUP_INDEX],
-                                rawMessage[EVENT_CODE_INDEX],
-                                rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX]));
-            }
-            if ((mFlags & (byte) ADDITIONAL_DATA_CHARACTERISTIC) != 0) {
-                rawMessage[ADDITIONAL_DATA_TYPE_INDEX] = mAdditionalDataType;
-                stringBuilder.append(
-                        String.format(", data id(%02X)", rawMessage[ADDITIONAL_DATA_TYPE_INDEX]));
-            }
-            Log.i(TAG, "Handshake Message: " + stringBuilder);
-            return rawMessage;
-        }
-
-        /**
-         * Builder class for action over BLE request.
-         */
-        public static class Builder extends HandshakeMessage.Builder<Builder> {
-
-            private byte mEventGroup;
-            private byte mEventCode;
-            @Nullable
-            private byte[] mEventData;
-            private byte mAdditionalDataType;
-
-            // Adds a flag to this handshake message. This can be called repeatedly for adding
-            // different preference.
-
-            /**
-             * Adds flag without changing other flags.
-             */
-            public Builder addFlag(@ActionOverBleFlag int flag) {
-                this.mFlags |= (byte) flag;
-                return this;
-            }
-
-            /**
-             * Set event group and event code.
-             */
-            public Builder setEvent(int eventGroup, int eventCode) {
-                this.mFlags |= (byte) DEVICE_ACTION;
-                this.mEventGroup = (byte) (eventGroup & 0xFF);
-                this.mEventCode = (byte) (eventCode & 0xFF);
-                return this;
-            }
-
-            /**
-             * Set event additional data.
-             */
-            public Builder setEventAdditionalData(byte[] data) {
-                this.mEventData = data;
-                return this;
-            }
-
-            /**
-             * Set event additional data type.
-             */
-            public Builder setAdditionalDataType(@AdditionalDataType int additionalDataType) {
-                this.mFlags |= (byte) ADDITIONAL_DATA_CHARACTERISTIC;
-                this.mAdditionalDataType = (byte) additionalDataType;
-                return this;
-            }
-
-            @Override
-            Builder getThis() {
-                return this;
-            }
-
-            ActionOverBle build() {
-                mType = TYPE_ACTION_OVER_BLE;
-                return new ActionOverBle(this);
-            }
-        }
-    }
-
-    /**
-     * Exception for handshake failure.
-     */
-    public static class HandshakeException extends PairingException {
-
-        private final BluetoothException mOriginalException;
-
-        @VisibleForTesting
-        HandshakeException(String format, BluetoothException e) {
-            super(format);
-            mOriginalException = e;
-        }
-
-        public BluetoothException getOriginalException() {
-            return mOriginalException;
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java
deleted file mode 100644
index 26ff79f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.Nullable;
-import androidx.core.content.FileProvider;
-
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * This class is subclass of real headset. It contains image url, battery value and charging
- * status.
- */
-public class HeadsetPiece implements Parcelable {
-    private int mLowLevelThreshold;
-    private int mBatteryLevel;
-    private String mImageUrl;
-    private boolean mCharging;
-    private Uri mImageContentUri;
-
-    private HeadsetPiece(
-            int lowLevelThreshold,
-            int batteryLevel,
-            String imageUrl,
-            boolean charging,
-            @Nullable Uri imageContentUri) {
-        this.mLowLevelThreshold = lowLevelThreshold;
-        this.mBatteryLevel = batteryLevel;
-        this.mImageUrl = imageUrl;
-        this.mCharging = charging;
-        this.mImageContentUri = imageContentUri;
-    }
-
-    /**
-     * Returns a builder of HeadsetPiece.
-     */
-    public static HeadsetPiece.Builder builder() {
-        return new HeadsetPiece.Builder();
-    }
-
-    /**
-     * The low level threshold.
-     */
-    public int lowLevelThreshold() {
-        return mLowLevelThreshold;
-    }
-
-    /**
-     * The battery level.
-     */
-    public int batteryLevel() {
-        return mBatteryLevel;
-    }
-
-    /**
-     * The web URL of the image.
-     */
-    public String imageUrl() {
-        return mImageUrl;
-    }
-
-    /**
-     * Whether the headset is charging.
-     */
-    public boolean charging() {
-        return mCharging;
-    }
-
-    /**
-     * The content Uri of the image if it could be downloaded from the web URL and generated through
-     * {@link FileProvider#getUriForFile} successfully, otherwise null.
-     */
-    @Nullable
-    public Uri imageContentUri() {
-        return mImageContentUri;
-    }
-
-    /**
-     * @return whether battery is low or not.
-     */
-    public boolean isBatteryLow() {
-        return batteryLevel() <= lowLevelThreshold() && batteryLevel() >= 0 && !charging();
-    }
-
-    @Override
-    public String toString() {
-        return "HeadsetPiece{"
-                + "lowLevelThreshold=" + mLowLevelThreshold + ", "
-                + "batteryLevel=" + mBatteryLevel + ", "
-                + "imageUrl=" + mImageUrl + ", "
-                + "charging=" + mCharging + ", "
-                + "imageContentUri=" + mImageContentUri
-                + "}";
-    }
-
-    /**
-     * Builder function for headset piece.
-     */
-    public static class Builder {
-        private int mLowLevelThreshold;
-        private int mBatteryLevel;
-        private String mImageUrl;
-        private boolean mCharging;
-        private Uri mImageContentUri;
-
-        /**
-         * Set low level threshold.
-         */
-        public HeadsetPiece.Builder setLowLevelThreshold(int lowLevelThreshold) {
-            this.mLowLevelThreshold = lowLevelThreshold;
-            return this;
-        }
-
-        /**
-         * Set battery level.
-         */
-        public HeadsetPiece.Builder setBatteryLevel(int level) {
-            this.mBatteryLevel = level;
-            return this;
-        }
-
-        /**
-         * Set image url.
-         */
-        public HeadsetPiece.Builder setImageUrl(String url) {
-            this.mImageUrl = url;
-            return this;
-        }
-
-        /**
-         * Set charging.
-         */
-        public HeadsetPiece.Builder setCharging(boolean charging) {
-            this.mCharging = charging;
-            return this;
-        }
-
-        /**
-         * Set image content Uri.
-         */
-        public HeadsetPiece.Builder setImageContentUri(Uri uri) {
-            this.mImageContentUri = uri;
-            return this;
-        }
-
-        /**
-         * Builds HeadSetPiece.
-         */
-        public HeadsetPiece build() {
-            return new HeadsetPiece(mLowLevelThreshold, mBatteryLevel, mImageUrl, mCharging,
-                    mImageContentUri);
-        }
-    }
-
-    @Override
-    public final void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(imageUrl());
-        dest.writeInt(lowLevelThreshold());
-        dest.writeInt(batteryLevel());
-        // Writes 1 if charging, otherwise 0.
-        dest.writeByte((byte) (charging() ? 1 : 0));
-        dest.writeParcelable(imageContentUri(), flags);
-    }
-
-    @Override
-    public final int describeContents() {
-        return 0;
-    }
-
-    public static final Creator<HeadsetPiece> CREATOR =
-            new Creator<HeadsetPiece>() {
-                @Override
-                public HeadsetPiece createFromParcel(Parcel in) {
-                    String imageUrl = in.readString();
-                    return HeadsetPiece.builder()
-                            .setImageUrl(imageUrl != null ? imageUrl : "")
-                            .setLowLevelThreshold(in.readInt())
-                            .setBatteryLevel(in.readInt())
-                            .setCharging(in.readByte() != 0)
-                            .setImageContentUri(in.readParcelable(Uri.class.getClassLoader()))
-                            .build();
-                }
-
-                @Override
-                public HeadsetPiece[] newArray(int size) {
-                    return new HeadsetPiece[size];
-                }
-            };
-
-    @Override
-    public final int hashCode() {
-        return Arrays.hashCode(
-                new Object[]{
-                        lowLevelThreshold(), batteryLevel(), imageUrl(), charging(),
-                        imageContentUri()
-                });
-    }
-
-    @Override
-    public final boolean equals(@Nullable Object other) {
-        if (other == null) {
-            return false;
-        }
-
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof HeadsetPiece)) {
-            return false;
-        }
-
-        HeadsetPiece that = (HeadsetPiece) other;
-        return lowLevelThreshold() == that.lowLevelThreshold()
-                && batteryLevel() == that.batteryLevel()
-                && Objects.equals(imageUrl(), that.imageUrl())
-                && charging() == that.charging()
-                && Objects.equals(imageContentUri(), that.imageContentUri());
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java
deleted file mode 100644
index cc7a300..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.security.GeneralSecurityException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * HMAC-SHA256 utility used to generate key-SHA256 based message authentication code. This is
- * specific for Fast Pair GATT connection exchanging data to verify both the data integrity and the
- * authentication of a message. It is defined as:
- *
- * <ol>
- *   <li>SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where
- *   <li>key is the given secret extended to 64 bytes by concat(secret, ZEROS).
- *   <li>opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c.
- *   <li>ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36.
- * </ol>
- *
- */
-final class HmacSha256 {
-    @VisibleForTesting static final int HMAC_SHA256_BLOCK_SIZE = 64;
-
-    private HmacSha256() {}
-
-    /**
-     * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection
-     * exchanging data which is encrypted using AES-CTR.
-     *
-     * @param secret 16 bytes shared secret.
-     * @param data the data encrypted using AES-CTR and the given nonce.
-     * @return HMAC-SHA256 result.
-     */
-    static byte[] build(byte[] secret, byte[] data) throws GeneralSecurityException {
-        // Currently we only accept AES-128 key here, the second check is to secure we won't
-        // modify KEY_LENGTH to > HMAC_SHA256_BLOCK_SIZE by mistake.
-        if (secret.length != KEY_LENGTH) {
-            throw new GeneralSecurityException("Incorrect key length, should be the AES-128 key.");
-        }
-        if (KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE) {
-            throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!");
-        }
-
-        return buildWith64BytesKey(secret, data);
-    }
-
-    /**
-     * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection
-     * exchanging data which is encrypted using AES-CTR.
-     *
-     * @param secret 16 bytes shared secret.
-     * @param data the data encrypted using AES-CTR and the given nonce.
-     * @return HMAC-SHA256 result.
-     */
-    static byte[] buildWith64BytesKey(byte[] secret, byte[] data) throws GeneralSecurityException {
-        if (secret.length > HMAC_SHA256_BLOCK_SIZE) {
-            throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!");
-        }
-
-        Mac mac = Mac.getInstance("HmacSHA256");
-        SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");
-        mac.init(keySpec);
-
-        return mac.doFinal(data);
-    }
-
-    /**
-     * Constant-time HMAC comparison to prevent a possible timing attack, e.g. time the same MAC
-     * with all different first byte for a given ciphertext, the right one will take longer as it
-     * will fail on the second byte's verification.
-     *
-     * @param hmac1 HMAC want to be compared with.
-     * @param hmac2 HMAC want to be compared with.
-     * @return true if and ony if the give 2 HMACs are identical and non-null.
-     */
-    static boolean compareTwoHMACs(byte[] hmac1, byte[] hmac2) {
-        if (hmac1 == null || hmac2 == null) {
-            return false;
-        }
-
-        if (hmac1.length != hmac2.length) {
-            return false;
-        }
-        // This is for constant-time comparison, don't optimize it.
-        int res = 0;
-        for (int i = 0; i < hmac1.length; i++) {
-            res |= hmac1[i] ^ hmac2[i];
-        }
-        return res == 0;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java
deleted file mode 100644
index 88c9484..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.io.BaseEncoding.base16;
-
-import com.google.common.primitives.Bytes;
-import com.google.errorprone.annotations.FormatMethod;
-import com.google.errorprone.annotations.FormatString;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A length, type, value (LTV) data block.
- */
-public class Ltv {
-
-    private static final int SIZE_OF_LEN_TYPE = 2;
-
-    final byte mType;
-    final byte[] mValue;
-
-    /**
-     * Thrown if there's an error during {@link #parse}.
-     */
-    public static class ParseException extends Exception {
-
-        @FormatMethod
-        private ParseException(@FormatString String format, Object... objects) {
-            super(String.format(format, objects));
-        }
-    }
-
-    /**
-     * Constructor.
-     */
-    public Ltv(byte type, byte... value) {
-        this.mType = type;
-        this.mValue = value;
-    }
-
-    /**
-     * Parses a list of LTV blocks out of the input byte block.
-     */
-    static List<Ltv> parse(byte[] bytes) throws ParseException {
-        List<Ltv> ltvs = new ArrayList<>();
-        // The "+ 2" is for the length and type bytes.
-        for (int valueLength, i = 0; i < bytes.length; i += SIZE_OF_LEN_TYPE + valueLength) {
-            // - 1 since the length in the packet includes the type byte.
-            valueLength = bytes[i] - 1;
-            if (valueLength < 0 || bytes.length < i + SIZE_OF_LEN_TYPE + valueLength) {
-                throw new ParseException(
-                        "Wrong length=%d at index=%d in LTVs=%s", bytes[i], i,
-                        base16().encode(bytes));
-            }
-            ltvs.add(new Ltv(bytes[i + 1], Arrays.copyOfRange(bytes, i + SIZE_OF_LEN_TYPE,
-                    i + SIZE_OF_LEN_TYPE + valueLength)));
-        }
-        return ltvs;
-    }
-
-    /**
-     * Returns an LTV block, where length is mValue.length + 1 (for the type byte).
-     */
-    public byte[] getBytes() {
-        return Bytes.concat(new byte[]{(byte) (mValue.length + 1), mType}, mValue);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java
deleted file mode 100644
index b04cf73..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.generateNonce;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-/**
- * Message stream utilities for encoding raw packet with HMAC.
- *
- * <p>Encoded packet is:
- *
- * <ol>
- *   <li>Packet[0 - (data length - 1)]: the raw data.
- *   <li>Packet[data length - (data length + 7)]: the 8-byte message nonce.
- *   <li>Packet[(data length + 8) - (data length + 15)]: the 8-byte of HMAC.
- * </ol>
- */
-public class MessageStreamHmacEncoder {
-    public static final int EXTRACT_HMAC_SIZE = 8;
-    public static final int SECTION_NONCE_LENGTH = 8;
-
-    private MessageStreamHmacEncoder() {}
-
-    /** Encodes Message Packet. */
-    public static byte[] encodeMessagePacket(byte[] accountKey, byte[] sectionNonce, byte[] data)
-            throws GeneralSecurityException {
-        checkAccountKeyAndSectionNonce(accountKey, sectionNonce);
-
-        if (data == null || data.length == 0) {
-            throw new GeneralSecurityException("No input data for encodeMessagePacket");
-        }
-
-        byte[] messageNonce = generateNonce();
-        byte[] extractedHmac =
-                Arrays.copyOf(
-                        HmacSha256.buildWith64BytesKey(
-                                accountKey, concat(sectionNonce, messageNonce, data)),
-                        EXTRACT_HMAC_SIZE);
-
-        return concat(data, messageNonce, extractedHmac);
-    }
-
-    /** Verifies Hmac. */
-    public static boolean verifyHmac(byte[] accountKey, byte[] sectionNonce, byte[] data)
-            throws GeneralSecurityException {
-        checkAccountKeyAndSectionNonce(accountKey, sectionNonce);
-        if (data == null) {
-            throw new GeneralSecurityException("data is null");
-        }
-        if (data.length <= EXTRACT_HMAC_SIZE + SECTION_NONCE_LENGTH) {
-            throw new GeneralSecurityException("data.length too short");
-        }
-
-        byte[] hmac = Arrays.copyOfRange(data, data.length - EXTRACT_HMAC_SIZE, data.length);
-        byte[] messageNonce =
-                Arrays.copyOfRange(
-                        data,
-                        data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH,
-                        data.length - EXTRACT_HMAC_SIZE);
-        byte[] rawData = Arrays.copyOf(
-                data, data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH);
-        return Arrays.equals(
-                Arrays.copyOf(
-                        HmacSha256.buildWith64BytesKey(
-                                accountKey, concat(sectionNonce, messageNonce, rawData)),
-                        EXTRACT_HMAC_SIZE),
-                hmac);
-    }
-
-    private static void checkAccountKeyAndSectionNonce(byte[] accountKey, byte[] sectionNonce)
-            throws GeneralSecurityException {
-        if (accountKey == null || accountKey.length == 0) {
-            throw new GeneralSecurityException(
-                    "Incorrect accountKey for encoding message packet, accountKey.length = "
-                            + (accountKey == null ? "NULL" : accountKey.length));
-        }
-
-        if (sectionNonce == null || sectionNonce.length != SECTION_NONCE_LENGTH) {
-            throw new GeneralSecurityException(
-                    "Incorrect sectionNonce for encoding message packet, sectionNonce.length = "
-                            + (sectionNonce == null ? "NULL" : sectionNonce.length));
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java
deleted file mode 100644
index 1521be6..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.annotation.TargetApi;
-import android.os.Build.VERSION_CODES;
-
-import com.google.common.base.Utf8;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-/**
- * Naming utilities for encoding naming packet, decoding naming packet and verifying both the data
- * integrity and the authentication of a message by checking HMAC.
- *
- * <p>Naming packet is:
- *
- * <ol>
- *   <li>Naming_Packet[0 - 7]: the first 8-byte of HMAC.
- *   <li>Naming_Packet[8 - var]: the encrypted name (with 8-byte nonce appended to the front).
- * </ol>
- */
-@TargetApi(VERSION_CODES.M)
-public final class NamingEncoder {
-
-    static final int EXTRACT_HMAC_SIZE = 8;
-    static final int MAX_LENGTH_OF_NAME = 48;
-
-    private NamingEncoder() {
-    }
-
-    /**
-     * Encodes the name to naming packet by the given secret.
-     *
-     * @param secret AES-128 key for encryption.
-     * @param name the given name to be encoded.
-     * @return the encrypted data with the 8-byte extracted HMAC appended to the front.
-     * @throws GeneralSecurityException if the given key or name is invalid for encoding.
-     */
-    public static byte[] encodeNamingPacket(byte[] secret, String name)
-            throws GeneralSecurityException {
-        if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
-            throw new GeneralSecurityException(
-                    "Incorrect secret for encoding name packet, secret.length = "
-                            + (secret == null ? "NULL" : secret.length));
-        }
-
-        if ((name == null) || (name.length() == 0) || (Utf8.encodedLength(name)
-                > MAX_LENGTH_OF_NAME)) {
-            throw new GeneralSecurityException(
-                    "Invalid name for encoding name packet, Utf8.encodedLength(name) = "
-                            + (name == null ? "NULL" : Utf8.encodedLength(name)));
-        }
-
-        byte[] encryptedData = AesCtrMultipleBlockEncryption.encrypt(secret, name.getBytes(UTF_8));
-        byte[] extractedHmac =
-                Arrays.copyOf(HmacSha256.build(secret, encryptedData), EXTRACT_HMAC_SIZE);
-
-        return concat(extractedHmac, encryptedData);
-    }
-
-    /**
-     * Decodes the name from naming packet by the given secret.
-     *
-     * @param secret AES-128 key used in the encryption to decrypt data.
-     * @param namingPacket naming packet which is encoded by the given secret..
-     * @return the name decoded from the given packet.
-     * @throws GeneralSecurityException if the given key or naming packet is invalid for decoding.
-     */
-    public static String decodeNamingPacket(byte[] secret, byte[] namingPacket)
-            throws GeneralSecurityException {
-        if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) {
-            throw new GeneralSecurityException(
-                    "Incorrect secret for decoding name packet, secret.length = "
-                            + (secret == null ? "NULL" : secret.length));
-        }
-        if (namingPacket == null
-                || namingPacket.length <= EXTRACT_HMAC_SIZE
-                || namingPacket.length > (MAX_LENGTH_OF_NAME + EXTRACT_HMAC_SIZE + NONCE_SIZE)) {
-            throw new GeneralSecurityException(
-                    "Naming packet size is incorrect, namingPacket.length is "
-                            + (namingPacket == null ? "NULL" : namingPacket.length));
-        }
-
-        if (!verifyHmac(secret, namingPacket)) {
-            throw new GeneralSecurityException(
-                    "Verify HMAC failed, could be incorrect key or naming packet.");
-        }
-        byte[] encryptedData = Arrays
-                .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length);
-        return new String(AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData), UTF_8);
-    }
-
-    // Computes the HMAC of the given key and name, and compares the first 8-byte of the HMAC result
-    // with the one from name packet. Must call constant-time comparison to prevent a possible
-    // timing attack, e.g. time the same MAC with all different first byte for a given ciphertext,
-    // the right one will take longer as it will fail on the second byte's verification.
-    private static boolean verifyHmac(byte[] key, byte[] namingPacket)
-            throws GeneralSecurityException {
-        byte[] packetHmac = Arrays.copyOfRange(namingPacket, /* from= */ 0, EXTRACT_HMAC_SIZE);
-        byte[] encryptedData = Arrays
-                .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length);
-        byte[] computedHmac = Arrays
-                .copyOf(HmacSha256.build(key, encryptedData), EXTRACT_HMAC_SIZE);
-
-        return HmacSha256.compareTwoHMACs(packetHmac, computedHmac);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java
deleted file mode 100644
index 722dc85..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/** Base class for pairing exceptions. */
-// TODO(b/200594968): convert exceptions into error codes to save memory.
-public class PairingException extends Exception {
-    PairingException(String format, Object... objects) {
-        super(String.format(format, objects));
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java
deleted file mode 100644
index 270cb42..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import androidx.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Callback interface for pairing progress. */
-public interface PairingProgressListener {
-
-    /** Fast Pair Bond State. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    PairingEvent.START,
-                    PairingEvent.SUCCESS,
-                    PairingEvent.FAILED,
-                    PairingEvent.UNKNOWN,
-            })
-    public @interface PairingEvent {
-        int START = 0;
-        int SUCCESS = 1;
-        int FAILED = 2;
-        int UNKNOWN = 3;
-    }
-
-    /** Returns enum based on the ordinal index. */
-    static @PairingEvent int fromOrdinal(int ordinal) {
-        switch (ordinal) {
-            case 0:
-                return PairingEvent.START;
-            case 1:
-                return PairingEvent.SUCCESS;
-            case 2:
-                return PairingEvent.FAILED;
-            case 3:
-                return PairingEvent.UNKNOWN;
-            default:
-                return PairingEvent.UNKNOWN;
-        }
-    }
-
-    /** Callback function upon pairing progress update. */
-    void onPairingProgressUpdating(@PairingEvent int event, String message);
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java
deleted file mode 100644
index f5807a3..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.bluetooth.BluetoothDevice;
-
-/** Interface for getting the passkey confirmation request. */
-public interface PasskeyConfirmationHandler {
-    /** Called when getting the passkey confirmation request while pairing. */
-    void onPasskeyConfirmation(BluetoothDevice device, int passkey);
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
deleted file mode 100644
index bb7b71b..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
+++ /dev/null
@@ -1,2309 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.primitives.Shorts;
-
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Preferences that tweak the Fast Pairing process: timeouts, number of retries... All preferences
- * have default values which should be reasonable for all clients.
- */
-public class Preferences {
-
-    private final int mGattOperationTimeoutSeconds;
-    private final int mGattConnectionTimeoutSeconds;
-    private final int mBluetoothToggleTimeoutSeconds;
-    private final int mBluetoothToggleSleepSeconds;
-    private final int mClassicDiscoveryTimeoutSeconds;
-    private final int mNumDiscoverAttempts;
-    private final int mDiscoveryRetrySleepSeconds;
-    private final boolean mIgnoreDiscoveryError;
-    private final int mSdpTimeoutSeconds;
-    private final int mNumSdpAttempts;
-    private final int mNumCreateBondAttempts;
-    private final int mNumConnectAttempts;
-    private final int mNumWriteAccountKeyAttempts;
-    private final boolean mToggleBluetoothOnFailure;
-    private final boolean mBluetoothStateUsesPolling;
-    private final int mBluetoothStatePollingMillis;
-    private final int mNumAttempts;
-    private final boolean mEnableBrEdrHandover;
-    private final short mBrHandoverDataCharacteristicId;
-    private final short mBluetoothSigDataCharacteristicId;
-    private final short mFirmwareVersionCharacteristicId;
-    private final short mBrTransportBlockDataDescriptorId;
-    private final boolean mWaitForUuidsAfterBonding;
-    private final boolean mReceiveUuidsAndBondedEventBeforeClose;
-    private final int mRemoveBondTimeoutSeconds;
-    private final int mRemoveBondSleepMillis;
-    private final int mCreateBondTimeoutSeconds;
-    private final int mHidCreateBondTimeoutSeconds;
-    private final int mProxyTimeoutSeconds;
-    private final boolean mRejectPhonebookAccess;
-    private final boolean mRejectMessageAccess;
-    private final boolean mRejectSimAccess;
-    private final int mWriteAccountKeySleepMillis;
-    private final boolean mSkipDisconnectingGattBeforeWritingAccountKey;
-    private final boolean mMoreEventLogForQuality;
-    private final boolean mRetryGattConnectionAndSecretHandshake;
-    private final long mGattConnectShortTimeoutMs;
-    private final long mGattConnectLongTimeoutMs;
-    private final long mGattConnectShortTimeoutRetryMaxSpentTimeMs;
-    private final long mAddressRotateRetryMaxSpentTimeMs;
-    private final long mPairingRetryDelayMs;
-    private final long mSecretHandshakeShortTimeoutMs;
-    private final long mSecretHandshakeLongTimeoutMs;
-    private final long mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs;
-    private final long mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs;
-    private final long mSecretHandshakeRetryAttempts;
-    private final long mSecretHandshakeRetryGattConnectionMaxSpentTimeMs;
-    private final long mSignalLostRetryMaxSpentTimeMs;
-    private final ImmutableSet<Integer> mGattConnectionAndSecretHandshakeNoRetryGattError;
-    private final boolean mRetrySecretHandshakeTimeout;
-    private final boolean mLogUserManualRetry;
-    private final int mPairFailureCounts;
-    private final String mCachedDeviceAddress;
-    private final String mPossibleCachedDeviceAddress;
-    private final int mSameModelIdPairedDeviceCount;
-    private final boolean mIsDeviceFinishCheckAddressFromCache;
-    private final boolean mLogPairWithCachedModelId;
-    private final boolean mDirectConnectProfileIfModelIdInCache;
-    private final boolean mAcceptPasskey;
-    private final byte[] mSupportedProfileUuids;
-    private final boolean mProviderInitiatesBondingIfSupported;
-    private final boolean mAttemptDirectConnectionWhenPreviouslyBonded;
-    private final boolean mAutomaticallyReconnectGattWhenNeeded;
-    private final boolean mSkipConnectingProfiles;
-    private final boolean mIgnoreUuidTimeoutAfterBonded;
-    private final boolean mSpecifyCreateBondTransportType;
-    private final int mCreateBondTransportType;
-    private final boolean mIncreaseIntentFilterPriority;
-    private final boolean mEvaluatePerformance;
-    private final Preferences.ExtraLoggingInformation mExtraLoggingInformation;
-    private final boolean mEnableNamingCharacteristic;
-    private final boolean mEnableFirmwareVersionCharacteristic;
-    private final boolean mKeepSameAccountKeyWrite;
-    private final boolean mIsRetroactivePairing;
-    private final int mNumSdpAttemptsAfterBonded;
-    private final boolean mSupportHidDevice;
-    private final boolean mEnablePairingWhileDirectlyConnecting;
-    private final boolean mAcceptConsentForFastPairOne;
-    private final int mGattConnectRetryTimeoutMillis;
-    private final boolean mEnable128BitCustomGattCharacteristicsId;
-    private final boolean mEnableSendExceptionStepToValidator;
-    private final boolean mEnableAdditionalDataTypeWhenActionOverBle;
-    private final boolean mCheckBondStateWhenSkipConnectingProfiles;
-    private final boolean mHandlePasskeyConfirmationByUi;
-    private final boolean mEnablePairFlowShowUiWithoutProfileConnection;
-
-    private Preferences(
-            int gattOperationTimeoutSeconds,
-            int gattConnectionTimeoutSeconds,
-            int bluetoothToggleTimeoutSeconds,
-            int bluetoothToggleSleepSeconds,
-            int classicDiscoveryTimeoutSeconds,
-            int numDiscoverAttempts,
-            int discoveryRetrySleepSeconds,
-            boolean ignoreDiscoveryError,
-            int sdpTimeoutSeconds,
-            int numSdpAttempts,
-            int numCreateBondAttempts,
-            int numConnectAttempts,
-            int numWriteAccountKeyAttempts,
-            boolean toggleBluetoothOnFailure,
-            boolean bluetoothStateUsesPolling,
-            int bluetoothStatePollingMillis,
-            int numAttempts,
-            boolean enableBrEdrHandover,
-            short brHandoverDataCharacteristicId,
-            short bluetoothSigDataCharacteristicId,
-            short firmwareVersionCharacteristicId,
-            short brTransportBlockDataDescriptorId,
-            boolean waitForUuidsAfterBonding,
-            boolean receiveUuidsAndBondedEventBeforeClose,
-            int removeBondTimeoutSeconds,
-            int removeBondSleepMillis,
-            int createBondTimeoutSeconds,
-            int hidCreateBondTimeoutSeconds,
-            int proxyTimeoutSeconds,
-            boolean rejectPhonebookAccess,
-            boolean rejectMessageAccess,
-            boolean rejectSimAccess,
-            int writeAccountKeySleepMillis,
-            boolean skipDisconnectingGattBeforeWritingAccountKey,
-            boolean moreEventLogForQuality,
-            boolean retryGattConnectionAndSecretHandshake,
-            long gattConnectShortTimeoutMs,
-            long gattConnectLongTimeoutMs,
-            long gattConnectShortTimeoutRetryMaxSpentTimeMs,
-            long addressRotateRetryMaxSpentTimeMs,
-            long pairingRetryDelayMs,
-            long secretHandshakeShortTimeoutMs,
-            long secretHandshakeLongTimeoutMs,
-            long secretHandshakeShortTimeoutRetryMaxSpentTimeMs,
-            long secretHandshakeLongTimeoutRetryMaxSpentTimeMs,
-            long secretHandshakeRetryAttempts,
-            long secretHandshakeRetryGattConnectionMaxSpentTimeMs,
-            long signalLostRetryMaxSpentTimeMs,
-            ImmutableSet<Integer> gattConnectionAndSecretHandshakeNoRetryGattError,
-            boolean retrySecretHandshakeTimeout,
-            boolean logUserManualRetry,
-            int pairFailureCounts,
-            String cachedDeviceAddress,
-            String possibleCachedDeviceAddress,
-            int sameModelIdPairedDeviceCount,
-            boolean isDeviceFinishCheckAddressFromCache,
-            boolean logPairWithCachedModelId,
-            boolean directConnectProfileIfModelIdInCache,
-            boolean acceptPasskey,
-            byte[] supportedProfileUuids,
-            boolean providerInitiatesBondingIfSupported,
-            boolean attemptDirectConnectionWhenPreviouslyBonded,
-            boolean automaticallyReconnectGattWhenNeeded,
-            boolean skipConnectingProfiles,
-            boolean ignoreUuidTimeoutAfterBonded,
-            boolean specifyCreateBondTransportType,
-            int createBondTransportType,
-            boolean increaseIntentFilterPriority,
-            boolean evaluatePerformance,
-            @Nullable Preferences.ExtraLoggingInformation extraLoggingInformation,
-            boolean enableNamingCharacteristic,
-            boolean enableFirmwareVersionCharacteristic,
-            boolean keepSameAccountKeyWrite,
-            boolean isRetroactivePairing,
-            int numSdpAttemptsAfterBonded,
-            boolean supportHidDevice,
-            boolean enablePairingWhileDirectlyConnecting,
-            boolean acceptConsentForFastPairOne,
-            int gattConnectRetryTimeoutMillis,
-            boolean enable128BitCustomGattCharacteristicsId,
-            boolean enableSendExceptionStepToValidator,
-            boolean enableAdditionalDataTypeWhenActionOverBle,
-            boolean checkBondStateWhenSkipConnectingProfiles,
-            boolean handlePasskeyConfirmationByUi,
-            boolean enablePairFlowShowUiWithoutProfileConnection) {
-        this.mGattOperationTimeoutSeconds = gattOperationTimeoutSeconds;
-        this.mGattConnectionTimeoutSeconds = gattConnectionTimeoutSeconds;
-        this.mBluetoothToggleTimeoutSeconds = bluetoothToggleTimeoutSeconds;
-        this.mBluetoothToggleSleepSeconds = bluetoothToggleSleepSeconds;
-        this.mClassicDiscoveryTimeoutSeconds = classicDiscoveryTimeoutSeconds;
-        this.mNumDiscoverAttempts = numDiscoverAttempts;
-        this.mDiscoveryRetrySleepSeconds = discoveryRetrySleepSeconds;
-        this.mIgnoreDiscoveryError = ignoreDiscoveryError;
-        this.mSdpTimeoutSeconds = sdpTimeoutSeconds;
-        this.mNumSdpAttempts = numSdpAttempts;
-        this.mNumCreateBondAttempts = numCreateBondAttempts;
-        this.mNumConnectAttempts = numConnectAttempts;
-        this.mNumWriteAccountKeyAttempts = numWriteAccountKeyAttempts;
-        this.mToggleBluetoothOnFailure = toggleBluetoothOnFailure;
-        this.mBluetoothStateUsesPolling = bluetoothStateUsesPolling;
-        this.mBluetoothStatePollingMillis = bluetoothStatePollingMillis;
-        this.mNumAttempts = numAttempts;
-        this.mEnableBrEdrHandover = enableBrEdrHandover;
-        this.mBrHandoverDataCharacteristicId = brHandoverDataCharacteristicId;
-        this.mBluetoothSigDataCharacteristicId = bluetoothSigDataCharacteristicId;
-        this.mFirmwareVersionCharacteristicId = firmwareVersionCharacteristicId;
-        this.mBrTransportBlockDataDescriptorId = brTransportBlockDataDescriptorId;
-        this.mWaitForUuidsAfterBonding = waitForUuidsAfterBonding;
-        this.mReceiveUuidsAndBondedEventBeforeClose = receiveUuidsAndBondedEventBeforeClose;
-        this.mRemoveBondTimeoutSeconds = removeBondTimeoutSeconds;
-        this.mRemoveBondSleepMillis = removeBondSleepMillis;
-        this.mCreateBondTimeoutSeconds = createBondTimeoutSeconds;
-        this.mHidCreateBondTimeoutSeconds = hidCreateBondTimeoutSeconds;
-        this.mProxyTimeoutSeconds = proxyTimeoutSeconds;
-        this.mRejectPhonebookAccess = rejectPhonebookAccess;
-        this.mRejectMessageAccess = rejectMessageAccess;
-        this.mRejectSimAccess = rejectSimAccess;
-        this.mWriteAccountKeySleepMillis = writeAccountKeySleepMillis;
-        this.mSkipDisconnectingGattBeforeWritingAccountKey =
-                skipDisconnectingGattBeforeWritingAccountKey;
-        this.mMoreEventLogForQuality = moreEventLogForQuality;
-        this.mRetryGattConnectionAndSecretHandshake = retryGattConnectionAndSecretHandshake;
-        this.mGattConnectShortTimeoutMs = gattConnectShortTimeoutMs;
-        this.mGattConnectLongTimeoutMs = gattConnectLongTimeoutMs;
-        this.mGattConnectShortTimeoutRetryMaxSpentTimeMs =
-                gattConnectShortTimeoutRetryMaxSpentTimeMs;
-        this.mAddressRotateRetryMaxSpentTimeMs = addressRotateRetryMaxSpentTimeMs;
-        this.mPairingRetryDelayMs = pairingRetryDelayMs;
-        this.mSecretHandshakeShortTimeoutMs = secretHandshakeShortTimeoutMs;
-        this.mSecretHandshakeLongTimeoutMs = secretHandshakeLongTimeoutMs;
-        this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs =
-                secretHandshakeShortTimeoutRetryMaxSpentTimeMs;
-        this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs =
-                secretHandshakeLongTimeoutRetryMaxSpentTimeMs;
-        this.mSecretHandshakeRetryAttempts = secretHandshakeRetryAttempts;
-        this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs =
-                secretHandshakeRetryGattConnectionMaxSpentTimeMs;
-        this.mSignalLostRetryMaxSpentTimeMs = signalLostRetryMaxSpentTimeMs;
-        this.mGattConnectionAndSecretHandshakeNoRetryGattError =
-                gattConnectionAndSecretHandshakeNoRetryGattError;
-        this.mRetrySecretHandshakeTimeout = retrySecretHandshakeTimeout;
-        this.mLogUserManualRetry = logUserManualRetry;
-        this.mPairFailureCounts = pairFailureCounts;
-        this.mCachedDeviceAddress = cachedDeviceAddress;
-        this.mPossibleCachedDeviceAddress = possibleCachedDeviceAddress;
-        this.mSameModelIdPairedDeviceCount = sameModelIdPairedDeviceCount;
-        this.mIsDeviceFinishCheckAddressFromCache = isDeviceFinishCheckAddressFromCache;
-        this.mLogPairWithCachedModelId = logPairWithCachedModelId;
-        this.mDirectConnectProfileIfModelIdInCache = directConnectProfileIfModelIdInCache;
-        this.mAcceptPasskey = acceptPasskey;
-        this.mSupportedProfileUuids = supportedProfileUuids;
-        this.mProviderInitiatesBondingIfSupported = providerInitiatesBondingIfSupported;
-        this.mAttemptDirectConnectionWhenPreviouslyBonded =
-                attemptDirectConnectionWhenPreviouslyBonded;
-        this.mAutomaticallyReconnectGattWhenNeeded = automaticallyReconnectGattWhenNeeded;
-        this.mSkipConnectingProfiles = skipConnectingProfiles;
-        this.mIgnoreUuidTimeoutAfterBonded = ignoreUuidTimeoutAfterBonded;
-        this.mSpecifyCreateBondTransportType = specifyCreateBondTransportType;
-        this.mCreateBondTransportType = createBondTransportType;
-        this.mIncreaseIntentFilterPriority = increaseIntentFilterPriority;
-        this.mEvaluatePerformance = evaluatePerformance;
-        this.mExtraLoggingInformation = extraLoggingInformation;
-        this.mEnableNamingCharacteristic = enableNamingCharacteristic;
-        this.mEnableFirmwareVersionCharacteristic = enableFirmwareVersionCharacteristic;
-        this.mKeepSameAccountKeyWrite = keepSameAccountKeyWrite;
-        this.mIsRetroactivePairing = isRetroactivePairing;
-        this.mNumSdpAttemptsAfterBonded = numSdpAttemptsAfterBonded;
-        this.mSupportHidDevice = supportHidDevice;
-        this.mEnablePairingWhileDirectlyConnecting = enablePairingWhileDirectlyConnecting;
-        this.mAcceptConsentForFastPairOne = acceptConsentForFastPairOne;
-        this.mGattConnectRetryTimeoutMillis = gattConnectRetryTimeoutMillis;
-        this.mEnable128BitCustomGattCharacteristicsId = enable128BitCustomGattCharacteristicsId;
-        this.mEnableSendExceptionStepToValidator = enableSendExceptionStepToValidator;
-        this.mEnableAdditionalDataTypeWhenActionOverBle = enableAdditionalDataTypeWhenActionOverBle;
-        this.mCheckBondStateWhenSkipConnectingProfiles = checkBondStateWhenSkipConnectingProfiles;
-        this.mHandlePasskeyConfirmationByUi = handlePasskeyConfirmationByUi;
-        this.mEnablePairFlowShowUiWithoutProfileConnection =
-                enablePairFlowShowUiWithoutProfileConnection;
-    }
-
-    /**
-     * Timeout for each GATT operation (not for the whole pairing process).
-     */
-    public int getGattOperationTimeoutSeconds() {
-        return mGattOperationTimeoutSeconds;
-    }
-
-    /**
-     * Timeout for Gatt connection operation.
-     */
-    public int getGattConnectionTimeoutSeconds() {
-        return mGattConnectionTimeoutSeconds;
-    }
-
-    /**
-     * Timeout for Bluetooth toggle.
-     */
-    public int getBluetoothToggleTimeoutSeconds() {
-        return mBluetoothToggleTimeoutSeconds;
-    }
-
-    /**
-     * Sleep time for Bluetooth toggle.
-     */
-    public int getBluetoothToggleSleepSeconds() {
-        return mBluetoothToggleSleepSeconds;
-    }
-
-    /**
-     * Timeout for classic discovery.
-     */
-    public int getClassicDiscoveryTimeoutSeconds() {
-        return mClassicDiscoveryTimeoutSeconds;
-    }
-
-    /**
-     * Number of discovery attempts allowed.
-     */
-    public int getNumDiscoverAttempts() {
-        return mNumDiscoverAttempts;
-    }
-
-    /**
-     * Sleep time between discovery retry.
-     */
-    public int getDiscoveryRetrySleepSeconds() {
-        return mDiscoveryRetrySleepSeconds;
-    }
-
-    /**
-     * Whether to ignore error incurred during discovery.
-     */
-    public boolean getIgnoreDiscoveryError() {
-        return mIgnoreDiscoveryError;
-    }
-
-    /**
-     * Timeout for Sdp.
-     */
-    public int getSdpTimeoutSeconds() {
-        return mSdpTimeoutSeconds;
-    }
-
-    /**
-     * Number of Sdp attempts allowed.
-     */
-    public int getNumSdpAttempts() {
-        return mNumSdpAttempts;
-    }
-
-    /**
-     * Number of create bond attempts allowed.
-     */
-    public int getNumCreateBondAttempts() {
-        return mNumCreateBondAttempts;
-    }
-
-    /**
-     * Number of connect attempts allowed.
-     */
-    public int getNumConnectAttempts() {
-        return mNumConnectAttempts;
-    }
-
-    /**
-     * Number of write account key attempts allowed.
-     */
-    public int getNumWriteAccountKeyAttempts() {
-        return mNumWriteAccountKeyAttempts;
-    }
-
-    /**
-     * Returns whether it is OK toggle bluetooth to retry upon failure.
-     */
-    public boolean getToggleBluetoothOnFailure() {
-        return mToggleBluetoothOnFailure;
-    }
-
-    /**
-     * Whether to get Bluetooth state using polling.
-     */
-    public boolean getBluetoothStateUsesPolling() {
-        return mBluetoothStateUsesPolling;
-    }
-
-    /**
-     * Polling time when retrieving Bluetooth state.
-     */
-    public int getBluetoothStatePollingMillis() {
-        return mBluetoothStatePollingMillis;
-    }
-
-    /**
-     * The number of times to attempt a generic operation, before giving up.
-     */
-    public int getNumAttempts() {
-        return mNumAttempts;
-    }
-
-    /**
-     * Returns whether BrEdr handover is enabled.
-     */
-    public boolean getEnableBrEdrHandover() {
-        return mEnableBrEdrHandover;
-    }
-
-    /**
-     * Returns characteristic Id for Br Handover data.
-     */
-    public short getBrHandoverDataCharacteristicId() {
-        return mBrHandoverDataCharacteristicId;
-    }
-
-    /**
-     * Returns characteristic Id for Bluethoth Sig data.
-     */
-    public short getBluetoothSigDataCharacteristicId() {
-        return mBluetoothSigDataCharacteristicId;
-    }
-
-    /**
-     * Returns characteristic Id for Firmware version.
-     */
-    public short getFirmwareVersionCharacteristicId() {
-        return mFirmwareVersionCharacteristicId;
-    }
-
-    /**
-     * Returns descripter Id for Br transport block data.
-     */
-    public short getBrTransportBlockDataDescriptorId() {
-        return mBrTransportBlockDataDescriptorId;
-    }
-
-    /**
-     * Whether to wait for Uuids after bonding.
-     */
-    public boolean getWaitForUuidsAfterBonding() {
-        return mWaitForUuidsAfterBonding;
-    }
-
-    /**
-     * Whether to get received Uuids and bonded events before close.
-     */
-    public boolean getReceiveUuidsAndBondedEventBeforeClose() {
-        return mReceiveUuidsAndBondedEventBeforeClose;
-    }
-
-    /**
-     * Timeout for remove bond operation.
-     */
-    public int getRemoveBondTimeoutSeconds() {
-        return mRemoveBondTimeoutSeconds;
-    }
-
-    /**
-     * Sleep time for remove bond operation.
-     */
-    public int getRemoveBondSleepMillis() {
-        return mRemoveBondSleepMillis;
-    }
-
-    /**
-     * This almost always succeeds (or fails) in 2-10 seconds (Taimen running O -> Nexus 6P sim).
-     */
-    public int getCreateBondTimeoutSeconds() {
-        return mCreateBondTimeoutSeconds;
-    }
-
-    /**
-     * Timeout for creating bond with Hid devices.
-     */
-    public int getHidCreateBondTimeoutSeconds() {
-        return mHidCreateBondTimeoutSeconds;
-    }
-
-    /**
-     * Timeout for get proxy operation.
-     */
-    public int getProxyTimeoutSeconds() {
-        return mProxyTimeoutSeconds;
-    }
-
-    /**
-     * Whether to reject phone book access.
-     */
-    public boolean getRejectPhonebookAccess() {
-        return mRejectPhonebookAccess;
-    }
-
-    /**
-     * Whether to reject message access.
-     */
-    public boolean getRejectMessageAccess() {
-        return mRejectMessageAccess;
-    }
-
-    /**
-     * Whether to reject sim access.
-     */
-    public boolean getRejectSimAccess() {
-        return mRejectSimAccess;
-    }
-
-    /**
-     * Sleep time for write account key operation.
-     */
-    public int getWriteAccountKeySleepMillis() {
-        return mWriteAccountKeySleepMillis;
-    }
-
-    /**
-     * Whether to skip disconneting gatt before writing account key.
-     */
-    public boolean getSkipDisconnectingGattBeforeWritingAccountKey() {
-        return mSkipDisconnectingGattBeforeWritingAccountKey;
-    }
-
-    /**
-     * Whether to get more event log for quality improvement.
-     */
-    public boolean getMoreEventLogForQuality() {
-        return mMoreEventLogForQuality;
-    }
-
-    /**
-     * Whether to retry gatt connection and secrete handshake.
-     */
-    public boolean getRetryGattConnectionAndSecretHandshake() {
-        return mRetryGattConnectionAndSecretHandshake;
-    }
-
-    /**
-     * Short Gatt connection timeoout.
-     */
-    public long getGattConnectShortTimeoutMs() {
-        return mGattConnectShortTimeoutMs;
-    }
-
-    /**
-     * Long Gatt connection timeout.
-     */
-    public long getGattConnectLongTimeoutMs() {
-        return mGattConnectLongTimeoutMs;
-    }
-
-    /**
-     * Short Timeout for Gatt connection, including retry.
-     */
-    public long getGattConnectShortTimeoutRetryMaxSpentTimeMs() {
-        return mGattConnectShortTimeoutRetryMaxSpentTimeMs;
-    }
-
-    /**
-     * Timeout for address rotation, including retry.
-     */
-    public long getAddressRotateRetryMaxSpentTimeMs() {
-        return mAddressRotateRetryMaxSpentTimeMs;
-    }
-
-    /**
-     * Returns pairing retry delay time.
-     */
-    public long getPairingRetryDelayMs() {
-        return mPairingRetryDelayMs;
-    }
-
-    /**
-     * Short timeout for secrete handshake.
-     */
-    public long getSecretHandshakeShortTimeoutMs() {
-        return mSecretHandshakeShortTimeoutMs;
-    }
-
-    /**
-     * Long timeout for secret handshake.
-     */
-    public long getSecretHandshakeLongTimeoutMs() {
-        return mSecretHandshakeLongTimeoutMs;
-    }
-
-    /**
-     * Short timeout for secret handshake, including retry.
-     */
-    public long getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() {
-        return mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs;
-    }
-
-    /**
-     * Long timeout for secret handshake, including retry.
-     */
-    public long getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs() {
-        return mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs;
-    }
-
-    /**
-     * Number of secrete handshake retry allowed.
-     */
-    public long getSecretHandshakeRetryAttempts() {
-        return mSecretHandshakeRetryAttempts;
-    }
-
-    /**
-     * Timeout for secrete handshake and gatt connection, including retry.
-     */
-    public long getSecretHandshakeRetryGattConnectionMaxSpentTimeMs() {
-        return mSecretHandshakeRetryGattConnectionMaxSpentTimeMs;
-    }
-
-    /**
-     * Timeout for signal lost handling, including retry.
-     */
-    public long getSignalLostRetryMaxSpentTimeMs() {
-        return mSignalLostRetryMaxSpentTimeMs;
-    }
-
-    /**
-     * Returns error for gatt connection and secrete handshake, without retry.
-     */
-    public ImmutableSet<Integer> getGattConnectionAndSecretHandshakeNoRetryGattError() {
-        return mGattConnectionAndSecretHandshakeNoRetryGattError;
-    }
-
-    /**
-     * Whether to retry upon secrete handshake timeout.
-     */
-    public boolean getRetrySecretHandshakeTimeout() {
-        return mRetrySecretHandshakeTimeout;
-    }
-
-    /**
-     * Wehther to log user manual retry.
-     */
-    public boolean getLogUserManualRetry() {
-        return mLogUserManualRetry;
-    }
-
-    /**
-     * Returns number of pairing failure counts.
-     */
-    public int getPairFailureCounts() {
-        return mPairFailureCounts;
-    }
-
-    /**
-     * Returns cached device address.
-     */
-    public String getCachedDeviceAddress() {
-        return mCachedDeviceAddress;
-    }
-
-    /**
-     * Returns possible cached device address.
-     */
-    public String getPossibleCachedDeviceAddress() {
-        return mPossibleCachedDeviceAddress;
-    }
-
-    /**
-     * Returns count of paired devices from the same model Id.
-     */
-    public int getSameModelIdPairedDeviceCount() {
-        return mSameModelIdPairedDeviceCount;
-    }
-
-    /**
-     * Whether the bonded device address is in the Cache .
-     */
-    public boolean getIsDeviceFinishCheckAddressFromCache() {
-        return mIsDeviceFinishCheckAddressFromCache;
-    }
-
-    /**
-     * Whether to log pairing info when cached model Id is hit.
-     */
-    public boolean getLogPairWithCachedModelId() {
-        return mLogPairWithCachedModelId;
-    }
-
-    /**
-     * Whether to directly connnect to a profile of a device, whose model Id is in cache.
-     */
-    public boolean getDirectConnectProfileIfModelIdInCache() {
-        return mDirectConnectProfileIfModelIdInCache;
-    }
-
-    /**
-     * Whether to auto-accept
-     * {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}.
-     * Only the Fast Pair Simulator (which runs on an Android device) sends this. Since real
-     * Bluetooth headphones don't have displays, they use secure simple pairing (no pin code
-     * confirmation; we get no pairing request broadcast at all). So we may want to turn this off in
-     * prod.
-     */
-    public boolean getAcceptPasskey() {
-        return mAcceptPasskey;
-    }
-
-    /**
-     * Returns Uuids for supported profiles.
-     */
-    @SuppressWarnings("mutable")
-    public byte[] getSupportedProfileUuids() {
-        return mSupportedProfileUuids;
-    }
-
-    /**
-     * If true, after the Key-based Pairing BLE handshake, we wait for the headphones to send a
-     * pairing request to us; if false, we send the request to them.
-     */
-    public boolean getProviderInitiatesBondingIfSupported() {
-        return mProviderInitiatesBondingIfSupported;
-    }
-
-    /**
-     * If true, the first step will be attempting to connect directly to our supported profiles when
-     * a device has previously been bonded. This will help with performance on subsequent bondings
-     * and help to increase reliability in some cases.
-     */
-    public boolean getAttemptDirectConnectionWhenPreviouslyBonded() {
-        return mAttemptDirectConnectionWhenPreviouslyBonded;
-    }
-
-    /**
-     * If true, closed Gatt connections will be reopened when they are needed again. Otherwise, they
-     * will remain closed until they are explicitly reopened.
-     */
-    public boolean getAutomaticallyReconnectGattWhenNeeded() {
-        return mAutomaticallyReconnectGattWhenNeeded;
-    }
-
-    /**
-     * If true, we'll finish the pairing process after we've created a bond instead of after
-     * connecting a profile.
-     */
-    public boolean getSkipConnectingProfiles() {
-        return mSkipConnectingProfiles;
-    }
-
-    /**
-     * If true, continues the pairing process if we've timed out due to not receiving UUIDs from the
-     * headset. We can still attempt to connect to A2DP afterwards. If false, Fast Pair will fail
-     * after this step since we're expecting to receive the UUIDs.
-     */
-    public boolean getIgnoreUuidTimeoutAfterBonded() {
-        return mIgnoreUuidTimeoutAfterBonded;
-    }
-
-    /**
-     * If true, a specific transport type will be included in the create bond request, which will be
-     * used for dual mode devices. Otherwise, we'll use the platform defined default which is
-     * BluetoothDevice.TRANSPORT_AUTO. See {@link #getCreateBondTransportType()}.
-     */
-    public boolean getSpecifyCreateBondTransportType() {
-        return mSpecifyCreateBondTransportType;
-    }
-
-    /**
-     * The transport type to use when creating a bond when
-     * {@link #getSpecifyCreateBondTransportType() is true. This should be one of
-     * BluetoothDevice.TRANSPORT_AUTO, BluetoothDevice.TRANSPORT_BREDR,
-     * or BluetoothDevice.TRANSPORT_LE.
-     */
-    public int getCreateBondTransportType() {
-        return mCreateBondTransportType;
-    }
-
-    /**
-     * Whether to increase intent filter priority.
-     */
-    public boolean getIncreaseIntentFilterPriority() {
-        return mIncreaseIntentFilterPriority;
-    }
-
-    /**
-     * Whether to evaluate performance.
-     */
-    public boolean getEvaluatePerformance() {
-        return mEvaluatePerformance;
-    }
-
-    /**
-     * Returns extra logging information.
-     */
-    @Nullable
-    public ExtraLoggingInformation getExtraLoggingInformation() {
-        return mExtraLoggingInformation;
-    }
-
-    /**
-     * Whether to enable naming characteristic.
-     */
-    public boolean getEnableNamingCharacteristic() {
-        return mEnableNamingCharacteristic;
-    }
-
-    /**
-     * Whether to enable firmware version characteristic.
-     */
-    public boolean getEnableFirmwareVersionCharacteristic() {
-        return mEnableFirmwareVersionCharacteristic;
-    }
-
-    /**
-     * If true, even Fast Pair identifies a provider have paired with the account, still writes the
-     * identified account key to the provider.
-     */
-    public boolean getKeepSameAccountKeyWrite() {
-        return mKeepSameAccountKeyWrite;
-    }
-
-    /**
-     * If true, run retroactive pairing.
-     */
-    public boolean getIsRetroactivePairing() {
-        return mIsRetroactivePairing;
-    }
-
-    /**
-     * If it's larger than 0, {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp} would be
-     * triggered with number of attempts after device is bonded and no profiles were automatically
-     * discovered".
-     */
-    public int getNumSdpAttemptsAfterBonded() {
-        return mNumSdpAttemptsAfterBonded;
-    }
-
-    /**
-     * If true, supports HID device for fastpair.
-     */
-    public boolean getSupportHidDevice() {
-        return mSupportHidDevice;
-    }
-
-    /**
-     * If true, we'll enable the pairing behavior to handle the state transition from BOND_BONDED to
-     * BOND_BONDING when directly connecting profiles.
-     */
-    public boolean getEnablePairingWhileDirectlyConnecting() {
-        return mEnablePairingWhileDirectlyConnecting;
-    }
-
-    /**
-     * If true, we will accept the user confirmation when bonding with FastPair 1.0 devices.
-     */
-    public boolean getAcceptConsentForFastPairOne() {
-        return mAcceptConsentForFastPairOne;
-    }
-
-    /**
-     * If it's larger than 0, we will retry connecting GATT within the timeout.
-     */
-    public int getGattConnectRetryTimeoutMillis() {
-        return mGattConnectRetryTimeoutMillis;
-    }
-
-    /**
-     * If true, then uses the new custom GATT characteristics {go/fastpair-128bit-gatt}.
-     */
-    public boolean getEnable128BitCustomGattCharacteristicsId() {
-        return mEnable128BitCustomGattCharacteristicsId;
-    }
-
-    /**
-     * If true, then sends the internal pair step or Exception to Validator by Intent.
-     */
-    public boolean getEnableSendExceptionStepToValidator() {
-        return mEnableSendExceptionStepToValidator;
-    }
-
-    /**
-     * If true, then adds the additional data type in the handshake packet when action over BLE.
-     */
-    public boolean getEnableAdditionalDataTypeWhenActionOverBle() {
-        return mEnableAdditionalDataTypeWhenActionOverBle;
-    }
-
-    /**
-     * If true, then checks the bond state when skips connecting profiles in the pairing shortcut.
-     */
-    public boolean getCheckBondStateWhenSkipConnectingProfiles() {
-        return mCheckBondStateWhenSkipConnectingProfiles;
-    }
-
-    /**
-     * If true, the passkey confirmation will be handled by the half-sheet UI.
-     */
-    public boolean getHandlePasskeyConfirmationByUi() {
-        return mHandlePasskeyConfirmationByUi;
-    }
-
-    /**
-     * If true, then use pair flow to show ui when pairing is finished without connecting profile.
-     */
-    public boolean getEnablePairFlowShowUiWithoutProfileConnection() {
-        return mEnablePairFlowShowUiWithoutProfileConnection;
-    }
-
-    @Override
-    public String toString() {
-        return "Preferences{"
-                + "gattOperationTimeoutSeconds=" + mGattOperationTimeoutSeconds + ", "
-                + "gattConnectionTimeoutSeconds=" + mGattConnectionTimeoutSeconds + ", "
-                + "bluetoothToggleTimeoutSeconds=" + mBluetoothToggleTimeoutSeconds + ", "
-                + "bluetoothToggleSleepSeconds=" + mBluetoothToggleSleepSeconds + ", "
-                + "classicDiscoveryTimeoutSeconds=" + mClassicDiscoveryTimeoutSeconds + ", "
-                + "numDiscoverAttempts=" + mNumDiscoverAttempts + ", "
-                + "discoveryRetrySleepSeconds=" + mDiscoveryRetrySleepSeconds + ", "
-                + "ignoreDiscoveryError=" + mIgnoreDiscoveryError + ", "
-                + "sdpTimeoutSeconds=" + mSdpTimeoutSeconds + ", "
-                + "numSdpAttempts=" + mNumSdpAttempts + ", "
-                + "numCreateBondAttempts=" + mNumCreateBondAttempts + ", "
-                + "numConnectAttempts=" + mNumConnectAttempts + ", "
-                + "numWriteAccountKeyAttempts=" + mNumWriteAccountKeyAttempts + ", "
-                + "toggleBluetoothOnFailure=" + mToggleBluetoothOnFailure + ", "
-                + "bluetoothStateUsesPolling=" + mBluetoothStateUsesPolling + ", "
-                + "bluetoothStatePollingMillis=" + mBluetoothStatePollingMillis + ", "
-                + "numAttempts=" + mNumAttempts + ", "
-                + "enableBrEdrHandover=" + mEnableBrEdrHandover + ", "
-                + "brHandoverDataCharacteristicId=" + mBrHandoverDataCharacteristicId + ", "
-                + "bluetoothSigDataCharacteristicId=" + mBluetoothSigDataCharacteristicId + ", "
-                + "firmwareVersionCharacteristicId=" + mFirmwareVersionCharacteristicId + ", "
-                + "brTransportBlockDataDescriptorId=" + mBrTransportBlockDataDescriptorId + ", "
-                + "waitForUuidsAfterBonding=" + mWaitForUuidsAfterBonding + ", "
-                + "receiveUuidsAndBondedEventBeforeClose=" + mReceiveUuidsAndBondedEventBeforeClose
-                + ", "
-                + "removeBondTimeoutSeconds=" + mRemoveBondTimeoutSeconds + ", "
-                + "removeBondSleepMillis=" + mRemoveBondSleepMillis + ", "
-                + "createBondTimeoutSeconds=" + mCreateBondTimeoutSeconds + ", "
-                + "hidCreateBondTimeoutSeconds=" + mHidCreateBondTimeoutSeconds + ", "
-                + "proxyTimeoutSeconds=" + mProxyTimeoutSeconds + ", "
-                + "rejectPhonebookAccess=" + mRejectPhonebookAccess + ", "
-                + "rejectMessageAccess=" + mRejectMessageAccess + ", "
-                + "rejectSimAccess=" + mRejectSimAccess + ", "
-                + "writeAccountKeySleepMillis=" + mWriteAccountKeySleepMillis + ", "
-                + "skipDisconnectingGattBeforeWritingAccountKey="
-                + mSkipDisconnectingGattBeforeWritingAccountKey + ", "
-                + "moreEventLogForQuality=" + mMoreEventLogForQuality + ", "
-                + "retryGattConnectionAndSecretHandshake=" + mRetryGattConnectionAndSecretHandshake
-                + ", "
-                + "gattConnectShortTimeoutMs=" + mGattConnectShortTimeoutMs + ", "
-                + "gattConnectLongTimeoutMs=" + mGattConnectLongTimeoutMs + ", "
-                + "gattConnectShortTimeoutRetryMaxSpentTimeMs="
-                + mGattConnectShortTimeoutRetryMaxSpentTimeMs + ", "
-                + "addressRotateRetryMaxSpentTimeMs=" + mAddressRotateRetryMaxSpentTimeMs + ", "
-                + "pairingRetryDelayMs=" + mPairingRetryDelayMs + ", "
-                + "secretHandshakeShortTimeoutMs=" + mSecretHandshakeShortTimeoutMs + ", "
-                + "secretHandshakeLongTimeoutMs=" + mSecretHandshakeLongTimeoutMs + ", "
-                + "secretHandshakeShortTimeoutRetryMaxSpentTimeMs="
-                + mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs + ", "
-                + "secretHandshakeLongTimeoutRetryMaxSpentTimeMs="
-                + mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs + ", "
-                + "secretHandshakeRetryAttempts=" + mSecretHandshakeRetryAttempts + ", "
-                + "secretHandshakeRetryGattConnectionMaxSpentTimeMs="
-                + mSecretHandshakeRetryGattConnectionMaxSpentTimeMs + ", "
-                + "signalLostRetryMaxSpentTimeMs=" + mSignalLostRetryMaxSpentTimeMs + ", "
-                + "gattConnectionAndSecretHandshakeNoRetryGattError="
-                + mGattConnectionAndSecretHandshakeNoRetryGattError + ", "
-                + "retrySecretHandshakeTimeout=" + mRetrySecretHandshakeTimeout + ", "
-                + "logUserManualRetry=" + mLogUserManualRetry + ", "
-                + "pairFailureCounts=" + mPairFailureCounts + ", "
-                + "cachedDeviceAddress=" + mCachedDeviceAddress + ", "
-                + "possibleCachedDeviceAddress=" + mPossibleCachedDeviceAddress + ", "
-                + "sameModelIdPairedDeviceCount=" + mSameModelIdPairedDeviceCount + ", "
-                + "isDeviceFinishCheckAddressFromCache=" + mIsDeviceFinishCheckAddressFromCache
-                + ", "
-                + "logPairWithCachedModelId=" + mLogPairWithCachedModelId + ", "
-                + "directConnectProfileIfModelIdInCache=" + mDirectConnectProfileIfModelIdInCache
-                + ", "
-                + "acceptPasskey=" + mAcceptPasskey + ", "
-                + "supportedProfileUuids=" + Arrays.toString(mSupportedProfileUuids) + ", "
-                + "providerInitiatesBondingIfSupported=" + mProviderInitiatesBondingIfSupported
-                + ", "
-                + "attemptDirectConnectionWhenPreviouslyBonded="
-                + mAttemptDirectConnectionWhenPreviouslyBonded + ", "
-                + "automaticallyReconnectGattWhenNeeded=" + mAutomaticallyReconnectGattWhenNeeded
-                + ", "
-                + "skipConnectingProfiles=" + mSkipConnectingProfiles + ", "
-                + "ignoreUuidTimeoutAfterBonded=" + mIgnoreUuidTimeoutAfterBonded + ", "
-                + "specifyCreateBondTransportType=" + mSpecifyCreateBondTransportType + ", "
-                + "createBondTransportType=" + mCreateBondTransportType + ", "
-                + "increaseIntentFilterPriority=" + mIncreaseIntentFilterPriority + ", "
-                + "evaluatePerformance=" + mEvaluatePerformance + ", "
-                + "extraLoggingInformation=" + mExtraLoggingInformation + ", "
-                + "enableNamingCharacteristic=" + mEnableNamingCharacteristic + ", "
-                + "enableFirmwareVersionCharacteristic=" + mEnableFirmwareVersionCharacteristic
-                + ", "
-                + "keepSameAccountKeyWrite=" + mKeepSameAccountKeyWrite + ", "
-                + "isRetroactivePairing=" + mIsRetroactivePairing + ", "
-                + "numSdpAttemptsAfterBonded=" + mNumSdpAttemptsAfterBonded + ", "
-                + "supportHidDevice=" + mSupportHidDevice + ", "
-                + "enablePairingWhileDirectlyConnecting=" + mEnablePairingWhileDirectlyConnecting
-                + ", "
-                + "acceptConsentForFastPairOne=" + mAcceptConsentForFastPairOne + ", "
-                + "gattConnectRetryTimeoutMillis=" + mGattConnectRetryTimeoutMillis + ", "
-                + "enable128BitCustomGattCharacteristicsId="
-                + mEnable128BitCustomGattCharacteristicsId + ", "
-                + "enableSendExceptionStepToValidator=" + mEnableSendExceptionStepToValidator + ", "
-                + "enableAdditionalDataTypeWhenActionOverBle="
-                + mEnableAdditionalDataTypeWhenActionOverBle + ", "
-                + "checkBondStateWhenSkipConnectingProfiles="
-                + mCheckBondStateWhenSkipConnectingProfiles + ", "
-                + "handlePasskeyConfirmationByUi=" + mHandlePasskeyConfirmationByUi + ", "
-                + "enablePairFlowShowUiWithoutProfileConnection="
-                + mEnablePairFlowShowUiWithoutProfileConnection
-                + "}";
-    }
-
-    /**
-     * Converts an instance to a builder.
-     */
-    public Builder toBuilder() {
-        return new Preferences.Builder(this);
-    }
-
-    /**
-     * Constructs a builder.
-     */
-    public static Builder builder() {
-        return new Preferences.Builder()
-                .setGattOperationTimeoutSeconds(3)
-                .setGattConnectionTimeoutSeconds(15)
-                .setBluetoothToggleTimeoutSeconds(10)
-                .setBluetoothToggleSleepSeconds(2)
-                .setClassicDiscoveryTimeoutSeconds(10)
-                .setNumDiscoverAttempts(3)
-                .setDiscoveryRetrySleepSeconds(1)
-                .setIgnoreDiscoveryError(false)
-                .setSdpTimeoutSeconds(10)
-                .setNumSdpAttempts(3)
-                .setNumCreateBondAttempts(3)
-                .setNumConnectAttempts(1)
-                .setNumWriteAccountKeyAttempts(3)
-                .setToggleBluetoothOnFailure(false)
-                .setBluetoothStateUsesPolling(true)
-                .setBluetoothStatePollingMillis(1000)
-                .setNumAttempts(2)
-                .setEnableBrEdrHandover(false)
-                .setBrHandoverDataCharacteristicId(get16BitUuid(
-                        Constants.TransportDiscoveryService.BrHandoverDataCharacteristic.ID))
-                .setBluetoothSigDataCharacteristicId(get16BitUuid(
-                        Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic.ID))
-                .setFirmwareVersionCharacteristicId(get16BitUuid(FirmwareVersionCharacteristic.ID))
-                .setBrTransportBlockDataDescriptorId(
-                        get16BitUuid(
-                                Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic
-                                        .BrTransportBlockDataDescriptor.ID))
-                .setWaitForUuidsAfterBonding(true)
-                .setReceiveUuidsAndBondedEventBeforeClose(true)
-                .setRemoveBondTimeoutSeconds(5)
-                .setRemoveBondSleepMillis(1000)
-                .setCreateBondTimeoutSeconds(15)
-                .setHidCreateBondTimeoutSeconds(40)
-                .setProxyTimeoutSeconds(2)
-                .setRejectPhonebookAccess(false)
-                .setRejectMessageAccess(false)
-                .setRejectSimAccess(false)
-                .setAcceptPasskey(true)
-                .setSupportedProfileUuids(Constants.getSupportedProfiles())
-                .setWriteAccountKeySleepMillis(2000)
-                .setProviderInitiatesBondingIfSupported(false)
-                .setAttemptDirectConnectionWhenPreviouslyBonded(false)
-                .setAutomaticallyReconnectGattWhenNeeded(false)
-                .setSkipDisconnectingGattBeforeWritingAccountKey(false)
-                .setSkipConnectingProfiles(false)
-                .setIgnoreUuidTimeoutAfterBonded(false)
-                .setSpecifyCreateBondTransportType(false)
-                .setCreateBondTransportType(0 /*BluetoothDevice.TRANSPORT_AUTO*/)
-                .setIncreaseIntentFilterPriority(true)
-                .setEvaluatePerformance(false)
-                .setKeepSameAccountKeyWrite(true)
-                .setEnableNamingCharacteristic(false)
-                .setEnableFirmwareVersionCharacteristic(false)
-                .setIsRetroactivePairing(false)
-                .setNumSdpAttemptsAfterBonded(1)
-                .setSupportHidDevice(false)
-                .setEnablePairingWhileDirectlyConnecting(true)
-                .setAcceptConsentForFastPairOne(true)
-                .setGattConnectRetryTimeoutMillis(0)
-                .setEnable128BitCustomGattCharacteristicsId(true)
-                .setEnableSendExceptionStepToValidator(true)
-                .setEnableAdditionalDataTypeWhenActionOverBle(true)
-                .setCheckBondStateWhenSkipConnectingProfiles(true)
-                .setHandlePasskeyConfirmationByUi(false)
-                .setMoreEventLogForQuality(true)
-                .setRetryGattConnectionAndSecretHandshake(true)
-                .setGattConnectShortTimeoutMs(7000)
-                .setGattConnectLongTimeoutMs(15000)
-                .setGattConnectShortTimeoutRetryMaxSpentTimeMs(10000)
-                .setAddressRotateRetryMaxSpentTimeMs(15000)
-                .setPairingRetryDelayMs(100)
-                .setSecretHandshakeShortTimeoutMs(3000)
-                .setSecretHandshakeLongTimeoutMs(10000)
-                .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(5000)
-                .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(7000)
-                .setSecretHandshakeRetryAttempts(3)
-                .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(15000)
-                .setSignalLostRetryMaxSpentTimeMs(15000)
-                .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of())
-                .setRetrySecretHandshakeTimeout(false)
-                .setLogUserManualRetry(true)
-                .setPairFailureCounts(0)
-                .setEnablePairFlowShowUiWithoutProfileConnection(true)
-                .setPairFailureCounts(0)
-                .setLogPairWithCachedModelId(true)
-                .setDirectConnectProfileIfModelIdInCache(false)
-                .setCachedDeviceAddress("")
-                .setPossibleCachedDeviceAddress("")
-                .setSameModelIdPairedDeviceCount(0)
-                .setIsDeviceFinishCheckAddressFromCache(true);
-    }
-
-    /**
-     * Constructs a builder from GmsLog.
-     */
-    // TODO(b/206668142): remove this builder once api is ready.
-    public static Builder builderFromGmsLog() {
-        return new Preferences.Builder()
-                .setGattOperationTimeoutSeconds(10)
-                .setGattConnectionTimeoutSeconds(15)
-                .setBluetoothToggleTimeoutSeconds(10)
-                .setBluetoothToggleSleepSeconds(2)
-                .setClassicDiscoveryTimeoutSeconds(13)
-                .setNumDiscoverAttempts(3)
-                .setDiscoveryRetrySleepSeconds(1)
-                .setIgnoreDiscoveryError(true)
-                .setSdpTimeoutSeconds(10)
-                .setNumSdpAttempts(0)
-                .setNumCreateBondAttempts(3)
-                .setNumConnectAttempts(2)
-                .setNumWriteAccountKeyAttempts(3)
-                .setToggleBluetoothOnFailure(false)
-                .setBluetoothStateUsesPolling(true)
-                .setBluetoothStatePollingMillis(1000)
-                .setNumAttempts(2)
-                .setEnableBrEdrHandover(false)
-                .setBrHandoverDataCharacteristicId((short) 11265)
-                .setBluetoothSigDataCharacteristicId((short) 11266)
-                .setFirmwareVersionCharacteristicId((short) 10790)
-                .setBrTransportBlockDataDescriptorId((short) 11267)
-                .setWaitForUuidsAfterBonding(true)
-                .setReceiveUuidsAndBondedEventBeforeClose(true)
-                .setRemoveBondTimeoutSeconds(5)
-                .setRemoveBondSleepMillis(1000)
-                .setCreateBondTimeoutSeconds(15)
-                .setHidCreateBondTimeoutSeconds(40)
-                .setProxyTimeoutSeconds(2)
-                .setRejectPhonebookAccess(false)
-                .setRejectMessageAccess(false)
-                .setRejectSimAccess(false)
-                .setAcceptPasskey(true)
-                .setSupportedProfileUuids(Constants.getSupportedProfiles())
-                .setWriteAccountKeySleepMillis(2000)
-                .setProviderInitiatesBondingIfSupported(false)
-                .setAttemptDirectConnectionWhenPreviouslyBonded(true)
-                .setAutomaticallyReconnectGattWhenNeeded(true)
-                .setSkipDisconnectingGattBeforeWritingAccountKey(true)
-                .setSkipConnectingProfiles(false)
-                .setIgnoreUuidTimeoutAfterBonded(true)
-                .setSpecifyCreateBondTransportType(false)
-                .setCreateBondTransportType(0 /*BluetoothDevice.TRANSPORT_AUTO*/)
-                .setIncreaseIntentFilterPriority(true)
-                .setEvaluatePerformance(true)
-                .setKeepSameAccountKeyWrite(true)
-                .setEnableNamingCharacteristic(true)
-                .setEnableFirmwareVersionCharacteristic(true)
-                .setIsRetroactivePairing(false)
-                .setNumSdpAttemptsAfterBonded(1)
-                .setSupportHidDevice(false)
-                .setEnablePairingWhileDirectlyConnecting(true)
-                .setAcceptConsentForFastPairOne(true)
-                .setGattConnectRetryTimeoutMillis(18000)
-                .setEnable128BitCustomGattCharacteristicsId(true)
-                .setEnableSendExceptionStepToValidator(true)
-                .setEnableAdditionalDataTypeWhenActionOverBle(true)
-                .setCheckBondStateWhenSkipConnectingProfiles(true)
-                .setHandlePasskeyConfirmationByUi(false)
-                .setMoreEventLogForQuality(true)
-                .setRetryGattConnectionAndSecretHandshake(true)
-                .setGattConnectShortTimeoutMs(7000)
-                .setGattConnectLongTimeoutMs(15000)
-                .setGattConnectShortTimeoutRetryMaxSpentTimeMs(10000)
-                .setAddressRotateRetryMaxSpentTimeMs(15000)
-                .setPairingRetryDelayMs(100)
-                .setSecretHandshakeShortTimeoutMs(3000)
-                .setSecretHandshakeLongTimeoutMs(10000)
-                .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(5000)
-                .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(7000)
-                .setSecretHandshakeRetryAttempts(3)
-                .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(15000)
-                .setSignalLostRetryMaxSpentTimeMs(15000)
-                .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257))
-                .setRetrySecretHandshakeTimeout(false)
-                .setLogUserManualRetry(true)
-                .setPairFailureCounts(0)
-                .setEnablePairFlowShowUiWithoutProfileConnection(true)
-                .setPairFailureCounts(0)
-                .setLogPairWithCachedModelId(true)
-                .setDirectConnectProfileIfModelIdInCache(true)
-                .setCachedDeviceAddress("")
-                .setPossibleCachedDeviceAddress("")
-                .setSameModelIdPairedDeviceCount(0)
-                .setIsDeviceFinishCheckAddressFromCache(true);
-    }
-
-    /**
-     * Preferences builder.
-     */
-    public static class Builder {
-
-        private int mGattOperationTimeoutSeconds;
-        private int mGattConnectionTimeoutSeconds;
-        private int mBluetoothToggleTimeoutSeconds;
-        private int mBluetoothToggleSleepSeconds;
-        private int mClassicDiscoveryTimeoutSeconds;
-        private int mNumDiscoverAttempts;
-        private int mDiscoveryRetrySleepSeconds;
-        private boolean mIgnoreDiscoveryError;
-        private int mSdpTimeoutSeconds;
-        private int mNumSdpAttempts;
-        private int mNumCreateBondAttempts;
-        private int mNumConnectAttempts;
-        private int mNumWriteAccountKeyAttempts;
-        private boolean mToggleBluetoothOnFailure;
-        private boolean mBluetoothStateUsesPolling;
-        private int mBluetoothStatePollingMillis;
-        private int mNumAttempts;
-        private boolean mEnableBrEdrHandover;
-        private short mBrHandoverDataCharacteristicId;
-        private short mBluetoothSigDataCharacteristicId;
-        private short mFirmwareVersionCharacteristicId;
-        private short mBrTransportBlockDataDescriptorId;
-        private boolean mWaitForUuidsAfterBonding;
-        private boolean mReceiveUuidsAndBondedEventBeforeClose;
-        private int mRemoveBondTimeoutSeconds;
-        private int mRemoveBondSleepMillis;
-        private int mCreateBondTimeoutSeconds;
-        private int mHidCreateBondTimeoutSeconds;
-        private int mProxyTimeoutSeconds;
-        private boolean mRejectPhonebookAccess;
-        private boolean mRejectMessageAccess;
-        private boolean mRejectSimAccess;
-        private int mWriteAccountKeySleepMillis;
-        private boolean mSkipDisconnectingGattBeforeWritingAccountKey;
-        private boolean mMoreEventLogForQuality;
-        private boolean mRetryGattConnectionAndSecretHandshake;
-        private long mGattConnectShortTimeoutMs;
-        private long mGattConnectLongTimeoutMs;
-        private long mGattConnectShortTimeoutRetryMaxSpentTimeMs;
-        private long mAddressRotateRetryMaxSpentTimeMs;
-        private long mPairingRetryDelayMs;
-        private long mSecretHandshakeShortTimeoutMs;
-        private long mSecretHandshakeLongTimeoutMs;
-        private long mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs;
-        private long mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs;
-        private long mSecretHandshakeRetryAttempts;
-        private long mSecretHandshakeRetryGattConnectionMaxSpentTimeMs;
-        private long mSignalLostRetryMaxSpentTimeMs;
-        private ImmutableSet<Integer> mGattConnectionAndSecretHandshakeNoRetryGattError;
-        private boolean mRetrySecretHandshakeTimeout;
-        private boolean mLogUserManualRetry;
-        private int mPairFailureCounts;
-        private String mCachedDeviceAddress;
-        private String mPossibleCachedDeviceAddress;
-        private int mSameModelIdPairedDeviceCount;
-        private boolean mIsDeviceFinishCheckAddressFromCache;
-        private boolean mLogPairWithCachedModelId;
-        private boolean mDirectConnectProfileIfModelIdInCache;
-        private boolean mAcceptPasskey;
-        private byte[] mSupportedProfileUuids;
-        private boolean mProviderInitiatesBondingIfSupported;
-        private boolean mAttemptDirectConnectionWhenPreviouslyBonded;
-        private boolean mAutomaticallyReconnectGattWhenNeeded;
-        private boolean mSkipConnectingProfiles;
-        private boolean mIgnoreUuidTimeoutAfterBonded;
-        private boolean mSpecifyCreateBondTransportType;
-        private int mCreateBondTransportType;
-        private boolean mIncreaseIntentFilterPriority;
-        private boolean mEvaluatePerformance;
-        private Preferences.ExtraLoggingInformation mExtraLoggingInformation;
-        private boolean mEnableNamingCharacteristic;
-        private boolean mEnableFirmwareVersionCharacteristic;
-        private boolean mKeepSameAccountKeyWrite;
-        private boolean mIsRetroactivePairing;
-        private int mNumSdpAttemptsAfterBonded;
-        private boolean mSupportHidDevice;
-        private boolean mEnablePairingWhileDirectlyConnecting;
-        private boolean mAcceptConsentForFastPairOne;
-        private int mGattConnectRetryTimeoutMillis;
-        private boolean mEnable128BitCustomGattCharacteristicsId;
-        private boolean mEnableSendExceptionStepToValidator;
-        private boolean mEnableAdditionalDataTypeWhenActionOverBle;
-        private boolean mCheckBondStateWhenSkipConnectingProfiles;
-        private boolean mHandlePasskeyConfirmationByUi;
-        private boolean mEnablePairFlowShowUiWithoutProfileConnection;
-
-        private Builder() {
-        }
-
-        private Builder(Preferences source) {
-            this.mGattOperationTimeoutSeconds = source.getGattOperationTimeoutSeconds();
-            this.mGattConnectionTimeoutSeconds = source.getGattConnectionTimeoutSeconds();
-            this.mBluetoothToggleTimeoutSeconds = source.getBluetoothToggleTimeoutSeconds();
-            this.mBluetoothToggleSleepSeconds = source.getBluetoothToggleSleepSeconds();
-            this.mClassicDiscoveryTimeoutSeconds = source.getClassicDiscoveryTimeoutSeconds();
-            this.mNumDiscoverAttempts = source.getNumDiscoverAttempts();
-            this.mDiscoveryRetrySleepSeconds = source.getDiscoveryRetrySleepSeconds();
-            this.mIgnoreDiscoveryError = source.getIgnoreDiscoveryError();
-            this.mSdpTimeoutSeconds = source.getSdpTimeoutSeconds();
-            this.mNumSdpAttempts = source.getNumSdpAttempts();
-            this.mNumCreateBondAttempts = source.getNumCreateBondAttempts();
-            this.mNumConnectAttempts = source.getNumConnectAttempts();
-            this.mNumWriteAccountKeyAttempts = source.getNumWriteAccountKeyAttempts();
-            this.mToggleBluetoothOnFailure = source.getToggleBluetoothOnFailure();
-            this.mBluetoothStateUsesPolling = source.getBluetoothStateUsesPolling();
-            this.mBluetoothStatePollingMillis = source.getBluetoothStatePollingMillis();
-            this.mNumAttempts = source.getNumAttempts();
-            this.mEnableBrEdrHandover = source.getEnableBrEdrHandover();
-            this.mBrHandoverDataCharacteristicId = source.getBrHandoverDataCharacteristicId();
-            this.mBluetoothSigDataCharacteristicId = source.getBluetoothSigDataCharacteristicId();
-            this.mFirmwareVersionCharacteristicId = source.getFirmwareVersionCharacteristicId();
-            this.mBrTransportBlockDataDescriptorId = source.getBrTransportBlockDataDescriptorId();
-            this.mWaitForUuidsAfterBonding = source.getWaitForUuidsAfterBonding();
-            this.mReceiveUuidsAndBondedEventBeforeClose = source
-                    .getReceiveUuidsAndBondedEventBeforeClose();
-            this.mRemoveBondTimeoutSeconds = source.getRemoveBondTimeoutSeconds();
-            this.mRemoveBondSleepMillis = source.getRemoveBondSleepMillis();
-            this.mCreateBondTimeoutSeconds = source.getCreateBondTimeoutSeconds();
-            this.mHidCreateBondTimeoutSeconds = source.getHidCreateBondTimeoutSeconds();
-            this.mProxyTimeoutSeconds = source.getProxyTimeoutSeconds();
-            this.mRejectPhonebookAccess = source.getRejectPhonebookAccess();
-            this.mRejectMessageAccess = source.getRejectMessageAccess();
-            this.mRejectSimAccess = source.getRejectSimAccess();
-            this.mWriteAccountKeySleepMillis = source.getWriteAccountKeySleepMillis();
-            this.mSkipDisconnectingGattBeforeWritingAccountKey = source
-                    .getSkipDisconnectingGattBeforeWritingAccountKey();
-            this.mMoreEventLogForQuality = source.getMoreEventLogForQuality();
-            this.mRetryGattConnectionAndSecretHandshake = source
-                    .getRetryGattConnectionAndSecretHandshake();
-            this.mGattConnectShortTimeoutMs = source.getGattConnectShortTimeoutMs();
-            this.mGattConnectLongTimeoutMs = source.getGattConnectLongTimeoutMs();
-            this.mGattConnectShortTimeoutRetryMaxSpentTimeMs = source
-                    .getGattConnectShortTimeoutRetryMaxSpentTimeMs();
-            this.mAddressRotateRetryMaxSpentTimeMs = source.getAddressRotateRetryMaxSpentTimeMs();
-            this.mPairingRetryDelayMs = source.getPairingRetryDelayMs();
-            this.mSecretHandshakeShortTimeoutMs = source.getSecretHandshakeShortTimeoutMs();
-            this.mSecretHandshakeLongTimeoutMs = source.getSecretHandshakeLongTimeoutMs();
-            this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs = source
-                    .getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs();
-            this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs = source
-                    .getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs();
-            this.mSecretHandshakeRetryAttempts = source.getSecretHandshakeRetryAttempts();
-            this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs = source
-                    .getSecretHandshakeRetryGattConnectionMaxSpentTimeMs();
-            this.mSignalLostRetryMaxSpentTimeMs = source.getSignalLostRetryMaxSpentTimeMs();
-            this.mGattConnectionAndSecretHandshakeNoRetryGattError = source
-                    .getGattConnectionAndSecretHandshakeNoRetryGattError();
-            this.mRetrySecretHandshakeTimeout = source.getRetrySecretHandshakeTimeout();
-            this.mLogUserManualRetry = source.getLogUserManualRetry();
-            this.mPairFailureCounts = source.getPairFailureCounts();
-            this.mCachedDeviceAddress = source.getCachedDeviceAddress();
-            this.mPossibleCachedDeviceAddress = source.getPossibleCachedDeviceAddress();
-            this.mSameModelIdPairedDeviceCount = source.getSameModelIdPairedDeviceCount();
-            this.mIsDeviceFinishCheckAddressFromCache = source
-                    .getIsDeviceFinishCheckAddressFromCache();
-            this.mLogPairWithCachedModelId = source.getLogPairWithCachedModelId();
-            this.mDirectConnectProfileIfModelIdInCache = source
-                    .getDirectConnectProfileIfModelIdInCache();
-            this.mAcceptPasskey = source.getAcceptPasskey();
-            this.mSupportedProfileUuids = source.getSupportedProfileUuids();
-            this.mProviderInitiatesBondingIfSupported = source
-                    .getProviderInitiatesBondingIfSupported();
-            this.mAttemptDirectConnectionWhenPreviouslyBonded = source
-                    .getAttemptDirectConnectionWhenPreviouslyBonded();
-            this.mAutomaticallyReconnectGattWhenNeeded = source
-                    .getAutomaticallyReconnectGattWhenNeeded();
-            this.mSkipConnectingProfiles = source.getSkipConnectingProfiles();
-            this.mIgnoreUuidTimeoutAfterBonded = source.getIgnoreUuidTimeoutAfterBonded();
-            this.mSpecifyCreateBondTransportType = source.getSpecifyCreateBondTransportType();
-            this.mCreateBondTransportType = source.getCreateBondTransportType();
-            this.mIncreaseIntentFilterPriority = source.getIncreaseIntentFilterPriority();
-            this.mEvaluatePerformance = source.getEvaluatePerformance();
-            this.mExtraLoggingInformation = source.getExtraLoggingInformation();
-            this.mEnableNamingCharacteristic = source.getEnableNamingCharacteristic();
-            this.mEnableFirmwareVersionCharacteristic = source
-                    .getEnableFirmwareVersionCharacteristic();
-            this.mKeepSameAccountKeyWrite = source.getKeepSameAccountKeyWrite();
-            this.mIsRetroactivePairing = source.getIsRetroactivePairing();
-            this.mNumSdpAttemptsAfterBonded = source.getNumSdpAttemptsAfterBonded();
-            this.mSupportHidDevice = source.getSupportHidDevice();
-            this.mEnablePairingWhileDirectlyConnecting = source
-                    .getEnablePairingWhileDirectlyConnecting();
-            this.mAcceptConsentForFastPairOne = source.getAcceptConsentForFastPairOne();
-            this.mGattConnectRetryTimeoutMillis = source.getGattConnectRetryTimeoutMillis();
-            this.mEnable128BitCustomGattCharacteristicsId = source
-                    .getEnable128BitCustomGattCharacteristicsId();
-            this.mEnableSendExceptionStepToValidator = source
-                    .getEnableSendExceptionStepToValidator();
-            this.mEnableAdditionalDataTypeWhenActionOverBle = source
-                    .getEnableAdditionalDataTypeWhenActionOverBle();
-            this.mCheckBondStateWhenSkipConnectingProfiles = source
-                    .getCheckBondStateWhenSkipConnectingProfiles();
-            this.mHandlePasskeyConfirmationByUi = source.getHandlePasskeyConfirmationByUi();
-            this.mEnablePairFlowShowUiWithoutProfileConnection = source
-                    .getEnablePairFlowShowUiWithoutProfileConnection();
-        }
-
-        /**
-         * Set gatt operation timeout.
-         */
-        public Builder setGattOperationTimeoutSeconds(int value) {
-            this.mGattOperationTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set gatt connection timeout.
-         */
-        public Builder setGattConnectionTimeoutSeconds(int value) {
-            this.mGattConnectionTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set bluetooth toggle timeout.
-         */
-        public Builder setBluetoothToggleTimeoutSeconds(int value) {
-            this.mBluetoothToggleTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set bluetooth toggle sleep time.
-         */
-        public Builder setBluetoothToggleSleepSeconds(int value) {
-            this.mBluetoothToggleSleepSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set classic discovery timeout.
-         */
-        public Builder setClassicDiscoveryTimeoutSeconds(int value) {
-            this.mClassicDiscoveryTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set number of discover attempts allowed.
-         */
-        public Builder setNumDiscoverAttempts(int value) {
-            this.mNumDiscoverAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set discovery retry sleep time.
-         */
-        public Builder setDiscoveryRetrySleepSeconds(int value) {
-            this.mDiscoveryRetrySleepSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set whether to ignore discovery error.
-         */
-        public Builder setIgnoreDiscoveryError(boolean value) {
-            this.mIgnoreDiscoveryError = value;
-            return this;
-        }
-
-        /**
-         * Set sdp timeout.
-         */
-        public Builder setSdpTimeoutSeconds(int value) {
-            this.mSdpTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set number of sdp attempts allowed.
-         */
-        public Builder setNumSdpAttempts(int value) {
-            this.mNumSdpAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set number of allowed attempts to create bond.
-         */
-        public Builder setNumCreateBondAttempts(int value) {
-            this.mNumCreateBondAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set number of connect attempts allowed.
-         */
-        public Builder setNumConnectAttempts(int value) {
-            this.mNumConnectAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set number of write account key attempts allowed.
-         */
-        public Builder setNumWriteAccountKeyAttempts(int value) {
-            this.mNumWriteAccountKeyAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set whether to retry by bluetooth toggle on failure.
-         */
-        public Builder setToggleBluetoothOnFailure(boolean value) {
-            this.mToggleBluetoothOnFailure = value;
-            return this;
-        }
-
-        /**
-         * Set whether to use polling to set bluetooth status.
-         */
-        public Builder setBluetoothStateUsesPolling(boolean value) {
-            this.mBluetoothStateUsesPolling = value;
-            return this;
-        }
-
-        /**
-         * Set Bluetooth state polling timeout.
-         */
-        public Builder setBluetoothStatePollingMillis(int value) {
-            this.mBluetoothStatePollingMillis = value;
-            return this;
-        }
-
-        /**
-         * Set number of attempts.
-         */
-        public Builder setNumAttempts(int value) {
-            this.mNumAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set whether to enable BrEdr handover.
-         */
-        public Builder setEnableBrEdrHandover(boolean value) {
-            this.mEnableBrEdrHandover = value;
-            return this;
-        }
-
-        /**
-         * Set Br handover data characteristic Id.
-         */
-        public Builder setBrHandoverDataCharacteristicId(short value) {
-            this.mBrHandoverDataCharacteristicId = value;
-            return this;
-        }
-
-        /**
-         * Set Bluetooth Sig data characteristic Id.
-         */
-        public Builder setBluetoothSigDataCharacteristicId(short value) {
-            this.mBluetoothSigDataCharacteristicId = value;
-            return this;
-        }
-
-        /**
-         * Set Firmware version characteristic id.
-         */
-        public Builder setFirmwareVersionCharacteristicId(short value) {
-            this.mFirmwareVersionCharacteristicId = value;
-            return this;
-        }
-
-        /**
-         * Set Br transport block data descriptor id.
-         */
-        public Builder setBrTransportBlockDataDescriptorId(short value) {
-            this.mBrTransportBlockDataDescriptorId = value;
-            return this;
-        }
-
-        /**
-         * Set whether to wait for Uuids after bonding.
-         */
-        public Builder setWaitForUuidsAfterBonding(boolean value) {
-            this.mWaitForUuidsAfterBonding = value;
-            return this;
-        }
-
-        /**
-         * Set whether to receive Uuids and bonded event before close.
-         */
-        public Builder setReceiveUuidsAndBondedEventBeforeClose(boolean value) {
-            this.mReceiveUuidsAndBondedEventBeforeClose = value;
-            return this;
-        }
-
-        /**
-         * Set remove bond timeout.
-         */
-        public Builder setRemoveBondTimeoutSeconds(int value) {
-            this.mRemoveBondTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set remove bound sleep time.
-         */
-        public Builder setRemoveBondSleepMillis(int value) {
-            this.mRemoveBondSleepMillis = value;
-            return this;
-        }
-
-        /**
-         * Set create bond timeout.
-         */
-        public Builder setCreateBondTimeoutSeconds(int value) {
-            this.mCreateBondTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set Hid create bond timeout.
-         */
-        public Builder setHidCreateBondTimeoutSeconds(int value) {
-            this.mHidCreateBondTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set proxy timeout.
-         */
-        public Builder setProxyTimeoutSeconds(int value) {
-            this.mProxyTimeoutSeconds = value;
-            return this;
-        }
-
-        /**
-         * Set whether to reject phone book access.
-         */
-        public Builder setRejectPhonebookAccess(boolean value) {
-            this.mRejectPhonebookAccess = value;
-            return this;
-        }
-
-        /**
-         * Set whether to reject message access.
-         */
-        public Builder setRejectMessageAccess(boolean value) {
-            this.mRejectMessageAccess = value;
-            return this;
-        }
-
-        /**
-         * Set whether to reject slim access.
-         */
-        public Builder setRejectSimAccess(boolean value) {
-            this.mRejectSimAccess = value;
-            return this;
-        }
-
-        /**
-         * Set whether to accept passkey.
-         */
-        public Builder setAcceptPasskey(boolean value) {
-            this.mAcceptPasskey = value;
-            return this;
-        }
-
-        /**
-         * Set supported profile Uuids.
-         */
-        public Builder setSupportedProfileUuids(byte[] value) {
-            this.mSupportedProfileUuids = value;
-            return this;
-        }
-
-        /**
-         * Set whether to collect more event log for quality.
-         */
-        public Builder setMoreEventLogForQuality(boolean value) {
-            this.mMoreEventLogForQuality = value;
-            return this;
-        }
-
-        /**
-         * Set supported profile Uuids.
-         */
-        public Builder setSupportedProfileUuids(short... uuids) {
-            return setSupportedProfileUuids(Bytes.toBytes(ByteOrder.BIG_ENDIAN, uuids));
-        }
-
-        /**
-         * Set write account key sleep time.
-         */
-        public Builder setWriteAccountKeySleepMillis(int value) {
-            this.mWriteAccountKeySleepMillis = value;
-            return this;
-        }
-
-        /**
-         * Set whether to do provider initialized bonding if supported.
-         */
-        public Builder setProviderInitiatesBondingIfSupported(boolean value) {
-            this.mProviderInitiatesBondingIfSupported = value;
-            return this;
-        }
-
-        /**
-         * Set whether to try direct connection when the device is previously bonded.
-         */
-        public Builder setAttemptDirectConnectionWhenPreviouslyBonded(boolean value) {
-            this.mAttemptDirectConnectionWhenPreviouslyBonded = value;
-            return this;
-        }
-
-        /**
-         * Set whether to automatically reconnect gatt when needed.
-         */
-        public Builder setAutomaticallyReconnectGattWhenNeeded(boolean value) {
-            this.mAutomaticallyReconnectGattWhenNeeded = value;
-            return this;
-        }
-
-        /**
-         * Set whether to skip disconnecting gatt before writing account key.
-         */
-        public Builder setSkipDisconnectingGattBeforeWritingAccountKey(boolean value) {
-            this.mSkipDisconnectingGattBeforeWritingAccountKey = value;
-            return this;
-        }
-
-        /**
-         * Set whether to skip connecting profiles.
-         */
-        public Builder setSkipConnectingProfiles(boolean value) {
-            this.mSkipConnectingProfiles = value;
-            return this;
-        }
-
-        /**
-         * Set whether to ignore Uuid timeout after bonded.
-         */
-        public Builder setIgnoreUuidTimeoutAfterBonded(boolean value) {
-            this.mIgnoreUuidTimeoutAfterBonded = value;
-            return this;
-        }
-
-        /**
-         * Set whether to include transport type in create bound request.
-         */
-        public Builder setSpecifyCreateBondTransportType(boolean value) {
-            this.mSpecifyCreateBondTransportType = value;
-            return this;
-        }
-
-        /**
-         * Set transport type used in create bond request.
-         */
-        public Builder setCreateBondTransportType(int value) {
-            this.mCreateBondTransportType = value;
-            return this;
-        }
-
-        /**
-         * Set whether to increase intent filter priority.
-         */
-        public Builder setIncreaseIntentFilterPriority(boolean value) {
-            this.mIncreaseIntentFilterPriority = value;
-            return this;
-        }
-
-        /**
-         * Set whether to evaluate performance.
-         */
-        public Builder setEvaluatePerformance(boolean value) {
-            this.mEvaluatePerformance = value;
-            return this;
-        }
-
-        /**
-         * Set extra logging info.
-         */
-        public Builder setExtraLoggingInformation(ExtraLoggingInformation value) {
-            this.mExtraLoggingInformation = value;
-            return this;
-        }
-
-        /**
-         * Set whether to enable naming characteristic.
-         */
-        public Builder setEnableNamingCharacteristic(boolean value) {
-            this.mEnableNamingCharacteristic = value;
-            return this;
-        }
-
-        /**
-         * Set whether to keep writing the account key to the provider, that has already paired with
-         * the account.
-         */
-        public Builder setKeepSameAccountKeyWrite(boolean value) {
-            this.mKeepSameAccountKeyWrite = value;
-            return this;
-        }
-
-        /**
-         * Set whether to enable firmware version characteristic.
-         */
-        public Builder setEnableFirmwareVersionCharacteristic(boolean value) {
-            this.mEnableFirmwareVersionCharacteristic = value;
-            return this;
-        }
-
-        /**
-         * Set whether it is retroactive pairing.
-         */
-        public Builder setIsRetroactivePairing(boolean value) {
-            this.mIsRetroactivePairing = value;
-            return this;
-        }
-
-        /**
-         * Set number of allowed sdp attempts after bonded.
-         */
-        public Builder setNumSdpAttemptsAfterBonded(int value) {
-            this.mNumSdpAttemptsAfterBonded = value;
-            return this;
-        }
-
-        /**
-         * Set whether to support Hid device.
-         */
-        public Builder setSupportHidDevice(boolean value) {
-            this.mSupportHidDevice = value;
-            return this;
-        }
-
-        /**
-         * Set wehther to enable the pairing behavior to handle the state transition from
-         * BOND_BONDED to BOND_BONDING when directly connecting profiles.
-         */
-        public Builder setEnablePairingWhileDirectlyConnecting(boolean value) {
-            this.mEnablePairingWhileDirectlyConnecting = value;
-            return this;
-        }
-
-        /**
-         * Set whether to accept consent for fast pair one.
-         */
-        public Builder setAcceptConsentForFastPairOne(boolean value) {
-            this.mAcceptConsentForFastPairOne = value;
-            return this;
-        }
-
-        /**
-         * Set Gatt connect retry timeout.
-         */
-        public Builder setGattConnectRetryTimeoutMillis(int value) {
-            this.mGattConnectRetryTimeoutMillis = value;
-            return this;
-        }
-
-        /**
-         * Set whether to enable 128 bit custom gatt characteristic Id.
-         */
-        public Builder setEnable128BitCustomGattCharacteristicsId(boolean value) {
-            this.mEnable128BitCustomGattCharacteristicsId = value;
-            return this;
-        }
-
-        /**
-         * Set whether to send exception step to validator.
-         */
-        public Builder setEnableSendExceptionStepToValidator(boolean value) {
-            this.mEnableSendExceptionStepToValidator = value;
-            return this;
-        }
-
-        /**
-         * Set wehther to add the additional data type in the handshake when action over BLE.
-         */
-        public Builder setEnableAdditionalDataTypeWhenActionOverBle(boolean value) {
-            this.mEnableAdditionalDataTypeWhenActionOverBle = value;
-            return this;
-        }
-
-        /**
-         * Set whether to check bond state when skip connecting profiles.
-         */
-        public Builder setCheckBondStateWhenSkipConnectingProfiles(boolean value) {
-            this.mCheckBondStateWhenSkipConnectingProfiles = value;
-            return this;
-        }
-
-        /**
-         * Set whether to handle passkey confirmation by UI.
-         */
-        public Builder setHandlePasskeyConfirmationByUi(boolean value) {
-            this.mHandlePasskeyConfirmationByUi = value;
-            return this;
-        }
-
-        /**
-         * Set wehther to retry gatt connection and secret handshake.
-         */
-        public Builder setRetryGattConnectionAndSecretHandshake(boolean value) {
-            this.mRetryGattConnectionAndSecretHandshake = value;
-            return this;
-        }
-
-        /**
-         * Set gatt connect short timeout.
-         */
-        public Builder setGattConnectShortTimeoutMs(long value) {
-            this.mGattConnectShortTimeoutMs = value;
-            return this;
-        }
-
-        /**
-         * Set gatt connect long timeout.
-         */
-        public Builder setGattConnectLongTimeoutMs(long value) {
-            this.mGattConnectLongTimeoutMs = value;
-            return this;
-        }
-
-        /**
-         * Set gatt connection short timoutout, including retry.
-         */
-        public Builder setGattConnectShortTimeoutRetryMaxSpentTimeMs(long value) {
-            this.mGattConnectShortTimeoutRetryMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set address rotate timeout, including retry.
-         */
-        public Builder setAddressRotateRetryMaxSpentTimeMs(long value) {
-            this.mAddressRotateRetryMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set pairing retry delay time.
-         */
-        public Builder setPairingRetryDelayMs(long value) {
-            this.mPairingRetryDelayMs = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake short timeout.
-         */
-        public Builder setSecretHandshakeShortTimeoutMs(long value) {
-            this.mSecretHandshakeShortTimeoutMs = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake long timeout.
-         */
-        public Builder setSecretHandshakeLongTimeoutMs(long value) {
-            this.mSecretHandshakeLongTimeoutMs = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake short timeout retry max spent time.
-         */
-        public Builder setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(long value) {
-            this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake long timeout retry max spent time.
-         */
-        public Builder setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(long value) {
-            this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake retry attempts allowed.
-         */
-        public Builder setSecretHandshakeRetryAttempts(long value) {
-            this.mSecretHandshakeRetryAttempts = value;
-            return this;
-        }
-
-        /**
-         * Set secret handshake retry gatt connection max spent time.
-         */
-        public Builder setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(long value) {
-            this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set signal loss retry max spent time.
-         */
-        public Builder setSignalLostRetryMaxSpentTimeMs(long value) {
-            this.mSignalLostRetryMaxSpentTimeMs = value;
-            return this;
-        }
-
-        /**
-         * Set gatt connection and secret handshake no retry gatt error.
-         */
-        public Builder setGattConnectionAndSecretHandshakeNoRetryGattError(
-                ImmutableSet<Integer> value) {
-            this.mGattConnectionAndSecretHandshakeNoRetryGattError = value;
-            return this;
-        }
-
-        /**
-         * Set retry secret handshake timeout.
-         */
-        public Builder setRetrySecretHandshakeTimeout(boolean value) {
-            this.mRetrySecretHandshakeTimeout = value;
-            return this;
-        }
-
-        /**
-         * Set whether to log user manual retry.
-         */
-        public Builder setLogUserManualRetry(boolean value) {
-            this.mLogUserManualRetry = value;
-            return this;
-        }
-
-        /**
-         * Set pair falure counts.
-         */
-        public Builder setPairFailureCounts(int counts) {
-            this.mPairFailureCounts = counts;
-            return this;
-        }
-
-        /**
-         * Set whether to use pair flow to show ui when pairing is finished without connecting
-         * profile..
-         */
-        public Builder setEnablePairFlowShowUiWithoutProfileConnection(boolean value) {
-            this.mEnablePairFlowShowUiWithoutProfileConnection = value;
-            return this;
-        }
-
-        /**
-         * Set whether to log pairing with cached module Id.
-         */
-        public Builder setLogPairWithCachedModelId(boolean value) {
-            this.mLogPairWithCachedModelId = value;
-            return this;
-        }
-
-        /**
-         * Set possible cached device address.
-         */
-        public Builder setPossibleCachedDeviceAddress(String value) {
-            this.mPossibleCachedDeviceAddress = value;
-            return this;
-        }
-
-        /**
-         * Set paired device count from the same module Id.
-         */
-        public Builder setSameModelIdPairedDeviceCount(int value) {
-            this.mSameModelIdPairedDeviceCount = value;
-            return this;
-        }
-
-        /**
-         * Set whether the bonded device address is from cache.
-         */
-        public Builder setIsDeviceFinishCheckAddressFromCache(boolean value) {
-            this.mIsDeviceFinishCheckAddressFromCache = value;
-            return this;
-        }
-
-        /**
-         * Set whether to directly connect profile if modelId is in cache.
-         */
-        public Builder setDirectConnectProfileIfModelIdInCache(boolean value) {
-            this.mDirectConnectProfileIfModelIdInCache = value;
-            return this;
-        }
-
-        /**
-         * Set cached device address.
-         */
-        public Builder setCachedDeviceAddress(String value) {
-            this.mCachedDeviceAddress = value;
-            return this;
-        }
-
-        /**
-         * Builds a Preferences instance.
-         */
-        public Preferences build() {
-            return new Preferences(
-                    this.mGattOperationTimeoutSeconds,
-                    this.mGattConnectionTimeoutSeconds,
-                    this.mBluetoothToggleTimeoutSeconds,
-                    this.mBluetoothToggleSleepSeconds,
-                    this.mClassicDiscoveryTimeoutSeconds,
-                    this.mNumDiscoverAttempts,
-                    this.mDiscoveryRetrySleepSeconds,
-                    this.mIgnoreDiscoveryError,
-                    this.mSdpTimeoutSeconds,
-                    this.mNumSdpAttempts,
-                    this.mNumCreateBondAttempts,
-                    this.mNumConnectAttempts,
-                    this.mNumWriteAccountKeyAttempts,
-                    this.mToggleBluetoothOnFailure,
-                    this.mBluetoothStateUsesPolling,
-                    this.mBluetoothStatePollingMillis,
-                    this.mNumAttempts,
-                    this.mEnableBrEdrHandover,
-                    this.mBrHandoverDataCharacteristicId,
-                    this.mBluetoothSigDataCharacteristicId,
-                    this.mFirmwareVersionCharacteristicId,
-                    this.mBrTransportBlockDataDescriptorId,
-                    this.mWaitForUuidsAfterBonding,
-                    this.mReceiveUuidsAndBondedEventBeforeClose,
-                    this.mRemoveBondTimeoutSeconds,
-                    this.mRemoveBondSleepMillis,
-                    this.mCreateBondTimeoutSeconds,
-                    this.mHidCreateBondTimeoutSeconds,
-                    this.mProxyTimeoutSeconds,
-                    this.mRejectPhonebookAccess,
-                    this.mRejectMessageAccess,
-                    this.mRejectSimAccess,
-                    this.mWriteAccountKeySleepMillis,
-                    this.mSkipDisconnectingGattBeforeWritingAccountKey,
-                    this.mMoreEventLogForQuality,
-                    this.mRetryGattConnectionAndSecretHandshake,
-                    this.mGattConnectShortTimeoutMs,
-                    this.mGattConnectLongTimeoutMs,
-                    this.mGattConnectShortTimeoutRetryMaxSpentTimeMs,
-                    this.mAddressRotateRetryMaxSpentTimeMs,
-                    this.mPairingRetryDelayMs,
-                    this.mSecretHandshakeShortTimeoutMs,
-                    this.mSecretHandshakeLongTimeoutMs,
-                    this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs,
-                    this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs,
-                    this.mSecretHandshakeRetryAttempts,
-                    this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs,
-                    this.mSignalLostRetryMaxSpentTimeMs,
-                    this.mGattConnectionAndSecretHandshakeNoRetryGattError,
-                    this.mRetrySecretHandshakeTimeout,
-                    this.mLogUserManualRetry,
-                    this.mPairFailureCounts,
-                    this.mCachedDeviceAddress,
-                    this.mPossibleCachedDeviceAddress,
-                    this.mSameModelIdPairedDeviceCount,
-                    this.mIsDeviceFinishCheckAddressFromCache,
-                    this.mLogPairWithCachedModelId,
-                    this.mDirectConnectProfileIfModelIdInCache,
-                    this.mAcceptPasskey,
-                    this.mSupportedProfileUuids,
-                    this.mProviderInitiatesBondingIfSupported,
-                    this.mAttemptDirectConnectionWhenPreviouslyBonded,
-                    this.mAutomaticallyReconnectGattWhenNeeded,
-                    this.mSkipConnectingProfiles,
-                    this.mIgnoreUuidTimeoutAfterBonded,
-                    this.mSpecifyCreateBondTransportType,
-                    this.mCreateBondTransportType,
-                    this.mIncreaseIntentFilterPriority,
-                    this.mEvaluatePerformance,
-                    this.mExtraLoggingInformation,
-                    this.mEnableNamingCharacteristic,
-                    this.mEnableFirmwareVersionCharacteristic,
-                    this.mKeepSameAccountKeyWrite,
-                    this.mIsRetroactivePairing,
-                    this.mNumSdpAttemptsAfterBonded,
-                    this.mSupportHidDevice,
-                    this.mEnablePairingWhileDirectlyConnecting,
-                    this.mAcceptConsentForFastPairOne,
-                    this.mGattConnectRetryTimeoutMillis,
-                    this.mEnable128BitCustomGattCharacteristicsId,
-                    this.mEnableSendExceptionStepToValidator,
-                    this.mEnableAdditionalDataTypeWhenActionOverBle,
-                    this.mCheckBondStateWhenSkipConnectingProfiles,
-                    this.mHandlePasskeyConfirmationByUi,
-                    this.mEnablePairFlowShowUiWithoutProfileConnection);
-        }
-    }
-
-    /**
-     * Whether a given Uuid is supported.
-     */
-    public boolean isSupportedProfile(short profileUuid) {
-        return Constants.PROFILES.containsKey(profileUuid)
-                && Shorts.contains(
-                Bytes.toShorts(ByteOrder.BIG_ENDIAN, getSupportedProfileUuids()), profileUuid);
-    }
-
-    /**
-     * Information that will be used for logging.
-     */
-    public static class ExtraLoggingInformation {
-
-        private final String mModelId;
-
-        private ExtraLoggingInformation(String modelId) {
-            this.mModelId = modelId;
-        }
-
-        /**
-         * Returns model Id.
-         */
-        public String getModelId() {
-            return mModelId;
-        }
-
-        /**
-         * Converts an instance to a builder.
-         */
-        public Builder toBuilder() {
-            return new Builder(this);
-        }
-
-        /**
-         * Creates a builder for ExtraLoggingInformation.
-         */
-        public static Builder builder() {
-            return new ExtraLoggingInformation.Builder();
-        }
-
-        @Override
-        public String toString() {
-            return "ExtraLoggingInformation{" + "modelId=" + mModelId + "}";
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (o instanceof ExtraLoggingInformation) {
-                Preferences.ExtraLoggingInformation that = (Preferences.ExtraLoggingInformation) o;
-                return this.mModelId.equals(that.getModelId());
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mModelId);
-        }
-
-        /**
-         * Extra logging information builder.
-         */
-        public static class Builder {
-
-            private String mModelId;
-
-            private Builder() {
-            }
-
-            private Builder(ExtraLoggingInformation source) {
-                this.mModelId = source.getModelId();
-            }
-
-            /**
-             * Set model ID.
-             */
-            public Builder setModelId(String modelId) {
-                this.mModelId = modelId;
-                return this;
-            }
-
-            /**
-             * Builds extra logging information.
-             */
-            public ExtraLoggingInformation build() {
-                return new ExtraLoggingInformation(mModelId);
-            }
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java
deleted file mode 100644
index a2603b5..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Utilities for calling methods using reflection. The main benefit of using this helper is to avoid
- * complications around exception handling when calling methods reflectively. It's not safe to use
- * Java 8's multicatch on such exceptions, because the java compiler converts multicatch into
- * ReflectiveOperationException in some instances, which doesn't work on older sdk versions.
- * Instead, use these utilities and catch ReflectionException.
- *
- * <p>Example usage:
- *
- * <pre>{@code
- * try {
- *   Reflect.on(btAdapter)
- *       .withMethod("setScanMode", int.class)
- *       .invoke(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
- * } catch (ReflectionException e) { }
- * }</pre>
- */
-// TODO(b/202549655): remove existing Reflect usage. New usage is not allowed! No exception!
-public final class Reflect {
-    private final Object mTargetObject;
-
-    private Reflect(Object targetObject) {
-        this.mTargetObject = targetObject;
-    }
-
-    /** Creates an instance of this helper to invoke methods on the given target object. */
-    public static Reflect on(Object targetObject) {
-        return new Reflect(targetObject);
-    }
-
-    /** Finds a method with the given name and parameter types. */
-    public ReflectionMethod withMethod(String methodName, Class<?>... paramTypes)
-            throws ReflectionException {
-        try {
-            return new ReflectionMethod(mTargetObject.getClass().getMethod(methodName, paramTypes));
-        } catch (NoSuchMethodException e) {
-            throw new ReflectionException(e);
-        }
-    }
-
-    /** Represents an invokable method found reflectively. */
-    public final class ReflectionMethod {
-        private final Method mMethod;
-
-        private ReflectionMethod(Method method) {
-            this.mMethod = method;
-        }
-
-        /**
-         * Invokes this instance method with the given parameters. The called method does not return
-         * a value.
-         */
-        public void invoke(Object... parameters) throws ReflectionException {
-            try {
-                mMethod.invoke(mTargetObject, parameters);
-            } catch (IllegalAccessException e) {
-                throw new ReflectionException(e);
-            } catch (InvocationTargetException e) {
-                throw new ReflectionException(e);
-            }
-        }
-
-        /**
-         * Invokes this instance method with the given parameters. The called method returns a non
-         * null value.
-         */
-        public Object get(Object... parameters) throws ReflectionException {
-            Object value;
-            try {
-                value = mMethod.invoke(mTargetObject, parameters);
-            } catch (IllegalAccessException e) {
-                throw new ReflectionException(e);
-            } catch (InvocationTargetException e) {
-                throw new ReflectionException(e);
-            }
-            if (value == null) {
-                throw new ReflectionException(new NullPointerException());
-            }
-            return value;
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java
deleted file mode 100644
index 1c20c55..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/**
- * An exception thrown during a reflection operation. Like ReflectiveOperationException, except
- * compatible on older API versions.
- */
-public final class ReflectionException extends Exception {
-    ReflectionException(Throwable cause) {
-        super(cause.getMessage(), cause);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalLostException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalLostException.java
deleted file mode 100644
index 244ee66..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalLostException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/** Base class for fast pair signal lost exceptions. */
-public class SignalLostException extends PairingException {
-    SignalLostException(String message, Exception e) {
-        super(message);
-        initCause(e);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java
deleted file mode 100644
index d0d2a5d..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-/** Base class for fast pair signal rotated exceptions. */
-public class SignalRotatedException extends PairingException {
-    private final String mNewAddress;
-
-    SignalRotatedException(String message, String newAddress, Exception e) {
-        super(message);
-        this.mNewAddress = newAddress;
-        initCause(e);
-    }
-
-    /** Returns the new BLE address for the model ID. */
-    public String getNewAddress() {
-        return mNewAddress;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java
deleted file mode 100644
index 7f525a7..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build.VERSION_CODES;
-import android.os.Handler;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.util.Arrays;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Like {@link BroadcastReceiver}, but:
- *
- * <ul>
- *   <li>Simpler to create and register, with a list of actions.
- *   <li>Implements AutoCloseable. If used as a resource in try-with-resources (available on
- *       KitKat+), unregisters itself automatically.
- *   <li>Lets you block waiting for your state transition with {@link #await}.
- * </ul>
- */
-// AutoCloseable only available on KitKat+.
-@TargetApi(VERSION_CODES.KITKAT)
-public abstract class SimpleBroadcastReceiver extends BroadcastReceiver implements AutoCloseable {
-
-    private static final String TAG = SimpleBroadcastReceiver.class.getSimpleName();
-
-    /**
-     * Creates a one shot receiver.
-     */
-    public static SimpleBroadcastReceiver oneShotReceiver(
-            Context context, Preferences preferences, String... actions) {
-        return new SimpleBroadcastReceiver(context, preferences, actions) {
-            @Override
-            protected void onReceive(Intent intent) {
-                close();
-            }
-        };
-    }
-
-    private final Context mContext;
-    private final SettableFuture<Void> mIsClosedFuture = SettableFuture.create();
-    private long mAwaitExtendSecond;
-
-    // Nullness checker complains about 'this' being @UnderInitialization
-    @SuppressWarnings("nullness")
-    public SimpleBroadcastReceiver(
-            Context context, Preferences preferences, @Nullable Handler handler,
-            String... actions) {
-        Log.v(TAG, this + " listening for actions " + Arrays.toString(actions));
-        this.mContext = context;
-        IntentFilter intentFilter = new IntentFilter();
-        if (preferences.getIncreaseIntentFilterPriority()) {
-            intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        }
-        for (String action : actions) {
-            intentFilter.addAction(action);
-        }
-        context.registerReceiver(this, intentFilter, /* broadcastPermission= */ null, handler);
-    }
-
-    public SimpleBroadcastReceiver(Context context, Preferences preferences, String... actions) {
-        this(context, preferences, /* handler= */ null, actions);
-    }
-
-    /**
-     * Any exception thrown by this method will be delivered via {@link #await}.
-     */
-    protected abstract void onReceive(Intent intent) throws Exception;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.v(TAG, "Got intent with action= " + intent.getAction());
-        try {
-            onReceive(intent);
-        } catch (Exception e) {
-            closeWithError(e);
-        }
-    }
-
-    @Override
-    public void close() {
-        closeWithError(null);
-    }
-
-    void closeWithError(@Nullable Exception e) {
-        try {
-            mContext.unregisterReceiver(this);
-        } catch (IllegalArgumentException ignored) {
-            // Ignore. Happens if you unregister twice.
-        }
-        if (e == null) {
-            mIsClosedFuture.set(null);
-        } else {
-            mIsClosedFuture.setException(e);
-        }
-    }
-
-    /**
-     * Extends the awaiting time.
-     */
-    public void extendAwaitSecond(int awaitExtendSecond) {
-        this.mAwaitExtendSecond = awaitExtendSecond;
-    }
-
-    /**
-     * Blocks until this receiver has closed (i.e. the state transition that this receiver is
-     * interested in has completed). Throws an exception on any error.
-     */
-    public void await(long timeout, TimeUnit timeUnit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        Log.v(TAG, this + " waiting on future for " + timeout + " " + timeUnit);
-        try {
-            mIsClosedFuture.get(timeout, timeUnit);
-        } catch (TimeoutException e) {
-            if (mAwaitExtendSecond <= 0) {
-                throw e;
-            }
-            Log.i(TAG, "Extend timeout for " + mAwaitExtendSecond + " seconds");
-            mIsClosedFuture.get(mAwaitExtendSecond, TimeUnit.SECONDS);
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java
deleted file mode 100644
index 7382ff3..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import com.android.server.nearby.intdefs.FastPairEventIntDefs.BrEdrHandoverErrorCode;
-
-import com.google.errorprone.annotations.FormatMethod;
-
-/**
- * Thrown when BR/EDR Handover fails.
- */
-public class TdsException extends Exception {
-
-    final @BrEdrHandoverErrorCode int mErrorCode;
-
-    @FormatMethod
-    TdsException(@BrEdrHandoverErrorCode int errorCode, String format, Object... objects) {
-        super(String.format(format, objects));
-        this.mErrorCode = errorCode;
-    }
-
-    /** Returns error code. */
-    public @BrEdrHandoverErrorCode int getErrorCode() {
-        return mErrorCode;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java
deleted file mode 100644
index 83ee309..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.ArrayDeque;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * A profiler for performance metrics.
- *
- * <p>This class aim to break down the execution time for each steps of process to figure out the
- * bottleneck.
- */
-public class TimingLogger {
-
-    private static final String TAG = TimingLogger.class.getSimpleName();
-
-    /**
-     * The name of this session.
-     */
-    private final String mName;
-
-    private final Preferences mPreference;
-
-    /**
-     * The ordered timing sequence data. It's composed by a paired {@link Timing} generated from
-     * {@link #start} and {@link #end}.
-     */
-    private final List<Timing> mTimings;
-
-    private final long mStartTimestampMs;
-
-    /** Constructor. */
-    public TimingLogger(String name, Preferences mPreference) {
-        this.mName = name;
-        this.mPreference = mPreference;
-        mTimings = new CopyOnWriteArrayList<>();
-        mStartTimestampMs = SystemClock.elapsedRealtime();
-    }
-
-    @VisibleForTesting
-    List<Timing> getTimings() {
-        return mTimings;
-    }
-
-    /**
-     * Start a new paired timing.
-     *
-     * @param label The split name of paired timing.
-     */
-    public void start(String label) {
-        if (mPreference.getEvaluatePerformance()) {
-            mTimings.add(new Timing(label));
-        }
-    }
-
-    /**
-     * End a paired timing.
-     */
-    public void end() {
-        if (mPreference.getEvaluatePerformance()) {
-            mTimings.add(new Timing(Timing.END_LABEL));
-        }
-    }
-
-    /**
-     * Print out the timing data.
-     */
-    public void dump() {
-        if (!mPreference.getEvaluatePerformance()) {
-            return;
-        }
-
-        calculateTiming();
-        Log.i(TAG, mName + "[Exclusive time] / [Total time] ([Timestamp])");
-        int indentCount = 0;
-        for (Timing timing : mTimings) {
-            if (timing.isEndTiming()) {
-                indentCount--;
-                continue;
-            }
-            indentCount++;
-            if (timing.mExclusiveTime == timing.mTotalTime) {
-                Log.i(TAG, getIndentString(indentCount) + timing.mName + " " + timing.mExclusiveTime
-                        + "ms (" + getRelativeTimestamp(timing.getTimestamp()) + ")");
-            } else {
-                Log.i(TAG, getIndentString(indentCount) + timing.mName + " " + timing.mExclusiveTime
-                        + "ms / " + timing.mTotalTime + "ms (" + getRelativeTimestamp(
-                        timing.getTimestamp()) + ")");
-            }
-        }
-        Log.i(TAG, mName + "end, " + getTotalTime() + "ms");
-    }
-
-    private void calculateTiming() {
-        ArrayDeque<Timing> arrayDeque = new ArrayDeque<>();
-        for (Timing timing : mTimings) {
-            if (timing.isStartTiming()) {
-                arrayDeque.addFirst(timing);
-                continue;
-            }
-
-            Timing timingStart = arrayDeque.removeFirst();
-            final long time = timing.mTimestamp - timingStart.mTimestamp;
-            timingStart.mExclusiveTime += time;
-            timingStart.mTotalTime += time;
-            if (!arrayDeque.isEmpty()) {
-                arrayDeque.peekFirst().mExclusiveTime -= time;
-            }
-        }
-    }
-
-    private String getIndentString(int indentCount) {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < indentCount; i++) {
-            sb.append("  ");
-        }
-        return sb.toString();
-    }
-
-    private long getRelativeTimestamp(long timestamp) {
-        return timestamp - mTimings.get(0).mTimestamp;
-    }
-
-    @VisibleForTesting
-    long getTotalTime() {
-        return mTimings.get(mTimings.size() - 1).mTimestamp - mTimings.get(0).mTimestamp;
-    }
-
-    /**
-     * Gets the current latency since this object was created.
-     */
-    public long getLatencyMs() {
-        return SystemClock.elapsedRealtime() - mStartTimestampMs;
-    }
-
-    @VisibleForTesting
-    static class Timing {
-
-        private static final String END_LABEL = "END_LABEL";
-
-        /**
-         * The name of this paired timing.
-         */
-        private final String mName;
-
-        /**
-         * System uptime in millisecond.
-         */
-        private final long mTimestamp;
-
-        /**
-         * The execution time exclude inner split timings.
-         */
-        private long mExclusiveTime;
-
-        /**
-         * The execution time within a start and an end timing.
-         */
-        private long mTotalTime;
-
-        private Timing(String name) {
-            this.mName = name;
-            mTimestamp = SystemClock.elapsedRealtime();
-            mExclusiveTime = 0;
-            mTotalTime = 0;
-        }
-
-        @VisibleForTesting
-        String getName() {
-            return mName;
-        }
-
-        @VisibleForTesting
-        long getTimestamp() {
-            return mTimestamp;
-        }
-
-        @VisibleForTesting
-        long getExclusiveTime() {
-            return mExclusiveTime;
-        }
-
-        @VisibleForTesting
-        long getTotalTime() {
-            return mTotalTime;
-        }
-
-        @VisibleForTesting
-        boolean isStartTiming() {
-            return !isEndTiming();
-        }
-
-        @VisibleForTesting
-        boolean isEndTiming() {
-            return END_LABEL.equals(mName);
-        }
-    }
-
-    /**
-     * This class ensures each split timing is paired with a start and an end timing.
-     */
-    public static class ScopedTiming implements AutoCloseable {
-
-        private final TimingLogger mTimingLogger;
-
-        public ScopedTiming(TimingLogger logger, String label) {
-            mTimingLogger = logger;
-            mTimingLogger.start(label);
-        }
-
-        @Override
-        public void close() {
-            mTimingLogger.end();
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java
deleted file mode 100644
index 41ac9f5..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-/** Task for toggling Bluetooth on and back off again. */
-interface ToggleBluetoothTask {
-
-    /**
-     * Toggles the bluetooth adapter off and back on again to help improve connection reliability.
-     *
-     * @throws InterruptedException when waiting for the bluetooth adapter's state to be set has
-     *     been interrupted.
-     * @throws ExecutionException when waiting for the bluetooth adapter's state to be set has
-     *     failed.
-     * @throws TimeoutException when the bluetooth adapter's state fails to be set on or off.
-     */
-    void toggleBluetooth() throws InterruptedException, ExecutionException, TimeoutException;
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java
deleted file mode 100644
index de131e4..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java
+++ /dev/null
@@ -1,781 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.gatt;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothStatusCodes;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.server.nearby.common.bluetooth.BluetoothConsts;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException;
-import com.android.server.nearby.common.bluetooth.ReservedUuids;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.ConnectionOptions;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.OperationType;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper;
-import com.android.server.nearby.common.bluetooth.util.BluetoothGattUtils;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.SynchronousOperation;
-
-import com.google.common.base.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Gatt connection to a Bluetooth device.
- */
-public class BluetoothGattConnection implements AutoCloseable {
-
-    private static final String TAG = BluetoothGattConnection.class.getSimpleName();
-
-    @VisibleForTesting
-    static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
-    @VisibleForTesting
-    static final long SLOW_OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
-
-    @VisibleForTesting
-    static final int GATT_INTERNAL_ERROR = 129;
-    @VisibleForTesting
-    static final int GATT_ERROR = 133;
-
-    private final BluetoothGattWrapper mGatt;
-    private final BluetoothOperationExecutor mBluetoothOperationExecutor;
-    private final ConnectionOptions mConnectionOptions;
-
-    private volatile boolean mServicesDiscovered = false;
-
-    private volatile boolean mIsConnected = false;
-
-    private volatile int mMtu = BluetoothConsts.DEFAULT_MTU;
-
-    private final ConcurrentMap<BluetoothGattCharacteristic, ChangeObserver> mChangeObservers =
-            new ConcurrentHashMap<>();
-
-    private final List<ConnectionCloseListener> mCloseListeners = new ArrayList<>();
-
-    private long mOperationTimeoutMillis = OPERATION_TIMEOUT_MILLIS;
-
-    BluetoothGattConnection(
-            BluetoothGattWrapper gatt,
-            BluetoothOperationExecutor bluetoothOperationExecutor,
-            ConnectionOptions connectionOptions) {
-        mGatt = gatt;
-        mBluetoothOperationExecutor = bluetoothOperationExecutor;
-        mConnectionOptions = connectionOptions;
-    }
-
-    /**
-     * Set operation timeout.
-     */
-    public void setOperationTimeout(long timeoutMillis) {
-        Preconditions.checkArgument(timeoutMillis > 0, "invalid time out value");
-        mOperationTimeoutMillis = timeoutMillis;
-    }
-
-    /**
-     * Returns connected device.
-     */
-    public BluetoothDevice getDevice() {
-        return mGatt.getDevice();
-    }
-
-    public ConnectionOptions getConnectionOptions() {
-        return mConnectionOptions;
-    }
-
-    public boolean isConnected() {
-        return mIsConnected;
-    }
-
-    /**
-     * Get service.
-     */
-    public BluetoothGattService getService(UUID uuid) throws BluetoothException {
-        Log.d(TAG, String.format("Getting service %s.", uuid));
-        if (!mServicesDiscovered) {
-            discoverServices();
-        }
-        BluetoothGattService match = null;
-        for (BluetoothGattService service : mGatt.getServices()) {
-            if (service.getUuid().equals(uuid)) {
-                if (match != null) {
-                    throw new BluetoothException(
-                            String.format("More than one service %s found on device %s.",
-                                    uuid,
-                                    mGatt.getDevice()));
-                }
-                match = service;
-            }
-        }
-        if (match == null) {
-            throw new BluetoothException(String.format("Service %s not found on device %s.",
-                    uuid,
-                    mGatt.getDevice()));
-        }
-        Log.d(TAG, "Service found.");
-        return match;
-    }
-
-    /**
-     * Returns a list of all characteristics under a given service UUID.
-     */
-    private List<BluetoothGattCharacteristic> getCharacteristics(UUID serviceUuid)
-            throws BluetoothException {
-        if (!mServicesDiscovered) {
-            discoverServices();
-        }
-        ArrayList<BluetoothGattCharacteristic> characteristics = new ArrayList<>();
-        for (BluetoothGattService service : mGatt.getServices()) {
-            // Add all characteristics under this service if its service UUID matches.
-            if (service.getUuid().equals(serviceUuid)) {
-                characteristics.addAll(service.getCharacteristics());
-            }
-        }
-        return characteristics;
-    }
-
-    /**
-     * Get characteristic.
-     */
-    public BluetoothGattCharacteristic getCharacteristic(UUID serviceUuid,
-            UUID characteristicUuid) throws BluetoothException {
-        Log.d(TAG, String.format("Getting characteristic %s on service %s.", characteristicUuid,
-                serviceUuid));
-        BluetoothGattCharacteristic match = null;
-        for (BluetoothGattCharacteristic characteristic : getCharacteristics(serviceUuid)) {
-            if (characteristic.getUuid().equals(characteristicUuid)) {
-                if (match != null) {
-                    throw new BluetoothException(String.format(
-                            "More than one characteristic %s found on service %s on device %s.",
-                            characteristicUuid,
-                            serviceUuid,
-                            mGatt.getDevice()));
-                }
-                match = characteristic;
-            }
-        }
-        if (match == null) {
-            throw new BluetoothException(String.format(
-                    "Characteristic %s not found on service %s of device %s.",
-                    characteristicUuid,
-                    serviceUuid,
-                    mGatt.getDevice()));
-        }
-        Log.d(TAG, "Characteristic found.");
-        return match;
-    }
-
-    /**
-     * Get descriptor.
-     */
-    public BluetoothGattDescriptor getDescriptor(UUID serviceUuid,
-            UUID characteristicUuid, UUID descriptorUuid) throws BluetoothException {
-        Log.d(TAG, String.format("Getting descriptor %s on characteristic %s on service %s.",
-                descriptorUuid, characteristicUuid, serviceUuid));
-        BluetoothGattDescriptor match = null;
-        for (BluetoothGattDescriptor descriptor :
-                getCharacteristic(serviceUuid, characteristicUuid).getDescriptors()) {
-            if (descriptor.getUuid().equals(descriptorUuid)) {
-                if (match != null) {
-                    throw new BluetoothException(String.format("More than one descriptor %s found "
-                                    + "on characteristic %s service %s on device %s.",
-                            descriptorUuid,
-                            characteristicUuid,
-                            serviceUuid,
-                            mGatt.getDevice()));
-                }
-                match = descriptor;
-            }
-        }
-        if (match == null) {
-            throw new BluetoothException(String.format(
-                    "Descriptor %s not found on characteristic %s on service %s of device %s.",
-                    descriptorUuid,
-                    characteristicUuid,
-                    serviceUuid,
-                    mGatt.getDevice()));
-        }
-        Log.d(TAG, "Descriptor found.");
-        return match;
-    }
-
-    /**
-     * Discover services.
-     */
-    public void discoverServices() throws BluetoothException {
-        mBluetoothOperationExecutor.execute(
-                new SynchronousOperation<Void>(OperationType.DISCOVER_SERVICES) {
-                    @Nullable
-                    @Override
-                    public Void call() throws BluetoothException {
-                        if (mServicesDiscovered) {
-                            return null;
-                        }
-                        boolean forceRefresh = false;
-                        try {
-                            discoverServicesInternal();
-                        } catch (BluetoothException e) {
-                            if (!(e instanceof BluetoothGattException)) {
-                                throw e;
-                            }
-                            int errorCode = ((BluetoothGattException) e).getGattErrorCode();
-                            if (errorCode != GATT_ERROR && errorCode != GATT_INTERNAL_ERROR) {
-                                throw e;
-                            }
-                            Log.e(TAG, e.getMessage()
-                                    + "\n Ignore the gatt error for post MNC apis and force "
-                                    + "a refresh");
-                            forceRefresh = true;
-                        }
-
-                        forceRefreshServiceCacheIfNeeded(forceRefresh);
-
-                        mServicesDiscovered = true;
-
-                        return null;
-                    }
-                });
-    }
-
-    private void discoverServicesInternal() throws BluetoothException {
-        Log.i(TAG, "Starting services discovery.");
-        long startTimeMillis = System.currentTimeMillis();
-        try {
-            mBluetoothOperationExecutor.execute(
-                    new Operation<Void>(OperationType.DISCOVER_SERVICES_INTERNAL, mGatt) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            boolean success = mGatt.discoverServices();
-                            if (!success) {
-                                throw new BluetoothException(
-                                        "gatt.discoverServices returned false.");
-                            }
-                        }
-                    },
-                    SLOW_OPERATION_TIMEOUT_MILLIS);
-            Log.i(TAG, String.format("Services discovered successfully in %s ms.",
-                    System.currentTimeMillis() - startTimeMillis));
-        } catch (BluetoothException e) {
-            if (e instanceof BluetoothGattException) {
-                throw new BluetoothGattException(String.format(
-                        "Failed to discover services on device: %s.",
-                        mGatt.getDevice()), ((BluetoothGattException) e).getGattErrorCode(), e);
-            } else {
-                throw new BluetoothException(String.format(
-                        "Failed to discover services on device: %s.",
-                        mGatt.getDevice()), e);
-            }
-        }
-    }
-
-    private boolean hasDynamicServices() {
-        BluetoothGattService gattService =
-                mGatt.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE);
-        if (gattService != null) {
-            BluetoothGattCharacteristic serviceChange =
-                    gattService.getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE);
-            if (serviceChange != null) {
-                return true;
-            }
-        }
-
-        // Check whether the server contains a self defined service dynamic characteristic.
-        gattService = mGatt.getService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE);
-        if (gattService != null) {
-            BluetoothGattCharacteristic serviceChange =
-                    gattService.getCharacteristic(BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC);
-            if (serviceChange != null) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private void forceRefreshServiceCacheIfNeeded(boolean forceRefresh) throws BluetoothException {
-        if (mGatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDED) {
-            // Device is not bonded, so services should not have been cached.
-            return;
-        }
-
-        if (!forceRefresh && !hasDynamicServices()) {
-            return;
-        }
-        Log.i(TAG, "Forcing a refresh of local cache of GATT services");
-        boolean success = mGatt.refresh();
-        if (!success) {
-            throw new BluetoothException("gatt.refresh returned false.");
-        }
-        discoverServicesInternal();
-    }
-
-    /**
-     * Read characteristic.
-     */
-    public byte[] readCharacteristic(UUID serviceUuid, UUID characteristicUuid)
-            throws BluetoothException {
-        return readCharacteristic(getCharacteristic(serviceUuid, characteristicUuid));
-    }
-
-    /**
-     * Read characteristic.
-     */
-    public byte[] readCharacteristic(final BluetoothGattCharacteristic characteristic)
-            throws BluetoothException {
-        try {
-            return mBluetoothOperationExecutor.executeNonnull(
-                    new Operation<byte[]>(OperationType.READ_CHARACTERISTIC, mGatt,
-                            characteristic) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            boolean success = mGatt.readCharacteristic(characteristic);
-                            if (!success) {
-                                throw new BluetoothException(
-                                        "gatt.readCharacteristic returned false.");
-                            }
-                        }
-                    },
-                    mOperationTimeoutMillis);
-        } catch (BluetoothException e) {
-            throw new BluetoothException(String.format(
-                    "Failed to read %s on device %s.",
-                    BluetoothGattUtils.toString(characteristic),
-                    mGatt.getDevice()), e);
-        }
-    }
-
-    /**
-     * Writes Characteristic.
-     */
-    public void writeCharacteristic(UUID serviceUuid, UUID characteristicUuid, byte[] value)
-            throws BluetoothException {
-        writeCharacteristic(getCharacteristic(serviceUuid, characteristicUuid), value);
-    }
-
-    /**
-     * Writes Characteristic.
-     */
-    public void writeCharacteristic(final BluetoothGattCharacteristic characteristic,
-            final byte[] value) throws BluetoothException {
-        Log.d(TAG, String.format("Writing %d bytes on %s on device %s.",
-                value.length,
-                BluetoothGattUtils.toString(characteristic),
-                mGatt.getDevice()));
-        if ((characteristic.getProperties() & (BluetoothGattCharacteristic.PROPERTY_WRITE
-                | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) {
-            throw new BluetoothException(String.format("%s is not writable!", characteristic));
-        }
-        try {
-            mBluetoothOperationExecutor.execute(
-                    new Operation<Void>(OperationType.WRITE_CHARACTERISTIC, mGatt, characteristic) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            int writeCharacteristicResponseCode = mGatt.writeCharacteristic(
-                                    characteristic, value, characteristic.getWriteType());
-                            if (writeCharacteristicResponseCode != BluetoothStatusCodes.SUCCESS) {
-                                throw new BluetoothException(
-                                        "gatt.writeCharacteristic returned "
-                                        + writeCharacteristicResponseCode);
-                            }
-                        }
-                    },
-                    mOperationTimeoutMillis);
-        } catch (BluetoothException e) {
-            throw new BluetoothException(String.format(
-                    "Failed to write %s on device %s.",
-                    BluetoothGattUtils.toString(characteristic),
-                    mGatt.getDevice()), e);
-        }
-        Log.d(TAG, "Writing characteristic done.");
-    }
-
-    /**
-     * Reads descriptor.
-     */
-    public byte[] readDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid)
-            throws BluetoothException {
-        return readDescriptor(getDescriptor(serviceUuid, characteristicUuid, descriptorUuid));
-    }
-
-    /**
-     * Reads descriptor.
-     */
-    public byte[] readDescriptor(final BluetoothGattDescriptor descriptor)
-            throws BluetoothException {
-        try {
-            return mBluetoothOperationExecutor.executeNonnull(
-                    new Operation<byte[]>(OperationType.READ_DESCRIPTOR, mGatt, descriptor) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            boolean success = mGatt.readDescriptor(descriptor);
-                            if (!success) {
-                                throw new BluetoothException("gatt.readDescriptor returned false.");
-                            }
-                        }
-                    },
-                    mOperationTimeoutMillis);
-        } catch (BluetoothException e) {
-            throw new BluetoothException(String.format(
-                    "Failed to read %s on %s on device %s.",
-                    descriptor.getUuid(),
-                    BluetoothGattUtils.toString(descriptor),
-                    mGatt.getDevice()), e);
-        }
-    }
-
-    /**
-     * Writes descriptor.
-     */
-    public void writeDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid,
-            byte[] value) throws BluetoothException {
-        writeDescriptor(getDescriptor(serviceUuid, characteristicUuid, descriptorUuid), value);
-    }
-
-    /**
-     * Writes descriptor.
-     */
-    public void writeDescriptor(final BluetoothGattDescriptor descriptor, final byte[] value)
-            throws BluetoothException {
-        Log.d(TAG, String.format(
-                "Writing %d bytes on %s on device %s.",
-                value.length,
-                BluetoothGattUtils.toString(descriptor),
-                mGatt.getDevice()));
-        long startTimeMillis = System.currentTimeMillis();
-        try {
-            mBluetoothOperationExecutor.execute(
-                    new Operation<Void>(OperationType.WRITE_DESCRIPTOR, mGatt, descriptor) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            int writeDescriptorResponseCode = mGatt.writeDescriptor(descriptor,
-                                    value);
-                            if (writeDescriptorResponseCode != BluetoothStatusCodes.SUCCESS) {
-                                throw new BluetoothException(
-                                        "gatt.writeDescriptor returned "
-                                        + writeDescriptorResponseCode);
-                            }
-                        }
-                    },
-                    mOperationTimeoutMillis);
-            Log.d(TAG, String.format("Writing descriptor done in %s ms.",
-                    System.currentTimeMillis() - startTimeMillis));
-        } catch (BluetoothException e) {
-            throw new BluetoothException(String.format(
-                    "Failed to write %s on device %s.",
-                    BluetoothGattUtils.toString(descriptor),
-                    mGatt.getDevice()), e);
-        }
-    }
-
-    /**
-     * Reads remote Rssi.
-     */
-    public int readRemoteRssi() throws BluetoothException {
-        try {
-            return mBluetoothOperationExecutor.executeNonnull(
-                    new Operation<Integer>(OperationType.READ_RSSI, mGatt) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            boolean success = mGatt.readRemoteRssi();
-                            if (!success) {
-                                throw new BluetoothException("gatt.readRemoteRssi returned false.");
-                            }
-                        }
-                    },
-                    mOperationTimeoutMillis);
-        } catch (BluetoothException e) {
-            throw new BluetoothException(
-                    String.format("Failed to read rssi on device %s.", mGatt.getDevice()), e);
-        }
-    }
-
-    public int getMtu() {
-        return mMtu;
-    }
-
-    /**
-     * Get max data packet size.
-     */
-    public int getMaxDataPacketSize() {
-        // Per BT specs (3.2.9), only MTU - 3 bytes can be used to transmit data
-        return mMtu - 3;
-    }
-
-    /** Set notification enabled or disabled. */
-    @VisibleForTesting
-    public void setNotificationEnabled(BluetoothGattCharacteristic characteristic, boolean enabled)
-            throws BluetoothException {
-        boolean isIndication;
-        int properties = characteristic.getProperties();
-        if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
-            isIndication = false;
-        } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
-            isIndication = true;
-        } else {
-            throw new BluetoothException(String.format(
-                    "%s on device %s supports neither notifications nor indications.",
-                    BluetoothGattUtils.toString(characteristic),
-                    mGatt.getDevice()));
-        }
-        BluetoothGattDescriptor clientConfigDescriptor =
-                characteristic.getDescriptor(
-                        ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION);
-        if (clientConfigDescriptor == null) {
-            throw new BluetoothException(String.format(
-                    "%s on device %s is missing client config descriptor.",
-                    BluetoothGattUtils.toString(characteristic),
-                    mGatt.getDevice()));
-        }
-        long startTime = System.currentTimeMillis();
-        Log.d(TAG, String.format("%s %s on characteristic %s.", enabled ? "Enabling" : "Disabling",
-                isIndication ? "indication" : "notification", characteristic.getUuid()));
-
-        if (enabled) {
-            mGatt.setCharacteristicNotification(characteristic, enabled);
-        }
-        writeDescriptor(clientConfigDescriptor,
-                enabled
-                        ? (isIndication
-                        ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE :
-                        BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
-                        : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
-        if (!enabled) {
-            mGatt.setCharacteristicNotification(characteristic, enabled);
-        }
-
-        Log.d(TAG, String.format("Done in %d ms.", System.currentTimeMillis() - startTime));
-    }
-
-    /**
-     * Enables notification.
-     */
-    public ChangeObserver enableNotification(UUID serviceUuid, UUID characteristicUuid)
-            throws BluetoothException {
-        return enableNotification(getCharacteristic(serviceUuid, characteristicUuid));
-    }
-
-    /**
-     * Enables notification.
-     */
-    public ChangeObserver enableNotification(final BluetoothGattCharacteristic characteristic)
-            throws BluetoothException {
-        return mBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<ChangeObserver>(
-                        OperationType.NOTIFICATION_CHANGE,
-                        characteristic) {
-                    @Override
-                    public ChangeObserver call() throws BluetoothException {
-                        ChangeObserver changeObserver = new ChangeObserver();
-                        mChangeObservers.put(characteristic, changeObserver);
-                        setNotificationEnabled(characteristic, true);
-                        return changeObserver;
-                    }
-                });
-    }
-
-    /**
-     * Disables notification.
-     */
-    public void disableNotification(UUID serviceUuid, UUID characteristicUuid)
-            throws BluetoothException {
-        disableNotification(getCharacteristic(serviceUuid, characteristicUuid));
-    }
-
-    /**
-     * Disables notification.
-     */
-    public void disableNotification(final BluetoothGattCharacteristic characteristic)
-            throws BluetoothException {
-        mBluetoothOperationExecutor.execute(
-                new SynchronousOperation<Void>(
-                        OperationType.NOTIFICATION_CHANGE,
-                        characteristic) {
-                    @Nullable
-                    @Override
-                    public Void call() throws BluetoothException {
-                        setNotificationEnabled(characteristic, false);
-                        mChangeObservers.remove(characteristic);
-                        return null;
-                    }
-                });
-    }
-
-    /**
-     * Adds a close listener.
-     */
-    public void addCloseListener(ConnectionCloseListener listener) {
-        mCloseListeners.add(listener);
-        if (!mIsConnected) {
-            listener.onClose();
-            return;
-        }
-    }
-
-    /**
-     * Removes a close listener.
-     */
-    public void removeCloseListener(ConnectionCloseListener listener) {
-        mCloseListeners.remove(listener);
-    }
-
-    /** onCharacteristicChanged callback. */
-    public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic, byte[] value) {
-        ChangeObserver observer = mChangeObservers.get(characteristic);
-        if (observer == null) {
-            return;
-        }
-        observer.onValueChange(value);
-    }
-
-    @Override
-    public void close() throws BluetoothException {
-        Log.d(TAG, "close");
-        try {
-            if (!mIsConnected) {
-                // Don't call disconnect on a closed connection, since Android framework won't
-                // provide any feedback.
-                return;
-            }
-            mBluetoothOperationExecutor.execute(
-                    new Operation<Void>(OperationType.DISCONNECT, mGatt.getDevice()) {
-                        @Override
-                        public void run() throws BluetoothException {
-                            mGatt.disconnect();
-                        }
-                    }, mOperationTimeoutMillis);
-        } finally {
-            mGatt.close();
-        }
-    }
-
-    /** onConnected callback. */
-    public void onConnected() {
-        Log.d(TAG, "onConnected");
-        mIsConnected = true;
-    }
-
-    /** onClosed callback. */
-    public void onClosed() {
-        Log.d(TAG, "onClosed");
-        if (!mIsConnected) {
-            return;
-        }
-        mIsConnected = false;
-        for (ConnectionCloseListener listener : mCloseListeners) {
-            listener.onClose();
-        }
-        mGatt.close();
-    }
-
-    /**
-     * Observer to wait or be called back when value change.
-     */
-    public static class ChangeObserver {
-
-        private final BlockingDeque<byte[]> mValues = new LinkedBlockingDeque<byte[]>();
-
-        @GuardedBy("mValues")
-        @Nullable
-        private volatile CharacteristicChangeListener mListener;
-
-        /**
-         * Set listener.
-         */
-        public void setListener(@Nullable CharacteristicChangeListener listener) {
-            synchronized (mValues) {
-                mListener = listener;
-                if (listener != null) {
-                    byte[] value;
-                    while ((value = mValues.poll()) != null) {
-                        listener.onValueChange(value);
-                    }
-                }
-            }
-        }
-
-        /**
-         * onValueChange callback.
-         */
-        public void onValueChange(byte[] newValue) {
-            synchronized (mValues) {
-                CharacteristicChangeListener listener = mListener;
-                if (listener == null) {
-                    mValues.add(newValue);
-                } else {
-                    listener.onValueChange(newValue);
-                }
-            }
-        }
-
-        /**
-         * Waits for update for a given time.
-         */
-        public byte[] waitForUpdate(long timeoutMillis) throws BluetoothException {
-            byte[] result;
-            try {
-                result = mValues.poll(timeoutMillis, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new BluetoothException("Operation interrupted.");
-            }
-            if (result == null) {
-                throw new BluetoothTimeoutException(
-                        String.format("Operation timed out after %dms", timeoutMillis));
-            }
-            return result;
-        }
-    }
-
-    /**
-     * Listener for characteristic data changes over notifications or indications.
-     */
-    public interface CharacteristicChangeListener {
-
-        /**
-         * onValueChange callback.
-         */
-        void onValueChange(byte[] newValue);
-    }
-
-    /**
-     * Listener for connection close events.
-     */
-    public interface ConnectionCloseListener {
-
-        /**
-         * onClose callback.
-         */
-        void onClose();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java
deleted file mode 100644
index 18a9f5f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java
+++ /dev/null
@@ -1,690 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.gatt;
-
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.content.Context;
-import android.os.ParcelUuid;
-import android.util.Log;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattCallback;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanCallback;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanResult;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-
-import com.google.common.base.Preconditions;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Wrapper of {@link BluetoothGattWrapper} that provides blocking methods, errors and timeout
- * handling.
- */
-@SuppressWarnings("Guava") // java.util.Optional is not available until API 24
-public class BluetoothGattHelper {
-
-    private static final String TAG = BluetoothGattHelper.class.getSimpleName();
-
-    @VisibleForTesting
-    static final long LOW_LATENCY_SCAN_MILLIS = TimeUnit.SECONDS.toMillis(5);
-    private static final long POLL_INTERVAL_MILLIS = 5L /* milliseconds */;
-
-    /**
-     * BT operation types that can be in flight.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    OperationType.SCAN,
-                    OperationType.CONNECT,
-                    OperationType.DISCOVER_SERVICES,
-                    OperationType.DISCOVER_SERVICES_INTERNAL,
-                    OperationType.NOTIFICATION_CHANGE,
-                    OperationType.READ_CHARACTERISTIC,
-                    OperationType.WRITE_CHARACTERISTIC,
-                    OperationType.READ_DESCRIPTOR,
-                    OperationType.WRITE_DESCRIPTOR,
-                    OperationType.READ_RSSI,
-                    OperationType.WRITE_RELIABLE,
-                    OperationType.CHANGE_MTU,
-                    OperationType.DISCONNECT,
-            })
-    public @interface OperationType {
-        int SCAN = 0;
-        int CONNECT = 1;
-        int DISCOVER_SERVICES = 2;
-        int DISCOVER_SERVICES_INTERNAL = 3;
-        int NOTIFICATION_CHANGE = 4;
-        int READ_CHARACTERISTIC = 5;
-        int WRITE_CHARACTERISTIC = 6;
-        int READ_DESCRIPTOR = 7;
-        int WRITE_DESCRIPTOR = 8;
-        int READ_RSSI = 9;
-        int WRITE_RELIABLE = 10;
-        int CHANGE_MTU = 11;
-        int DISCONNECT = 12;
-    }
-
-    @VisibleForTesting
-    final ScanCallback mScanCallback = new InternalScanCallback();
-    @VisibleForTesting
-    final BluetoothGattCallback mBluetoothGattCallback =
-            new InternalBluetoothGattCallback();
-    @VisibleForTesting
-    final ConcurrentMap<BluetoothGattWrapper, BluetoothGattConnection> mConnections =
-            new ConcurrentHashMap<>();
-
-    private final Context mApplicationContext;
-    private final BluetoothAdapter mBluetoothAdapter;
-    private final BluetoothOperationExecutor mBluetoothOperationExecutor;
-
-    @VisibleForTesting
-    BluetoothGattHelper(
-            Context applicationContext,
-            BluetoothAdapter bluetoothAdapter,
-            BluetoothOperationExecutor bluetoothOperationExecutor) {
-        mApplicationContext = applicationContext;
-        mBluetoothAdapter = bluetoothAdapter;
-        mBluetoothOperationExecutor = bluetoothOperationExecutor;
-    }
-
-    public BluetoothGattHelper(Context applicationContext, BluetoothAdapter bluetoothAdapter) {
-        this(
-                Preconditions.checkNotNull(applicationContext),
-                Preconditions.checkNotNull(bluetoothAdapter),
-                new BluetoothOperationExecutor(5));
-    }
-
-    /**
-     * Auto-connects a serice Uuid.
-     */
-    public BluetoothGattConnection autoConnect(final UUID serviceUuid) throws BluetoothException {
-        Log.d(TAG, String.format("Starting autoconnection to a device advertising service %s.",
-                serviceUuid));
-        BluetoothDevice device = null;
-        int retries = 3;
-        final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
-        if (scanner == null) {
-            throw new BluetoothException("Bluetooth is disabled or LE is not supported.");
-        }
-        final ScanFilter serviceFilter = new ScanFilter.Builder()
-                .setServiceUuid(new ParcelUuid(serviceUuid))
-                .build();
-        ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder()
-                .setReportDelay(0);
-        final ScanSettings scanSettingsLowLatency = scanSettingsBuilder
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
-                .build();
-        final ScanSettings scanSettingsLowPower = scanSettingsBuilder
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
-                .build();
-        while (true) {
-            long startTimeMillis = System.currentTimeMillis();
-            try {
-                Log.d(TAG, "Starting low latency scanning.");
-                device =
-                        mBluetoothOperationExecutor.executeNonnull(
-                                new Operation<BluetoothDevice>(OperationType.SCAN) {
-                                    @Override
-                                    public void run() throws BluetoothException {
-                                        scanner.startScan(Arrays.asList(serviceFilter),
-                                                scanSettingsLowLatency, mScanCallback);
-                                    }
-                                }, LOW_LATENCY_SCAN_MILLIS);
-            } catch (BluetoothOperationTimeoutException e) {
-                Log.d(TAG, String.format(
-                        "Cannot find a nearby device in low latency scanning after %s ms.",
-                        LOW_LATENCY_SCAN_MILLIS));
-            } finally {
-                scanner.stopScan(mScanCallback);
-            }
-            if (device == null) {
-                Log.d(TAG, "Starting low power scanning.");
-                try {
-                    device = mBluetoothOperationExecutor.executeNonnull(
-                            new Operation<BluetoothDevice>(OperationType.SCAN) {
-                                @Override
-                                public void run() throws BluetoothException {
-                                    scanner.startScan(Arrays.asList(serviceFilter),
-                                            scanSettingsLowPower, mScanCallback);
-                                }
-                            });
-                } finally {
-                    scanner.stopScan(mScanCallback);
-                }
-            }
-            Log.d(TAG, String.format("Scanning done in %d ms. Found device %s.",
-                    System.currentTimeMillis() - startTimeMillis, device));
-
-            try {
-                return connect(device);
-            } catch (BluetoothException e) {
-                retries--;
-                if (retries == 0) {
-                    throw e;
-                } else {
-                    Log.d(TAG, String.format(
-                            "Connection failed: %s. Retrying %d more times.", e, retries));
-                }
-            }
-        }
-    }
-
-    /**
-     * Connects to a device using default connection options.
-     */
-    public BluetoothGattConnection connect(BluetoothDevice bluetoothDevice)
-            throws BluetoothException {
-        return connect(bluetoothDevice, ConnectionOptions.builder().build());
-    }
-
-    /**
-     * Connects to a device using specifies connection options.
-     */
-    public BluetoothGattConnection connect(
-            BluetoothDevice bluetoothDevice, ConnectionOptions options) throws BluetoothException {
-        Log.d(TAG, String.format("Connecting to device %s.", bluetoothDevice));
-        long startTimeMillis = System.currentTimeMillis();
-
-        Operation<BluetoothGattConnection> connectOperation =
-                new Operation<BluetoothGattConnection>(OperationType.CONNECT, bluetoothDevice) {
-                    private final Object mLock = new Object();
-
-                    @GuardedBy("mLock")
-                    private boolean mIsCanceled = false;
-
-                    @GuardedBy("mLock")
-                    @Nullable(/* null before operation is executed */)
-                    private BluetoothGattWrapper mBluetoothGatt;
-
-                    @Override
-                    public void run() throws BluetoothException {
-                        synchronized (mLock) {
-                            if (mIsCanceled) {
-                                return;
-                            }
-                            BluetoothGattWrapper bluetoothGattWrapper;
-                            Log.d(TAG, "Use LE transport");
-                            bluetoothGattWrapper =
-                                    bluetoothDevice.connectGatt(
-                                            mApplicationContext,
-                                            options.autoConnect(),
-                                            mBluetoothGattCallback,
-                                            android.bluetooth.BluetoothDevice.TRANSPORT_LE);
-                            if (bluetoothGattWrapper == null) {
-                                throw new BluetoothException("connectGatt() returned null.");
-                            }
-
-                            try {
-                                // Set connection priority without waiting for connection callback.
-                                // Per code, btif_gatt_client.c, when priority is set before
-                                // connection, this sets preferred connection parameters that will
-                                // be used during the connection establishment.
-                                Optional<Integer> connectionPriorityOption =
-                                        options.connectionPriority();
-                                if (connectionPriorityOption.isPresent()) {
-                                    // requestConnectionPriority can only be called when
-                                    // BluetoothGatt is connected to the system BluetoothGatt
-                                    // service (see android/bluetooth/BluetoothGatt.java code).
-                                    // However, there is no callback to the app to inform when this
-                                    // is done. requestConnectionPriority will returns false with no
-                                    // side-effect before the service is connected, so we just poll
-                                    // here until true is returned.
-                                    int connectionPriority = connectionPriorityOption.get();
-                                    long startTimeMillis = System.currentTimeMillis();
-                                    while (!bluetoothGattWrapper.requestConnectionPriority(
-                                            connectionPriority)) {
-                                        if (System.currentTimeMillis() - startTimeMillis
-                                                > options.connectionTimeoutMillis()) {
-                                            throw new BluetoothException(
-                                                    String.format(
-                                                            Locale.US,
-                                                            "Failed to set connectionPriority "
-                                                                    + "after %dms.",
-                                                            options.connectionTimeoutMillis()));
-                                        }
-                                        try {
-                                            Thread.sleep(POLL_INTERVAL_MILLIS);
-                                        } catch (InterruptedException e) {
-                                            Thread.currentThread().interrupt();
-                                            throw new BluetoothException(
-                                                    "connect() operation interrupted.");
-                                        }
-                                    }
-                                }
-                            } catch (Exception e) {
-                                // Make sure to clean connection.
-                                bluetoothGattWrapper.disconnect();
-                                bluetoothGattWrapper.close();
-                                throw e;
-                            }
-
-                            BluetoothGattConnection connection = new BluetoothGattConnection(
-                                    bluetoothGattWrapper, mBluetoothOperationExecutor, options);
-                            mConnections.put(bluetoothGattWrapper, connection);
-                            mBluetoothGatt = bluetoothGattWrapper;
-                        }
-                    }
-
-                    @Override
-                    public void cancel() {
-                        // Clean connection if connection times out.
-                        synchronized (mLock) {
-                            if (mIsCanceled) {
-                                return;
-                            }
-                            mIsCanceled = true;
-                            BluetoothGattWrapper bluetoothGattWrapper = mBluetoothGatt;
-                            if (bluetoothGattWrapper == null) {
-                                return;
-                            }
-                            mConnections.remove(bluetoothGattWrapper);
-                            bluetoothGattWrapper.disconnect();
-                            bluetoothGattWrapper.close();
-                        }
-                    }
-                };
-        BluetoothGattConnection result;
-        if (options.autoConnect()) {
-            result = mBluetoothOperationExecutor.executeNonnull(connectOperation);
-        } else {
-            result =
-                    mBluetoothOperationExecutor.executeNonnull(
-                            connectOperation, options.connectionTimeoutMillis());
-        }
-        Log.d(TAG, String.format("Connection success in %d ms.",
-                System.currentTimeMillis() - startTimeMillis));
-        return result;
-    }
-
-    private BluetoothGattConnection getConnectionByGatt(BluetoothGattWrapper gatt)
-            throws BluetoothException {
-        BluetoothGattConnection connection = mConnections.get(gatt);
-        if (connection == null) {
-            throw new BluetoothException("Receive callback on unexpected device: " + gatt);
-        }
-        return connection;
-    }
-
-    private class InternalBluetoothGattCallback extends BluetoothGattCallback {
-
-        @Override
-        public void onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState) {
-            BluetoothGattConnection connection;
-            BluetoothDevice device = gatt.getDevice();
-            switch (newState) {
-                case BluetoothGatt.STATE_CONNECTED: {
-                    connection = mConnections.get(gatt);
-                    if (connection == null) {
-                        Log.w(TAG, String.format(
-                                "Received unexpected successful connection for dev %s! Ignoring.",
-                                device));
-                        break;
-                    }
-
-                    Operation<BluetoothGattConnection> operation =
-                            new Operation<>(OperationType.CONNECT, device);
-                    if (status != BluetoothGatt.GATT_SUCCESS) {
-                        mConnections.remove(gatt);
-                        gatt.disconnect();
-                        gatt.close();
-                        mBluetoothOperationExecutor.notifyCompletion(operation, status, null);
-                        break;
-                    }
-
-                    // Process connection options
-                    ConnectionOptions options = connection.getConnectionOptions();
-                    Optional<Integer> mtuOption = options.mtu();
-                    if (mtuOption.isPresent()) {
-                        // Requesting MTU and waiting for MTU callback.
-                        boolean success = gatt.requestMtu(mtuOption.get());
-                        if (!success) {
-                            mBluetoothOperationExecutor.notifyFailure(operation,
-                                    new BluetoothException(String.format(Locale.US,
-                                            "Failed to request MTU of %d for dev %s: "
-                                                    + "returned false.",
-                                            mtuOption.get(), device)));
-                            // Make sure to clean connection.
-                            mConnections.remove(gatt);
-                            gatt.disconnect();
-                            gatt.close();
-                        }
-                        break;
-                    }
-
-                    // Connection successful
-                    connection.onConnected();
-                    mBluetoothOperationExecutor.notifyCompletion(operation, status, connection);
-                    break;
-                }
-                case BluetoothGatt.STATE_DISCONNECTED: {
-                    connection = mConnections.remove(gatt);
-                    if (connection == null) {
-                        Log.w(TAG, String.format("Received unexpected disconnection"
-                                + " for device %s! Ignoring.", device));
-                        break;
-                    }
-                    if (!connection.isConnected()) {
-                        // This is a failed connection attempt
-                        if (status == BluetoothGatt.GATT_SUCCESS) {
-                            // This is weird... considering this as a failure
-                            Log.w(TAG, String.format(
-                                    "Received a success for a failed connection "
-                                            + "attempt for device %s! Ignoring.", device));
-                            status = BluetoothGatt.GATT_FAILURE;
-                        }
-                        mBluetoothOperationExecutor
-                                .notifyCompletion(new Operation<BluetoothGattConnection>(
-                                        OperationType.CONNECT, device), status, null);
-                        // Clean Gatt object in every case.
-                        gatt.disconnect();
-                        gatt.close();
-                        break;
-                    }
-                    connection.onClosed();
-                    mBluetoothOperationExecutor.notifyCompletion(
-                            new Operation<>(OperationType.DISCONNECT, device), status);
-                    break;
-                }
-                default:
-                    Log.e(TAG, "Unexpected connection state: " + newState);
-            }
-        }
-
-        @Override
-        public void onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status) {
-            BluetoothGattConnection connection = mConnections.get(gatt);
-            BluetoothDevice device = gatt.getDevice();
-            if (connection == null) {
-                Log.w(TAG, String.format(
-                        "Received unexpected MTU change for device %s! Ignoring.", device));
-                return;
-            }
-            if (connection.isConnected()) {
-                // This is the callback for the deprecated BluetoothGattConnection.requestMtu.
-                mBluetoothOperationExecutor.notifyCompletion(
-                        new Operation<>(OperationType.CHANGE_MTU, gatt), status, mtu);
-            } else {
-                // This is the callback when requesting MTU right after connecting.
-                connection.onConnected();
-                mBluetoothOperationExecutor.notifyCompletion(
-                        new Operation<>(OperationType.CONNECT, device), status, connection);
-                if (status != BluetoothGatt.GATT_SUCCESS) {
-                    Log.w(TAG, String.format(
-                            "%s responds MTU change failed, status %s.", device, status));
-                    // Clean connection if it's failed.
-                    mConnections.remove(gatt);
-                    gatt.disconnect();
-                    gatt.close();
-                    return;
-                }
-            }
-        }
-
-        @Override
-        public void onServicesDiscovered(BluetoothGattWrapper gatt, int status) {
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<Void>(OperationType.DISCOVER_SERVICES_INTERNAL, gatt), status);
-        }
-
-        @Override
-        public void onCharacteristicRead(BluetoothGattWrapper gatt,
-                BluetoothGattCharacteristic characteristic, int status) {
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<byte[]>(OperationType.READ_CHARACTERISTIC, gatt, characteristic),
-                    status, characteristic.getValue());
-        }
-
-        @Override
-        public void onCharacteristicWrite(BluetoothGattWrapper gatt,
-                BluetoothGattCharacteristic characteristic, int status) {
-            mBluetoothOperationExecutor.notifyCompletion(new Operation<Void>(
-                    OperationType.WRITE_CHARACTERISTIC, gatt, characteristic), status);
-        }
-
-        @Override
-        public void onDescriptorRead(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor,
-                int status) {
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<byte[]>(OperationType.READ_DESCRIPTOR, gatt, descriptor), status,
-                    descriptor.getValue());
-        }
-
-        @Override
-        public void onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor,
-                int status) {
-            Log.d(TAG, String.format("onDescriptorWrite %s, %s, %d",
-                    gatt.getDevice(), descriptor.getUuid(), status));
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<Void>(OperationType.WRITE_DESCRIPTOR, gatt, descriptor), status);
-        }
-
-        @Override
-        public void onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status) {
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<Integer>(OperationType.READ_RSSI, gatt), status, rssi);
-        }
-
-        @Override
-        public void onReliableWriteCompleted(BluetoothGattWrapper gatt, int status) {
-            mBluetoothOperationExecutor.notifyCompletion(
-                    new Operation<Void>(OperationType.WRITE_RELIABLE, gatt), status);
-        }
-
-        @Override
-        public void onCharacteristicChanged(BluetoothGattWrapper gatt,
-                BluetoothGattCharacteristic characteristic) {
-            byte[] value = characteristic.getValue();
-            if (value == null) {
-                // Value is not supposed to be null, but just to be safe...
-                value = new byte[0];
-            }
-            Log.d(TAG, String.format("Characteristic %s changed, Gatt device: %s",
-                    characteristic.getUuid(), gatt.getDevice()));
-            try {
-                getConnectionByGatt(gatt).onCharacteristicChanged(characteristic, value);
-            } catch (BluetoothException e) {
-                Log.e(TAG, "Error in onCharacteristicChanged", e);
-            }
-        }
-    }
-
-    private class InternalScanCallback extends ScanCallback {
-
-        @Override
-        public void onScanFailed(int errorCode) {
-            String errorMessage;
-            switch (errorCode) {
-                case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
-                    errorMessage = "SCAN_FAILED_ALREADY_STARTED";
-                    break;
-                case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
-                    errorMessage = "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED";
-                    break;
-                case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
-                    errorMessage = "SCAN_FAILED_FEATURE_UNSUPPORTED";
-                    break;
-                case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
-                    errorMessage = "SCAN_FAILED_INTERNAL_ERROR";
-                    break;
-                default:
-                    errorMessage = "Unknown error code - " + errorCode;
-            }
-            mBluetoothOperationExecutor.notifyFailure(
-                    new Operation<BluetoothDevice>(OperationType.SCAN),
-                    new BluetoothException("Scan failed: " + errorMessage));
-        }
-
-        @Override
-        public void onScanResult(int callbackType, ScanResult result) {
-            mBluetoothOperationExecutor.notifySuccess(
-                    new Operation<BluetoothDevice>(OperationType.SCAN), result.getDevice());
-        }
-    }
-
-    /**
-     * Options for {@link #connect}.
-     */
-    public static class ConnectionOptions {
-
-        private boolean mAutoConnect;
-        private long mConnectionTimeoutMillis;
-        private Optional<Integer> mConnectionPriority;
-        private Optional<Integer> mMtu;
-
-        private ConnectionOptions(boolean autoConnect, long connectionTimeoutMillis,
-                Optional<Integer> connectionPriority,
-                Optional<Integer> mtu) {
-            this.mAutoConnect = autoConnect;
-            this.mConnectionTimeoutMillis = connectionTimeoutMillis;
-            this.mConnectionPriority = connectionPriority;
-            this.mMtu = mtu;
-        }
-
-        boolean autoConnect() {
-            return mAutoConnect;
-        }
-
-        long connectionTimeoutMillis() {
-            return mConnectionTimeoutMillis;
-        }
-
-        Optional<Integer> connectionPriority() {
-            return mConnectionPriority;
-        }
-
-        Optional<Integer> mtu() {
-            return mMtu;
-        }
-
-        @Override
-        public String toString() {
-            return "ConnectionOptions{"
-                    + "autoConnect=" + mAutoConnect + ", "
-                    + "connectionTimeoutMillis=" + mConnectionTimeoutMillis + ", "
-                    + "connectionPriority=" + mConnectionPriority + ", "
-                    + "mtu=" + mMtu
-                    + "}";
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o instanceof ConnectionOptions) {
-                ConnectionOptions that = (ConnectionOptions) o;
-                return this.mAutoConnect == that.autoConnect()
-                        && this.mConnectionTimeoutMillis == that.connectionTimeoutMillis()
-                        && this.mConnectionPriority.equals(that.connectionPriority())
-                        && this.mMtu.equals(that.mtu());
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mAutoConnect, mConnectionTimeoutMillis, mConnectionPriority, mMtu);
-        }
-
-        /**
-         * Creates a builder of ConnectionOptions.
-         */
-        public static Builder builder() {
-            return new ConnectionOptions.Builder()
-                    .setAutoConnect(false)
-                    .setConnectionTimeoutMillis(TimeUnit.SECONDS.toMillis(5));
-        }
-
-        /**
-         * Builder for {@link ConnectionOptions}.
-         */
-        public static class Builder {
-
-            private boolean mAutoConnect;
-            private long mConnectionTimeoutMillis;
-            private Optional<Integer> mConnectionPriority = Optional.empty();
-            private Optional<Integer> mMtu = Optional.empty();
-
-            /**
-             * See {@link android.bluetooth.BluetoothDevice#connectGatt}.
-             */
-            public Builder setAutoConnect(boolean autoConnect) {
-                this.mAutoConnect = autoConnect;
-                return this;
-            }
-
-            /**
-             * See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}.
-             */
-            public Builder setConnectionPriority(int connectionPriority) {
-                this.mConnectionPriority = Optional.of(connectionPriority);
-                return this;
-            }
-
-            /**
-             * See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}.
-             */
-            public Builder setMtu(int mtu) {
-                this.mMtu = Optional.of(mtu);
-                return this;
-            }
-
-            /**
-             * Sets the timeout for the GATT connection.
-             */
-            public Builder setConnectionTimeoutMillis(long connectionTimeoutMillis) {
-                this.mConnectionTimeoutMillis = connectionTimeoutMillis;
-                return this;
-            }
-
-            /**
-             * Builds ConnectionOptions.
-             */
-            public ConnectionOptions build() {
-                return new ConnectionOptions(mAutoConnect, mConnectionTimeoutMillis,
-                        mConnectionPriority, mMtu);
-            }
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/NonnullProvider.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/NonnullProvider.java
deleted file mode 100644
index 16abd99..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/NonnullProvider.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability;
-
-/**
- * Provider that returns non-null instances.
- *
- * @param <T> Type of provided instance.
- */
-public interface NonnullProvider<T> {
-    /** Get a non-null instance. */
-    T get();
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java
deleted file mode 100644
index 6cfdd78..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability;
-
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-
-import javax.annotation.Nullable;
-
-/** Util class to convert from or to testable classes. */
-public class Testability {
-    /** Wraps a Bluetooth device. */
-    public static BluetoothDevice wrap(android.bluetooth.BluetoothDevice bluetoothDevice) {
-        return BluetoothDevice.wrap(bluetoothDevice);
-    }
-
-    /** Wraps a Bluetooth adapter. */
-    @Nullable
-    public static BluetoothAdapter wrap(
-            @Nullable android.bluetooth.BluetoothAdapter bluetoothAdapter) {
-        return BluetoothAdapter.wrap(bluetoothAdapter);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/TimeProvider.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/TimeProvider.java
deleted file mode 100644
index a4de913..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/TimeProvider.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability;
-
-/** Provider of time for testability. */
-public class TimeProvider {
-    public long getTimeMillis() {
-        return System.currentTimeMillis();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/VersionProvider.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/VersionProvider.java
deleted file mode 100644
index f46ea7a..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/VersionProvider.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability;
-
-import android.os.Build.VERSION;
-
-/**
- * Provider of android sdk version for testability
- */
-public class VersionProvider {
-    public int getSdkInt() {
-        return VERSION.SDK_INT;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java
deleted file mode 100644
index afa2a1b..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeAdvertiser;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.BluetoothAdapter}.
- */
-public class BluetoothAdapter {
-    /** See {@link android.bluetooth.BluetoothAdapter#ACTION_REQUEST_ENABLE}. */
-    public static final String ACTION_REQUEST_ENABLE =
-            android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED}. */
-    public static final String ACTION_STATE_CHANGED =
-            android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#EXTRA_STATE}. */
-    public static final String EXTRA_STATE =
-            android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#STATE_OFF}. */
-    public static final int STATE_OFF =
-            android.bluetooth.BluetoothAdapter.STATE_OFF;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#STATE_ON}. */
-    public static final int STATE_ON =
-            android.bluetooth.BluetoothAdapter.STATE_ON;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}. */
-    public static final int STATE_TURNING_OFF =
-            android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
-
-    /** See {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_ON}. */
-    public static final int STATE_TURNING_ON =
-            android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
-
-    private final android.bluetooth.BluetoothAdapter mWrappedBluetoothAdapter;
-
-    private BluetoothAdapter(android.bluetooth.BluetoothAdapter bluetoothAdapter) {
-        mWrappedBluetoothAdapter = bluetoothAdapter;
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#disable()}. */
-    public boolean disable() {
-        return mWrappedBluetoothAdapter.disable();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#enable()}. */
-    public boolean enable() {
-        return mWrappedBluetoothAdapter.enable();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getBluetoothLeScanner}. */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    @Nullable
-    public BluetoothLeScanner getBluetoothLeScanner() {
-        return BluetoothLeScanner.wrap(mWrappedBluetoothAdapter.getBluetoothLeScanner());
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getBluetoothLeAdvertiser()}. */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    @Nullable
-    public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
-        return BluetoothLeAdvertiser.wrap(mWrappedBluetoothAdapter.getBluetoothLeAdvertiser());
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getBondedDevices()}. */
-    @Nullable
-    public Set<BluetoothDevice> getBondedDevices() {
-        Set<android.bluetooth.BluetoothDevice> bondedDevices =
-                mWrappedBluetoothAdapter.getBondedDevices();
-        if (bondedDevices == null) {
-            return null;
-        }
-        Set<BluetoothDevice> result = new HashSet<BluetoothDevice>();
-        for (android.bluetooth.BluetoothDevice device : bondedDevices) {
-            if (device == null) {
-                continue;
-            }
-            result.add(BluetoothDevice.wrap(device));
-        }
-        return Collections.unmodifiableSet(result);
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getRemoteDevice(byte[])}. */
-    public BluetoothDevice getRemoteDevice(byte[] address) {
-        return BluetoothDevice.wrap(mWrappedBluetoothAdapter.getRemoteDevice(address));
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getRemoteDevice(String)}. */
-    public BluetoothDevice getRemoteDevice(String address) {
-        return BluetoothDevice.wrap(mWrappedBluetoothAdapter.getRemoteDevice(address));
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#isEnabled()}. */
-    public boolean isEnabled() {
-        return mWrappedBluetoothAdapter.isEnabled();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#isDiscovering()}. */
-    public boolean isDiscovering() {
-        return mWrappedBluetoothAdapter.isDiscovering();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#startDiscovery()}. */
-    public boolean startDiscovery() {
-        return mWrappedBluetoothAdapter.startDiscovery();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#cancelDiscovery()}. */
-    public boolean cancelDiscovery() {
-        return mWrappedBluetoothAdapter.cancelDiscovery();
-    }
-
-    /** See {@link android.bluetooth.BluetoothAdapter#getDefaultAdapter()}. */
-    @Nullable
-    public static BluetoothAdapter getDefaultAdapter() {
-        android.bluetooth.BluetoothAdapter adapter =
-                android.bluetooth.BluetoothAdapter.getDefaultAdapter();
-        if (adapter == null) {
-            return null;
-        }
-        return new BluetoothAdapter(adapter);
-    }
-
-    /** Wraps a Bluetooth adapter. */
-    @Nullable
-    public static BluetoothAdapter wrap(
-            @Nullable android.bluetooth.BluetoothAdapter bluetoothAdapter) {
-        if (bluetoothAdapter == null) {
-            return null;
-        }
-        return new BluetoothAdapter(bluetoothAdapter);
-    }
-
-    /** Unwraps a Bluetooth adapter. */
-    public android.bluetooth.BluetoothAdapter unwrap() {
-        return mWrappedBluetoothAdapter;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
deleted file mode 100644
index 5b45f61..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothSocket;
-import android.content.Context;
-import android.os.ParcelUuid;
-
-import java.io.IOException;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.BluetoothDevice}.
- */
-@TargetApi(18)
-public class BluetoothDevice {
-    /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */
-    public static final int BOND_BONDED = android.bluetooth.BluetoothDevice.BOND_BONDED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDING}. */
-    public static final int BOND_BONDING = android.bluetooth.BluetoothDevice.BOND_BONDING;
-
-    /** See {@link android.bluetooth.BluetoothDevice#BOND_NONE}. */
-    public static final int BOND_NONE = android.bluetooth.BluetoothDevice.BOND_NONE;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED}. */
-    public static final String ACTION_ACL_CONNECTED =
-            android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED}. */
-    public static final String ACTION_ACL_DISCONNECT_REQUESTED =
-            android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}. */
-    public static final String ACTION_ACL_DISCONNECTED =
-            android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED}. */
-    public static final String ACTION_BOND_STATE_CHANGED =
-            android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED}. */
-    public static final String ACTION_CLASS_CHANGED =
-            android.bluetooth.BluetoothDevice.ACTION_CLASS_CHANGED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_FOUND}. */
-    public static final String ACTION_FOUND = android.bluetooth.BluetoothDevice.ACTION_FOUND;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED}. */
-    public static final String ACTION_NAME_CHANGED =
-            android.bluetooth.BluetoothDevice.ACTION_NAME_CHANGED;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST}. */
-    // API 19 only
-    public static final String ACTION_PAIRING_REQUEST =
-            "android.bluetooth.device.action.PAIRING_REQUEST";
-
-    /** See {@link android.bluetooth.BluetoothDevice#ACTION_UUID}. */
-    public static final String ACTION_UUID = android.bluetooth.BluetoothDevice.ACTION_UUID;
-
-    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_CLASSIC}. */
-    public static final int DEVICE_TYPE_CLASSIC =
-            android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC;
-
-    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_DUAL}. */
-    public static final int DEVICE_TYPE_DUAL = android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL;
-
-    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_LE}. */
-    public static final int DEVICE_TYPE_LE = android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
-
-    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_UNKNOWN}. */
-    public static final int DEVICE_TYPE_UNKNOWN =
-            android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNKNOWN;
-
-    /** See {@link android.bluetooth.BluetoothDevice#ERROR}. */
-    public static final int ERROR = android.bluetooth.BluetoothDevice.ERROR;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_BOND_STATE}. */
-    public static final String EXTRA_BOND_STATE =
-            android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_CLASS}. */
-    public static final String EXTRA_CLASS = android.bluetooth.BluetoothDevice.EXTRA_CLASS;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_DEVICE}. */
-    public static final String EXTRA_DEVICE = android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_NAME}. */
-    public static final String EXTRA_NAME = android.bluetooth.BluetoothDevice.EXTRA_NAME;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_KEY}. */
-    // API 19 only
-    public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_VARIANT}. */
-    // API 19 only
-    public static final String EXTRA_PAIRING_VARIANT =
-            "android.bluetooth.device.extra.PAIRING_VARIANT";
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PREVIOUS_BOND_STATE}. */
-    public static final String EXTRA_PREVIOUS_BOND_STATE =
-            android.bluetooth.BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_RSSI}. */
-    public static final String EXTRA_RSSI = android.bluetooth.BluetoothDevice.EXTRA_RSSI;
-
-    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_UUID}. */
-    public static final String EXTRA_UUID = android.bluetooth.BluetoothDevice.EXTRA_UUID;
-
-    /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}. */
-    // API 19 only
-    public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
-
-    /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PIN}. */
-    // API 19 only
-    public static final int PAIRING_VARIANT_PIN = 0;
-
-    private final android.bluetooth.BluetoothDevice mWrappedBluetoothDevice;
-
-    private BluetoothDevice(android.bluetooth.BluetoothDevice bluetoothDevice) {
-        mWrappedBluetoothDevice = bluetoothDevice;
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean,
-     * android.bluetooth.BluetoothGattCallback)}.
-     */
-    @Nullable(/* when bt service is not available */)
-    public BluetoothGattWrapper connectGatt(Context context, boolean autoConnect,
-            BluetoothGattCallback callback) {
-        android.bluetooth.BluetoothGatt gatt =
-                mWrappedBluetoothDevice.connectGatt(context, autoConnect, callback.unwrap());
-        if (gatt == null) {
-            return null;
-        }
-        return BluetoothGattWrapper.wrap(gatt);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean,
-     * android.bluetooth.BluetoothGattCallback, int)}.
-     */
-    @TargetApi(23)
-    @Nullable(/* when bt service is not available */)
-    public BluetoothGattWrapper connectGatt(Context context, boolean autoConnect,
-            BluetoothGattCallback callback, int transport) {
-        android.bluetooth.BluetoothGatt gatt =
-                mWrappedBluetoothDevice.connectGatt(
-                        context, autoConnect, callback.unwrap(), transport);
-        if (gatt == null) {
-            return null;
-        }
-        return BluetoothGattWrapper.wrap(gatt);
-    }
-
-
-    /**
-     * See {@link android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(UUID)}.
-     */
-    public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
-        return mWrappedBluetoothDevice.createRfcommSocketToServiceRecord(uuid);
-    }
-
-    /**
-     * See
-     * {@link android.bluetooth.BluetoothDevice#createInsecureRfcommSocketToServiceRecord(UUID)}.
-     */
-    public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
-        return mWrappedBluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid);
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#setPin(byte[])}. */
-    @TargetApi(19)
-    public boolean setPairingConfirmation(byte[] pin) {
-        return mWrappedBluetoothDevice.setPin(pin);
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#setPairingConfirmation(boolean)}. */
-    public boolean setPairingConfirmation(boolean confirm) {
-        return mWrappedBluetoothDevice.setPairingConfirmation(confirm);
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp()}. */
-    public boolean fetchUuidsWithSdp() {
-        return mWrappedBluetoothDevice.fetchUuidsWithSdp();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#createBond()}. */
-    public boolean createBond() {
-        return mWrappedBluetoothDevice.createBond();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getUuids()}. */
-    @Nullable(/* on error */)
-    public ParcelUuid[] getUuids() {
-        return mWrappedBluetoothDevice.getUuids();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getBondState()}. */
-    public int getBondState() {
-        return mWrappedBluetoothDevice.getBondState();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getAddress()}. */
-    public String getAddress() {
-        return mWrappedBluetoothDevice.getAddress();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getBluetoothClass()}. */
-    @Nullable(/* on error */)
-    public BluetoothClass getBluetoothClass() {
-        return mWrappedBluetoothDevice.getBluetoothClass();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getType()}. */
-    public int getType() {
-        return mWrappedBluetoothDevice.getType();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#getName()}. */
-    @Nullable(/* on error */)
-    public String getName() {
-        return mWrappedBluetoothDevice.getName();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#toString()}. */
-    @Override
-    public String toString() {
-        return mWrappedBluetoothDevice.toString();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#hashCode()}. */
-    @Override
-    public int hashCode() {
-        return mWrappedBluetoothDevice.hashCode();
-    }
-
-    /** See {@link android.bluetooth.BluetoothDevice#equals(Object)}. */
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (o ==  this) {
-            return true;
-        }
-        if (!(o instanceof BluetoothDevice)) {
-            return false;
-        }
-        return mWrappedBluetoothDevice.equals(((BluetoothDevice) o).unwrap());
-    }
-
-    /** Unwraps a Bluetooth device. */
-    public android.bluetooth.BluetoothDevice unwrap() {
-        return mWrappedBluetoothDevice;
-    }
-
-    /** Wraps a Bluetooth device. */
-    public static BluetoothDevice wrap(android.bluetooth.BluetoothDevice bluetoothDevice) {
-        return new BluetoothDevice(bluetoothDevice);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java
deleted file mode 100644
index d36cfa2..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-
-/**
- * Wrapper of {@link android.bluetooth.BluetoothGattCallback} that uses mockable objects.
- */
-public abstract class BluetoothGattCallback {
-
-    private final android.bluetooth.BluetoothGattCallback mWrappedBluetoothGattCallback =
-            new InternalBluetoothGattCallback();
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(
-     * android.bluetooth.BluetoothGatt, int, int)}
-     */
-    public void onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onServicesDiscovered(
-     * android.bluetooth.BluetoothGatt,int)}
-     */
-    public void onServicesDiscovered(BluetoothGattWrapper gatt, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicRead(
-     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)}
-     */
-    public void onCharacteristicRead(BluetoothGattWrapper gatt, BluetoothGattCharacteristic
-            characteristic, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(
-     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)}
-     */
-    public void onCharacteristicWrite(BluetoothGattWrapper gatt,
-            BluetoothGattCharacteristic characteristic, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorRead(
-     * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)}
-     */
-    public void onDescriptorRead(
-            BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(
-     * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)}
-     */
-    public void onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor,
-            int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onReadRemoteRssi(
-     * android.bluetooth.BluetoothGatt, int, int)}
-     */
-    public void onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattCallback#onReliableWriteCompleted(
-     * android.bluetooth.BluetoothGatt, int)}
-     */
-    public void onReliableWriteCompleted(BluetoothGattWrapper gatt, int status) {}
-
-    /**
-     * See
-     * {@link android.bluetooth.BluetoothGattCallback#onMtuChanged(android.bluetooth.BluetoothGatt,
-     * int, int)}
-     */
-    public void onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status) {}
-
-    /**
-     * See
-     * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicChanged(
-     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic)}
-     */
-    public void onCharacteristicChanged(BluetoothGattWrapper gatt,
-            BluetoothGattCharacteristic characteristic) {}
-
-    /** Unwraps a Bluetooth Gatt callback. */
-    public android.bluetooth.BluetoothGattCallback unwrap() {
-        return mWrappedBluetoothGattCallback;
-    }
-
-    /** Forward callback to testable instance. */
-    private class InternalBluetoothGattCallback extends android.bluetooth.BluetoothGattCallback {
-        @Override
-        public void onConnectionStateChange(android.bluetooth.BluetoothGatt gatt, int status,
-                int newState) {
-            BluetoothGattCallback.this.onConnectionStateChange(BluetoothGattWrapper.wrap(gatt),
-                    status, newState);
-        }
-
-        @Override
-        public void onServicesDiscovered(android.bluetooth.BluetoothGatt gatt, int status) {
-            BluetoothGattCallback.this.onServicesDiscovered(BluetoothGattWrapper.wrap(gatt),
-                    status);
-        }
-
-        @Override
-        public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
-                BluetoothGattCharacteristic characteristic, int status) {
-            BluetoothGattCallback.this.onCharacteristicRead(
-                    BluetoothGattWrapper.wrap(gatt), characteristic, status);
-        }
-
-        @Override
-        public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
-                BluetoothGattCharacteristic characteristic, int status) {
-            BluetoothGattCallback.this.onCharacteristicWrite(
-                    BluetoothGattWrapper.wrap(gatt), characteristic, status);
-        }
-
-        @Override
-        public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
-                BluetoothGattDescriptor descriptor, int status) {
-            BluetoothGattCallback.this.onDescriptorRead(
-                    BluetoothGattWrapper.wrap(gatt), descriptor, status);
-        }
-
-        @Override
-        public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
-                BluetoothGattDescriptor descriptor, int status) {
-            BluetoothGattCallback.this.onDescriptorWrite(
-                    BluetoothGattWrapper.wrap(gatt), descriptor, status);
-        }
-
-        @Override
-        public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt, int rssi, int status) {
-            BluetoothGattCallback.this.onReadRemoteRssi(BluetoothGattWrapper.wrap(gatt), rssi,
-                    status);
-        }
-
-        @Override
-        public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt, int status) {
-            BluetoothGattCallback.this.onReliableWriteCompleted(BluetoothGattWrapper.wrap(gatt),
-                    status);
-        }
-
-        @Override
-        public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status) {
-            BluetoothGattCallback.this.onMtuChanged(BluetoothGattWrapper.wrap(gatt), mtu, status);
-        }
-
-        @Override
-        public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
-                BluetoothGattCharacteristic characteristic) {
-            BluetoothGattCallback.this.onCharacteristicChanged(
-                    BluetoothGattWrapper.wrap(gatt), characteristic);
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
deleted file mode 100644
index 3f6f361..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattService;
-
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.BluetoothGattServer}.
- */
-public class BluetoothGattServer {
-
-    /** See {@link android.bluetooth.BluetoothGattServer#STATE_CONNECTED}. */
-    public static final int STATE_CONNECTED = android.bluetooth.BluetoothGattServer.STATE_CONNECTED;
-
-    /** See {@link android.bluetooth.BluetoothGattServer#STATE_DISCONNECTED}. */
-    public static final int STATE_DISCONNECTED =
-            android.bluetooth.BluetoothGattServer.STATE_DISCONNECTED;
-
-    private android.bluetooth.BluetoothGattServer mWrappedInstance;
-
-    private BluetoothGattServer(android.bluetooth.BluetoothGattServer instance) {
-        mWrappedInstance = instance;
-    }
-
-    /** Wraps a Bluetooth Gatt server. */
-    @Nullable
-    public static BluetoothGattServer wrap(
-            @Nullable android.bluetooth.BluetoothGattServer instance) {
-        if (instance == null) {
-            return null;
-        }
-        return new BluetoothGattServer(instance);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServer#connect(
-     * android.bluetooth.BluetoothDevice, boolean)}
-     */
-    public boolean connect(BluetoothDevice device, boolean autoConnect) {
-        return mWrappedInstance.connect(device.unwrap(), autoConnect);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGattServer#addService(BluetoothGattService)}. */
-    public boolean addService(BluetoothGattService service) {
-        return mWrappedInstance.addService(service);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGattServer#clearServices()}. */
-    public void clearServices() {
-        mWrappedInstance.clearServices();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGattServer#close()}. */
-    public void close() {
-        mWrappedInstance.close();
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServer#notifyCharacteristicChanged(
-     * android.bluetooth.BluetoothDevice, BluetoothGattCharacteristic, boolean)}.
-     */
-    public boolean notifyCharacteristicChanged(BluetoothDevice device,
-            BluetoothGattCharacteristic characteristic, boolean confirm) {
-        return mWrappedInstance.notifyCharacteristicChanged(
-                device.unwrap(), characteristic, confirm);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServer#sendResponse(
-     * android.bluetooth.BluetoothDevice, int, int, int, byte[])}.
-     */
-    public void sendResponse(BluetoothDevice device, int requestId, int status, int offset,
-            @Nullable byte[] value) {
-        mWrappedInstance.sendResponse(device.unwrap(), requestId, status, offset, value);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServer#cancelConnection(
-     * android.bluetooth.BluetoothDevice)}.
-     */
-    public void cancelConnection(BluetoothDevice device) {
-        mWrappedInstance.cancelConnection(device.unwrap());
-    }
-
-    /** See {@link android.bluetooth.BluetoothGattServer#getService(UUID uuid)}. */
-    public BluetoothGattService getService(UUID uuid) {
-        return mWrappedInstance.getService(uuid);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java
deleted file mode 100644
index 875dad5..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-
-/**
- * Wrapper of {@link android.bluetooth.BluetoothGattServerCallback} that uses mockable objects.
- */
-public abstract class BluetoothGattServerCallback {
-
-    private final android.bluetooth.BluetoothGattServerCallback mWrappedInstance =
-            new InternalBluetoothGattServerCallback();
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onCharacteristicReadRequest(
-     * android.bluetooth.BluetoothDevice, int, int, BluetoothGattCharacteristic)}
-     */
-    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
-            int offset, BluetoothGattCharacteristic characteristic) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onCharacteristicWriteRequest(
-     * android.bluetooth.BluetoothDevice, int, BluetoothGattCharacteristic, boolean, boolean, int,
-     * byte[])}
-     */
-    public void onCharacteristicWriteRequest(BluetoothDevice device,
-            int requestId,
-            BluetoothGattCharacteristic characteristic,
-            boolean preparedWrite,
-            boolean responseNeeded,
-            int offset,
-            byte[] value) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onConnectionStateChange(
-     * android.bluetooth.BluetoothDevice, int, int)}
-     */
-    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onDescriptorReadRequest(
-     * android.bluetooth.BluetoothDevice, int, int, BluetoothGattDescriptor)}
-     */
-    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
-            BluetoothGattDescriptor descriptor) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onDescriptorWriteRequest(
-     * android.bluetooth.BluetoothDevice, int, BluetoothGattDescriptor, boolean, boolean, int,
-     * byte[])}
-     */
-    public void onDescriptorWriteRequest(BluetoothDevice device,
-            int requestId,
-            BluetoothGattDescriptor descriptor,
-            boolean preparedWrite,
-            boolean responseNeeded,
-            int offset,
-            byte[] value) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onExecuteWrite(
-     * android.bluetooth.BluetoothDevice, int, boolean)}
-     */
-    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged(
-     * android.bluetooth.BluetoothDevice, int)}
-     */
-    public void onMtuChanged(BluetoothDevice device, int mtu) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onNotificationSent(
-     * android.bluetooth.BluetoothDevice, int)}
-     */
-    public void onNotificationSent(BluetoothDevice device, int status) {}
-
-    /**
-     * See {@link android.bluetooth.BluetoothGattServerCallback#onServiceAdded(int,
-     * BluetoothGattService)}
-     */
-    public void onServiceAdded(int status, BluetoothGattService service) {}
-
-    /** Unwraps a Bluetooth Gatt server callback. */
-    public android.bluetooth.BluetoothGattServerCallback unwrap() {
-        return mWrappedInstance;
-    }
-
-    /** Forward callback to testable instance. */
-    private class InternalBluetoothGattServerCallback extends
-            android.bluetooth.BluetoothGattServerCallback {
-        @Override
-        public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice device,
-                int requestId, int offset, BluetoothGattCharacteristic characteristic) {
-            BluetoothGattServerCallback.this.onCharacteristicReadRequest(
-                    BluetoothDevice.wrap(device), requestId, offset, characteristic);
-        }
-
-        @Override
-        public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice device,
-                int requestId,
-                BluetoothGattCharacteristic characteristic,
-                boolean preparedWrite,
-                boolean responseNeeded,
-                int offset,
-                byte[] value) {
-            BluetoothGattServerCallback.this.onCharacteristicWriteRequest(
-                    BluetoothDevice.wrap(device),
-                    requestId,
-                    characteristic,
-                    preparedWrite,
-                    responseNeeded,
-                    offset,
-                    value);
-        }
-
-        @Override
-        public void onConnectionStateChange(android.bluetooth.BluetoothDevice device, int status,
-                int newState) {
-            BluetoothGattServerCallback.this.onConnectionStateChange(
-                    BluetoothDevice.wrap(device), status, newState);
-        }
-
-        @Override
-        public void onDescriptorReadRequest(android.bluetooth.BluetoothDevice device, int requestId,
-                int offset, BluetoothGattDescriptor descriptor) {
-            BluetoothGattServerCallback.this.onDescriptorReadRequest(BluetoothDevice.wrap(device),
-                    requestId, offset, descriptor);
-        }
-
-        @Override
-        public void onDescriptorWriteRequest(android.bluetooth.BluetoothDevice device,
-                int requestId,
-                BluetoothGattDescriptor descriptor,
-                boolean preparedWrite,
-                boolean responseNeeded,
-                int offset,
-                byte[] value) {
-            BluetoothGattServerCallback.this.onDescriptorWriteRequest(BluetoothDevice.wrap(device),
-                    requestId,
-                    descriptor,
-                    preparedWrite,
-                    responseNeeded,
-                    offset,
-                    value);
-        }
-
-        @Override
-        public void onExecuteWrite(android.bluetooth.BluetoothDevice device, int requestId,
-                boolean execute) {
-            BluetoothGattServerCallback.this.onExecuteWrite(BluetoothDevice.wrap(device), requestId,
-                    execute);
-        }
-
-        @Override
-        public void onMtuChanged(android.bluetooth.BluetoothDevice device, int mtu) {
-            BluetoothGattServerCallback.this.onMtuChanged(BluetoothDevice.wrap(device), mtu);
-        }
-
-        @Override
-        public void onNotificationSent(android.bluetooth.BluetoothDevice device, int status) {
-            BluetoothGattServerCallback.this.onNotificationSent(
-                    BluetoothDevice.wrap(device), status);
-        }
-
-        @Override
-        public void onServiceAdded(int status, BluetoothGattService service) {
-            BluetoothGattServerCallback.this.onServiceAdded(status, service);
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java
deleted file mode 100644
index 453ee5d..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.os.Build;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
-/** Mockable wrapper of {@link android.bluetooth.BluetoothGatt}. */
-@TargetApi(Build.VERSION_CODES.TIRAMISU)
-public class BluetoothGattWrapper {
-    private final android.bluetooth.BluetoothGatt mWrappedBluetoothGatt;
-
-    private BluetoothGattWrapper(android.bluetooth.BluetoothGatt bluetoothGatt) {
-        mWrappedBluetoothGatt = bluetoothGatt;
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#getDevice()}. */
-    public BluetoothDevice getDevice() {
-        return BluetoothDevice.wrap(mWrappedBluetoothGatt.getDevice());
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#getServices()}. */
-    public List<BluetoothGattService> getServices() {
-        return mWrappedBluetoothGatt.getServices();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#getService(UUID)}. */
-    @Nullable(/* null if service is not found */)
-    public BluetoothGattService getService(UUID uuid) {
-        return mWrappedBluetoothGatt.getService(uuid);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#discoverServices()}. */
-    public boolean discoverServices() {
-        return mWrappedBluetoothGatt.discoverServices();
-    }
-
-    /**
-     * Hidden method. Clears the internal cache and forces a refresh of the services from the remote
-     * device.
-     */
-    // TODO(b/201300471): remove refresh call using reflection.
-    public boolean refresh() {
-        try {
-            Method refreshMethod = android.bluetooth.BluetoothGatt.class.getMethod("refresh");
-            return (Boolean) refreshMethod.invoke(mWrappedBluetoothGatt);
-        } catch (NoSuchMethodException
-            | IllegalAccessException
-            | IllegalArgumentException
-            | InvocationTargetException e) {
-            return false;
-        }
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)}.
-     */
-    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
-        return mWrappedBluetoothGatt.readCharacteristic(characteristic);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic,
-     * byte[], int)} .
-     */
-    public int writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value,
-            int writeType) {
-        return mWrappedBluetoothGatt.writeCharacteristic(characteristic, value, writeType);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */
-    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
-        return mWrappedBluetoothGatt.readDescriptor(descriptor);
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothGatt#writeDescriptor(BluetoothGattDescriptor,
-     * byte[])}.
-     */
-    public int writeDescriptor(BluetoothGattDescriptor descriptor, byte[] value) {
-        return mWrappedBluetoothGatt.writeDescriptor(descriptor, value);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#readRemoteRssi()}. */
-    public boolean readRemoteRssi() {
-        return mWrappedBluetoothGatt.readRemoteRssi();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. */
-    public boolean requestConnectionPriority(int connectionPriority) {
-        return mWrappedBluetoothGatt.requestConnectionPriority(connectionPriority);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}. */
-    public boolean requestMtu(int mtu) {
-        return mWrappedBluetoothGatt.requestMtu(mtu);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#setCharacteristicNotification}. */
-    public boolean setCharacteristicNotification(
-            BluetoothGattCharacteristic characteristic, boolean enable) {
-        return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable);
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#disconnect()}. */
-    public void disconnect() {
-        mWrappedBluetoothGatt.disconnect();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#close()}. */
-    public void close() {
-        mWrappedBluetoothGatt.close();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#hashCode()}. */
-    @Override
-    public int hashCode() {
-        return mWrappedBluetoothGatt.hashCode();
-    }
-
-    /** See {@link android.bluetooth.BluetoothGatt#equals(Object)}. */
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof BluetoothGattWrapper)) {
-            return false;
-        }
-        return mWrappedBluetoothGatt.equals(((BluetoothGattWrapper) o).unwrap());
-    }
-
-    /** Unwraps a Bluetooth Gatt instance. */
-    public android.bluetooth.BluetoothGatt unwrap() {
-        return mWrappedBluetoothGatt;
-    }
-
-    /** Wraps a Bluetooth Gatt instance. */
-    public static BluetoothGattWrapper wrap(android.bluetooth.BluetoothGatt gatt) {
-        return new BluetoothGattWrapper(gatt);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
deleted file mode 100644
index 6fe4432..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth.le;
-
-import android.annotation.TargetApi;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.os.Build;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.le.BluetoothLeAdvertiser}.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class BluetoothLeAdvertiser {
-
-    private final android.bluetooth.le.BluetoothLeAdvertiser mWrappedInstance;
-
-    private BluetoothLeAdvertiser(
-            android.bluetooth.le.BluetoothLeAdvertiser bluetoothLeAdvertiser) {
-        mWrappedInstance = bluetoothLeAdvertiser;
-    }
-
-    /**
-     * See {@link android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(AdvertiseSettings,
-     * AdvertiseData, AdvertiseCallback)}.
-     */
-    public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData,
-            AdvertiseCallback callback) {
-        mWrappedInstance.startAdvertising(settings, advertiseData, callback);
-    }
-
-    /**
-     * See {@link android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(AdvertiseSettings,
-     * AdvertiseData, AdvertiseData, AdvertiseCallback)}.
-     */
-    public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData,
-            AdvertiseData scanResponse, AdvertiseCallback callback) {
-        mWrappedInstance.startAdvertising(settings, advertiseData, scanResponse, callback);
-    }
-
-    /**
-     * See {@link android.bluetooth.le.BluetoothLeAdvertiser#stopAdvertising(AdvertiseCallback)}.
-     */
-    public void stopAdvertising(AdvertiseCallback callback) {
-        mWrappedInstance.stopAdvertising(callback);
-    }
-
-    /** Wraps a Bluetooth LE advertiser. */
-    @Nullable
-    public static BluetoothLeAdvertiser wrap(
-            @Nullable android.bluetooth.le.BluetoothLeAdvertiser bluetoothLeAdvertiser) {
-        if (bluetoothLeAdvertiser == null) {
-            return null;
-        }
-        return new BluetoothLeAdvertiser(bluetoothLeAdvertiser);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
deleted file mode 100644
index 8a13abe..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth.le;
-
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.os.Build;
-
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.le.BluetoothLeScanner}.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class BluetoothLeScanner {
-
-    private final android.bluetooth.le.BluetoothLeScanner mWrappedBluetoothLeScanner;
-
-    private BluetoothLeScanner(android.bluetooth.le.BluetoothLeScanner bluetoothLeScanner) {
-        mWrappedBluetoothLeScanner = bluetoothLeScanner;
-    }
-
-    /**
-     * See {@link android.bluetooth.le.BluetoothLeScanner#startScan(List, ScanSettings,
-     * android.bluetooth.le.ScanCallback)}.
-     */
-    public void startScan(List<ScanFilter> filters, ScanSettings settings,
-            ScanCallback callback) {
-        mWrappedBluetoothLeScanner.startScan(filters, settings, callback.unwrap());
-    }
-
-    /**
-     * See {@link android.bluetooth.le.BluetoothLeScanner#startScan(List, ScanSettings,
-     * PendingIntent)}.
-     */
-    public void startScan(
-            List<ScanFilter> filters, ScanSettings settings, PendingIntent callbackIntent) {
-        mWrappedBluetoothLeScanner.startScan(filters, settings, callbackIntent);
-    }
-
-    /**
-     * See {@link
-     * android.bluetooth.le.BluetoothLeScanner#startScan(android.bluetooth.le.ScanCallback)}.
-     */
-    public void startScan(ScanCallback callback) {
-        mWrappedBluetoothLeScanner.startScan(callback.unwrap());
-    }
-
-    /**
-     * See
-     * {@link android.bluetooth.le.BluetoothLeScanner#stopScan(android.bluetooth.le.ScanCallback)}.
-     */
-    public void stopScan(ScanCallback callback) {
-        mWrappedBluetoothLeScanner.stopScan(callback.unwrap());
-    }
-
-    /** See {@link android.bluetooth.le.BluetoothLeScanner#stopScan(PendingIntent)}. */
-    public void stopScan(PendingIntent callbackIntent) {
-        mWrappedBluetoothLeScanner.stopScan(callbackIntent);
-    }
-
-    /** Wraps a Bluetooth LE scanner. */
-    @Nullable
-    public static BluetoothLeScanner wrap(
-            @Nullable android.bluetooth.le.BluetoothLeScanner bluetoothLeScanner) {
-        if (bluetoothLeScanner == null) {
-            return null;
-        }
-        return new BluetoothLeScanner(bluetoothLeScanner);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java
deleted file mode 100644
index 70926a7..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth.le;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Wrapper of {@link android.bluetooth.le.ScanCallback} that uses mockable objects.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public abstract class ScanCallback {
-
-    /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_ALREADY_STARTED} */
-    public static final int SCAN_FAILED_ALREADY_STARTED =
-            android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
-
-    /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_APPLICATION_REGISTRATION_FAILED} */
-    public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED =
-            android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
-
-    /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_FEATURE_UNSUPPORTED} */
-    public static final int SCAN_FAILED_FEATURE_UNSUPPORTED =
-            android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
-
-    /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_INTERNAL_ERROR} */
-    public static final int SCAN_FAILED_INTERNAL_ERROR =
-            android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
-
-    private final android.bluetooth.le.ScanCallback mWrappedScanCallback =
-            new InternalScanCallback();
-
-    /**
-     * See {@link android.bluetooth.le.ScanCallback#onScanFailed(int)}
-     */
-    public void onScanFailed(int errorCode) {}
-
-    /**
-     * See
-     * {@link android.bluetooth.le.ScanCallback#onScanResult(int, android.bluetooth.le.ScanResult)}.
-     */
-    public void onScanResult(int callbackType, ScanResult result) {}
-
-    /**
-     * See {@link
-     * android.bluetooth.le.ScanCallback#onBatchScanResult(List<android.bluetooth.le.ScanResult>)}.
-     */
-    public void onBatchScanResults(List<ScanResult> results) {}
-
-    /** Unwraps scan callback. */
-    public android.bluetooth.le.ScanCallback unwrap() {
-        return mWrappedScanCallback;
-    }
-
-    /** Forward callback to testable instance. */
-    private class InternalScanCallback extends android.bluetooth.le.ScanCallback {
-        @Override
-        public void onScanFailed(int errorCode) {
-            ScanCallback.this.onScanFailed(errorCode);
-        }
-
-        @Override
-        public void onScanResult(int callbackType, android.bluetooth.le.ScanResult result) {
-            ScanCallback.this.onScanResult(callbackType, ScanResult.wrap(result));
-        }
-
-        @Override
-        public void onBatchScanResults(List<android.bluetooth.le.ScanResult> results) {
-            List<ScanResult> wrappedScanResults = new ArrayList<>();
-            for (android.bluetooth.le.ScanResult result : results) {
-                wrappedScanResults.add(ScanResult.wrap(result));
-            }
-            ScanCallback.this.onBatchScanResults(wrappedScanResults);
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java
deleted file mode 100644
index 1a6b7b3..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth.le;
-
-import android.annotation.TargetApi;
-import android.bluetooth.le.ScanRecord;
-import android.os.Build;
-
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-
-import javax.annotation.Nullable;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.le.ScanResult}.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class ScanResult {
-
-    private final android.bluetooth.le.ScanResult mWrappedScanResult;
-
-    private ScanResult(android.bluetooth.le.ScanResult scanResult) {
-        mWrappedScanResult = scanResult;
-    }
-
-    /** See {@link android.bluetooth.le.ScanResult#getScanRecord()}. */
-    @Nullable
-    public ScanRecord getScanRecord() {
-        return mWrappedScanResult.getScanRecord();
-    }
-
-    /** See {@link android.bluetooth.le.ScanResult#getRssi()}. */
-    public int getRssi() {
-        return mWrappedScanResult.getRssi();
-    }
-
-    /** See {@link android.bluetooth.le.ScanResult#getTimestampNanos()}. */
-    public long getTimestampNanos() {
-        return mWrappedScanResult.getTimestampNanos();
-    }
-
-    /** See {@link android.bluetooth.le.ScanResult#getDevice()}. */
-    public BluetoothDevice getDevice() {
-        return BluetoothDevice.wrap(mWrappedScanResult.getDevice());
-    }
-
-    /** Creates a wrapper of scan result. */
-    public static ScanResult wrap(android.bluetooth.le.ScanResult scanResult) {
-        return new ScanResult(scanResult);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java
deleted file mode 100644
index bb51920..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.util;
-
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-
-import javax.annotation.Nullable;
-
-/**
- * Utils for Gatt profile.
- */
-public class BluetoothGattUtils {
-
-    /**
-     * Returns a string message for a BluetoothGatt status codes.
-     */
-    public static String getMessageForStatusCode(int statusCode) {
-        switch (statusCode) {
-            case BluetoothGatt.GATT_SUCCESS:
-                return "GATT_SUCCESS";
-            case BluetoothGatt.GATT_FAILURE:
-                return "GATT_FAILURE";
-            case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
-                return "GATT_INSUFFICIENT_AUTHENTICATION";
-            case BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION:
-                return "GATT_INSUFFICIENT_AUTHORIZATION";
-            case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION:
-                return "GATT_INSUFFICIENT_ENCRYPTION";
-            case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
-                return "GATT_INVALID_ATTRIBUTE_LENGTH";
-            case BluetoothGatt.GATT_INVALID_OFFSET:
-                return "GATT_INVALID_OFFSET";
-            case BluetoothGatt.GATT_READ_NOT_PERMITTED:
-                return "GATT_READ_NOT_PERMITTED";
-            case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
-                return "GATT_REQUEST_NOT_SUPPORTED";
-            case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
-                return "GATT_WRITE_NOT_PERMITTED";
-            case BluetoothGatt.GATT_CONNECTION_CONGESTED:
-                return "GATT_CONNECTION_CONGESTED";
-            default:
-                return "Unknown error code";
-        }
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattDescriptor}. */
-    public static String toString(@Nullable BluetoothGattDescriptor descriptor) {
-        if (descriptor == null) {
-            return "null descriptor";
-        }
-        return String.format("descriptor %s on %s",
-                descriptor.getUuid(),
-                toString(descriptor.getCharacteristic()));
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattCharacteristic}. */
-    public static String toString(@Nullable BluetoothGattCharacteristic characteristic) {
-        if (characteristic == null) {
-            return "null characteristic";
-        }
-        return String.format("characteristic %s on %s",
-                characteristic.getUuid(),
-                toString(characteristic.getService()));
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattService}. */
-    public static String toString(@Nullable BluetoothGattService service) {
-        if (service == null) {
-            return "null service";
-        }
-        return String.format("service %s", service.getUuid());
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java
deleted file mode 100644
index fecf483..0000000
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.util;
-
-import android.bluetooth.BluetoothGatt;
-import android.util.Log;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.testability.NonnullProvider;
-import com.android.server.nearby.common.bluetooth.testability.TimeProvider;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.annotation.Nullable;
-
-/**
- * Scheduler to coordinate parallel bluetooth operations.
- */
-public class BluetoothOperationExecutor {
-
-    private static final String TAG = BluetoothOperationExecutor.class.getSimpleName();
-
-    /**
-     * Special value to indicate that the result is null (since {@link BlockingQueue} doesn't allow
-     * null elements).
-     */
-    private static final Object NULL_RESULT = new Object();
-
-    /**
-     * Special value to indicate that there should be no timeout on the operation.
-     */
-    private static final long NO_TIMEOUT = -1;
-
-    private final NonnullProvider<BlockingQueue<Object>> mBlockingQueueProvider;
-    private final TimeProvider mTimeProvider;
-    @VisibleForTesting
-    final Map<Operation<?>, Queue<Object>> mOperationResultQueues = new HashMap<>();
-    private final Semaphore mOperationSemaphore;
-
-    /**
-     * New instance that limits concurrent operations to maxConcurrentOperations.
-     */
-    public BluetoothOperationExecutor(int maxConcurrentOperations) {
-        this(
-                new Semaphore(maxConcurrentOperations, true),
-                new TimeProvider(),
-                new NonnullProvider<BlockingQueue<Object>>() {
-                    @Override
-                    public BlockingQueue<Object> get() {
-                        return new LinkedBlockingDeque<Object>();
-                    }
-                });
-    }
-
-    /**
-     * Constructor for unit tests.
-     */
-    @VisibleForTesting
-    BluetoothOperationExecutor(Semaphore operationSemaphore,
-            TimeProvider timeProvider,
-            NonnullProvider<BlockingQueue<Object>> blockingQueueProvider) {
-        mOperationSemaphore = operationSemaphore;
-        mTimeProvider = timeProvider;
-        mBlockingQueueProvider = blockingQueueProvider;
-    }
-
-    /**
-     * Executes the operation and waits for its completion.
-     */
-    @Nullable
-    public <T> T execute(Operation<T> operation) throws BluetoothException {
-        return getResult(schedule(operation));
-    }
-
-    /**
-     * Executes the operation and waits for its completion and returns a non-null result.
-     */
-    public <T> T executeNonnull(Operation<T> operation) throws BluetoothException {
-        T result = getResult(schedule(operation));
-        if (result == null) {
-            throw new BluetoothException(
-                    String.format(Locale.US, "Operation %s returned a null result.", operation));
-        }
-        return result;
-    }
-
-    /**
-     * Executes the operation and waits for its completion with a timeout.
-     */
-    @Nullable
-    public <T> T execute(Operation<T> bluetoothOperation, long timeoutMillis)
-            throws BluetoothException, BluetoothOperationTimeoutException {
-        return getResult(schedule(bluetoothOperation), timeoutMillis);
-    }
-
-    /**
-     * Executes the operation and waits for its completion with a timeout and returns a non-null
-     * result.
-     */
-    public <T> T executeNonnull(Operation<T> bluetoothOperation, long timeoutMillis)
-            throws BluetoothException {
-        T result = getResult(schedule(bluetoothOperation), timeoutMillis);
-        if (result == null) {
-            throw new BluetoothException(
-                    String.format(Locale.US, "Operation %s returned a null result.",
-                            bluetoothOperation));
-        }
-        return result;
-    }
-
-    /**
-     * Schedules an operation and returns a {@link Future} that waits on operation completion and
-     * gets its result.
-     */
-    public <T> Future<T> schedule(Operation<T> bluetoothOperation) {
-        BlockingQueue<Object> resultQueue = mBlockingQueueProvider.get();
-        mOperationResultQueues.put(bluetoothOperation, resultQueue);
-
-        boolean semaphoreAcquired = mOperationSemaphore.tryAcquire();
-        Log.d(TAG, String.format(Locale.US,
-                "Scheduling operation %s; %d permits available; Semaphore acquired: %b",
-                bluetoothOperation,
-                mOperationSemaphore.availablePermits(),
-                semaphoreAcquired));
-
-        if (semaphoreAcquired) {
-            bluetoothOperation.execute(this);
-        }
-        return new BluetoothOperationFuture<T>(resultQueue, bluetoothOperation, semaphoreAcquired);
-    }
-
-    /**
-     * Notifies that this operation has completed with success.
-     */
-    public void notifySuccess(Operation<Void> bluetoothOperation) {
-        postResult(bluetoothOperation, null);
-    }
-
-    /**
-     * Notifies that this operation has completed with success and with a result.
-     */
-    public <T> void notifySuccess(Operation<T> bluetoothOperation, T result) {
-        postResult(bluetoothOperation, result);
-    }
-
-    /**
-     * Notifies that this operation has completed with the given BluetoothGatt status code (which
-     * may indicate success or failure).
-     */
-    public void notifyCompletion(Operation<Void> bluetoothOperation, int status) {
-        notifyCompletion(bluetoothOperation, status, null);
-    }
-
-    /**
-     * Notifies that this operation has completed with the given BluetoothGatt status code (which
-     * may indicate success or failure) and with a result.
-     */
-    public <T> void notifyCompletion(Operation<T> bluetoothOperation, int status,
-            @Nullable T result) {
-        if (status != BluetoothGatt.GATT_SUCCESS) {
-            notifyFailure(bluetoothOperation, new BluetoothGattException(
-                    String.format(Locale.US,
-                            "Operation %s failed: %d - %s.", bluetoothOperation, status,
-                            BluetoothGattUtils.getMessageForStatusCode(status)),
-                    status));
-            return;
-        }
-        postResult(bluetoothOperation, result);
-    }
-
-    /**
-     * Notifies that this operation has completed with failure.
-     */
-    public void notifyFailure(Operation<?> bluetoothOperation, BluetoothException exception) {
-        postResult(bluetoothOperation, exception);
-    }
-
-    private void postResult(Operation<?> bluetoothOperation, @Nullable Object result) {
-        Queue<Object> resultQueue = mOperationResultQueues.get(bluetoothOperation);
-        if (resultQueue == null) {
-            Log.e(TAG, String.format(Locale.US,
-                    "Receive completion for unexpected operation: %s.", bluetoothOperation));
-            return;
-        }
-        resultQueue.add(result == null ? NULL_RESULT : result);
-        mOperationResultQueues.remove(bluetoothOperation);
-        mOperationSemaphore.release();
-        Log.d(TAG, String.format(Locale.US,
-                "Released semaphore for operation %s. There are %d permits left",
-                bluetoothOperation, mOperationSemaphore.availablePermits()));
-    }
-
-    /**
-     * Waits for all future on the list to complete, ignoring the results.
-     */
-    public <T> void waitFor(List<Future<T>> futures) throws BluetoothException {
-        for (Future<T> future : futures) {
-            if (future == null) {
-                continue;
-            }
-            getResult(future);
-        }
-    }
-
-    /**
-     * Waits with timeout for all future on the list to complete, ignoring the results.
-     */
-    public <T> void waitFor(List<Future<T>> futures, long timeoutMillis)
-            throws BluetoothException {
-        long startTime = mTimeProvider.getTimeMillis();
-        for (Future<T> future : futures) {
-            if (future == null) {
-                continue;
-            }
-            getResult(future,
-                    timeoutMillis - (mTimeProvider.getTimeMillis() - startTime));
-        }
-    }
-
-    /**
-     * Waits for a future to complete and returns the result.
-     */
-    @Nullable
-    public static <T> T getResult(Future<T> future) throws BluetoothException {
-        return getResultInternal(future, NO_TIMEOUT);
-    }
-
-    /**
-     * Waits for a future to complete and returns the result with timeout.
-     */
-    @Nullable
-    public static <T> T getResult(Future<T> future, long timeoutMillis) throws BluetoothException {
-        return getResultInternal(future, Math.max(0, timeoutMillis));
-    }
-
-    @Nullable
-    private static <T> T getResultInternal(Future<T> future, long timeoutMillis)
-            throws BluetoothException {
-        try {
-            if (timeoutMillis == NO_TIMEOUT) {
-                return future.get();
-            } else {
-                return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
-            }
-        } catch (InterruptedException e) {
-            try {
-                boolean cancelSuccess = future.cancel(true);
-                if (!cancelSuccess && future.isDone()) {
-                    // Operation has succeeded before we send cancel to it.
-                    return getResultInternal(future, NO_TIMEOUT);
-                }
-            } finally {
-                // Re-interrupt the thread last since we're recursively calling getResultInternal.
-                // We know the future is done, so there's no need to be interrupted while we call.
-                Thread.currentThread().interrupt();
-            }
-            throw new BluetoothException("Wait interrupted");
-        } catch (ExecutionException e) {
-            Throwable cause = e.getCause();
-            if (cause instanceof BluetoothException) {
-                throw (BluetoothException) cause;
-            }
-            throw new RuntimeException(e);
-        } catch (TimeoutException e) {
-            boolean cancelSuccess = future.cancel(true);
-            if (!cancelSuccess && future.isDone()) {
-                // Operation has succeeded before we send cancel to it.
-                return getResultInternal(future, NO_TIMEOUT);
-            }
-            throw new BluetoothOperationTimeoutException(
-                    String.format(Locale.US, "Wait timed out after %s ms.", timeoutMillis), e);
-        }
-    }
-
-    /**
-     * Asynchronous bluetooth operation to schedule.
-     *
-     * <p>An instance that doesn't implemented run() can be used to notify operation result.
-     *
-     * @param <T> Type of provided instance.
-     */
-    public static class Operation<T> {
-
-        private Object[] mElements;
-
-        public Operation(Object... elements) {
-            mElements = elements;
-        }
-
-        /**
-         * Executes operation using executor.
-         */
-        public void execute(BluetoothOperationExecutor executor) {
-            try {
-                run();
-            } catch (BluetoothException e) {
-                executor.postResult(this, e);
-            }
-        }
-
-        /**
-         * Run function. Not supported.
-         */
-        @SuppressWarnings("unused")
-        public void run() throws BluetoothException {
-            throw new RuntimeException("Not implemented");
-        }
-
-        /**
-         * Try to cancel operation when a timeout occurs.
-         */
-        public void cancel() {
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (!Operation.class.isInstance(o)) {
-                return false;
-            }
-            Operation<?> other = (Operation<?>) o;
-            return Arrays.equals(mElements, other.mElements);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(mElements);
-        }
-
-        @Override
-        public String toString() {
-            return Joiner.on('-').join(mElements);
-        }
-    }
-
-    /**
-     * Synchronous bluetooth operation to schedule.
-     *
-     * @param <T> Type of provided instance.
-     */
-    public static class SynchronousOperation<T> extends Operation<T> {
-
-        public SynchronousOperation(Object... elements) {
-            super(elements);
-        }
-
-        @Override
-        public void execute(BluetoothOperationExecutor executor) {
-            try {
-                Object result = call();
-                if (result == null) {
-                    result = NULL_RESULT;
-                }
-                executor.postResult(this, result);
-            } catch (BluetoothException e) {
-                executor.postResult(this, e);
-            }
-        }
-
-        /**
-         * Call function. Not supported.
-         */
-        @SuppressWarnings("unused")
-        @Nullable
-        public T call() throws BluetoothException {
-            throw new RuntimeException("Not implemented");
-        }
-    }
-
-    /**
-     * {@link Future} to wait / get result of an operation.
-     *
-     * <li>Waits for operation to complete
-     * <li>Handles timeouts if needed
-     * <li>Queues identical Bluetooth operations
-     * <li>Unwraps Exceptions and null values
-     */
-    private class BluetoothOperationFuture<T> implements Future<T> {
-
-        private final Object mLock = new Object();
-
-        /**
-         * Queue that will be used to store the result. It should normally contains one element
-         * maximum, but using a queue avoid some race conditions.
-         */
-        private final BlockingQueue<Object> mResultQueue;
-        private final Operation<T> mBluetoothOperation;
-        private final boolean mOperationExecuted;
-        private boolean mIsCancelled = false;
-        private boolean mIsDone = false;
-
-        BluetoothOperationFuture(BlockingQueue<Object> resultQueue,
-                Operation<T> bluetoothOperation, boolean operationExecuted) {
-            mResultQueue = resultQueue;
-            mBluetoothOperation = bluetoothOperation;
-            mOperationExecuted = operationExecuted;
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning) {
-            synchronized (mLock) {
-                if (mIsDone) {
-                    return false;
-                }
-                if (mIsCancelled) {
-                    return true;
-                }
-                mBluetoothOperation.cancel();
-                mIsCancelled = true;
-                notifyFailure(mBluetoothOperation, new BluetoothException("Operation cancelled."));
-                return true;
-            }
-        }
-
-        @Override
-        public boolean isCancelled() {
-            synchronized (mLock) {
-                return mIsCancelled;
-            }
-        }
-
-        @Override
-        public boolean isDone() {
-            synchronized (mLock) {
-                return mIsDone;
-            }
-        }
-
-        @Override
-        @Nullable
-        public T get() throws InterruptedException, ExecutionException {
-            try {
-                return getInternal(NO_TIMEOUT, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                throw new RuntimeException(e); // This is not supposed to be thrown
-            }
-        }
-
-        @Override
-        @Nullable
-        public T get(long timeoutMillis, TimeUnit unit)
-                throws InterruptedException, ExecutionException, TimeoutException {
-            return getInternal(Math.max(0, timeoutMillis), unit);
-        }
-
-        @SuppressWarnings("unchecked")
-        @Nullable
-        private T getInternal(long timeoutMillis, TimeUnit unit)
-                throws ExecutionException, InterruptedException, TimeoutException {
-            // Prevent parallel executions of this method.
-            long startTime = mTimeProvider.getTimeMillis();
-            synchronized (this) {
-                synchronized (mLock) {
-                    if (mIsDone) {
-                        throw new ExecutionException(
-                                new BluetoothException("get() called twice..."));
-                    }
-                }
-                if (!mOperationExecuted) {
-                    if (timeoutMillis == NO_TIMEOUT) {
-                        mOperationSemaphore.acquire();
-                    } else {
-                        if (!mOperationSemaphore.tryAcquire(timeoutMillis
-                                - (mTimeProvider.getTimeMillis() - startTime), unit)) {
-                            throw new TimeoutException(String.format(Locale.US,
-                                    "A timeout occurred when processing %s after %s %s.",
-                                    mBluetoothOperation, timeoutMillis, unit));
-                        }
-                    }
-                    mBluetoothOperation.execute(BluetoothOperationExecutor.this);
-                }
-                Object result;
-
-                if (timeoutMillis == NO_TIMEOUT) {
-                    result = mResultQueue.take();
-                } else {
-                    result = mResultQueue.poll(
-                            timeoutMillis - (mTimeProvider.getTimeMillis() - startTime), unit);
-                }
-
-                if (result == null) {
-                    throw new TimeoutException(String.format(Locale.US,
-                            "A timeout occurred when processing %s after %s ms.",
-                            mBluetoothOperation, timeoutMillis));
-                }
-                synchronized (mLock) {
-                    mIsDone = true;
-                }
-                if (result instanceof BluetoothException) {
-                    throw new ExecutionException((BluetoothException) result);
-                }
-                if (result == NULL_RESULT) {
-                    result = null;
-                }
-                return (T) result;
-            }
-        }
-    }
-
-    /**
-     * Exception thrown when an operation execution times out. Since state of the system is unknown
-     * afterward (operation may still complete or not), it is recommended to disconnect and
-     * reconnect.
-     */
-    public static class BluetoothOperationTimeoutException extends BluetoothException {
-
-        public BluetoothOperationTimeoutException(String message) {
-            super(message);
-        }
-
-        public BluetoothOperationTimeoutException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java b/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java
deleted file mode 100644
index 44c9422..0000000
--- a/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.eventloop;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.BinderThread;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * A collection of threading annotations relating to EventLoop. These should be used in conjunction
- * with {@link UiThread}, {@link BinderThread}, {@link WorkerThread}, and {@link AnyThread}.
- */
-public class Annotations {
-
-    /**
-     * Denotes that the annotated method or constructor should only be called on the EventLoop
-     * thread.
-     */
-    @Retention(CLASS)
-    @Target({METHOD, CONSTRUCTOR, TYPE})
-    public @interface EventThread {
-    }
-
-    /** Denotes that the annotated method or constructor should only be called on a Network
-     * thread. */
-    @Retention(CLASS)
-    @Target({METHOD, CONSTRUCTOR, TYPE})
-    public @interface NetworkThread {
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java b/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java
deleted file mode 100644
index c89366f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.eventloop;
-
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Looper;
-
-/**
- * Handles executing runnables on a background thread.
- *
- * <p>Nearby services follow an event loop model where events can be queued and delivered in the
- * future. All code that is run in this EventLoop is guaranteed to be run on this thread. The main
- * advantage of this model is that all modules don't have to deal with synchronization and race
- * conditions, while making it easy to handle the several asynchronous tasks that are expected to be
- * needed for this type of provider (such as starting a WiFi scan and waiting for the result,
- * starting BLE scans, doing a server request and waiting for the response etc.).
- *
- * <p>Code that needs to wait for an event should not spawn a new thread nor sleep. It should simply
- * deliver a new message to the event queue when the reply of the event happens.
- */
-// TODO(b/177675274): Resolve nullness suppression.
-@SuppressWarnings("nullness")
-public class EventLoop {
-
-    private final Interface mImpl;
-
-    private EventLoop(Interface impl) {
-        this.mImpl = impl;
-    }
-
-    protected EventLoop(String name) {
-        this(new HandlerEventLoopImpl(name));
-    }
-
-    /** Creates an EventLoop. */
-    public static EventLoop newInstance(String name) {
-        return new EventLoop(name);
-    }
-
-    /** Creates an EventLoop. */
-    public static EventLoop newInstance(String name, Looper looper) {
-        return new EventLoop(new HandlerEventLoopImpl(name, looper));
-    }
-
-    /** Marks the EventLoop as destroyed. Any further messages received will be ignored. */
-    public void destroy() {
-        mImpl.destroy();
-    }
-
-    /**
-     * Posts a runnable to this event loop, blocking until the runnable has been executed. This
-     * should
-     * be used rarely. It could be useful, for example, for a runnable that initializes the system
-     * and
-     * must block the posting of all other runnables.
-     *
-     * @param runnable a Runnable to post. This method will not return until the run() method of the
-     *                 given runnable has executed on the background thread.
-     */
-    public void postAndWait(final NamedRunnable runnable) throws InterruptedException {
-        mImpl.postAndWait(runnable);
-    }
-
-    /**
-     * Posts a runnable to this to the front of the event loop, blocking until the runnable has been
-     * executed. This should be used rarely, as it can starve the event loop.
-     *
-     * @param runnable a Runnable to post. This method will not return until the run() method of the
-     *                 given runnable has executed on the background thread.
-     */
-    public void postToFrontAndWait(final NamedRunnable runnable) throws InterruptedException {
-        mImpl.postToFrontAndWait(runnable);
-    }
-
-    /** Checks if there are any pending posts of the Runnable in the queue. */
-    public boolean isPosted(NamedRunnable runnable) {
-        return mImpl.isPosted(runnable);
-    }
-
-    /**
-     * Run code on the event loop thread.
-     *
-     * @param runnable the runnable to execute.
-     */
-    public void postRunnable(NamedRunnable runnable) {
-        mImpl.postRunnable(runnable);
-    }
-
-    /**
-     * Run code to be executed when there is no runnable scheduled.
-     *
-     * @param runnable last runnable to execute.
-     */
-    public void postEmptyQueueRunnable(final NamedRunnable runnable) {
-        mImpl.postEmptyQueueRunnable(runnable);
-    }
-
-    /**
-     * Run code on the event loop thread after delayedMillis.
-     *
-     * @param runnable      the runnable to execute.
-     * @param delayedMillis the number of milliseconds before executing the runnable.
-     */
-    public void postRunnableDelayed(NamedRunnable runnable, long delayedMillis) {
-        mImpl.postRunnableDelayed(runnable, delayedMillis);
-    }
-
-    /**
-     * Removes and cancels the specified {@code runnable} if it had not posted/started yet. Calling
-     * with null does nothing.
-     */
-    public void removeRunnable(@Nullable NamedRunnable runnable) {
-        mImpl.removeRunnable(runnable);
-    }
-
-    /** Asserts that the current operation is being executed in the Event Loop's thread. */
-    public void checkThread() {
-        mImpl.checkThread();
-    }
-
-    public Handler getHandler() {
-        return mImpl.getHandler();
-    }
-
-    interface Interface {
-        void destroy();
-
-        void postAndWait(NamedRunnable runnable) throws InterruptedException;
-
-        void postToFrontAndWait(NamedRunnable runnable) throws InterruptedException;
-
-        boolean isPosted(NamedRunnable runnable);
-
-        void postRunnable(NamedRunnable runnable);
-
-        void postEmptyQueueRunnable(NamedRunnable runnable);
-
-        void postRunnableDelayed(NamedRunnable runnable, long delayedMillis);
-
-        void removeRunnable(NamedRunnable runnable);
-
-        void checkThread();
-
-        Handler getHandler();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java b/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java
deleted file mode 100644
index 018dcdb..0000000
--- a/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.eventloop;
-
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.MessageQueue;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Handles executing runnables on a background thread.
- *
- * <p>Nearby services follow an event loop model where events can be queued and delivered in the
- * future. All code that is run in this package is guaranteed to be run on this thread. The main
- * advantage of this model is that all modules don't have to deal with synchronization and race
- * conditions, while making it easy to handle the several asynchronous tasks that are expected to be
- * needed for this type of provider (such as starting a WiFi scan and waiting for the result,
- * starting BLE scans, doing a server request and waiting for the response etc.).
- *
- * <p>Code that needs to wait for an event should not spawn a new thread nor sleep. It should simply
- * deliver a new message to the event queue when the reply of the event happens.
- *
- * <p>
- */
-// TODO(b/203471261) use executor instead of handler
-// TODO(b/177675274): Resolve nullness suppression.
-@SuppressWarnings("nullness")
-final class HandlerEventLoopImpl implements EventLoop.Interface {
-    /** The {@link Message#what} code for all messages that we post to the EventLoop. */
-    private static final int WHAT = 0;
-
-    private static final long ELAPSED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(5);
-    private static final long RUNNABLE_DELAY_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(2);
-    private static final String TAG = HandlerEventLoopImpl.class.getSimpleName();
-    private final MyHandler mHandler;
-
-    private volatile boolean mIsDestroyed = false;
-
-    /** Constructs an EventLoop. */
-    HandlerEventLoopImpl(String name) {
-        this(name, createHandlerThread(name));
-    }
-
-    HandlerEventLoopImpl(String name, Looper looper) {
-
-        mHandler = new MyHandler(looper);
-        Log.d(TAG,
-                "Created EventLoop for thread '" + looper.getThread().getName()
-                        + "(id: " + looper.getThread().getId() + ")'");
-    }
-
-    private static Looper createHandlerThread(String name) {
-        HandlerThread handlerThread = new HandlerThread(name, Process.THREAD_PRIORITY_BACKGROUND);
-        handlerThread.start();
-
-        return handlerThread.getLooper();
-    }
-
-    /**
-     * Wrapper to satisfy Android Lint. {@link Looper#getQueue()} is public and available since ICS,
-     * but was marked @hide until Marshmallow. Tested that this code doesn't crash pre-Marshmallow.
-     * /aosp-ics/frameworks/base/core/java/android/os/Looper.java?l=218
-     */
-    @SuppressLint("NewApi")
-    private static MessageQueue getQueue(Handler handler) {
-        return handler.getLooper().getQueue();
-    }
-
-    /** Marks the EventLoop as destroyed. Any further messages received will be ignored. */
-    @Override
-    public void destroy() {
-        Looper looper = mHandler.getLooper();
-        Log.d(TAG,
-                "Destroying EventLoop for thread " + looper.getThread().getName()
-                        + " (id: " + looper.getThread().getId() + ")");
-        looper.quit();
-        mIsDestroyed = true;
-    }
-
-    /**
-     * Posts a runnable to this event loop, blocking until the runnable has been executed. This
-     * should
-     * be used rarely. It could be useful, for example, for a runnable that initializes the system
-     * and
-     * must block the posting of all other runnables.
-     *
-     * @param runnable a Runnable to post. This method will not return until the run() method of the
-     *                 given runnable has executed on the background thread.
-     */
-    @Override
-    public void postAndWait(final NamedRunnable runnable) throws InterruptedException {
-        internalPostAndWait(runnable, false);
-    }
-
-    @Override
-    public void postToFrontAndWait(final NamedRunnable runnable) throws InterruptedException {
-        internalPostAndWait(runnable, true);
-    }
-
-    /** Checks if there are any pending posts of the Runnable in the queue. */
-    @Override
-    public boolean isPosted(NamedRunnable runnable) {
-        return mHandler.hasMessages(WHAT, runnable);
-    }
-
-    /**
-     * Run code on the event loop thread.
-     *
-     * @param runnable the runnable to execute.
-     */
-    @Override
-    public void postRunnable(NamedRunnable runnable) {
-        Log.d(TAG, "Posting " + runnable);
-        mHandler.post(runnable, 0L, false);
-    }
-
-    /**
-     * Run code to be executed when there is no runnable scheduled.
-     *
-     * @param runnable last runnable to execute.
-     */
-    @Override
-    public void postEmptyQueueRunnable(final NamedRunnable runnable) {
-        mHandler.post(
-                () ->
-                        getQueue(mHandler)
-                                .addIdleHandler(
-                                        () -> {
-                                            if (mHandler.hasMessages(WHAT)) {
-                                                return true;
-                                            } else {
-                                                // Only stop if start has not been called since
-                                                // this was queued
-                                                runnable.run();
-                                                return false;
-                                            }
-                                        }));
-    }
-
-    /**
-     * Run code on the event loop thread after delayedMillis.
-     *
-     * @param runnable      the runnable to execute.
-     * @param delayedMillis the number of milliseconds before executing the runnable.
-     */
-    @Override
-    public void postRunnableDelayed(NamedRunnable runnable, long delayedMillis) {
-        Log.d(TAG, "Posting " + runnable + " [delay " + delayedMillis + "]");
-        mHandler.post(runnable, delayedMillis, false);
-    }
-
-    /**
-     * Removes and cancels the specified {@code runnable} if it had not posted/started yet. Calling
-     * with null does nothing.
-     */
-    @Override
-    public void removeRunnable(@Nullable NamedRunnable runnable) {
-        if (runnable != null) {
-            // Removes any pending sent messages where what=WHAT and obj=runnable. We can't use
-            // removeCallbacks(runnable) because we're not posting the runnable directly, we're
-            // sending a Message with the runnable as its obj.
-            mHandler.removeMessages(WHAT, runnable);
-        }
-    }
-
-    /** Asserts that the current operation is being executed in the Event Loop's thread. */
-    @Override
-    public void checkThread() {
-
-        Thread currentThread = Looper.myLooper().getThread();
-        Thread expectedThread = mHandler.getLooper().getThread();
-        if (currentThread.getId() != expectedThread.getId()) {
-            throw new IllegalStateException(
-                    String.format(
-                            "This method must run in the EventLoop thread '%s (id: %s)'. "
-                                    + "Was called from thread '%s (id: %s)'.",
-                            expectedThread.getName(),
-                            expectedThread.getId(),
-                            currentThread.getName(),
-                            currentThread.getId()));
-        }
-
-    }
-
-    @Override
-    public Handler getHandler() {
-        return mHandler;
-    }
-
-    private void internalPostAndWait(final NamedRunnable runnable, boolean postToFront)
-            throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        NamedRunnable delegate =
-                new NamedRunnable(runnable.name) {
-                    @Override
-                    public void run() {
-                        try {
-                            runnable.run();
-                        } finally {
-                            latch.countDown();
-                        }
-                    }
-                };
-
-        Log.d(TAG, "Posting " + delegate + " and wait");
-        if (!mHandler.post(delegate, 0L, postToFront)) {
-            // Do not wait if delegate is not posted.
-            Log.d(TAG, delegate + " not posted");
-            latch.countDown();
-        }
-        latch.await();
-    }
-
-    /** Handler that executes code on a private event loop thread. */
-    private class MyHandler extends Handler {
-
-        MyHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            NamedRunnable runnable = (NamedRunnable) msg.obj;
-
-            if (mIsDestroyed) {
-                Log.w(TAG, "Runnable " + runnable
-                        + " attempted to run after the EventLoop was destroyed. Ignoring");
-                return;
-            }
-            Log.i(TAG, "Executing " + runnable);
-
-            // Did this runnable start much later than we expected it to? If so, then log.
-            long expectedStartTime = (long) msg.arg1 << 32 | (msg.arg2 & 0xFFFFFFFFL);
-            logIfExceedsThreshold(
-                    RUNNABLE_DELAY_THRESHOLD_MS, expectedStartTime, runnable, "was delayed for");
-
-            long startTimeMillis = SystemClock.elapsedRealtime();
-            try {
-                runnable.run();
-            } catch (Exception t) {
-                Log.e(TAG, runnable + "crashed.");
-                throw t;
-            } finally {
-                logIfExceedsThreshold(ELAPSED_THRESHOLD_MS, startTimeMillis, runnable, "ran for");
-            }
-        }
-
-        private boolean post(NamedRunnable runnable, long delayedMillis, boolean postToFront) {
-            if (mIsDestroyed) {
-                Log.w(TAG, runnable + " not posted since EventLoop is destroyed");
-                return false;
-            }
-            long expectedStartTime = SystemClock.elapsedRealtime() + delayedMillis;
-            int arg1 = (int) (expectedStartTime >> 32);
-            int arg2 = (int) expectedStartTime;
-            Message message = obtainMessage(WHAT, arg1, arg2, runnable /* obj */);
-            boolean sent =
-                    postToFront
-                            ? sendMessageAtFrontOfQueue(message)
-                            : sendMessageDelayed(message, delayedMillis);
-            if (!sent) {
-                Log.w(TAG, runnable + "not posted since looper is exiting");
-            }
-            return sent;
-        }
-
-        private void logIfExceedsThreshold(
-                long thresholdMillis, long startTimeMillis, NamedRunnable runnable,
-                String message) {
-            long elapsedMillis = SystemClock.elapsedRealtime() - startTimeMillis;
-            if (elapsedMillis > thresholdMillis) {
-                String elapsedFormatted =
-                        new SimpleDateFormat("mm:ss.SSS", Locale.US).format(elapsedMillis);
-                Log.w(TAG, runnable + " " + message + " " + elapsedFormatted);
-            }
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/NamedRunnable.java b/nearby/service/java/com/android/server/nearby/common/eventloop/NamedRunnable.java
deleted file mode 100644
index 578e3f6..0000000
--- a/nearby/service/java/com/android/server/nearby/common/eventloop/NamedRunnable.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.eventloop;
-
-/** A Runnable with a name, for logging purposes. */
-public abstract class NamedRunnable implements Runnable {
-    public final String name;
-
-    public NamedRunnable(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public String toString() {
-        return "Runnable[" + name + "]";
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java b/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java
deleted file mode 100644
index 35a1a9f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.fastpair;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.core.graphics.ColorUtils;
-
-/** Utility methods for icon size verification. */
-public class IconUtils {
-    private static final int MIN_ICON_SIZE = 16;
-    private static final int DESIRED_ICON_SIZE = 32;
-    private static final double NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE = 0.125;
-    private static final double NOTIFICATION_BACKGROUND_ALPHA = 0.7;
-
-    /**
-     * Verify that the icon is non null and falls in the small bucket. Just because an icon isn't
-     * small doesn't guarantee it is large or exists.
-     */
-    @VisibleForTesting
-    static boolean isIconSizedSmall(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        int min = MIN_ICON_SIZE;
-        int desired = DESIRED_ICON_SIZE;
-        return bitmap.getWidth() >= min
-                && bitmap.getWidth() < desired
-                && bitmap.getHeight() >= min
-                && bitmap.getHeight() < desired;
-    }
-
-    /**
-     * Verify that the icon is non null and falls in the regular / default size bucket. Doesn't
-     * guarantee if not regular then it is small.
-     */
-    @VisibleForTesting
-    static boolean isIconSizedRegular(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        return bitmap.getWidth() >= DESIRED_ICON_SIZE
-                && bitmap.getHeight() >= DESIRED_ICON_SIZE;
-    }
-
-    // All icons that are sized correctly (larger than the min icon size) are resize on the server
-    // to the desired icon size so that they appear correct in notifications.
-
-    /**
-     * All icons that are sized correctly (larger than the min icon size) are resize on the server
-     * to the desired icon size so that they appear correct in notifications.
-     */
-    public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return false;
-        }
-        return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap);
-    }
-
-    /** Adds a circular, white background to the bitmap. */
-    @Nullable
-    public static Bitmap addWhiteCircleBackground(Context context, @Nullable Bitmap bitmap) {
-        if (bitmap == null) {
-            return null;
-        }
-
-        if (bitmap.getWidth() != bitmap.getHeight()) {
-            return bitmap;
-        }
-
-        int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE);
-        Bitmap bitmapWithBackground =
-                Bitmap.createBitmap(
-                        bitmap.getWidth() + (2 * padding),
-                        bitmap.getHeight() + (2 * padding),
-                        bitmap.getConfig());
-        Canvas canvas = new Canvas(bitmapWithBackground);
-        Paint paint = new Paint();
-        paint.setColor(
-                ColorUtils.setAlphaComponent(
-                        Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA)));
-        paint.setStyle(Paint.Style.FILL);
-        paint.setAntiAlias(true);
-        canvas.drawCircle(
-                bitmapWithBackground.getWidth() / 2f,
-                bitmapWithBackground.getHeight() / 2f,
-                bitmapWithBackground.getWidth() / 2f,
-                paint);
-        canvas.drawBitmap(bitmap, padding, padding, null);
-        return bitmapWithBackground;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java b/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
deleted file mode 100644
index 67d87e3..0000000
--- a/nearby/service/java/com/android/server/nearby/common/fastpair/service/UserActionHandlerBase.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.fastpair.service;
-
-/** Handles intents to {@link com.android.server.nearby.fastpair.FastPairManager}. */
-public class UserActionHandlerBase {
-    public static final String PREFIX = "com.android.server.nearby.fastpair.";
-    public static final String ACTION_PREFIX = "com.android.server.nearby:";
-
-    public static final String EXTRA_ITEM_ID = PREFIX + "EXTRA_ITEM_ID";
-    public static final String EXTRA_COMPANION_APP = ACTION_PREFIX + "EXTRA_COMPANION_APP";
-    public static final String EXTRA_MAC_ADDRESS = PREFIX + "EXTRA_MAC_ADDRESS";
-
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
deleted file mode 100644
index f8b43a6..0000000
--- a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.locator;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-
-/** Collection of bindings that map service types to their respective implementation(s). */
-public class Locator {
-    private static final Object UNBOUND = new Object();
-    private final Context mContext;
-    @Nullable
-    private Locator mParent;
-    private final String mTag; // For debugging
-    private final Map<Class<?>, Object> mBindings = new HashMap<>();
-    private final ArrayList<Module> mModules = new ArrayList<>();
-
-    /** Thrown upon attempt to bind an interface twice. */
-    public static class DuplicateBindingException extends RuntimeException {
-        DuplicateBindingException(String msg) {
-            super(msg);
-        }
-    }
-
-    /** Constructor with a null parent. */
-    public Locator(Context context) {
-        this(context, null);
-    }
-
-    /**
-     * Constructor. Supply a valid context and the Locator's parent.
-     *
-     * <p>To find a suitable parent you may want to use findLocator.
-     */
-    public Locator(Context context, @Nullable Locator parent) {
-        this.mContext = context;
-        this.mParent = parent;
-        this.mTag = context.getClass().getName();
-    }
-
-    /** Attaches the parent to the locator. */
-    public void attachParent(Locator parent) {
-        this.mParent = parent;
-    }
-
-    /** Associates the specified type with the supplied instance. */
-    public <T extends Object> Locator bind(Class<T> type, T instance) {
-        bindKeyValue(type, instance);
-        return this;
-    }
-
-    /** For tests only. Disassociates the specified type from any instance. */
-    @VisibleForTesting
-    public <T extends Object> Locator overrideBindingForTest(Class<T> type, T instance) {
-        mBindings.remove(type);
-        return bind(type, instance);
-    }
-
-    /** For tests only. Force Locator to return null when try to get an instance. */
-    @VisibleForTesting
-    public <T> Locator removeBindingForTest(Class<T> type) {
-        Locator locator = this;
-        do {
-            locator.mBindings.put(type, UNBOUND);
-            locator = locator.mParent;
-        } while (locator != null);
-        return this;
-    }
-
-    /** Binds a module. */
-    public synchronized Locator bind(Module module) {
-        mModules.add(module);
-        return this;
-    }
-
-    /**
-     * Searches the chain of locators for a binding for the given type.
-     *
-     * @throws IllegalStateException if no binding is found.
-     */
-    public <T> T get(Class<T> type) {
-        T instance = getOptional(type);
-        if (instance != null) {
-            return instance;
-        }
-
-        String errorMessage = getUnboundErrorMessage(type);
-        throw new IllegalStateException(errorMessage);
-    }
-
-    private String getUnboundErrorMessage(Class<?> type) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("Unbound type: ").append(type.getName()).append("\n").append(
-                "Searched locators:\n");
-        Locator locator = this;
-        while (true) {
-            sb.append(locator.mTag);
-            locator = locator.mParent;
-            if (locator == null) {
-                break;
-            }
-            sb.append(" ->\n");
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Searches the chain of locators for a binding for the given type. Returns null if no locator
-     * was
-     * found.
-     */
-    @Nullable
-    public <T> T getOptional(Class<T> type) {
-        Locator locator = this;
-        do {
-            T instance = locator.getInstance(type);
-            if (instance != null) {
-                return instance;
-            }
-            locator = locator.mParent;
-        } while (locator != null);
-        return null;
-    }
-
-    private synchronized <T extends Object> void bindKeyValue(Class<T> key, T value) {
-        Object boundInstance = mBindings.get(key);
-        if (boundInstance != null) {
-            if (boundInstance == UNBOUND) {
-                Log.w(mTag, "Bind call too late - someone already tried to get: " + key);
-            } else {
-                throw new DuplicateBindingException("Duplicate binding: " + key);
-            }
-        }
-        mBindings.put(key, value);
-    }
-
-    // Suppress warning of cast from Object -> T
-    @SuppressWarnings("unchecked")
-    @Nullable
-    private synchronized <T> T getInstance(Class<T> type) {
-        if (mContext == null) {
-            throw new IllegalStateException("Locator not initialized yet.");
-        }
-
-        T instance = (T) mBindings.get(type);
-        if (instance != null) {
-            return instance != UNBOUND ? instance : null;
-        }
-
-        // Ask modules to supply a binding
-        int moduleCount = mModules.size();
-        for (int i = 0; i < moduleCount; i++) {
-            mModules.get(i).configure(mContext, type, this);
-        }
-
-        instance = (T) mBindings.get(type);
-        if (instance == null) {
-            mBindings.put(type, UNBOUND);
-        }
-        return instance;
-    }
-
-    /**
-     * Iterates over all bound objects and gives the modules a chance to clean up the objects they
-     * have created.
-     */
-    public synchronized void destroy() {
-        for (Class<?> type : mBindings.keySet()) {
-            Object instance = mBindings.get(type);
-            if (instance == UNBOUND) {
-                continue;
-            }
-
-            for (Module module : mModules) {
-                module.destroy(mContext, type, instance);
-            }
-        }
-        mBindings.clear();
-    }
-
-    /** Returns true if there are no bindings. */
-    public boolean isEmpty() {
-        return mBindings.isEmpty();
-    }
-
-    /** Returns the parent locator or null if no parent. */
-    @Nullable
-    public Locator getParent() {
-        return mParent;
-    }
-
-    /**
-     * Finds the first locator, then searches the chain of locators for a binding for the given
-     * type.
-     *
-     * @throws IllegalStateException if no binding is found.
-     */
-    public static <T> T get(Context context, Class<T> type) {
-        Locator locator = findLocator(context);
-        if (locator == null) {
-            throw new IllegalStateException("No locator found in context " + context);
-        }
-        return locator.get(type);
-    }
-
-    /**
-     * Find the first locator from the context wrapper.
-     */
-    public static <T> T getFromContextWrapper(LocatorContextWrapper wrapper, Class<T> type) {
-        Locator locator = wrapper.getLocator();
-        if (locator == null) {
-            throw new IllegalStateException("No locator found in context wrapper");
-        }
-        return locator.get(type);
-    }
-
-    /**
-     * Finds the first locator, then searches the chain of locators for a binding for the given
-     * type.
-     * Returns null if no binding was found.
-     */
-    @Nullable
-    public static <T> T getOptional(Context context, Class<T> type) {
-        Locator locator = findLocator(context);
-        if (locator == null) {
-            return null;
-        }
-        return locator.getOptional(type);
-    }
-
-    /** Finds the first locator in the context hierarchy. */
-    @Nullable
-    public static Locator findLocator(Context context) {
-        Context applicationContext = context.getApplicationContext();
-        boolean applicationContextVisited = false;
-
-        Context searchContext = context;
-        do {
-            Locator locator = tryGetLocator(searchContext);
-            if (locator != null) {
-                return locator;
-            }
-
-            applicationContextVisited |= (searchContext == applicationContext);
-
-            if (searchContext instanceof ContextWrapper) {
-                searchContext = ((ContextWrapper) context).getBaseContext();
-
-                if (searchContext == null) {
-                    throw new IllegalStateException(
-                            "Invalid ContextWrapper -- If this is a Robolectric test, "
-                                    + "have you called ActivityController.create()?");
-                }
-            } else if (!applicationContextVisited) {
-                searchContext = applicationContext;
-            } else {
-                searchContext = null;
-            }
-        } while (searchContext != null);
-
-        return null;
-    }
-
-    @Nullable
-    private static Locator tryGetLocator(Object object) {
-        if (object instanceof LocatorContext) {
-            Locator locator = ((LocatorContext) object).getLocator();
-            if (locator == null) {
-                throw new IllegalStateException(
-                        "LocatorContext must not return null Locator: " + object);
-            }
-            return locator;
-        }
-        return null;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java
deleted file mode 100644
index 06eef8a..0000000
--- a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.locator;
-
-/**
- * An object that has a {@link Locator}. The locator can be used to resolve service types to their
- * respective implementation(s).
- */
-public interface LocatorContext {
-    /** Returns the locator. May not return null. */
-    Locator getLocator();
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java
deleted file mode 100644
index 03df33f..0000000
--- a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.locator;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.ContextWrapper;
-
-/**
- * Wraps a Context and associates it with a Locator, optionally linking it with a parent locator.
- */
-public class LocatorContextWrapper extends ContextWrapper implements LocatorContext {
-    private final Locator mLocator;
-    private final Context mContext;
-    /** Constructs a context wrapper with a Locator linked to the passed locator. */
-    public LocatorContextWrapper(Context context, @Nullable Locator parentLocator) {
-        super(context);
-        mContext = context;
-        // Assigning under initialization object, but it's safe, since locator is used lazily.
-        this.mLocator = new Locator(this, parentLocator);
-    }
-
-    /**
-     * Constructs a context wrapper.
-     *
-     * <p>Uses the Locator associated with the passed context as the parent.
-     */
-    public LocatorContextWrapper(Context context) {
-        this(context, Locator.findLocator(context));
-    }
-
-    /**
-     * Get the context of the context wrapper.
-     */
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public Locator getLocator() {
-        return mLocator;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Module.java b/nearby/service/java/com/android/server/nearby/common/locator/Module.java
deleted file mode 100644
index 0131c44..0000000
--- a/nearby/service/java/com/android/server/nearby/common/locator/Module.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.locator;
-
-import android.content.Context;
-
-/** Configures late bindings of service types to their concrete implementations. */
-public abstract class Module {
-    /**
-     * Configures the binding between the {@code type} and its implementation by calling methods on
-     * the {@code locator}, for example:
-     *
-     * <pre>{@code
-     * void configure(Context context, Class<?> type, Locator locator) {
-     *   if (type == MyService.class) {
-     *     locator.bind(MyService.class, new MyImplementation(context));
-     *   }
-     * }
-     * }</pre>
-     *
-     * <p>If the module does not recognize the specified type, the method does not have to do
-     * anything.
-     */
-    public abstract void configure(Context context, Class<?> type, Locator locator);
-
-    /**
-     * Notifies you that a binding of class {@code type} is no longer needed and can now release
-     * everything it was holding on to, such as a database connection.
-     *
-     * <pre>{@code
-     * void destroy(Context context, Class<?> type, Object instance) {
-     *   if (type == MyService.class) {
-     *     ((MyService) instance).destroy();
-     *   }
-     * }
-     * }</pre>
-     *
-     * <p>If the module does not recognize the specified type, the method does not have to do
-     * anything.
-     */
-    public void destroy(Context context, Class<?> type, Object instance) {}
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java
deleted file mode 100644
index 80248e8..0000000
--- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.servicemonitor;
-
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceProvider;
-
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * This is mostly borrowed from frameworks CurrentUserServiceSupplier.
- * Provides services based on the current active user and version as defined in the service
- * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
- * ensure only system (ie, privileged) services are matched. It also handles services that are not
- * direct boot aware, and will automatically pick the best service as the user's direct boot state
- * changes.
- */
-public final class CurrentUserServiceProvider extends BroadcastReceiver implements
-        ServiceProvider<CurrentUserServiceProvider.BoundServiceInfo> {
-
-    private static final String TAG = "CurrentUserServiceProvider";
-
-    private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
-
-    // This is equal to the hidden Intent.ACTION_USER_SWITCHED.
-    private static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
-    // This is equal to the hidden Intent.EXTRA_USER_HANDLE.
-    private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
-    // This is equal to the hidden UserHandle.USER_NULL.
-    private static final int USER_NULL = -10000;
-
-    private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
-        if (o1 == o2) {
-            return 0;
-        } else if (o1 == null) {
-            return -1;
-        } else if (o2 == null) {
-            return 1;
-        }
-
-        // ServiceInfos with higher version numbers always win.
-        return Integer.compare(o1.getVersion(), o2.getVersion());
-    };
-
-    /** Bound service information with version information. */
-    public static class BoundServiceInfo extends ServiceMonitor.BoundServiceInfo {
-
-        private static int parseUid(ResolveInfo resolveInfo) {
-            return resolveInfo.serviceInfo.applicationInfo.uid;
-        }
-
-        private static int parseVersion(ResolveInfo resolveInfo) {
-            int version = Integer.MIN_VALUE;
-            if (resolveInfo.serviceInfo.metaData != null) {
-                version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
-            }
-            return version;
-        }
-
-        private final int mVersion;
-
-        protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
-            this(
-                    action,
-                    parseUid(resolveInfo),
-                    new ComponentName(
-                            resolveInfo.serviceInfo.packageName,
-                            resolveInfo.serviceInfo.name),
-                    parseVersion(resolveInfo));
-        }
-
-        protected BoundServiceInfo(String action, int uid, ComponentName componentName,
-                int version) {
-            super(action, uid, componentName);
-            mVersion = version;
-        }
-
-        public int getVersion() {
-            return mVersion;
-        }
-
-        @Override
-        public String toString() {
-            return super.toString() + "@" + mVersion;
-        }
-    }
-
-    /**
-     * Creates an instance with the specific service details.
-     *
-     * @param context the context the provider is to use
-     * @param action the action the service must declare in its intent-filter
-     */
-    public static CurrentUserServiceProvider create(Context context, String action) {
-        return new CurrentUserServiceProvider(context, action);
-    }
-
-    private final Context mContext;
-    private final Intent mIntent;
-    private volatile ServiceChangedListener mListener;
-
-    private CurrentUserServiceProvider(Context context, String action) {
-        mContext = context;
-        mIntent = new Intent(action);
-    }
-
-    @Override
-    public boolean hasMatchingService() {
-        int intentQueryFlags =
-                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY;
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
-                mIntent, intentQueryFlags, UserHandle.SYSTEM);
-        return !resolveInfos.isEmpty();
-    }
-
-    @Override
-    public void register(ServiceChangedListener listener) {
-        Preconditions.checkState(mListener == null);
-
-        mListener = listener;
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_USER_SWITCHED);
-        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiverForAllUsers(this, intentFilter, null,
-                ForegroundThread.getHandler());
-    }
-
-    @Override
-    public void unregister() {
-        Preconditions.checkArgument(mListener != null);
-
-        mListener = null;
-        mContext.unregisterReceiver(this);
-    }
-
-    @Override
-    public BoundServiceInfo getServiceInfo() {
-        BoundServiceInfo bestServiceInfo = null;
-
-        // only allow services in the correct direct boot state to match
-        int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA | MATCH_SYSTEM_ONLY;
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
-                mIntent, intentQueryFlags, UserHandle.of(ActivityManager.getCurrentUser()));
-        for (ResolveInfo resolveInfo : resolveInfos) {
-            BoundServiceInfo serviceInfo =
-                    new BoundServiceInfo(mIntent.getAction(), resolveInfo);
-
-            if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
-                bestServiceInfo = serviceInfo;
-            }
-        }
-
-        return bestServiceInfo;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        if (action == null) {
-            return;
-        }
-        int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
-        if (userId == USER_NULL) {
-            return;
-        }
-        ServiceChangedListener listener = mListener;
-        if (listener == null) {
-            return;
-        }
-
-        switch (action) {
-            case ACTION_USER_SWITCHED:
-                listener.onServiceChanged();
-                break;
-            case Intent.ACTION_USER_UNLOCKED:
-                // user unlocked implies direct boot mode may have changed
-                if (userId == ActivityManager.getCurrentUser()) {
-                    listener.onServiceChanged();
-                }
-                break;
-            default:
-                break;
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java
deleted file mode 100644
index 2c363f8..0000000
--- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.servicemonitor;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import com.android.modules.utils.HandlerExecutor;
-
-import java.util.concurrent.Executor;
-
-/**
- * Thread for asynchronous event processing. This thread is configured as
- * {@link android.os.Process#THREAD_PRIORITY_FOREGROUND}, which means more CPU
- * resources will be dedicated to it, and it will be treated like "a user
- * interface that the user is interacting with."
- * <p>
- * This thread is best suited for tasks that the user is actively waiting for,
- * or for tasks that the user expects to be executed immediately.
- *
- */
-public final class ForegroundThread extends HandlerThread {
-    private static ForegroundThread sInstance;
-    private static Handler sHandler;
-    private static HandlerExecutor sHandlerExecutor;
-
-    private ForegroundThread() {
-        super("nearbyfg", android.os.Process.THREAD_PRIORITY_FOREGROUND);
-    }
-
-    private static void ensureThreadLocked() {
-        if (sInstance == null) {
-            sInstance = new ForegroundThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-            sHandlerExecutor = new HandlerExecutor(sHandler);
-        }
-    }
-
-    /** Get ForegroundThread singleton instance. */
-    public static ForegroundThread get() {
-        synchronized (ForegroundThread.class) {
-            ensureThreadLocked();
-            return sInstance;
-        }
-    }
-
-    /** Get ForegroundThread singleton handler. */
-    public static Handler getHandler() {
-        synchronized (ForegroundThread.class) {
-            ensureThreadLocked();
-            return sHandler;
-        }
-    }
-
-    /** Get ForegroundThread singleton executor. */
-    public static Executor getExecutor() {
-        synchronized (ForegroundThread.class) {
-            ensureThreadLocked();
-            return sHandlerExecutor;
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java
deleted file mode 100644
index 7d1db57..0000000
--- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.servicemonitor;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-
-import com.android.modules.utils.BackgroundThread;
-
-import java.util.Objects;
-
-/**
- * This is mostly from frameworks PackageMonitor.
- * Helper class for watching somePackagesChanged.
- */
-public abstract class PackageWatcher extends BroadcastReceiver {
-    static final String TAG = "PackageWatcher";
-    static final IntentFilter sPackageFilt = new IntentFilter();
-    static final IntentFilter sNonDataFilt = new IntentFilter();
-    static final IntentFilter sExternalFilt = new IntentFilter();
-
-    static {
-        sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
-        sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        sPackageFilt.addDataScheme("package");
-        sNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        sNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-        sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-    }
-
-    Context mRegisteredContext;
-    Handler mRegisteredHandler;
-    boolean mSomePackagesChanged;
-
-    public PackageWatcher() {
-    }
-
-    void register(Context context, Looper thread, boolean externalStorage) {
-        register(context, externalStorage,
-                (thread == null) ? BackgroundThread.getHandler() : new Handler(thread));
-    }
-
-    void register(Context context, boolean externalStorage, Handler handler) {
-        if (mRegisteredContext != null) {
-            throw new IllegalStateException("Already registered");
-        }
-        mRegisteredContext = context;
-        mRegisteredHandler = Objects.requireNonNull(handler);
-        context.registerReceiverForAllUsers(this, sPackageFilt, null, mRegisteredHandler);
-        context.registerReceiverForAllUsers(this, sNonDataFilt, null, mRegisteredHandler);
-        if (externalStorage) {
-            context.registerReceiverForAllUsers(this, sExternalFilt, null, mRegisteredHandler);
-        }
-    }
-
-    void unregister() {
-        if (mRegisteredContext == null) {
-            throw new IllegalStateException("Not registered");
-        }
-        mRegisteredContext.unregisterReceiver(this);
-        mRegisteredContext = null;
-    }
-
-    // Called when some package has been changed.
-    abstract void onSomePackagesChanged();
-
-    String getPackageName(Intent intent) {
-        Uri uri = intent.getData();
-        String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
-        return pkg;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        mSomePackagesChanged = false;
-
-        String action = intent.getAction();
-        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-            // We consider something to have changed regardless of whether
-            // this is just an update, because the update is now finished
-            // and the contents of the package may have changed.
-            mSomePackagesChanged = true;
-        } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-            String pkg = getPackageName(intent);
-            if (pkg != null) {
-                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                    mSomePackagesChanged = true;
-                }
-            }
-        } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-            String pkg = getPackageName(intent);
-            if (pkg != null) {
-                mSomePackagesChanged = true;
-            }
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            mSomePackagesChanged = true;
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-            mSomePackagesChanged = true;
-        } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
-            mSomePackagesChanged = true;
-        } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
-            mSomePackagesChanged = true;
-        }
-
-        if (mSomePackagesChanged) {
-            onSomePackagesChanged();
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java
deleted file mode 100644
index a86af85..0000000
--- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.servicemonitor;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * This is exported from frameworks ServiceWatcher.
- * A ServiceMonitor is responsible for continuously maintaining an active binding to a service
- * selected by it's {@link ServiceProvider}. The {@link ServiceProvider} may change the service it
- * selects over time, and the currently bound service may crash, restart, have a user change, have
- * changes made to its package, and so on and so forth. The ServiceMonitor is responsible for
- * maintaining the binding across all these changes.
- *
- * <p>Clients may invoke {@link BinderOperation}s on the ServiceMonitor, and it will make a best
- * effort to run these on the currently bound service, but individual operations may fail (if there
- * is no service currently bound for instance). In order to help clients maintain the correct state,
- * clients may supply a {@link ServiceListener}, which is informed when the ServiceMonitor connects
- * and disconnects from a service. This allows clients to bring a bound service back into a known
- * state on connection, and then run binder operations from there. In order to help clients
- * accomplish this, ServiceMonitor guarantees that {@link BinderOperation}s and the
- * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
- * can be established between them.
- *
- * There is never any guarantee of whether a ServiceMonitor is currently connected to a service, and
- * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
- * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
- * failures.
- */
-public interface ServiceMonitor {
-
-    /**
-     * Operation to run on a binder interface. All operations will be run on the thread used by the
-     * ServiceMonitor this is run with.
-     */
-    interface BinderOperation {
-        /** Invoked to run the operation. Run on the ServiceMonitor thread. */
-        void run(IBinder binder) throws RemoteException;
-
-        /**
-         * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
-         * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
-         * {@link RuntimeException}). This callback is only intended for resource deallocation and
-         * cleanup in response to a single binder operation, it should not be used to propagate
-         * errors further. Run on the ServiceMonitor thread.
-         */
-        default void onError() {}
-    }
-
-    /**
-     * Listener for bind and unbind events. All operations will be run on the thread used by the
-     * ServiceMonitor this is run with.
-     *
-     * @param <TBoundServiceInfo> type of bound service
-     */
-    interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
-        /** Invoked when a service is bound. Run on the ServiceMonitor thread. */
-        void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
-
-        /** Invoked when a service is unbound. Run on the ServiceMonitor thread. */
-        void onUnbind();
-    }
-
-    /**
-     * A listener for when a {@link ServiceProvider} decides that the current service has changed.
-     */
-    interface ServiceChangedListener {
-        /**
-         * Should be invoked when the current service may have changed.
-         */
-        void onServiceChanged();
-    }
-
-    /**
-     * This provider encapsulates the logic of deciding what service a {@link ServiceMonitor} should
-     * be bound to at any given moment.
-     *
-     * @param <TBoundServiceInfo> type of bound service
-     */
-    interface ServiceProvider<TBoundServiceInfo extends BoundServiceInfo> {
-        /**
-         * Should return true if there exists at least one service capable of meeting the criteria
-         * of this provider. This does not imply that {@link #getServiceInfo()} will always return a
-         * non-null result, as any service may be disqualified for various reasons at any point in
-         * time. May be invoked at any time from any thread and thus should generally not have any
-         * dependency on the other methods in this interface.
-         */
-        boolean hasMatchingService();
-
-        /**
-         * Invoked when the provider should start monitoring for any changes that could result in a
-         * different service selection, and should invoke
-         * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
-         * may be invoked after this method is called.
-         */
-        void register(ServiceChangedListener listener);
-
-        /**
-         * Invoked when the provider should stop monitoring for any changes that could result in a
-         * different service selection, should no longer invoke
-         * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
-         * invoked after this method is called.
-         */
-        void unregister();
-
-        /**
-         * Must be implemented to return the current service selected by this provider. May return
-         * null if no service currently meets the criteria. Only invoked while registered.
-         */
-        @Nullable TBoundServiceInfo getServiceInfo();
-    }
-
-    /**
-     * Information on the service selected as the best option for binding.
-     */
-    class BoundServiceInfo {
-
-        protected final @Nullable String mAction;
-        protected final int mUid;
-        protected final ComponentName mComponentName;
-
-        protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
-            mAction = action;
-            mUid = uid;
-            mComponentName = Objects.requireNonNull(componentName);
-        }
-
-        /** Returns the action associated with this bound service. */
-        public @Nullable String getAction() {
-            return mAction;
-        }
-
-        /** Returns the component of this bound service. */
-        public ComponentName getComponentName() {
-            return mComponentName;
-        }
-
-        @Override
-        public final boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (!(o instanceof BoundServiceInfo)) {
-                return false;
-            }
-
-            BoundServiceInfo that = (BoundServiceInfo) o;
-            return mUid == that.mUid
-                    && Objects.equals(mAction, that.mAction)
-                    && mComponentName.equals(that.mComponentName);
-        }
-
-        @Override
-        public final int hashCode() {
-            return Objects.hash(mAction, mUid, mComponentName);
-        }
-
-        @Override
-        public String toString() {
-            if (mComponentName == null) {
-                return "none";
-            } else {
-                return mUid + "/" + mComponentName.flattenToShortString();
-            }
-        }
-    }
-
-    /**
-     * Creates a new ServiceMonitor instance.
-     */
-    static <TBoundServiceInfo extends BoundServiceInfo> ServiceMonitor create(
-            Context context,
-            String tag,
-            ServiceProvider<TBoundServiceInfo> serviceProvider,
-            @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
-        return create(context, ForegroundThread.getHandler(), ForegroundThread.getExecutor(), tag,
-                serviceProvider, serviceListener);
-    }
-
-    /**
-     * Creates a new ServiceMonitor instance that runs on the given handler.
-     */
-    static <TBoundServiceInfo extends BoundServiceInfo> ServiceMonitor create(
-            Context context,
-            Handler handler,
-            Executor executor,
-            String tag,
-            ServiceProvider<TBoundServiceInfo> serviceProvider,
-            @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
-        return new ServiceMonitorImpl<>(context, handler, executor, tag, serviceProvider,
-                serviceListener);
-    }
-
-    /**
-     * Returns true if there is at least one service that the ServiceMonitor could hypothetically
-     * bind to, as selected by the {@link ServiceProvider}.
-     */
-    boolean checkServiceResolves();
-
-    /**
-     * Registers the ServiceMonitor, so that it will begin maintaining an active binding to the
-     * service selected by {@link ServiceProvider}, until {@link #unregister()} is called.
-     */
-    void register();
-
-    /**
-     * Unregisters the ServiceMonitor, so that it will release any active bindings. If the
-     * ServiceMonitor is currently bound, this will result in one final
-     * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
-     * (but which is guaranteed to occur before any further
-     * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
-     * call to {@link #register()}).
-     */
-    void unregister();
-
-    /**
-     * Runs the given binder operation on the currently bound service (if available). The operation
-     * will always fail if the ServiceMonitor is not currently registered.
-     */
-    void runOnBinder(BinderOperation operation);
-
-    /**
-     * Dumps ServiceMonitor information.
-     */
-    void dump(PrintWriter pw);
-}
diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java
deleted file mode 100644
index d0d6c3b..0000000
--- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.servicemonitor;
-
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor.BoundServiceInfo;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Implementation of ServiceMonitor. Keeping the implementation separate from the interface allows
- * us to store the generic relationship between the service provider and the service listener, while
- * hiding the generics from clients, simplifying the API.
- */
-class ServiceMonitorImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceMonitor,
-        ServiceChangedListener {
-
-    private static final String TAG = "ServiceMonitor";
-    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-    private static final long RETRY_DELAY_MS = 15 * 1000;
-
-    // This is the same as Context.BIND_NOT_VISIBLE.
-    private static final int BIND_NOT_VISIBLE = 0x40000000;
-
-    final Context mContext;
-    final Handler mHandler;
-    final Executor mExecutor;
-    final String mTag;
-    final ServiceProvider<TBoundServiceInfo> mServiceProvider;
-    final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
-
-    private final PackageWatcher mPackageWatcher = new PackageWatcher() {
-        @Override
-        public void onSomePackagesChanged() {
-            onServiceChanged(false);
-        }
-    };
-
-    @GuardedBy("this")
-    private boolean mRegistered = false;
-    @GuardedBy("this")
-    private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
-
-    ServiceMonitorImpl(Context context, Handler handler, Executor executor, String tag,
-            ServiceProvider<TBoundServiceInfo> serviceProvider,
-            ServiceListener<? super TBoundServiceInfo> serviceListener) {
-        mContext = context;
-        mExecutor = executor;
-        mHandler = handler;
-        mTag = tag;
-        mServiceProvider = serviceProvider;
-        mServiceListener = serviceListener;
-    }
-
-    @Override
-    public boolean checkServiceResolves() {
-        return mServiceProvider.hasMatchingService();
-    }
-
-    @Override
-    public synchronized void register() {
-        Preconditions.checkState(!mRegistered);
-
-        mRegistered = true;
-        mPackageWatcher.register(mContext, /*externalStorage=*/ true, mHandler);
-        mServiceProvider.register(this);
-
-        onServiceChanged(false);
-    }
-
-    @Override
-    public synchronized void unregister() {
-        Preconditions.checkState(mRegistered);
-
-        mServiceProvider.unregister();
-        mPackageWatcher.unregister();
-        mRegistered = false;
-
-        onServiceChanged(false);
-    }
-
-    @Override
-    public synchronized void onServiceChanged() {
-        onServiceChanged(false);
-    }
-
-    @Override
-    public synchronized void runOnBinder(BinderOperation operation) {
-        MyServiceConnection serviceConnection = mServiceConnection;
-        mHandler.post(() -> serviceConnection.runOnBinder(operation));
-    }
-
-    synchronized void onServiceChanged(boolean forceRebind) {
-        TBoundServiceInfo newBoundServiceInfo;
-        if (mRegistered) {
-            newBoundServiceInfo = mServiceProvider.getServiceInfo();
-        } else {
-            newBoundServiceInfo = null;
-        }
-
-        if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
-                newBoundServiceInfo)) {
-            Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo);
-            MyServiceConnection oldServiceConnection = mServiceConnection;
-            MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
-            mServiceConnection = newServiceConnection;
-            mHandler.post(() -> {
-                oldServiceConnection.unbind();
-                newServiceConnection.bind();
-            });
-        }
-    }
-
-    @Override
-    public String toString() {
-        MyServiceConnection serviceConnection;
-        synchronized (this) {
-            serviceConnection = mServiceConnection;
-        }
-
-        return serviceConnection.getBoundServiceInfo().toString();
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        MyServiceConnection serviceConnection;
-        synchronized (this) {
-            serviceConnection = mServiceConnection;
-        }
-
-        pw.println("target service=" + serviceConnection.getBoundServiceInfo());
-        pw.println("connected=" + serviceConnection.isConnected());
-    }
-
-    // runs on the handler thread, and expects most of its methods to be called from that thread
-    private class MyServiceConnection implements ServiceConnection {
-
-        private final @Nullable TBoundServiceInfo mBoundServiceInfo;
-
-        // volatile so that isConnected can be called from any thread easily
-        private volatile @Nullable IBinder mBinder;
-        private @Nullable Runnable mRebinder;
-
-        MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
-            mBoundServiceInfo = boundServiceInfo;
-        }
-
-        // may be called from any thread
-        @Nullable TBoundServiceInfo getBoundServiceInfo() {
-            return mBoundServiceInfo;
-        }
-
-        // may be called from any thread
-        boolean isConnected() {
-            return mBinder != null;
-        }
-
-        void bind() {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-            if (mBoundServiceInfo == null) {
-                return;
-            }
-
-            if (D) {
-                Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
-            }
-
-            Intent bindIntent = new Intent(mBoundServiceInfo.getAction())
-                    .setComponent(mBoundServiceInfo.getComponentName());
-            if (!mContext.bindService(bindIntent,
-                    BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
-                    mExecutor, this)) {
-                Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
-                mRebinder = this::bind;
-                mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
-            } else {
-                mRebinder = null;
-            }
-        }
-
-        void unbind() {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-            if (mBoundServiceInfo == null) {
-                return;
-            }
-
-            if (D) {
-                Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
-            }
-
-            if (mRebinder != null) {
-                mHandler.removeCallbacks(mRebinder);
-                mRebinder = null;
-            } else {
-                mContext.unbindService(this);
-            }
-
-            onServiceDisconnected(mBoundServiceInfo.getComponentName());
-        }
-
-        void runOnBinder(BinderOperation operation) {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-            if (mBinder == null) {
-                operation.onError();
-                return;
-            }
-
-            try {
-                operation.run(mBinder);
-            } catch (RuntimeException | RemoteException e) {
-                // binders may propagate some specific non-RemoteExceptions from the other side
-                // through the binder as well - we cannot allow those to crash the system server
-                Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
-                operation.onError();
-            }
-        }
-
-        @Override
-        public final void onServiceConnected(ComponentName component, IBinder binder) {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-            Preconditions.checkState(mBinder == null);
-
-            Log.i(TAG, "[" + mTag + "] connected to " + component.toShortString());
-
-            mBinder = binder;
-
-            if (mServiceListener != null) {
-                try {
-                    mServiceListener.onBind(binder, mBoundServiceInfo);
-                } catch (RuntimeException | RemoteException e) {
-                    // binders may propagate some specific non-RemoteExceptions from the other side
-                    // through the binder as well - we cannot allow those to crash the system server
-                    Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
-                }
-            }
-        }
-
-        @Override
-        public final void onServiceDisconnected(ComponentName component) {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-            if (mBinder == null) {
-                return;
-            }
-
-            Log.i(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
-
-            mBinder = null;
-            if (mServiceListener != null) {
-                mServiceListener.onUnbind();
-            }
-        }
-
-        @Override
-        public final void onBindingDied(ComponentName component) {
-            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-            Log.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
-
-            // introduce a small delay to prevent spamming binding over and over, since the likely
-            // cause of a binding dying is some package event that may take time to recover from
-            mHandler.postDelayed(() -> onServiceChanged(true), 500);
-        }
-
-        @Override
-        public final void onNullBinding(ComponentName component) {
-            Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
deleted file mode 100644
index 0695b5f..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-/**
- * String constant for half sheet.
- */
-public class Constant {
-
-    /**
-     * Value represents true for {@link android.provider.Settings.Secure}
-     */
-    public static final int SETTINGS_TRUE_VALUE = 1;
-
-    /**
-     * Tag for Fast Pair service related logs.
-     */
-    public static final String TAG = "FastPairService";
-
-    public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
-    public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
-    public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL =
-            "com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL";
-    public static final String EXTRA_HALF_SHEET_INFO =
-            "com.android.nearby.halfsheet.HALF_SHEET";
-    public static final String EXTRA_HALF_SHEET_TYPE =
-            "com.android.nearby.halfsheet.HALF_SHEET_TYPE";
-    public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING";
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
deleted file mode 100644
index 2ecce47..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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.server.nearby.fastpair;
-
-import static com.android.server.nearby.fastpair.Constant.TAG;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import android.accounts.Account;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.nearby.FastPairDevice;
-import android.nearby.NearbyDevice;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.nearby.common.ble.decode.FastPairDecoder;
-import com.android.server.nearby.common.ble.util.RangingUtils;
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
-import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.provider.FastPairDataProvider;
-import com.android.server.nearby.util.DataUtils;
-import com.android.server.nearby.util.Hex;
-
-import java.util.List;
-
-import service.proto.Cache;
-import service.proto.Data;
-import service.proto.Rpcs;
-
-/**
- * Handler that handle fast pair related broadcast.
- */
-public class FastPairAdvHandler {
-    Context mContext;
-    String mBleAddress;
-    // Need to be deleted after notification manager in use.
-    private boolean mIsFirst = false;
-    private FastPairDataProvider mPairDataProvider;
-    private static final double NEARBY_DISTANCE_THRESHOLD = 0.6;
-
-    /** The types about how the bloomfilter is processed. */
-    public enum ProcessBloomFilterType {
-        IGNORE, // The bloomfilter is not handled. e.g. distance is too far away.
-        CACHE, // The bloomfilter is recognized in the local cache.
-        FOOTPRINT, // Need to check the bloomfilter from the footprints.
-        ACCOUNT_KEY_HIT // The specified account key was hit the bloom filter.
-    }
-
-    /**
-     * Constructor function.
-     */
-    public FastPairAdvHandler(Context context) {
-        mContext = context;
-    }
-
-    @VisibleForTesting
-    FastPairAdvHandler(Context context, FastPairDataProvider dataProvider) {
-        mContext = context;
-        mPairDataProvider = dataProvider;
-    }
-
-    /**
-     * Handles all of the scanner result. Fast Pair will handle model id broadcast bloomfilter
-     * broadcast and battery level broadcast.
-     */
-    public void handleBroadcast(NearbyDevice device) {
-        FastPairDevice fastPairDevice = (FastPairDevice) device;
-        mBleAddress = fastPairDevice.getBluetoothAddress();
-        if (mPairDataProvider == null) {
-            mPairDataProvider = FastPairDataProvider.getInstance();
-        }
-        if (mPairDataProvider == null) {
-            return;
-        }
-
-        if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
-            byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
-            // Use api to get anti spoofing key from model id.
-            try {
-                List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
-                Rpcs.GetObservedDeviceResponse response =
-                        mPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(model);
-                if (response == null) {
-                    Log.e(TAG, "server does not have model id "
-                            + Hex.bytesToStringLowercase(model));
-                    return;
-                }
-                // Check the distance of the device if the distance is larger than the threshold
-                // do not show half sheet.
-                if (!isNearby(fastPairDevice.getRssi(),
-                        response.getDevice().getBleTxPower() == 0 ? fastPairDevice.getTxPower()
-                                : response.getDevice().getBleTxPower())) {
-                    return;
-                }
-                Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
-                        DataUtils.toScanFastPairStoreItem(
-                                response, mBleAddress,
-                                accountList.isEmpty() ? null : accountList.get(0).name));
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
-            }
-        } else {
-            // Start to process bloom filter
-            try {
-                List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
-                byte[] bloomFilterByteArray = FastPairDecoder
-                        .getBloomFilter(fastPairDevice.getData());
-                byte[] bloomFilterSalt = FastPairDecoder
-                        .getBloomFilterSalt(fastPairDevice.getData());
-                if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
-                    return;
-                }
-                for (Account account : accountList) {
-                    List<Data.FastPairDeviceWithAccountKey> listDevices =
-                            mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
-                    Data.FastPairDeviceWithAccountKey recognizedDevice =
-                            findRecognizedDevice(listDevices,
-                                    new BloomFilter(bloomFilterByteArray,
-                                            new FastPairBloomFilterHasher()), bloomFilterSalt);
-
-                    if (recognizedDevice != null) {
-                        Log.d(TAG, "find matched device show notification to remind"
-                                + " user to pair");
-                        // Check the distance of the device if the distance is larger than the
-                        // threshold
-                        // do not show half sheet.
-                        if (!isNearby(fastPairDevice.getRssi(),
-                                recognizedDevice.getDiscoveryItem().getTxPower() == 0
-                                        ? fastPairDevice.getTxPower()
-                                        : recognizedDevice.getDiscoveryItem().getTxPower())) {
-                            return;
-                        }
-                        // Check if the device is already paired
-                        List<Cache.StoredFastPairItem> storedFastPairItemList =
-                                Locator.get(mContext, FastPairCacheManager.class)
-                                        .getAllSavedStoredFastPairItem();
-                        Cache.StoredFastPairItem recognizedStoredFastPairItem =
-                                findRecognizedDeviceFromCachedItem(storedFastPairItemList,
-                                        new BloomFilter(bloomFilterByteArray,
-                                                new FastPairBloomFilterHasher()), bloomFilterSalt);
-                        if (recognizedStoredFastPairItem != null) {
-                            // The bloomfilter is recognized in the cache so the device is paired
-                            // before
-                            Log.d(TAG, "bloom filter is recognized in the cache");
-                            continue;
-                        } else {
-                            Log.d(TAG, "bloom filter is recognized not paired before should"
-                                    + "show subsequent pairing notification");
-                            if (mIsFirst) {
-                                mIsFirst = false;
-                                // Get full info from api the initial request will only return
-                                // part of the info due to size limit.
-                                List<Data.FastPairDeviceWithAccountKey> resList =
-                                        mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
-                                                List.of(recognizedDevice.getAccountKey()
-                                                        .toByteArray()));
-                                if (resList != null && resList.size() > 0) {
-                                    //Saved device from footprint does not have ble address so
-                                    // fill ble address with current scan result.
-                                    Cache.StoredDiscoveryItem storedDiscoveryItem =
-                                            resList.get(0).getDiscoveryItem().toBuilder()
-                                                    .setMacAddress(
-                                                            fastPairDevice.getBluetoothAddress())
-                                                    .build();
-                                    Locator.get(mContext, FastPairController.class).pair(
-                                            new DiscoveryItem(mContext, storedDiscoveryItem),
-                                            resList.get(0).getAccountKey().toByteArray(),
-                                            /** companionApp=*/null);
-                                }
-                            }
-                        }
-
-                        return;
-                    }
-                }
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
-            }
-
-        }
-    }
-
-    /**
-     * Checks the bloom filter to see if any of the devices are recognized and should have a
-     * notification displayed for them. A device is recognized if the account key + salt combination
-     * is inside the bloom filter.
-     */
-    @Nullable
-    static Data.FastPairDeviceWithAccountKey findRecognizedDevice(
-            List<Data.FastPairDeviceWithAccountKey> devices, BloomFilter bloomFilter, byte[] salt) {
-        Log.d(TAG, "saved devices size in the account is " + devices.size());
-        for (Data.FastPairDeviceWithAccountKey device : devices) {
-            if (device.getAccountKey().toByteArray() == null || salt == null) {
-                return null;
-            }
-            byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
-            StringBuilder sb = new StringBuilder();
-            for (byte b : rotatedKey) {
-                sb.append(b);
-            }
-            if (bloomFilter.possiblyContains(rotatedKey)) {
-                Log.d(TAG, "match " + sb.toString());
-                return device;
-            } else {
-                Log.d(TAG, "not match " + sb.toString());
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    static Cache.StoredFastPairItem findRecognizedDeviceFromCachedItem(
-            List<Cache.StoredFastPairItem> devices, BloomFilter bloomFilter, byte[] salt) {
-        for (Cache.StoredFastPairItem device : devices) {
-            if (device.getAccountKey().toByteArray() == null || salt == null) {
-                return null;
-            }
-            byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
-            if (bloomFilter.possiblyContains(rotatedKey)) {
-                return device;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Check the device distance for certain rssi value.
-     */
-    boolean isNearby(int rssi, int txPower) {
-        return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD;
-    }
-
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
deleted file mode 100644
index e1db7e5..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import static com.google.common.primitives.Bytes.concat;
-
-import android.accounts.Account;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.nearby.FastPairDevice;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress;
-import com.android.server.nearby.common.eventloop.Annotations;
-import com.android.server.nearby.common.eventloop.EventLoop;
-import com.android.server.nearby.common.eventloop.NamedRunnable;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
-import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
-import com.android.server.nearby.provider.FastPairDataProvider;
-
-import com.google.common.hash.Hashing;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-import service.proto.Cache;
-
-/**
- * FastPair controller after get the info from intent handler Fast Pair controller is responsible
- * for pairing control.
- */
-public class FastPairController {
-    private static final String TAG = "FastPairController";
-    private final Context mContext;
-    private final EventLoop mEventLoop;
-    private final FastPairCacheManager mFastPairCacheManager;
-    private final FootprintsDeviceManager mFootprintsDeviceManager;
-    private boolean mIsFastPairing = false;
-    // boolean flag whether upload to footprint or not.
-    private boolean mShouldUpload = false;
-    @Nullable
-    private Callback mCallback;
-
-    public FastPairController(Context context) {
-        mContext = context;
-        mEventLoop = Locator.get(mContext, EventLoop.class);
-        mFastPairCacheManager = Locator.get(mContext, FastPairCacheManager.class);
-        mFootprintsDeviceManager = Locator.get(mContext, FootprintsDeviceManager.class);
-    }
-
-    /**
-     * Should be called on create lifecycle.
-     */
-    @WorkerThread
-    public void onCreate() {
-        mEventLoop.postRunnable(new NamedRunnable("FastPairController::InitializeScanner") {
-            @Override
-            public void run() {
-                // init scanner here and start scan.
-            }
-        });
-    }
-
-    /**
-     * Should be called on destroy lifecycle.
-     */
-    @WorkerThread
-    public void onDestroy() {
-        mEventLoop.postRunnable(new NamedRunnable("FastPairController::DestroyScanner") {
-            @Override
-            public void run() {
-                // Unregister scanner from here
-            }
-        });
-    }
-
-    /**
-     * Pairing function.
-     */
-    public void pair(FastPairDevice fastPairDevice) {
-        byte[] discoveryItem = fastPairDevice.getData();
-        String modelId = fastPairDevice.getModelId();
-
-        Log.v(TAG, "pair: fastPairDevice " + fastPairDevice);
-        mEventLoop.postRunnable(
-                new NamedRunnable("fastPairWith=" + modelId) {
-                    @Override
-                    public void run() {
-                        try {
-                            DiscoveryItem item = new DiscoveryItem(mContext,
-                                    Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
-                            if (TextUtils.isEmpty(item.getMacAddress())) {
-                                Log.w(TAG, "There is no mac address in the DiscoveryItem,"
-                                        + " ignore pairing");
-                                return;
-                            }
-                            // Check enabled state to prevent multiple pair attempts if we get the
-                            // intent more than once (this can happen due to an Android platform
-                            // bug - b/31459521).
-                            if (item.getState()
-                                    != Cache.StoredDiscoveryItem.State.STATE_ENABLED) {
-                                Log.d(TAG, "Incorrect state, ignore pairing");
-                                return;
-                            }
-                            boolean useLargeNotifications =
-                                    item.getAuthenticationPublicKeySecp256R1() != null;
-                            FastPairNotificationManager fastPairNotificationManager =
-                                    new FastPairNotificationManager(mContext, item,
-                                            useLargeNotifications);
-                            FastPairHalfSheetManager fastPairHalfSheetManager =
-                                    Locator.get(mContext, FastPairHalfSheetManager.class);
-                            mFastPairCacheManager.saveDiscoveryItem(item);
-
-                            PairingProgressHandlerBase pairingProgressHandlerBase =
-                                    PairingProgressHandlerBase.create(
-                                            mContext,
-                                            item,
-                                            /* companionApp= */ null,
-                                            /* accountKey= */ null,
-                                            mFootprintsDeviceManager,
-                                            fastPairNotificationManager,
-                                            fastPairHalfSheetManager,
-                                            /* isRetroactivePair= */ false);
-
-                            pair(item,
-                                    /* accountKey= */ null,
-                                    /* companionApp= */ null,
-                                    pairingProgressHandlerBase);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG,
-                                    "Error parsing serialized discovery item with size "
-                                            + discoveryItem.length);
-                        }
-                    }
-                });
-    }
-
-    /**
-     * Subsequent pairing entry.
-     */
-    public void pair(DiscoveryItem item,
-            @Nullable byte[] accountKey,
-            @Nullable String companionApp) {
-        FastPairNotificationManager fastPairNotificationManager =
-                new FastPairNotificationManager(mContext, item, false);
-        FastPairHalfSheetManager fastPairHalfSheetManager =
-                Locator.get(mContext, FastPairHalfSheetManager.class);
-        PairingProgressHandlerBase pairingProgressHandlerBase =
-                PairingProgressHandlerBase.create(
-                        mContext,
-                        item,
-                        /* companionApp= */ null,
-                        /* accountKey= */ accountKey,
-                        mFootprintsDeviceManager,
-                        fastPairNotificationManager,
-                        fastPairHalfSheetManager,
-                        /* isRetroactivePair= */ false);
-        pair(item, accountKey, companionApp, pairingProgressHandlerBase);
-    }
-    /**
-     * Pairing function
-     */
-    @Annotations.EventThread
-    public void pair(
-            DiscoveryItem item,
-            @Nullable byte[] accountKey,
-            @Nullable String companionApp,
-            PairingProgressHandlerBase pairingProgressHandlerBase) {
-        if (mIsFastPairing) {
-            Log.d(TAG, "FastPair: fastpairing, skip pair request");
-            return;
-        }
-        mIsFastPairing = true;
-        Log.d(TAG, "FastPair: start pair");
-
-        // Hide all "tap to pair" notifications until after the flow completes.
-        mEventLoop.removeRunnable(mReEnableAllDeviceItemsRunnable);
-        if (mCallback != null) {
-            mCallback.fastPairUpdateDeviceItemsEnabled(false);
-        }
-
-        Future<Void> task =
-                FastPairManager.pair(
-                        Executors.newSingleThreadExecutor(),
-                        mContext,
-                        item,
-                        accountKey,
-                        companionApp,
-                        mFootprintsDeviceManager,
-                        pairingProgressHandlerBase);
-        mIsFastPairing = false;
-    }
-
-    /** Fixes a companion app package name with extra spaces. */
-    private static String trimCompanionApp(String companionApp) {
-        return companionApp == null ? null : companionApp.trim();
-    }
-
-    /**
-     * Function to handle when scanner find bloomfilter.
-     */
-    @Annotations.EventThread
-    public FastPairAdvHandler.ProcessBloomFilterType onBloomFilterDetect(FastPairAdvHandler handler,
-            boolean advertiseInRange) {
-        if (mIsFastPairing) {
-            return FastPairAdvHandler.ProcessBloomFilterType.IGNORE;
-        }
-        // Check if the device is in the cache or footprint.
-        return FastPairAdvHandler.ProcessBloomFilterType.CACHE;
-    }
-
-    /**
-     * Add newly paired device info to footprint
-     */
-    @WorkerThread
-    public void addDeviceToFootprint(String publicAddress, byte[] accountKey,
-            DiscoveryItem discoveryItem) {
-        if (!mShouldUpload) {
-            return;
-        }
-        Log.d(TAG, "upload device to footprint");
-        FastPairManager.processBackgroundTask(() -> {
-            Cache.StoredDiscoveryItem storedDiscoveryItem =
-                    prepareStoredDiscoveryItemForFootprints(discoveryItem);
-            byte[] hashValue =
-                    Hashing.sha256()
-                            .hashBytes(
-                                    concat(accountKey, BluetoothAddress.decode(publicAddress)))
-                            .asBytes();
-            FastPairUploadInfo uploadInfo =
-                    new FastPairUploadInfo(storedDiscoveryItem, ByteString.copyFrom(accountKey),
-                            ByteString.copyFrom(hashValue));
-            // account data place holder here
-            try {
-                FastPairDataProvider fastPairDataProvider = FastPairDataProvider.getInstance();
-                if (fastPairDataProvider == null) {
-                    return;
-                }
-                List<Account> accountList = fastPairDataProvider.loadFastPairEligibleAccounts();
-                if (accountList.size() > 0) {
-                    fastPairDataProvider.optIn(accountList.get(0));
-                    fastPairDataProvider.upload(accountList.get(0), uploadInfo);
-                }
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
-            }
-        });
-    }
-
-    @Nullable
-    private Cache.StoredDiscoveryItem getStoredDiscoveryItemFromAddressForFootprints(
-            String bleAddress) {
-
-        List<DiscoveryItem> discoveryItems = new ArrayList<>();
-        //cacheManager.getAllDiscoveryItems();
-        for (DiscoveryItem discoveryItem : discoveryItems) {
-            if (bleAddress.equals(discoveryItem.getMacAddress())) {
-                return prepareStoredDiscoveryItemForFootprints(discoveryItem);
-            }
-        }
-        return null;
-    }
-
-    static Cache.StoredDiscoveryItem prepareStoredDiscoveryItemForFootprints(
-            DiscoveryItem discoveryItem) {
-        Cache.StoredDiscoveryItem.Builder storedDiscoveryItem =
-                discoveryItem.getCopyOfStoredItem().toBuilder();
-        // Strip the mac address so we aren't storing it in the cloud and ensure the item always
-        // starts as enabled and in a good state.
-        storedDiscoveryItem.clearMacAddress();
-
-        return storedDiscoveryItem.build();
-    }
-
-    /**
-     * FastPairConnection will check whether write account key result if the account key is
-     * generated change the parameter.
-     */
-    public void setShouldUpload(boolean shouldUpload) {
-        mShouldUpload = shouldUpload;
-    }
-
-    private final NamedRunnable mReEnableAllDeviceItemsRunnable =
-            new NamedRunnable("reEnableAllDeviceItems") {
-                @Override
-                public void run() {
-                    if (mCallback != null) {
-                        mCallback.fastPairUpdateDeviceItemsEnabled(true);
-                    }
-                }
-            };
-
-    interface Callback {
-        void fastPairUpdateDeviceItemsEnabled(boolean enabled);
-    }
-}
\ No newline at end of file
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
deleted file mode 100644
index f368080..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import static com.android.server.nearby.fastpair.Constant.TAG;
-
-import android.annotation.Nullable;
-import android.annotation.WorkerThread;
-import android.app.KeyguardManager;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.nearby.FastPairDevice;
-import android.nearby.NearbyDevice;
-import android.nearby.NearbyManager;
-import android.nearby.ScanCallback;
-import android.nearby.ScanRequest;
-import android.net.Uri;
-import android.provider.Settings;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.server.nearby.common.ble.decode.FastPairDecoder;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection;
-import com.android.server.nearby.common.bluetooth.fastpair.PairingException;
-import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
-import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException;
-import com.android.server.nearby.common.bluetooth.fastpair.SimpleBroadcastReceiver;
-import com.android.server.nearby.common.eventloop.Annotations;
-import com.android.server.nearby.common.eventloop.EventLoop;
-import com.android.server.nearby.common.eventloop.NamedRunnable;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
-import com.android.server.nearby.util.ForegroundThread;
-import com.android.server.nearby.util.Hex;
-
-import com.google.common.collect.ImmutableList;
-import com.google.protobuf.ByteString;
-
-import java.security.GeneralSecurityException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import service.proto.Cache;
-import service.proto.Rpcs;
-
-/**
- * FastPairManager is the class initiated in nearby service to handle Fast Pair related
- * work.
- */
-
-public class FastPairManager {
-
-    private static final String ACTION_PREFIX = UserActionHandler.PREFIX;
-    private static final int WAIT_FOR_UNLOCK_MILLIS = 5000;
-
-    /** A notification ID which should be dismissed */
-    public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
-    public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
-
-    private static Executor sFastPairExecutor;
-
-    private ContentObserver mFastPairScanChangeContentObserver = null;
-
-    final LocatorContextWrapper mLocatorContextWrapper;
-    final IntentFilter mIntentFilter;
-    final Locator mLocator;
-    private boolean mScanEnabled;
-
-    private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)
-                    || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
-                Log.d(TAG, "onReceive: ACTION_SCREEN_ON or boot complete.");
-                invalidateScan();
-            } else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
-                processBluetoothConnectionEvent(intent);
-            }
-        }
-    };
-
-    public FastPairManager(LocatorContextWrapper contextWrapper) {
-        mLocatorContextWrapper = contextWrapper;
-        mIntentFilter = new IntentFilter();
-        mLocator = mLocatorContextWrapper.getLocator();
-        mLocator.bind(new FastPairModule());
-        Rpcs.GetObservedDeviceResponse getObservedDeviceResponse =
-                Rpcs.GetObservedDeviceResponse.newBuilder().build();
-    }
-
-    final ScanCallback mScanCallback = new ScanCallback() {
-        @Override
-        public void onDiscovered(@NonNull NearbyDevice device) {
-            Locator.get(mLocatorContextWrapper, FastPairAdvHandler.class).handleBroadcast(device);
-        }
-
-        @Override
-        public void onUpdated(@NonNull NearbyDevice device) {
-            FastPairDevice fastPairDevice = (FastPairDevice) device;
-            byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d(TAG, "update model id" + Hex.bytesToStringLowercase(modelArray));
-        }
-
-        @Override
-        public void onLost(@NonNull NearbyDevice device) {
-            FastPairDevice fastPairDevice = (FastPairDevice) device;
-            byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
-            Log.d(TAG, "lost model id" + Hex.bytesToStringLowercase(modelArray));
-        }
-    };
-
-    /**
-     * Function called when nearby service start.
-     */
-    public void initiate() {
-        mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
-        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        mIntentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
-
-        mLocatorContextWrapper.getContext()
-                .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
-
-        Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
-        // Default false for now.
-        mScanEnabled = NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext());
-        registerFastPairScanChangeContentObserver(mLocatorContextWrapper.getContentResolver());
-    }
-
-    /**
-     * Function to free up fast pair resource.
-     */
-    public void cleanUp() {
-        mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver);
-        if (mFastPairScanChangeContentObserver != null) {
-            mLocatorContextWrapper.getContentResolver().unregisterContentObserver(
-                    mFastPairScanChangeContentObserver);
-        }
-    }
-
-    /**
-     * Starts fast pair process.
-     */
-    @Annotations.EventThread
-    public static Future<Void> pair(
-            ExecutorService executor,
-            Context context,
-            DiscoveryItem item,
-            @Nullable byte[] accountKey,
-            @Nullable String companionApp,
-            FootprintsDeviceManager footprints,
-            PairingProgressHandlerBase pairingProgressHandlerBase) {
-        return executor.submit(
-                () -> pairInternal(context, item, companionApp, accountKey, footprints,
-                        pairingProgressHandlerBase), /* result= */ null);
-    }
-
-    /**
-     * Starts fast pair
-     */
-    @WorkerThread
-    public static void pairInternal(
-            Context context,
-            DiscoveryItem item,
-            @Nullable String companionApp,
-            @Nullable byte[] accountKey,
-            FootprintsDeviceManager footprints,
-            PairingProgressHandlerBase pairingProgressHandlerBase) {
-        FastPairHalfSheetManager fastPairHalfSheetManager =
-                Locator.get(context, FastPairHalfSheetManager.class);
-        try {
-            pairingProgressHandlerBase.onPairingStarted();
-            if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) {
-                // Do nothing due to we are not showing the status notification in some pairing
-                // types, e.g. the retroactive pairing.
-            } else {
-                // If the screen is locked when the user taps to pair, the screen will unlock. We
-                // must wait for the unlock to complete before showing the status notification, or
-                // it won't be heads-up.
-                pairingProgressHandlerBase.onWaitForScreenUnlock();
-                waitUntilScreenIsUnlocked(context);
-                pairingProgressHandlerBase.onScreenUnlocked();
-            }
-            BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(context);
-
-            boolean isBluetoothEnabled = bluetoothAdapter != null && bluetoothAdapter.isEnabled();
-            if (!isBluetoothEnabled) {
-                if (bluetoothAdapter == null || !bluetoothAdapter.enable()) {
-                    Log.d(TAG, "FastPair: Failed to enable bluetooth");
-                    return;
-                }
-                Log.v(TAG, "FastPair: Enabling bluetooth for fast pair");
-
-                Locator.get(context, EventLoop.class)
-                        .postRunnable(
-                                new NamedRunnable("enableBluetoothToast") {
-                                    @Override
-                                    public void run() {
-                                        Log.d(TAG, "Enable bluetooth toast test");
-                                    }
-                                });
-                // Set up call back to call this function again once bluetooth has been
-                // enabled; this does not seem to be a problem as the device connects without a
-                // problem, but in theory the timeout also includes turning on bluetooth now.
-            }
-
-            pairingProgressHandlerBase.onReadyToPair();
-
-            String modelId = item.getTriggerId();
-            Preferences.Builder prefsBuilder =
-                    Preferences.builderFromGmsLog()
-                            .setEnableBrEdrHandover(false)
-                            .setIgnoreDiscoveryError(true);
-            pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
-            if (item.getFastPairInformation() != null) {
-                prefsBuilder.setSkipConnectingProfiles(
-                        item.getFastPairInformation().getDataOnlyConnection());
-            }
-            // When add watch and auto device needs to change the config
-            prefsBuilder.setRejectMessageAccess(true);
-            prefsBuilder.setRejectPhonebookAccess(true);
-            prefsBuilder.setHandlePasskeyConfirmationByUi(false);
-
-            FastPairConnection connection = new FastPairDualConnection(
-                    context, item.getMacAddress(),
-                    prefsBuilder.build(),
-                    null);
-            pairingProgressHandlerBase.onPairingSetupCompleted();
-
-            FastPairConnection.SharedSecret sharedSecret;
-            if ((accountKey != null || item.getAuthenticationPublicKeySecp256R1() != null)) {
-                sharedSecret =
-                        connection.pair(
-                                accountKey != null ? accountKey
-                                        : item.getAuthenticationPublicKeySecp256R1());
-                if (accountKey == null) {
-                    // Account key is null so it is initial pairing
-                    if (sharedSecret != null) {
-                        Locator.get(context, FastPairController.class).addDeviceToFootprint(
-                                connection.getPublicAddress(), sharedSecret.getKey(), item);
-                        cacheFastPairDevice(context, connection.getPublicAddress(),
-                                sharedSecret.getKey(), item);
-                    }
-                }
-            } else {
-                // Fast Pair one
-                connection.pair();
-            }
-            // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
-            // pairingProgressHandlerBase class.
-            fastPairHalfSheetManager.showPairingSuccessHalfSheet(connection.getPublicAddress());
-            pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress());
-        } catch (BluetoothException
-                | InterruptedException
-                | ReflectionException
-                | TimeoutException
-                | ExecutionException
-                | PairingException
-                | GeneralSecurityException e) {
-            Log.e(TAG, "Failed to pair.", e);
-
-            // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
-            // pairingProgressHandlerBase class.
-            fastPairHalfSheetManager.showPairingFailed();
-            pairingProgressHandlerBase.onPairingFailed(e);
-        }
-    }
-
-    private static void cacheFastPairDevice(Context context, String publicAddress, byte[] key,
-            DiscoveryItem item) {
-        try {
-            Locator.get(context, EventLoop.class).postAndWait(
-                    new NamedRunnable("FastPairCacheDevice") {
-                        @Override
-                        public void run() {
-                            Cache.StoredFastPairItem storedFastPairItem =
-                                    Cache.StoredFastPairItem.newBuilder()
-                                            .setMacAddress(publicAddress)
-                                            .setAccountKey(ByteString.copyFrom(key))
-                                            .setModelId(item.getTriggerId())
-                                            .addAllFeatures(item.getFastPairInformation() == null
-                                                    ? ImmutableList.of() :
-                                                    item.getFastPairInformation().getFeaturesList())
-                                            .setDiscoveryItem(item.getCopyOfStoredItem())
-                                            .build();
-                            Locator.get(context, FastPairCacheManager.class)
-                                    .putStoredFastPairItem(storedFastPairItem);
-                        }
-                    }
-            );
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Fail to insert paired device into cache");
-        }
-    }
-
-    /** Checks if the pairing is initial pairing with fast pair 2.0 design. */
-    public static boolean isThroughFastPair2InitialPairing(
-            DiscoveryItem item, @Nullable byte[] accountKey) {
-        return accountKey == null && item.getAuthenticationPublicKeySecp256R1() != null;
-    }
-
-    private static void waitUntilScreenIsUnlocked(Context context)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
-
-        // KeyguardManager's isScreenLocked() counterintuitively returns false when the lock screen
-        // is showing if the user has set "swipe to unlock" (i.e. no required password, PIN, or
-        // pattern) So we use this method instead, which returns true when on the lock screen
-        // regardless.
-        if (keyguardManager.isKeyguardLocked()) {
-            Log.v(TAG, "FastPair: Screen is locked, waiting until unlocked "
-                    + "to show status notifications.");
-            try (SimpleBroadcastReceiver isUnlockedReceiver =
-                         SimpleBroadcastReceiver.oneShotReceiver(
-                                 context, FlagUtils.getPreferencesBuilder().build(),
-                                 Intent.ACTION_USER_PRESENT)) {
-                isUnlockedReceiver.await(WAIT_FOR_UNLOCK_MILLIS, TimeUnit.MILLISECONDS);
-            }
-        }
-    }
-
-    private void registerFastPairScanChangeContentObserver(ContentResolver resolver) {
-        mFastPairScanChangeContentObserver = new ContentObserver(ForegroundThread.getHandler()) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                super.onChange(selfChange, uri);
-                setScanEnabled(
-                        NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext()));
-            }
-        };
-        try {
-            resolver.registerContentObserver(
-                    Settings.Secure.getUriFor(NearbyManager.FAST_PAIR_SCAN_ENABLED),
-                    /* notifyForDescendants= */ false,
-                    mFastPairScanChangeContentObserver);
-        } catch (SecurityException e) {
-            Log.e(TAG, "Failed to register content observer for fast pair scan.", e);
-        }
-    }
-
-    /**
-     * Processed task in a background thread
-     */
-    @Annotations.EventThread
-    public static void processBackgroundTask(Runnable runnable) {
-        getExecutor().execute(runnable);
-    }
-
-    /**
-     * This function should only be called on main thread since there is no lock
-     */
-    private static Executor getExecutor() {
-        if (sFastPairExecutor != null) {
-            return sFastPairExecutor;
-        }
-        sFastPairExecutor = Executors.newSingleThreadExecutor();
-        return sFastPairExecutor;
-    }
-
-    /**
-     * Null when the Nearby Service is not available.
-     */
-    @Nullable
-    private NearbyManager getNearbyManager() {
-        return (NearbyManager) mLocatorContextWrapper
-                .getApplicationContext().getSystemService(Context.NEARBY_SERVICE);
-    }
-    private void setScanEnabled(boolean scanEnabled) {
-        if (mScanEnabled == scanEnabled) {
-            return;
-        }
-        mScanEnabled = scanEnabled;
-        invalidateScan();
-    }
-
-    /**
-     * Starts or stops scanning according to mAllowScan value.
-     */
-    private void invalidateScan() {
-        NearbyManager nearbyManager = getNearbyManager();
-        if (nearbyManager == null) {
-            Log.w(TAG, "invalidateScan: "
-                    + "failed to start or stop scannning because NearbyManager is null.");
-            return;
-        }
-        if (mScanEnabled) {
-            Log.v(TAG, "invalidateScan: scan is enabled");
-            nearbyManager.startScan(new ScanRequest.Builder()
-                            .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(),
-                    ForegroundThread.getExecutor(),
-                    mScanCallback);
-        } else {
-            Log.v(TAG, "invalidateScan: scan is disabled");
-            nearbyManager.stopScan(mScanCallback);
-        }
-    }
-
-    /**
-     * When certain device is forgotten we need to remove the info from database because the info
-     * is no longer useful.
-     */
-    private void processBluetoothConnectionEvent(Intent intent) {
-        int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
-                BluetoothDevice.ERROR);
-        if (bondState == BluetoothDevice.BOND_NONE) {
-            BluetoothDevice device =
-                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (device != null) {
-                Log.d("FastPairService", "Forget device detect");
-                processBackgroundTask(new Runnable() {
-                    @Override
-                    public void run() {
-                        mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class)
-                                .removeStoredFastPairItem(device.getAddress());
-                    }
-                });
-            }
-
-        }
-    }
-
-    /**
-     * Helper function to get bluetooth adapter.
-     */
-    @Nullable
-    public static BluetoothAdapter getBluetoothAdapter(Context context) {
-        BluetoothManager manager = context.getSystemService(BluetoothManager.class);
-        return manager == null ? null : manager.getAdapter();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
deleted file mode 100644
index d7946d1..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import android.content.Context;
-
-import com.android.server.nearby.common.eventloop.EventLoop;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.Module;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-
-/**
- * Module that associates all of the fast pair related singleton class
- */
-public class FastPairModule extends Module {
-    /**
-     * Initiate the class that needs to be singleton.
-     */
-    @Override
-    public void configure(Context context, Class<?> type, Locator locator) {
-        if (type.equals(FastPairCacheManager.class)) {
-            locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context));
-        } else if (type.equals(FootprintsDeviceManager.class)) {
-            locator.bind(FootprintsDeviceManager.class, new FootprintsDeviceManager());
-        } else if (type.equals(EventLoop.class)) {
-            locator.bind(EventLoop.class, EventLoop.newInstance("NearbyFastPair"));
-        } else if (type.equals(FastPairController.class)) {
-            locator.bind(FastPairController.class, new FastPairController(context));
-        } else if (type.equals(FastPairCacheManager.class)) {
-            locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context));
-        } else if (type.equals(FastPairHalfSheetManager.class)) {
-            locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager(context));
-        } else if (type.equals(FastPairAdvHandler.class)) {
-            locator.bind(FastPairAdvHandler.class, new FastPairAdvHandler(context));
-        } else if (type.equals(Clock.class)) {
-            locator.bind(Clock.class, new Clock() {
-                @Override
-                public ZoneId getZone() {
-                    return null;
-                }
-
-                @Override
-                public Clock withZone(ZoneId zone) {
-                    return null;
-                }
-
-                @Override
-                public Instant instant() {
-                    return null;
-                }
-            });
-        }
-
-    }
-
-    /**
-     * Clean up the singleton classes.
-     */
-    @Override
-    public void destroy(Context context, Class<?> type, Object instance) {
-        super.destroy(context, type, instance);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java b/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java
deleted file mode 100644
index 883a1f8..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import android.text.TextUtils;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableSet;
-
-/**
- * This is fast pair connection preference
- */
-public class FlagUtils {
-    private static final int GATT_OPERATION_TIME_OUT_SECOND = 10;
-    private static final int GATT_CONNECTION_TIME_OUT_SECOND = 15;
-    private static final int BLUETOOTH_TOGGLE_TIME_OUT_SECOND = 10;
-    private static final int BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND = 2;
-    private static final int CLASSIC_DISCOVERY_TIME_OUT_SECOND = 13;
-    private static final int NUM_DISCOVER_ATTEMPTS = 3;
-    private static final int DISCOVERY_RETRY_SLEEP_SECONDS = 1;
-    private static final int SDP_TIME_OUT_SECONDS = 10;
-    private static final int NUM_SDP_ATTEMPTS = 0;
-    private static final int NUM_CREATED_BOND_ATTEMPTS = 3;
-    private static final int NUM_CONNECT_ATTEMPT = 2;
-    private static final int NUM_WRITE_ACCOUNT_KEY_ATTEMPT = 3;
-    private static final boolean TOGGLE_BLUETOOTH_ON_FAILURE = false;
-    private static final boolean BLUETOOTH_STATE_POOLING = true;
-    private static final int BLUETOOTH_STATE_POOLING_MILLIS = 1000;
-    private static final int NUM_ATTEMPTS = 2;
-    private static final short BREDR_HANDOVER_DATA_CHARACTERISTIC_ID = 11265; // 0x2c01
-    private static final short BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID = 11266; // 0x2c02
-    private static final short TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID = 11267; // 0x2c03
-    private static final boolean WAIT_FOR_UUID_AFTER_BONDING = true;
-    private static final boolean RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE = true;
-    private static final int REMOVE_BOND_TIME_OUT_SECONDS = 5;
-    private static final int REMOVE_BOND_SLEEP_MILLIS = 1000;
-    private static final int CREATE_BOND_TIME_OUT_SECONDS = 15;
-    private static final int HIDE_CREATED_BOND_TIME_OUT_SECONDS = 40;
-    private static final int PROXY_TIME_OUT_SECONDS = 2;
-    private static final boolean REJECT_ACCESS = false;
-    private static final boolean ACCEPT_PASSKEY = true;
-    private static final int WRITE_ACCOUNT_KEY_SLEEP_MILLIS = 2000;
-    private static final boolean PROVIDER_INITIATE_BONDING = false;
-    private static final boolean SPECIFY_CREATE_BOND_TRANSPORT_TYPE = false;
-    private static final int CREATE_BOND_TRANSPORT_TYPE = 0;
-    private static final boolean KEEP_SAME_ACCOUNT_KEY_WRITE = true;
-    private static final boolean ENABLE_NAMING_CHARACTERISTIC = true;
-    private static final boolean CHECK_FIRMWARE_VERSION = true;
-    private static final int SDP_ATTEMPTS_AFTER_BONDED = 1;
-    private static final boolean SUPPORT_HID = false;
-    private static final boolean ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING = true;
-    private static final boolean ACCEPT_CONSENT_FOR_FP_ONE = true;
-    private static final int GATT_CONNECT_RETRY_TIMEOUT_MILLIS = 18000;
-    private static final boolean ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC = true;
-    private static final boolean ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR = true;
-    private static final boolean ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE = true;
-    private static final boolean CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE = true;
-    private static final boolean MORE_LOG_FOR_QUALITY = true;
-    private static final boolean RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE = true;
-    private static final int GATT_CONNECT_SHORT_TIMEOUT_MS = 7000;
-    private static final int GATT_CONNECTION_LONG_TIME_OUT_MS = 15000;
-    private static final int GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 1000;
-    private static final int ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS = 15000;
-    private static final int PAIRING_RETRY_DELAY_MS = 100;
-    private static final int HANDSHAKE_SHORT_TIMEOUT_MS = 3000;
-    private static final int HANDSHAKE_LONG_TIMEOUT_MS = 1000;
-    private static final int SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 5000;
-    private static final int SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 7000;
-    private static final int SECRET_HANDSHAKE_RETRY_ATTEMPTS = 3;
-    private static final int SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS = 15000;
-    private static final int SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS = 15000;
-    private static final boolean RETRY_SECRET_HANDSHAKE_TIMEOUT = false;
-    private static final boolean LOG_USER_MANUAL_RETRY = true;
-    private static final boolean ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION = false;
-    private static final boolean LOG_USER_MANUAL_CITY = true;
-    private static final boolean LOG_PAIR_WITH_CACHED_MODEL_ID = true;
-    private static final boolean DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE = false;
-
-    public static Preferences.Builder getPreferencesBuilder() {
-        return Preferences.builder()
-                .setGattOperationTimeoutSeconds(GATT_OPERATION_TIME_OUT_SECOND)
-                .setGattConnectionTimeoutSeconds(GATT_CONNECTION_TIME_OUT_SECOND)
-                .setBluetoothToggleTimeoutSeconds(BLUETOOTH_TOGGLE_TIME_OUT_SECOND)
-                .setBluetoothToggleSleepSeconds(BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND)
-                .setClassicDiscoveryTimeoutSeconds(CLASSIC_DISCOVERY_TIME_OUT_SECOND)
-                .setNumDiscoverAttempts(NUM_DISCOVER_ATTEMPTS)
-                .setDiscoveryRetrySleepSeconds(DISCOVERY_RETRY_SLEEP_SECONDS)
-                .setSdpTimeoutSeconds(SDP_TIME_OUT_SECONDS)
-                .setNumSdpAttempts(NUM_SDP_ATTEMPTS)
-                .setNumCreateBondAttempts(NUM_CREATED_BOND_ATTEMPTS)
-                .setNumConnectAttempts(NUM_CONNECT_ATTEMPT)
-                .setNumWriteAccountKeyAttempts(NUM_WRITE_ACCOUNT_KEY_ATTEMPT)
-                .setToggleBluetoothOnFailure(TOGGLE_BLUETOOTH_ON_FAILURE)
-                .setBluetoothStateUsesPolling(BLUETOOTH_STATE_POOLING)
-                .setBluetoothStatePollingMillis(BLUETOOTH_STATE_POOLING_MILLIS)
-                .setNumAttempts(NUM_ATTEMPTS)
-                .setBrHandoverDataCharacteristicId(BREDR_HANDOVER_DATA_CHARACTERISTIC_ID)
-                .setBluetoothSigDataCharacteristicId(BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID)
-                .setBrTransportBlockDataDescriptorId(TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID)
-                .setWaitForUuidsAfterBonding(WAIT_FOR_UUID_AFTER_BONDING)
-                .setReceiveUuidsAndBondedEventBeforeClose(
-                        RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE)
-                .setRemoveBondTimeoutSeconds(REMOVE_BOND_TIME_OUT_SECONDS)
-                .setRemoveBondSleepMillis(REMOVE_BOND_SLEEP_MILLIS)
-                .setCreateBondTimeoutSeconds(CREATE_BOND_TIME_OUT_SECONDS)
-                .setHidCreateBondTimeoutSeconds(HIDE_CREATED_BOND_TIME_OUT_SECONDS)
-                .setProxyTimeoutSeconds(PROXY_TIME_OUT_SECONDS)
-                .setRejectPhonebookAccess(REJECT_ACCESS)
-                .setRejectMessageAccess(REJECT_ACCESS)
-                .setRejectSimAccess(REJECT_ACCESS)
-                .setAcceptPasskey(ACCEPT_PASSKEY)
-                .setWriteAccountKeySleepMillis(WRITE_ACCOUNT_KEY_SLEEP_MILLIS)
-                .setProviderInitiatesBondingIfSupported(PROVIDER_INITIATE_BONDING)
-                .setAttemptDirectConnectionWhenPreviouslyBonded(true)
-                .setAutomaticallyReconnectGattWhenNeeded(true)
-                .setSkipDisconnectingGattBeforeWritingAccountKey(true)
-                .setIgnoreUuidTimeoutAfterBonded(true)
-                .setSpecifyCreateBondTransportType(SPECIFY_CREATE_BOND_TRANSPORT_TYPE)
-                .setCreateBondTransportType(CREATE_BOND_TRANSPORT_TYPE)
-                .setIncreaseIntentFilterPriority(true)
-                .setEvaluatePerformance(false)
-                .setKeepSameAccountKeyWrite(KEEP_SAME_ACCOUNT_KEY_WRITE)
-                .setEnableNamingCharacteristic(ENABLE_NAMING_CHARACTERISTIC)
-                .setEnableFirmwareVersionCharacteristic(CHECK_FIRMWARE_VERSION)
-                .setNumSdpAttemptsAfterBonded(SDP_ATTEMPTS_AFTER_BONDED)
-                .setSupportHidDevice(SUPPORT_HID)
-                .setEnablePairingWhileDirectlyConnecting(
-                        ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING)
-                .setAcceptConsentForFastPairOne(ACCEPT_CONSENT_FOR_FP_ONE)
-                .setGattConnectRetryTimeoutMillis(GATT_CONNECT_RETRY_TIMEOUT_MILLIS)
-                .setEnable128BitCustomGattCharacteristicsId(
-                        ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC)
-                .setEnableSendExceptionStepToValidator(ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR)
-                .setEnableAdditionalDataTypeWhenActionOverBle(
-                        ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE)
-                .setCheckBondStateWhenSkipConnectingProfiles(
-                        CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE)
-                .setMoreEventLogForQuality(MORE_LOG_FOR_QUALITY)
-                .setRetryGattConnectionAndSecretHandshake(
-                        RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE)
-                .setGattConnectShortTimeoutMs(GATT_CONNECT_SHORT_TIMEOUT_MS)
-                .setGattConnectLongTimeoutMs(GATT_CONNECTION_LONG_TIME_OUT_MS)
-                .setGattConnectShortTimeoutRetryMaxSpentTimeMs(
-                        GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
-                .setAddressRotateRetryMaxSpentTimeMs(ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS)
-                .setPairingRetryDelayMs(PAIRING_RETRY_DELAY_MS)
-                .setSecretHandshakeShortTimeoutMs(HANDSHAKE_SHORT_TIMEOUT_MS)
-                .setSecretHandshakeLongTimeoutMs(HANDSHAKE_LONG_TIMEOUT_MS)
-                .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(
-                        SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
-                .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(
-                        SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS)
-                .setSecretHandshakeRetryAttempts(SECRET_HANDSHAKE_RETRY_ATTEMPTS)
-                .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(
-                        SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS)
-                .setSignalLostRetryMaxSpentTimeMs(SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS)
-                .setGattConnectionAndSecretHandshakeNoRetryGattError(
-                        getGattConnectionAndSecretHandshakeNoRetryGattError())
-                .setRetrySecretHandshakeTimeout(RETRY_SECRET_HANDSHAKE_TIMEOUT)
-                .setLogUserManualRetry(LOG_USER_MANUAL_RETRY)
-                .setEnablePairFlowShowUiWithoutProfileConnection(
-                        ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION)
-                .setLogUserManualRetry(LOG_USER_MANUAL_CITY)
-                .setLogPairWithCachedModelId(LOG_PAIR_WITH_CACHED_MODEL_ID)
-                .setDirectConnectProfileIfModelIdInCache(
-                        DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE);
-    }
-
-    private static ImmutableSet<Integer> getGattConnectionAndSecretHandshakeNoRetryGattError() {
-        ImmutableSet.Builder<Integer> noRetryGattErrorsBuilder = ImmutableSet.builder();
-        // When GATT connection fail we will not retry on error code 257
-        for (String errorCode :
-                Splitter.on(",").split("257,")) {
-            if (!TextUtils.isDigitsOnly(errorCode)) {
-                continue;
-            }
-
-            try {
-                noRetryGattErrorsBuilder.add(Integer.parseInt(errorCode));
-            } catch (NumberFormatException e) {
-                // Ignore
-            }
-        }
-        return noRetryGattErrorsBuilder.build();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
deleted file mode 100644
index 674633d..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/UserActionHandler.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import com.android.server.nearby.common.fastpair.service.UserActionHandlerBase;
-
-/**
- * User action handler class.
- */
-public class UserActionHandler extends UserActionHandlerBase {
-
-    public static final String EXTRA_DISCOVERY_ITEM = PREFIX + "EXTRA_DISCOVERY_ITEM";
-    public static final String EXTRA_FAST_PAIR_SECRET = PREFIX + "EXTRA_FAST_PAIR_SECRET";
-    public static final String ACTION_FAST_PAIR = ACTION_PREFIX + "ACTION_FAST_PAIR";
-    public static final String EXTRA_PRIVATE_BLE_ADDRESS =
-            ACTION_PREFIX + "EXTRA_PRIVATE_BLE_ADDRESS";
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
deleted file mode 100644
index 6065f99..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.cache;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_FAST_PAIR_SECRET;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.server.nearby.common.ble.util.RangingUtils;
-import com.android.server.nearby.common.fastpair.IconUtils;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.URISyntaxException;
-import java.time.Clock;
-import java.util.Objects;
-
-import service.proto.Cache;
-
-/**
- * Wrapper class around StoredDiscoveryItem. A centralized place for methods related to
- * updating/parsing StoredDiscoveryItem.
- */
-public class DiscoveryItem implements Comparable<DiscoveryItem> {
-
-    private static final String ACTION_FAST_PAIR =
-            "com.android.server.nearby:ACTION_FAST_PAIR";
-    private static final int BEACON_STALENESS_MILLIS = 120000;
-    private static final int ITEM_EXPIRATION_MILLIS = 20000;
-    private static final int APP_INSTALL_EXPIRATION_MILLIS = 600000;
-    private static final int ITEM_DELETABLE_MILLIS = 15000;
-
-    private final FastPairCacheManager mFastPairCacheManager;
-    private final Clock mClock;
-
-    private Cache.StoredDiscoveryItem mStoredDiscoveryItem;
-
-    /** IntDef for StoredDiscoveryItem.State */
-    @IntDef({
-            Cache.StoredDiscoveryItem.State.STATE_ENABLED_VALUE,
-            Cache.StoredDiscoveryItem.State.STATE_MUTED_VALUE,
-            Cache.StoredDiscoveryItem.State.STATE_DISABLED_BY_SYSTEM_VALUE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ItemState {
-    }
-
-    public DiscoveryItem(LocatorContextWrapper locatorContextWrapper,
-            Cache.StoredDiscoveryItem mStoredDiscoveryItem) {
-        this.mFastPairCacheManager =
-                locatorContextWrapper.getLocator().get(FastPairCacheManager.class);
-        this.mClock =
-                locatorContextWrapper.getLocator().get(Clock.class);
-        this.mStoredDiscoveryItem = mStoredDiscoveryItem;
-    }
-
-    public DiscoveryItem(Context context, Cache.StoredDiscoveryItem mStoredDiscoveryItem) {
-        this.mFastPairCacheManager = Locator.get(context, FastPairCacheManager.class);
-        this.mClock = Locator.get(context, Clock.class);
-        this.mStoredDiscoveryItem = mStoredDiscoveryItem;
-    }
-
-    /** @return A new StoredDiscoveryItem with state fields set to their defaults. */
-    public static Cache.StoredDiscoveryItem newStoredDiscoveryItem() {
-        Cache.StoredDiscoveryItem.Builder storedDiscoveryItem =
-                Cache.StoredDiscoveryItem.newBuilder();
-        storedDiscoveryItem.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
-        return storedDiscoveryItem.build();
-    }
-
-    /**
-     * Checks if store discovery item support fast pair or not.
-     */
-    public boolean isFastPair() {
-        Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl());
-        if (intent == null) {
-            Log.w("FastPairDiscovery", "FastPair: fail to parse action url"
-                    + mStoredDiscoveryItem.getActionUrl());
-            return false;
-        }
-        return ACTION_FAST_PAIR.equals(intent.getAction());
-    }
-
-    /**
-     * Sets the store discovery item mac address.
-     */
-    public void setMacAddress(String address) {
-        mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().setMacAddress(address).build();
-
-        mFastPairCacheManager.saveDiscoveryItem(this);
-    }
-
-    /**
-     * Checks if the item is expired. Expired items are those over getItemExpirationMillis() eg. 2
-     * minutes
-     */
-    public static boolean isExpired(
-            long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) {
-        if (lastObservationTimestampMillis == null) {
-            return true;
-        }
-        return (currentTimestampMillis - lastObservationTimestampMillis)
-                >= ITEM_EXPIRATION_MILLIS;
-    }
-
-    /**
-     * Checks if the item is deletable for saving disk space. Deletable items are those over
-     * getItemDeletableMillis eg. over 25 hrs.
-     */
-    public static boolean isDeletable(
-            long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) {
-        if (lastObservationTimestampMillis == null) {
-            return true;
-        }
-        return currentTimestampMillis - lastObservationTimestampMillis
-                >= ITEM_DELETABLE_MILLIS;
-    }
-
-    /** Checks if the item has a pending app install */
-    public boolean isPendingAppInstallValid() {
-        return isPendingAppInstallValid(mClock.millis());
-    }
-
-    /**
-     * Checks if pending app valid.
-     */
-    public boolean isPendingAppInstallValid(long appInstallMillis) {
-        return isPendingAppInstallValid(appInstallMillis, mStoredDiscoveryItem);
-    }
-
-    /**
-     * Checks if the app install time expired.
-     */
-    public static boolean isPendingAppInstallValid(
-            long currentMillis, Cache.StoredDiscoveryItem storedItem) {
-        return currentMillis - storedItem.getPendingAppInstallTimestampMillis()
-                < APP_INSTALL_EXPIRATION_MILLIS;
-    }
-
-
-    /** Checks if the item has enough data to be shown */
-    public boolean isReadyForDisplay() {
-        boolean hasUrlOrPopularApp = !mStoredDiscoveryItem.getActionUrl().isEmpty();
-
-        return !TextUtils.isEmpty(mStoredDiscoveryItem.getTitle()) && hasUrlOrPopularApp;
-    }
-
-    /** Checks if the action url is app install */
-    public boolean isApp() {
-        return mStoredDiscoveryItem.getActionUrlType() == Cache.ResolvedUrlType.APP;
-    }
-
-    /** Returns true if an item is muted, or if state is unavailable. */
-    public boolean isMuted() {
-        return mStoredDiscoveryItem.getState() != Cache.StoredDiscoveryItem.State.STATE_ENABLED;
-    }
-
-    /**
-     * Returns the state of store discovery item.
-     */
-    public Cache.StoredDiscoveryItem.State getState() {
-        return mStoredDiscoveryItem.getState();
-    }
-
-    /** Checks if it's device item. e.g. Chromecast / Wear */
-    public static boolean isDeviceType(Cache.NearbyType type) {
-        return type == Cache.NearbyType.NEARBY_CHROMECAST
-                || type == Cache.NearbyType.NEARBY_WEAR
-                || type == Cache.NearbyType.NEARBY_DEVICE;
-    }
-
-    /**
-     * Check if the type is supported.
-     */
-    public static boolean isTypeEnabled(Cache.NearbyType type) {
-        switch (type) {
-            case NEARBY_WEAR:
-            case NEARBY_CHROMECAST:
-            case NEARBY_DEVICE:
-                return true;
-            default:
-                Log.e("FastPairDiscoveryItem", "Invalid item type " + type.name());
-                return false;
-        }
-    }
-
-    /** Gets hash code of UI related data so we can collapse identical items. */
-    public int getUiHashCode() {
-        return Objects.hash(
-                        mStoredDiscoveryItem.getTitle(),
-                        mStoredDiscoveryItem.getDescription(),
-                        mStoredDiscoveryItem.getAppName(),
-                        mStoredDiscoveryItem.getDisplayUrl(),
-                        mStoredDiscoveryItem.getMacAddress());
-    }
-
-    // Getters below
-
-    /**
-     * Returns the id of store discovery item.
-     */
-    @Nullable
-    public String getId() {
-        return mStoredDiscoveryItem.getId();
-    }
-
-    /**
-     * Returns the title of discovery item.
-     */
-    @Nullable
-    public String getTitle() {
-        return mStoredDiscoveryItem.getTitle();
-    }
-
-    /**
-     * Returns the description of discovery item.
-     */
-    @Nullable
-    public String getDescription() {
-        return mStoredDiscoveryItem.getDescription();
-    }
-
-    /**
-     * Returns the mac address of discovery item.
-     */
-    @Nullable
-    public String getMacAddress() {
-        return mStoredDiscoveryItem.getMacAddress();
-    }
-
-    /**
-     * Returns the display url of discovery item.
-     */
-    @Nullable
-    public String getDisplayUrl() {
-        return mStoredDiscoveryItem.getDisplayUrl();
-    }
-
-    /**
-     * Returns the public key of discovery item.
-     */
-    @Nullable
-    public byte[] getAuthenticationPublicKeySecp256R1() {
-        return mStoredDiscoveryItem.getAuthenticationPublicKeySecp256R1().toByteArray();
-    }
-
-    /**
-     * Returns the pairing secret.
-     */
-    @Nullable
-    public String getFastPairSecretKey() {
-        Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl());
-        if (intent == null) {
-            Log.d("FastPairDiscoveryItem", "FastPair: fail to parse action url "
-                    + mStoredDiscoveryItem.getActionUrl());
-            return null;
-        }
-        return intent.getStringExtra(EXTRA_FAST_PAIR_SECRET);
-    }
-
-    /**
-     * Returns the fast pair info of discovery item.
-     */
-    @Nullable
-    public Cache.FastPairInformation getFastPairInformation() {
-        return mStoredDiscoveryItem.hasFastPairInformation()
-                ? mStoredDiscoveryItem.getFastPairInformation() : null;
-    }
-
-    /**
-     * Returns the app name of discovery item.
-     */
-    @Nullable
-    private String getAppName() {
-        return mStoredDiscoveryItem.getAppName();
-    }
-
-    /**
-     * Returns the package name of discovery item.
-     */
-    @Nullable
-    public String getAppPackageName() {
-        return mStoredDiscoveryItem.getPackageName();
-    }
-
-    /**
-     * Returns the action url of discovery item.
-     */
-    @Nullable
-    public String getActionUrl() {
-        return mStoredDiscoveryItem.getActionUrl();
-    }
-
-    /**
-     * Returns the rssi value of discovery item.
-     */
-    @Nullable
-    public Integer getRssi() {
-        return mStoredDiscoveryItem.getRssi();
-    }
-
-    /**
-     * Returns the TX power of discovery item.
-     */
-    @Nullable
-    public Integer getTxPower() {
-        return mStoredDiscoveryItem.getTxPower();
-    }
-
-    /**
-     * Returns the first observed time stamp of discovery item.
-     */
-    @Nullable
-    public Long getFirstObservationTimestampMillis() {
-        return mStoredDiscoveryItem.getFirstObservationTimestampMillis();
-    }
-
-    /**
-     * Returns the last observed time stamp of discovery item.
-     */
-    @Nullable
-    public Long getLastObservationTimestampMillis() {
-        return mStoredDiscoveryItem.getLastObservationTimestampMillis();
-    }
-
-    /**
-     * Calculates an estimated distance for the item, computed from the TX power (at 1m) and RSSI.
-     *
-     * @return estimated distance, or null if there is no RSSI or no TX power.
-     */
-    @Nullable
-    public Double getEstimatedDistance() {
-        // In the future, we may want to do a foreground subscription to leverage onDistanceChanged.
-        return RangingUtils.distanceFromRssiAndTxPower(mStoredDiscoveryItem.getRssi(),
-                mStoredDiscoveryItem.getTxPower());
-    }
-
-    /**
-     * Gets icon Bitmap from icon store.
-     *
-     * @return null if no icon or icon size is incorrect.
-     */
-    @Nullable
-    public Bitmap getIcon() {
-        Bitmap icon =
-                BitmapFactory.decodeByteArray(
-                        mStoredDiscoveryItem.getIconPng().toByteArray(),
-                        0 /* offset */, mStoredDiscoveryItem.getIconPng().size());
-        if (IconUtils.isIconSizeCorrect(icon)) {
-            return icon;
-        } else {
-            return null;
-        }
-    }
-
-    /** Gets a FIFE URL of the icon. */
-    @Nullable
-    public String getIconFifeUrl() {
-        return mStoredDiscoveryItem.getIconFifeUrl();
-    }
-
-    /**
-     * Compares this object to the specified object: 1. By device type. Device setups are 'greater
-     * than' beacons. 2. By relevance. More relevant items are 'greater than' less relevant items.
-     * 3.By distance. Nearer items are 'greater than' further items.
-     *
-     * <p>In the list view, we sort in descending order, i.e. we put the most relevant items first.
-     */
-    @Override
-    public int compareTo(DiscoveryItem another) {
-        // For items of the same relevance, compare distance.
-        Double distance1 = getEstimatedDistance();
-        Double distance2 = another.getEstimatedDistance();
-        distance1 = distance1 != null ? distance1 : Double.MAX_VALUE;
-        distance2 = distance2 != null ? distance2 : Double.MAX_VALUE;
-        // Negate because closer items are better ("greater than") further items.
-        return -distance1.compareTo(distance2);
-    }
-
-    @Nullable
-    public String getTriggerId() {
-        return mStoredDiscoveryItem.getTriggerId();
-    }
-
-    @Override
-    public boolean equals(Object another) {
-        if (another instanceof DiscoveryItem) {
-            return ((DiscoveryItem) another).mStoredDiscoveryItem.equals(mStoredDiscoveryItem);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return mStoredDiscoveryItem.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return String.format(
-                "[triggerId=%s], [id=%s], [title=%s], [url=%s], [ready=%s], [macAddress=%s]",
-                getTriggerId(),
-                getId(),
-                getTitle(),
-                getActionUrl(),
-                isReadyForDisplay(),
-                maskBluetoothAddress(getMacAddress()));
-    }
-
-    /**
-     * Gets a copy of the StoredDiscoveryItem proto backing this DiscoveryItem. Currently needed for
-     * Fast Pair 2.0: We store the item in the cloud associated with a user's account, to enable
-     * pairing with other devices owned by the user.
-     */
-    public Cache.StoredDiscoveryItem getCopyOfStoredItem() {
-        return mStoredDiscoveryItem;
-    }
-
-    /**
-     * Gets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate
-     * values that production code should not manipulate.
-     */
-
-    public Cache.StoredDiscoveryItem getStoredItemForTest() {
-        return mStoredDiscoveryItem;
-    }
-
-    /**
-     * Sets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate
-     * values that production code should not manipulate.
-     */
-    public void setStoredItemForTest(Cache.StoredDiscoveryItem s) {
-        mStoredDiscoveryItem = s;
-    }
-
-    /**
-     * Parse the intent from item url.
-     */
-    public static Intent parseIntentScheme(String uri) {
-        try {
-            return Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
-        } catch (URISyntaxException e) {
-            return null;
-        }
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemContract.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemContract.java
deleted file mode 100644
index 61ca3fd..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemContract.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.cache;
-
-import android.provider.BaseColumns;
-
-/**
- * Defines DiscoveryItem database schema.
- */
-public class DiscoveryItemContract {
-    private DiscoveryItemContract() {}
-
-    /**
-     * Discovery item entry related info.
-     */
-    public static class DiscoveryItemEntry implements BaseColumns {
-        public static final String TABLE_NAME = "SCAN_RESULT";
-        public static final String COLUMN_MODEL_ID = "MODEL_ID";
-        public static final String COLUMN_SCAN_BYTE = "SCAN_RESULT_BYTE";
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
deleted file mode 100644
index b840091..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.cache;
-
-import android.bluetooth.le.ScanResult;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
-
-import com.android.server.nearby.common.eventloop.Annotations;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import service.proto.Cache;
-import service.proto.Rpcs;
-
-
-/**
- * Save FastPair device info to database to avoid multiple requesting.
- */
-public class FastPairCacheManager {
-    private final Context mContext;
-    private final FastPairDbHelper mFastPairDbHelper;
-
-    public FastPairCacheManager(Context context) {
-        mContext = context;
-        mFastPairDbHelper = new FastPairDbHelper(context);
-    }
-
-    /**
-     * Clean up function to release db
-     */
-    public void cleanUp() {
-        mFastPairDbHelper.close();
-    }
-
-    /**
-     * Saves the response to the db
-     */
-    private void saveDevice() {
-    }
-
-    Cache.ServerResponseDbItem getDeviceFromScanResult(ScanResult scanResult) {
-        return Cache.ServerResponseDbItem.newBuilder().build();
-    }
-
-    /**
-     * Checks if the entry can be auto deleted from the cache
-     */
-    public boolean isDeletable(Cache.ServerResponseDbItem entry) {
-        if (!entry.getExpirable()) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Save discovery item into database. Discovery item is item that discovered through Ble before
-     * pairing success.
-     */
-    public boolean saveDiscoveryItem(DiscoveryItem item) {
-
-        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
-        ContentValues values = new ContentValues();
-        values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID, item.getTriggerId());
-        values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE,
-                item.getCopyOfStoredItem().toByteArray());
-        db.insert(DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME, null, values);
-        return true;
-    }
-
-
-    @Annotations.EventThread
-    private Rpcs.GetObservedDeviceResponse getObservedDeviceInfo(ScanResult scanResult) {
-        return Rpcs.GetObservedDeviceResponse.getDefaultInstance();
-    }
-
-    /**
-     * Get discovery item from item id.
-     */
-    public DiscoveryItem getDiscoveryItem(String itemId) {
-        return new DiscoveryItem(mContext, getStoredDiscoveryItem(itemId));
-    }
-
-    /**
-     * Get discovery item from item id.
-     */
-    public Cache.StoredDiscoveryItem getStoredDiscoveryItem(String itemId) {
-        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
-        String[] projection = {
-                DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID,
-                DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
-        };
-        String selection = DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID + " =? ";
-        String[] selectionArgs = {itemId};
-        Cursor cursor = db.query(
-                DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME,
-                projection,
-                selection,
-                selectionArgs,
-                null,
-                null,
-                null
-        );
-
-        if (cursor.moveToNext()) {
-            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(
-                    DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE));
-            try {
-                Cache.StoredDiscoveryItem item = Cache.StoredDiscoveryItem.parseFrom(res);
-                return item;
-            } catch (InvalidProtocolBufferException e) {
-                Log.e("FastPairCacheManager", "storediscovery has error");
-            }
-        }
-        cursor.close();
-        return Cache.StoredDiscoveryItem.getDefaultInstance();
-    }
-
-    /**
-     * Get all of the discovery item related info in the cache.
-     */
-    public List<Cache.StoredDiscoveryItem> getAllSavedStoreDiscoveryItem() {
-        List<Cache.StoredDiscoveryItem> storedDiscoveryItemList = new ArrayList<>();
-        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
-        String[] projection = {
-                DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID,
-                DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
-        };
-        Cursor cursor = db.query(
-                DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME,
-                projection,
-                null,
-                null,
-                null,
-                null,
-                null
-        );
-
-        while (cursor.moveToNext()) {
-            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(
-                    DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE));
-            try {
-                Cache.StoredDiscoveryItem item = Cache.StoredDiscoveryItem.parseFrom(res);
-                storedDiscoveryItemList.add(item);
-            } catch (InvalidProtocolBufferException e) {
-                Log.e("FastPairCacheManager", "storediscovery has error");
-            }
-
-        }
-        cursor.close();
-        return storedDiscoveryItemList;
-    }
-
-    /**
-     * Get scan result from local database use model id
-     */
-    public Cache.StoredScanResult getStoredScanResult(String modelId) {
-        return Cache.StoredScanResult.getDefaultInstance();
-    }
-
-    /**
-     * Gets the paired Fast Pair item that paired to the phone through mac address.
-     */
-    public Cache.StoredFastPairItem getStoredFastPairItemFromMacAddress(String macAddress) {
-        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
-        String[] projection = {
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
-        };
-        String selection =
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + " =? ";
-        String[] selectionArgs = {macAddress};
-        Cursor cursor = db.query(
-                StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
-                projection,
-                selection,
-                selectionArgs,
-                null,
-                null,
-                null
-        );
-
-        if (cursor.moveToNext()) {
-            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(
-                    StoredFastPairItemContract.StoredFastPairItemEntry
-                            .COLUMN_STORED_FAST_PAIR_BYTE));
-            try {
-                Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res);
-                return item;
-            } catch (InvalidProtocolBufferException e) {
-                Log.e("FastPairCacheManager", "storediscovery has error");
-            }
-        }
-        cursor.close();
-        return Cache.StoredFastPairItem.getDefaultInstance();
-    }
-
-    /**
-     * Save paired fast pair item into the database.
-     */
-    public boolean putStoredFastPairItem(Cache.StoredFastPairItem storedFastPairItem) {
-        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
-        ContentValues values = new ContentValues();
-        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
-                storedFastPairItem.getMacAddress());
-        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
-                storedFastPairItem.getAccountKey().toString());
-        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE,
-                storedFastPairItem.toByteArray());
-        db.insert(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, null, values);
-        return true;
-
-    }
-
-    /**
-     * Removes certain storedFastPairItem so that it can update timely.
-     */
-    public void removeStoredFastPairItem(String macAddress) {
-        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
-        int res = db.delete(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + "=?",
-                new String[]{macAddress});
-
-    }
-
-    /**
-     * Get all of the store fast pair item related info in the cache.
-     */
-    public List<Cache.StoredFastPairItem> getAllSavedStoredFastPairItem() {
-        List<Cache.StoredFastPairItem> storedFastPairItemList = new ArrayList<>();
-        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
-        String[] projection = {
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
-                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
-        };
-        Cursor cursor = db.query(
-                StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
-                projection,
-                null,
-                null,
-                null,
-                null,
-                null
-        );
-
-        while (cursor.moveToNext()) {
-            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(StoredFastPairItemContract
-                    .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE));
-            try {
-                Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res);
-                storedFastPairItemList.add(item);
-            } catch (InvalidProtocolBufferException e) {
-                Log.e("FastPairCacheManager", "storediscovery has error");
-            }
-
-        }
-        cursor.close();
-        return storedFastPairItemList;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java
deleted file mode 100644
index d950d8d..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.cache;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Fast Pair db helper handle all of the db actions related Fast Pair.
- */
-public class FastPairDbHelper extends SQLiteOpenHelper {
-
-    public static final int DATABASE_VERSION = 1;
-    public static final String DATABASE_NAME = "FastPair.db";
-    private static final String SQL_CREATE_DISCOVERY_ITEM_DB =
-            "CREATE TABLE IF NOT EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME
-                    + " (" + DiscoveryItemContract.DiscoveryItemEntry._ID
-                    + "INTEGER PRIMARY KEY,"
-                    + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID
-                    + " TEXT," + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
-                    + " BLOB)";
-    private static final String SQL_DELETE_DISCOVERY_ITEM_DB =
-            "DROP TABLE IF EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME;
-    private static final String SQL_CREATE_FAST_PAIR_ITEM_DB =
-            "CREATE TABLE IF NOT EXISTS "
-                    + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME
-                    + " (" + StoredFastPairItemContract.StoredFastPairItemEntry._ID
-                    + "INTEGER PRIMARY KEY,"
-                    + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS
-                    + " TEXT,"
-                    + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY
-                    + " TEXT,"
-                    + StoredFastPairItemContract
-                    .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
-                    + " BLOB)";
-    private static final String SQL_DELETE_FAST_PAIR_ITEM_DB =
-            "DROP TABLE IF EXISTS " + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME;
-
-    public FastPairDbHelper(Context context) {
-        super(context, DATABASE_NAME, null, DATABASE_VERSION);
-    }
-
-    @Override
-    public void onCreate(SQLiteDatabase db) {
-        db.execSQL(SQL_CREATE_DISCOVERY_ITEM_DB);
-        db.execSQL(SQL_CREATE_FAST_PAIR_ITEM_DB);
-    }
-
-    @Override
-    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // Since the outdated data has no value so just remove the data.
-        db.execSQL(SQL_DELETE_DISCOVERY_ITEM_DB);
-        db.execSQL(SQL_DELETE_FAST_PAIR_ITEM_DB);
-        onCreate(db);
-    }
-
-    @Override
-    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        super.onDowngrade(db, oldVersion, newVersion);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java
deleted file mode 100644
index 9980565..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.server.nearby.fastpair.cache;
-
-import android.provider.BaseColumns;
-
-/**
- * Defines fast pair item database schema.
- */
-public class StoredFastPairItemContract {
-    private StoredFastPairItemContract() {}
-
-    /**
-     * StoredFastPairItem entry related info.
-     */
-    public static class StoredFastPairItemEntry implements BaseColumns {
-        public static final String TABLE_NAME = "STORED_FAST_PAIR_ITEM";
-        public static final String COLUMN_MAC_ADDRESS = "MAC_ADDRESS";
-        public static final String COLUMN_ACCOUNT_KEY = "ACCOUNT_KEY";
-
-        public static final String COLUMN_STORED_FAST_PAIR_BYTE = "STORED_FAST_PAIR_BYTE";
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java
deleted file mode 100644
index 6c9aff0..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.server.nearby.fastpair.footprint;
-
-
-import com.google.protobuf.ByteString;
-
-import service.proto.Cache;
-
-/**
- * Wrapper class that upload the pair info to the footprint.
- */
-public class FastPairUploadInfo {
-
-    private Cache.StoredDiscoveryItem mStoredDiscoveryItem;
-
-    private ByteString mAccountKey;
-
-    private  ByteString mSha256AccountKeyPublicAddress;
-
-
-    public FastPairUploadInfo(Cache.StoredDiscoveryItem storedDiscoveryItem, ByteString accountKey,
-            ByteString sha256AccountKeyPublicAddress) {
-        mStoredDiscoveryItem = storedDiscoveryItem;
-        mAccountKey = accountKey;
-        mSha256AccountKeyPublicAddress = sha256AccountKeyPublicAddress;
-    }
-
-    public Cache.StoredDiscoveryItem getStoredDiscoveryItem() {
-        return mStoredDiscoveryItem;
-    }
-
-    public ByteString getAccountKey() {
-        return mAccountKey;
-    }
-
-
-    public ByteString getSha256AccountKeyPublicAddress() {
-        return mSha256AccountKeyPublicAddress;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java
deleted file mode 100644
index 68217c1..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.footprint;
-
-/**
- * FootprintDeviceManager is responsible for all of the foot print operation. Footprint will
- * store all of device info that already paired with certain account. This class will call AOSP
- * api to let OEM save certain device.
- */
-public class FootprintsDeviceManager {
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
deleted file mode 100644
index 553d5ce..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.halfsheet;
-
-import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
-import static com.android.server.nearby.fastpair.FastPairManager.ACTION_RESOURCES_APK;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.nearby.FastPairDevice;
-import android.nearby.FastPairStatusCallback;
-import android.nearby.PairStatusMetadata;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.FastPairController;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.util.Environment;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import service.proto.Cache;
-
-/**
- * Fast Pair ux manager for half sheet.
- */
-public class FastPairHalfSheetManager {
-    private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
-    private static final String HALF_SHEET_CLASS_NAME =
-            "com.android.nearby.halfsheet.HalfSheetActivity";
-    private static final String TAG = "FPHalfSheetManager";
-
-    private String mHalfSheetApkPkgName;
-    private final LocatorContextWrapper mLocatorContextWrapper;
-
-    FastPairUiServiceImpl mFastPairUiService;
-
-    public FastPairHalfSheetManager(Context context) {
-        this(new LocatorContextWrapper(context));
-    }
-
-    @VisibleForTesting
-    FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
-        mLocatorContextWrapper = locatorContextWrapper;
-        mFastPairUiService = new FastPairUiServiceImpl();
-    }
-
-    /**
-     * Invokes half sheet in the other apk. This function can only be called in Nearby because other
-     * app can't get the correct component name.
-     */
-    public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
-        try {
-            if (mLocatorContextWrapper != null) {
-                String packageName = getHalfSheetApkPkgName();
-                if (packageName == null) {
-                    Log.e(TAG, "package name is null");
-                    return;
-                }
-                mFastPairUiService.setFastPairController(
-                        mLocatorContextWrapper.getLocator().get(FastPairController.class));
-                Bundle bundle = new Bundle();
-                bundle.putBinder(EXTRA_BINDER, mFastPairUiService);
-                mLocatorContextWrapper
-                        .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
-                                        .putExtra(EXTRA_HALF_SHEET_INFO,
-                                                scanFastPairStoreItem.toByteArray())
-                                        .putExtra(EXTRA_HALF_SHEET_TYPE,
-                                                DEVICE_PAIRING_FRAGMENT_TYPE)
-                                        .putExtra(EXTRA_BUNDLE, bundle)
-                                        .setComponent(new ComponentName(packageName,
-                                                HALF_SHEET_CLASS_NAME)),
-                                UserHandle.CURRENT);
-            }
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "Can't resolve package that contains half sheet");
-        }
-    }
-
-    /**
-     * Shows pairing fail half sheet.
-     */
-    public void showPairingFailed() {
-        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
-        if (pairStatusCallback != null) {
-            Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL");
-            pairStatusCallback.onPairUpdate(new FastPairDevice.Builder().build(),
-                    new PairStatusMetadata(PairStatusMetadata.Status.FAIL));
-        } else {
-            Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
-                    + "the pairStatusCallback is null");
-        }
-    }
-
-    /**
-     * Get the half sheet status whether it is foreground or dismissed
-     */
-    public boolean getHalfSheetForegroundState() {
-        return true;
-    }
-
-    /**
-     * Show passkey confirmation info on half sheet
-     */
-    public void showPasskeyConfirmation(BluetoothDevice device, int passkey) {
-    }
-
-    /**
-     * This function will handle pairing steps for half sheet.
-     */
-    public void showPairingHalfSheet(DiscoveryItem item) {
-        Log.d(TAG, "show pairing half sheet");
-    }
-
-    /**
-     * Shows pairing success info.
-     */
-    public void showPairingSuccessHalfSheet(String address) {
-        FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback();
-        if (pairStatusCallback != null) {
-            pairStatusCallback.onPairUpdate(
-                    new FastPairDevice.Builder().setBluetoothAddress(address).build(),
-                    new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS));
-        } else {
-            Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
-                    + "the pairStatusCallback is null");
-        }
-    }
-
-    /**
-     * Removes dismiss runnable.
-     */
-    public void disableDismissRunnable() {
-    }
-
-    /**
-     * Destroys the bluetooth pairing controller.
-     */
-    public void destroyBluetoothPairController() {
-    }
-
-    /**
-     * Notify manager the pairing has finished.
-     */
-    public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {
-    }
-
-    /**
-     * Gets the package name of HalfSheet.apk
-     * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
-     * race condition check. Since there is no lock for mHalfSheetApkPkgName.
-     */
-    String getHalfSheetApkPkgName() {
-        if (mHalfSheetApkPkgName != null) {
-            return mHalfSheetApkPkgName;
-        }
-        List<ResolveInfo> resolveInfos = mLocatorContextWrapper
-                .getPackageManager().queryIntentActivities(
-                        new Intent(ACTION_RESOURCES_APK),
-                        PackageManager.MATCH_SYSTEM_ONLY);
-
-        // remove apps that don't live in the nearby apex
-        resolveInfos.removeIf(info ->
-                !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
-
-        if (resolveInfos.isEmpty()) {
-            // Resource APK not loaded yet, print a stack trace to see where this is called from
-            Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
-                            + " APK is installed or package manager can't resolve correctly!",
-                    new IllegalStateException());
-            return null;
-        }
-
-        if (resolveInfos.size() > 1) {
-            // multiple apps found, log a warning, but continue
-            Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
-                    + resolveInfos.stream()
-                    .map(info -> info.activityInfo.applicationInfo.packageName)
-                    .collect(Collectors.joining(", ")));
-        }
-
-        // Assume the first ResolveInfo is the one we're looking for
-        ResolveInfo info = resolveInfos.get(0);
-        mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
-        Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
-        return mHalfSheetApkPkgName;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
deleted file mode 100644
index 3bd273e..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.server.nearby.fastpair.halfsheet;
-
-import static com.android.server.nearby.fastpair.Constant.TAG;
-
-import android.nearby.FastPairDevice;
-import android.nearby.FastPairStatusCallback;
-import android.nearby.PairStatusMetadata;
-import android.nearby.aidl.IFastPairStatusCallback;
-import android.nearby.aidl.IFastPairUiService;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.server.nearby.fastpair.FastPairController;
-
-/**
- * Service implementing Fast Pair functionality.
- *
- * @hide
- */
-public class FastPairUiServiceImpl extends IFastPairUiService.Stub {
-
-    private IBinder mStatusCallbackProxy;
-    private FastPairController mFastPairController;
-    private FastPairStatusCallback mFastPairStatusCallback;
-
-    /**
-     * Registers the Binder call back in the server notifies the proxy when there is an update
-     * in the server.
-     */
-    @Override
-    public void registerCallback(IFastPairStatusCallback iFastPairStatusCallback) {
-        mStatusCallbackProxy = iFastPairStatusCallback.asBinder();
-        mFastPairStatusCallback = new FastPairStatusCallback() {
-            @Override
-            public void onPairUpdate(FastPairDevice fastPairDevice,
-                    PairStatusMetadata pairStatusMetadata) {
-                try {
-                    iFastPairStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to update pair status.", e);
-                }
-            }
-        };
-    }
-
-    /**
-     * Unregisters the Binder call back in the server.
-     */
-    @Override
-    public void unregisterCallback(IFastPairStatusCallback iFastPairStatusCallback) {
-        mStatusCallbackProxy = null;
-        mFastPairStatusCallback = null;
-    }
-
-    /**
-     * Asks the Fast Pair service to pair the device. initial pairing.
-     */
-    @Override
-    public void connect(FastPairDevice fastPairDevice) {
-        if (mFastPairController != null) {
-            mFastPairController.pair(fastPairDevice);
-        } else {
-            Log.w(TAG, "Failed to connect because there is no FastPairController.");
-        }
-    }
-
-    /**
-     * Cancels Fast Pair connection and dismisses half sheet.
-     */
-    @Override
-    public void cancel(FastPairDevice fastPairDevice) {
-    }
-
-    public FastPairStatusCallback getPairStatusCallback() {
-        return mFastPairStatusCallback;
-    }
-
-    /**
-     * Sets function for Fast Pair controller.
-     */
-    public void setFastPairController(FastPairController fastPairController) {
-        mFastPairController = fastPairController;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
deleted file mode 100644
index b1ae573..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.notification;
-
-
-import android.annotation.Nullable;
-import android.content.Context;
-
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-
-/**
- * Responsible for show notification logic.
- */
-public class FastPairNotificationManager {
-
-    /**
-     * FastPair notification manager that handle notification ui for fast pair.
-     */
-    public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon,
-            int notificationId) {
-    }
-    /**
-     * FastPair notification manager that handle notification ui for fast pair.
-     */
-    public FastPairNotificationManager(Context context, DiscoveryItem item, boolean useLargeIcon) {
-
-    }
-
-    /**
-     * Shows pairing in progress notification.
-     */
-    public void showConnectingNotification() {}
-
-    /**
-     * Shows success notification
-     */
-    public void showPairingSucceededNotification(
-            @Nullable String companionApp,
-            int batteryLevel,
-            @Nullable String deviceName,
-            String address) {
-
-    }
-
-    /**
-     * Shows failed notification.
-     */
-    public void showPairingFailedNotification(byte[] accountKey) {
-
-    }
-
-    /**
-     * Notify the pairing process is done.
-     */
-    public void notifyPairingProcessDone(boolean success, boolean forceNotify,
-            String privateAddress, String publicAddress) {}
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
deleted file mode 100644
index c95f74f..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.pairinghandler;
-
-
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs;
-
-/** Pairing progress handler that handle pairing come from half sheet. */
-public final class HalfSheetPairingProgressHandler extends PairingProgressHandlerBase {
-
-    private final FastPairHalfSheetManager mFastPairHalfSheetManager;
-    private final boolean mIsSubsequentPair;
-    private final DiscoveryItem mItemResurface;
-
-    HalfSheetPairingProgressHandler(
-            Context context,
-            DiscoveryItem item,
-            @Nullable String companionApp,
-            @Nullable byte[] accountKey) {
-        super(context, item);
-        this.mFastPairHalfSheetManager = Locator.get(context, FastPairHalfSheetManager.class);
-        this.mIsSubsequentPair =
-                item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null;
-        this.mItemResurface = item;
-    }
-
-    @Override
-    protected int getPairStartEventCode() {
-        return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START
-                : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START;
-    }
-
-    @Override
-    protected int getPairEndEventCode() {
-        return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END
-                : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END;
-    }
-
-    @Override
-    public void onPairingStarted() {
-        super.onPairingStarted();
-        // Half sheet is not in the foreground reshow half sheet, also avoid showing HalfSheet on TV
-        if (!mFastPairHalfSheetManager.getHalfSheetForegroundState()) {
-            mFastPairHalfSheetManager.showPairingHalfSheet(mItemResurface);
-        }
-        mFastPairHalfSheetManager.disableDismissRunnable();
-    }
-
-    @Override
-    public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) {
-        super.onHandlePasskeyConfirmation(device, passkey);
-        mFastPairHalfSheetManager.showPasskeyConfirmation(device, passkey);
-    }
-
-    @Nullable
-    @Override
-    public String onPairedCallbackCalled(
-            FastPairConnection connection,
-            byte[] accountKey,
-            FootprintsDeviceManager footprints,
-            String address) {
-        String deviceName = super.onPairedCallbackCalled(connection, accountKey,
-                footprints, address);
-        mFastPairHalfSheetManager.showPairingSuccessHalfSheet(address);
-        mFastPairHalfSheetManager.disableDismissRunnable();
-        return deviceName;
-    }
-
-    @Override
-    public void onPairingFailed(Throwable throwable) {
-        super.onPairingFailed(throwable);
-        mFastPairHalfSheetManager.disableDismissRunnable();
-        mFastPairHalfSheetManager.showPairingFailed();
-        mFastPairHalfSheetManager.notifyPairingProcessDone(
-                /* success= */ false, /* publicAddress= */ null, mItem);
-        // fix auto rebond issue
-        mFastPairHalfSheetManager.destroyBluetoothPairController();
-    }
-
-    @Override
-    public void onPairingSuccess(String address) {
-        super.onPairingSuccess(address);
-        mFastPairHalfSheetManager.disableDismissRunnable();
-        mFastPairHalfSheetManager
-                .notifyPairingProcessDone(/* success= */ true, address, mItem);
-        mFastPairHalfSheetManager.destroyBluetoothPairController();
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
deleted file mode 100644
index d469c45..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.pairinghandler;
-
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
-import com.android.server.nearby.intdefs.NearbyEventIntDefs;
-
-/** Pairing progress handler for pairing coming from notifications. */
-@SuppressWarnings("nullness")
-public class NotificationPairingProgressHandler extends PairingProgressHandlerBase {
-    private final FastPairNotificationManager mFastPairNotificationManager;
-    @Nullable
-    private final String mCompanionApp;
-    @Nullable
-    private final byte[] mAccountKey;
-    private final boolean mIsSubsequentPair;
-
-    NotificationPairingProgressHandler(
-            Context context,
-            DiscoveryItem item,
-            @Nullable String companionApp,
-            @Nullable byte[] accountKey,
-            FastPairNotificationManager mFastPairNotificationManager) {
-        super(context, item);
-        this.mFastPairNotificationManager = mFastPairNotificationManager;
-        this.mCompanionApp = companionApp;
-        this.mAccountKey = accountKey;
-        this.mIsSubsequentPair =
-                item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null;
-    }
-
-    @Override
-    public int getPairStartEventCode() {
-        return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START
-                : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START;
-    }
-
-    @Override
-    public int getPairEndEventCode() {
-        return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END
-                : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END;
-    }
-
-    @Override
-    public void onReadyToPair() {
-        super.onReadyToPair();
-        mFastPairNotificationManager.showConnectingNotification();
-    }
-
-    @Override
-    public String onPairedCallbackCalled(
-            FastPairConnection connection,
-            byte[] accountKey,
-            FootprintsDeviceManager footprints,
-            String address) {
-        String deviceName = super.onPairedCallbackCalled(connection, accountKey, footprints,
-                address);
-
-        int batteryLevel = -1;
-
-        BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
-        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
-        if (bluetoothAdapter != null) {
-            // Need to check battery level here set that to -1 for now
-            batteryLevel = -1;
-        } else {
-            Log.v(
-                    "NotificationPairingProgressHandler",
-                    "onPairedCallbackCalled getBatteryLevel failed,"
-                            + " adapter is null");
-        }
-        mFastPairNotificationManager.showPairingSucceededNotification(
-                !TextUtils.isEmpty(mCompanionApp) ? mCompanionApp : null,
-                batteryLevel,
-                deviceName,
-                address);
-        return deviceName;
-    }
-
-    @Override
-    public void onPairingFailed(Throwable throwable) {
-        super.onPairingFailed(throwable);
-        mFastPairNotificationManager.showPairingFailedNotification(mAccountKey);
-        mFastPairNotificationManager.notifyPairingProcessDone(
-                /* success= */ false,
-                /* forceNotify= */ false,
-                /* privateAddress= */ mItem.getMacAddress(),
-                /* publicAddress= */ null);
-    }
-
-    @Override
-    public void onPairingSuccess(String address) {
-        super.onPairingSuccess(address);
-        mFastPairNotificationManager.notifyPairingProcessDone(
-                /* success= */ true,
-                /* forceNotify= */ false,
-                /* privateAddress= */ mItem.getMacAddress(),
-                /* publicAddress= */ address);
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
deleted file mode 100644
index ccd7e5e..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.pairinghandler;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
-import static com.android.server.nearby.fastpair.FastPairManager.isThroughFastPair2InitialPairing;
-
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.util.Log;
-
-import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
-import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
-import com.android.server.nearby.intdefs.FastPairEventIntDefs;
-
-/** Base class for pairing progress handler. */
-public abstract class PairingProgressHandlerBase {
-    protected final Context mContext;
-    protected final DiscoveryItem mItem;
-    @Nullable
-    private FastPairEventIntDefs.ErrorCode mRescueFromError;
-
-    protected abstract int getPairStartEventCode();
-
-    protected abstract int getPairEndEventCode();
-
-    protected PairingProgressHandlerBase(Context context, DiscoveryItem item) {
-        this.mContext = context;
-        this.mItem = item;
-    }
-
-
-    /**
-     * Pairing progress init function.
-     */
-    public static PairingProgressHandlerBase create(
-            Context context,
-            DiscoveryItem item,
-            @Nullable String companionApp,
-            @Nullable byte[] accountKey,
-            FootprintsDeviceManager footprints,
-            FastPairNotificationManager notificationManager,
-            FastPairHalfSheetManager fastPairHalfSheetManager,
-            boolean isRetroactivePair) {
-        PairingProgressHandlerBase pairingProgressHandlerBase;
-        // Disable half sheet on subsequent pairing
-        if (item.getAuthenticationPublicKeySecp256R1() != null
-                && accountKey != null) {
-            // Subsequent pairing
-            pairingProgressHandlerBase =
-                    new NotificationPairingProgressHandler(
-                            context, item, companionApp, accountKey, notificationManager);
-        } else {
-            pairingProgressHandlerBase =
-                    new HalfSheetPairingProgressHandler(context, item, companionApp, accountKey);
-        }
-
-
-        Log.v("PairingHandler",
-                "PairingProgressHandler:Create "
-                        + item.getMacAddress() + " for pairing");
-        return pairingProgressHandlerBase;
-    }
-
-
-    /**
-     * Function calls when pairing start.
-     */
-    public void onPairingStarted() {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingStarted");
-    }
-
-    /**
-     * Waits for screen to unlock.
-     */
-    public void onWaitForScreenUnlock() {
-        Log.v("PairingHandler", "PairingProgressHandler:onWaitForScreenUnlock");
-    }
-
-    /**
-     * Function calls when screen unlock.
-     */
-    public void onScreenUnlocked() {
-        Log.v("PairingHandler", "PairingProgressHandler:onScreenUnlocked");
-    }
-
-    /**
-     * Calls when the handler is ready to pair.
-     */
-    public void onReadyToPair() {
-        Log.v("PairingHandler", "PairingProgressHandler:onReadyToPair");
-    }
-
-    /**
-     * Helps to set up pairing preference.
-     */
-    public void onSetupPreferencesBuilder(Preferences.Builder builder) {
-        Log.v("PairingHandler", "PairingProgressHandler:onSetupPreferencesBuilder");
-    }
-
-    /**
-     * Calls when pairing setup complete.
-     */
-    public void onPairingSetupCompleted() {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingSetupCompleted");
-    }
-
-    /** Called while pairing if needs to handle the passkey confirmation by Ui. */
-    public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) {
-        Log.v("PairingHandler", "PairingProgressHandler:onHandlePasskeyConfirmation");
-    }
-
-    /**
-     * In this callback, we know if it is a real initial pairing by existing account key, and do
-     * following things:
-     * <li>1, optIn footprint for initial pairing.
-     * <li>2, write the device name to provider
-     * <li>2.1, generate default personalized name for initial pairing or get the personalized name
-     * from footprint for subsequent pairing.
-     * <li>2.2, set alias name for the bluetooth device.
-     * <li>2.3, update the device name for connection to write into provider for initial pair.
-     * <li>3, suppress battery notifications until oobe finishes.
-     *
-     * @return display name of the pairing device
-     */
-    @Nullable
-    public String onPairedCallbackCalled(
-            FastPairConnection connection,
-            byte[] accountKey,
-            FootprintsDeviceManager footprints,
-            String address) {
-        Log.v("PairingHandler",
-                "PairingProgressHandler:onPairedCallbackCalled with address: "
-                        + address);
-
-        byte[] existingAccountKey = connection.getExistingAccountKey();
-        optInFootprintsForInitialPairing(footprints, mItem, accountKey, existingAccountKey);
-        // Add support for naming the device
-        return null;
-    }
-
-    /**
-     * Gets the related info from db use account key.
-     */
-    @Nullable
-    public byte[] getKeyForLocalCache(
-            byte[] accountKey, FastPairConnection connection,
-            FastPairConnection.SharedSecret sharedSecret) {
-        Log.v("PairingHandler", "PairingProgressHandler:getKeyForLocalCache");
-        return accountKey != null ? accountKey : connection.getExistingAccountKey();
-    }
-
-    /**
-     * Function handles pairing fail.
-     */
-    public void onPairingFailed(Throwable throwable) {
-        Log.w("PairingHandler", "PairingProgressHandler:onPairingFailed");
-    }
-
-    /**
-     * Function handles pairing success.
-     */
-    public void onPairingSuccess(String address) {
-        Log.v("PairingHandler", "PairingProgressHandler:onPairingSuccess with address: "
-                + maskBluetoothAddress(address));
-    }
-
-    private static void optInFootprintsForInitialPairing(
-            FootprintsDeviceManager footprints,
-            DiscoveryItem item,
-            byte[] accountKey,
-            @Nullable byte[] existingAccountKey) {
-        if (isThroughFastPair2InitialPairing(item, accountKey) && existingAccountKey == null) {
-            // enable the save to footprint
-            Log.v("PairingHandler", "footprint should call opt in here");
-        }
-    }
-
-    /**
-     * Returns {@code true} if the PairingProgressHandler is running at the background.
-     *
-     * <p>In order to keep the following status notification shows as a heads up, we must wait for
-     * the screen unlocked to continue.
-     */
-    public boolean skipWaitingScreenUnlock() {
-        return false;
-    }
-}
-
diff --git a/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java b/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java
deleted file mode 100644
index 8bb7980..0000000
--- a/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.intdefs;
-
-import androidx.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds integer definitions for FastPair. */
-public class FastPairEventIntDefs {
-
-    /** Fast Pair Bond State. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    BondState.UNKNOWN_BOND_STATE,
-                    BondState.NONE,
-                    BondState.BONDING,
-                    BondState.BONDED,
-            })
-    public @interface BondState {
-        int UNKNOWN_BOND_STATE = 0;
-        int NONE = 10;
-        int BONDING = 11;
-        int BONDED = 12;
-    }
-
-    /** Fast Pair error code. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    ErrorCode.UNKNOWN_ERROR_CODE,
-                    ErrorCode.OTHER_ERROR,
-                    ErrorCode.TIMEOUT,
-                    ErrorCode.INTERRUPTED,
-                    ErrorCode.REFLECTIVE_OPERATION_EXCEPTION,
-                    ErrorCode.EXECUTION_EXCEPTION,
-                    ErrorCode.PARSE_EXCEPTION,
-                    ErrorCode.MDH_REMOTE_EXCEPTION,
-                    ErrorCode.SUCCESS_RETRY_GATT_ERROR,
-                    ErrorCode.SUCCESS_RETRY_GATT_TIMEOUT,
-                    ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR,
-                    ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT,
-                    ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT,
-                    ErrorCode.SUCCESS_ADDRESS_ROTATE,
-                    ErrorCode.SUCCESS_SIGNAL_LOST,
-            })
-    public @interface ErrorCode {
-        int UNKNOWN_ERROR_CODE = 0;
-
-        // Check the other fields for a more specific error code.
-        int OTHER_ERROR = 1;
-
-        // The operation timed out.
-        int TIMEOUT = 2;
-
-        // The thread was interrupted.
-        int INTERRUPTED = 3;
-
-        // Some reflective call failed (should never happen).
-        int REFLECTIVE_OPERATION_EXCEPTION = 4;
-
-        // A Future threw an exception (should never happen).
-        int EXECUTION_EXCEPTION = 5;
-
-        // Parsing something (e.g. BR/EDR Handover data) failed.
-        int PARSE_EXCEPTION = 6;
-
-        // A failure at MDH.
-        int MDH_REMOTE_EXCEPTION = 7;
-
-        // For errors on GATT connection and retry success
-        int SUCCESS_RETRY_GATT_ERROR = 8;
-
-        // For timeout on GATT connection and retry success
-        int SUCCESS_RETRY_GATT_TIMEOUT = 9;
-
-        // For errors on secret handshake and retry success
-        int SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR = 10;
-
-        // For timeout on secret handshake and retry success
-        int SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT = 11;
-
-        // For secret handshake fail and restart GATT connection success
-        int SUCCESS_SECRET_HANDSHAKE_RECONNECT = 12;
-
-        // For address rotate and retry with new address success
-        int SUCCESS_ADDRESS_ROTATE = 13;
-
-        // For signal lost and retry with old address still success
-        int SUCCESS_SIGNAL_LOST = 14;
-    }
-
-    /** Fast Pair BrEdrHandover Error Code. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    BrEdrHandoverErrorCode.UNKNOWN_BR_EDR_HANDOVER_ERROR_CODE,
-                    BrEdrHandoverErrorCode.CONTROL_POINT_RESULT_CODE_NOT_SUCCESS,
-                    BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID,
-                    BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID,
-            })
-    public @interface BrEdrHandoverErrorCode {
-        int UNKNOWN_BR_EDR_HANDOVER_ERROR_CODE = 0;
-        int CONTROL_POINT_RESULT_CODE_NOT_SUCCESS = 1;
-        int BLUETOOTH_MAC_INVALID = 2;
-        int TRANSPORT_BLOCK_INVALID = 3;
-    }
-
-    /** Fast Pair CreateBound Error Code. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    CreateBondErrorCode.UNKNOWN_BOND_ERROR_CODE,
-                    CreateBondErrorCode.BOND_BROKEN,
-                    CreateBondErrorCode.POSSIBLE_MITM,
-                    CreateBondErrorCode.NO_PERMISSION,
-                    CreateBondErrorCode.INCORRECT_VARIANT,
-                    CreateBondErrorCode.FAILED_BUT_ALREADY_RECEIVE_PASS_KEY,
-            })
-    public @interface CreateBondErrorCode {
-        int UNKNOWN_BOND_ERROR_CODE = 0;
-        int BOND_BROKEN = 1;
-        int POSSIBLE_MITM = 2;
-        int NO_PERMISSION = 3;
-        int INCORRECT_VARIANT = 4;
-        int FAILED_BUT_ALREADY_RECEIVE_PASS_KEY = 5;
-    }
-
-    /** Fast Pair Connect Error Code. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    ConnectErrorCode.UNKNOWN_CONNECT_ERROR_CODE,
-                    ConnectErrorCode.UNSUPPORTED_PROFILE,
-                    ConnectErrorCode.GET_PROFILE_PROXY_FAILED,
-                    ConnectErrorCode.DISCONNECTED,
-                    ConnectErrorCode.LINK_KEY_CLEARED,
-                    ConnectErrorCode.FAIL_TO_DISCOVERY,
-                    ConnectErrorCode.DISCOVERY_NOT_FINISHED,
-            })
-    public @interface ConnectErrorCode {
-        int UNKNOWN_CONNECT_ERROR_CODE = 0;
-        int UNSUPPORTED_PROFILE = 1;
-        int GET_PROFILE_PROXY_FAILED = 2;
-        int DISCONNECTED = 3;
-        int LINK_KEY_CLEARED = 4;
-        int FAIL_TO_DISCOVERY = 5;
-        int DISCOVERY_NOT_FINISHED = 6;
-    }
-
-    private FastPairEventIntDefs() {}
-}
diff --git a/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java b/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java
deleted file mode 100644
index 91bf49a..0000000
--- a/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.intdefs;
-
-import androidx.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds integer definitions for NearbyEvent. */
-public class NearbyEventIntDefs {
-
-    /** NearbyEvent Code. */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {
-                    EventCode.UNKNOWN_EVENT_TYPE,
-                    EventCode.MAGIC_PAIR_START,
-                    EventCode.WAIT_FOR_SCREEN_UNLOCK,
-                    EventCode.GATT_CONNECT,
-                    EventCode.BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST,
-                    EventCode.BR_EDR_HANDOVER_READ_BLUETOOTH_MAC,
-                    EventCode.BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK,
-                    EventCode.GET_PROFILES_VIA_SDP,
-                    EventCode.DISCOVER_DEVICE,
-                    EventCode.CANCEL_DISCOVERY,
-                    EventCode.REMOVE_BOND,
-                    EventCode.CANCEL_BOND,
-                    EventCode.CREATE_BOND,
-                    EventCode.CONNECT_PROFILE,
-                    EventCode.DISABLE_BLUETOOTH,
-                    EventCode.ENABLE_BLUETOOTH,
-                    EventCode.MAGIC_PAIR_END,
-                    EventCode.SECRET_HANDSHAKE,
-                    EventCode.WRITE_ACCOUNT_KEY,
-                    EventCode.WRITE_TO_FOOTPRINTS,
-                    EventCode.PASSKEY_EXCHANGE,
-                    EventCode.DEVICE_RECOGNIZED,
-                    EventCode.GET_LOCAL_PUBLIC_ADDRESS,
-                    EventCode.DIRECTLY_CONNECTED_TO_PROFILE,
-                    EventCode.DEVICE_ALIAS_CHANGED,
-                    EventCode.WRITE_DEVICE_NAME,
-                    EventCode.UPDATE_PROVIDER_NAME_START,
-                    EventCode.UPDATE_PROVIDER_NAME_END,
-                    EventCode.READ_FIRMWARE_VERSION,
-                    EventCode.RETROACTIVE_PAIR_START,
-                    EventCode.RETROACTIVE_PAIR_END,
-                    EventCode.SUBSEQUENT_PAIR_START,
-                    EventCode.SUBSEQUENT_PAIR_END,
-                    EventCode.BISTO_PAIR_START,
-                    EventCode.BISTO_PAIR_END,
-                    EventCode.REMOTE_PAIR_START,
-                    EventCode.REMOTE_PAIR_END,
-                    EventCode.BEFORE_CREATE_BOND,
-                    EventCode.BEFORE_CREATE_BOND_BONDING,
-                    EventCode.BEFORE_CREATE_BOND_BONDED,
-                    EventCode.BEFORE_CONNECT_PROFILE,
-                    EventCode.HANDLE_PAIRING_REQUEST,
-                    EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION,
-                    EventCode.GATT_CONNECTION_AND_SECRET_HANDSHAKE,
-                    EventCode.CHECK_SIGNAL_AFTER_HANDSHAKE,
-                    EventCode.RECOVER_BY_RETRY_GATT,
-                    EventCode.RECOVER_BY_RETRY_HANDSHAKE,
-                    EventCode.RECOVER_BY_RETRY_HANDSHAKE_RECONNECT,
-                    EventCode.GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS,
-                    EventCode.PAIR_WITH_CACHED_MODEL_ID,
-                    EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS,
-                    EventCode.PAIR_WITH_NEW_MODEL,
-            })
-    public @interface EventCode {
-        int UNKNOWN_EVENT_TYPE = 0;
-
-        // Codes for Magic Pair.
-        // Starting at 1000 to not conflict with other existing codes (e.g.
-        // DiscoveryEvent) that may be migrated to become official Event Codes.
-        int MAGIC_PAIR_START = 1010;
-        int WAIT_FOR_SCREEN_UNLOCK = 1020;
-        int GATT_CONNECT = 1030;
-        int BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST = 1040;
-        int BR_EDR_HANDOVER_READ_BLUETOOTH_MAC = 1050;
-        int BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK = 1060;
-        int GET_PROFILES_VIA_SDP = 1070;
-        int DISCOVER_DEVICE = 1080;
-        int CANCEL_DISCOVERY = 1090;
-        int REMOVE_BOND = 1100;
-        int CANCEL_BOND = 1110;
-        int CREATE_BOND = 1120;
-        int CONNECT_PROFILE = 1130;
-        int DISABLE_BLUETOOTH = 1140;
-        int ENABLE_BLUETOOTH = 1150;
-        int MAGIC_PAIR_END = 1160;
-        int SECRET_HANDSHAKE = 1170;
-        int WRITE_ACCOUNT_KEY = 1180;
-        int WRITE_TO_FOOTPRINTS = 1190;
-        int PASSKEY_EXCHANGE = 1200;
-        int DEVICE_RECOGNIZED = 1210;
-        int GET_LOCAL_PUBLIC_ADDRESS = 1220;
-        int DIRECTLY_CONNECTED_TO_PROFILE = 1230;
-        int DEVICE_ALIAS_CHANGED = 1240;
-        int WRITE_DEVICE_NAME = 1250;
-        int UPDATE_PROVIDER_NAME_START = 1260;
-        int UPDATE_PROVIDER_NAME_END = 1270;
-        int READ_FIRMWARE_VERSION = 1280;
-        int RETROACTIVE_PAIR_START = 1290;
-        int RETROACTIVE_PAIR_END = 1300;
-        int SUBSEQUENT_PAIR_START = 1310;
-        int SUBSEQUENT_PAIR_END = 1320;
-        int BISTO_PAIR_START = 1330;
-        int BISTO_PAIR_END = 1340;
-        int REMOTE_PAIR_START = 1350;
-        int REMOTE_PAIR_END = 1360;
-        int BEFORE_CREATE_BOND = 1370;
-        int BEFORE_CREATE_BOND_BONDING = 1380;
-        int BEFORE_CREATE_BOND_BONDED = 1390;
-        int BEFORE_CONNECT_PROFILE = 1400;
-        int HANDLE_PAIRING_REQUEST = 1410;
-        int SECRET_HANDSHAKE_GATT_COMMUNICATION = 1420;
-        int GATT_CONNECTION_AND_SECRET_HANDSHAKE = 1430;
-        int CHECK_SIGNAL_AFTER_HANDSHAKE = 1440;
-        int RECOVER_BY_RETRY_GATT = 1450;
-        int RECOVER_BY_RETRY_HANDSHAKE = 1460;
-        int RECOVER_BY_RETRY_HANDSHAKE_RECONNECT = 1470;
-        int GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS = 1480;
-        int PAIR_WITH_CACHED_MODEL_ID = 1490;
-        int DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS = 1500;
-        int PAIR_WITH_NEW_MODEL = 1510;
-    }
-
-    private NearbyEventIntDefs() {}
-}
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
index c355df2..39bc60b 100644
--- a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
@@ -16,9 +16,7 @@
 
 package com.android.server.nearby.presence;
 
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
-
-import java.util.UUID;
+import android.os.ParcelUuid;
 
 /**
  * Constants for Nearby Presence operations.
@@ -26,5 +24,6 @@
 public class PresenceConstants {
 
     /** Presence advertisement service data uuid. */
-    public static final UUID PRESENCE_UUID = to128BitUuid((short) 0xFCF1);
+    public static final ParcelUuid PRESENCE_UUID =
+            ParcelUuid.fromString("0000fcf1-0000-1000-8000-00805f9b34fb");
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index e8aea79..e2fbe77 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.nearby.provider;
 
 import static com.android.server.nearby.NearbyService.TAG;
+import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID;
 
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
@@ -33,9 +34,7 @@
 import android.os.ParcelUuid;
 import android.util.Log;
 
-import com.android.server.nearby.common.bluetooth.fastpair.Constants;
 import com.android.server.nearby.injector.Injector;
-import com.android.server.nearby.presence.PresenceConstants;
 import com.android.server.nearby.util.ForegroundThread;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -50,10 +49,6 @@
  */
 public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
 
-    @VisibleForTesting
-    static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID);
-    private static final ParcelUuid PRESENCE_UUID = new ParcelUuid(PresenceConstants.PRESENCE_UUID);
-
     // Don't block the thread as it may be used by other services.
     private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
     private final Injector mInjector;
@@ -75,14 +70,9 @@
                         }
                         Map<ParcelUuid, byte[]> serviceDataMap = record.getServiceData();
                         if (serviceDataMap != null) {
-                            byte[] fastPairData = serviceDataMap.get(FAST_PAIR_UUID);
-                            if (fastPairData != null) {
-                                builder.setData(serviceDataMap.get(FAST_PAIR_UUID));
-                            } else {
-                                byte[] presenceData = serviceDataMap.get(PRESENCE_UUID);
-                                if (presenceData != null) {
-                                    builder.setData(serviceDataMap.get(PRESENCE_UUID));
-                                }
+                            byte[] presenceData = serviceDataMap.get(PRESENCE_UUID);
+                            if (presenceData != null) {
+                                builder.setData(serviceDataMap.get(PRESENCE_UUID));
                             }
                         }
                     }
@@ -104,7 +94,7 @@
         List<ScanFilter> scanFilterList = new ArrayList<>();
         scanFilterList.add(
                 new ScanFilter.Builder()
-                        .setServiceData(FAST_PAIR_UUID, new byte[]{0}, new byte[]{0})
+                        .setServiceData(PRESENCE_UUID, new byte[]{0}, new byte[]{0})
                         .build());
         return scanFilterList;
     }
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
deleted file mode 100644
index 0f99a2f..0000000
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.provider;
-
-import android.accounts.Account;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.nearby.FastPairDataProviderService;
-import android.nearby.aidl.ByteArrayParcel;
-import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
-import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
-import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
-import android.nearby.aidl.FastPairManageAccountRequestParcel;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
-import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import service.proto.Data;
-import service.proto.Rpcs;
-
-/**
- * FastPairDataProvider is a singleton that implements APIs to get FastPair data.
- */
-public class FastPairDataProvider {
-
-    private static final String TAG = "FastPairDataProvider";
-
-    private static FastPairDataProvider sInstance;
-
-    private ProxyFastPairDataProvider mProxyFastPairDataProvider;
-
-    /**
-     * Initializes FastPairDataProvider singleton.
-     */
-    public static synchronized FastPairDataProvider init(Context context) {
-        if (sInstance == null) {
-            sInstance = new FastPairDataProvider(context);
-        }
-        if (sInstance.mProxyFastPairDataProvider == null) {
-            Log.w(TAG, "no proxy fast pair data provider found");
-        } else {
-            sInstance.mProxyFastPairDataProvider.register();
-        }
-        return sInstance;
-    }
-
-    @Nullable
-    public static synchronized FastPairDataProvider getInstance() {
-        return sInstance;
-    }
-
-    private FastPairDataProvider(Context context) {
-        mProxyFastPairDataProvider = ProxyFastPairDataProvider.create(
-                context, FastPairDataProviderService.ACTION_FAST_PAIR_DATA_PROVIDER);
-        if (mProxyFastPairDataProvider == null) {
-            Log.d("FastPairService", "fail to initiate the fast pair proxy provider");
-        } else {
-            Log.d("FastPairService", "the fast pair proxy provider initiated");
-        }
-    }
-
-    /**
-     * Loads FastPairAntispoofKeyDeviceMetadata.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    @WorkerThread
-    @Nullable
-    public Rpcs.GetObservedDeviceResponse loadFastPairAntispoofKeyDeviceMetadata(byte[] modelId) {
-        if (mProxyFastPairDataProvider != null) {
-            FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel =
-                    new FastPairAntispoofKeyDeviceMetadataRequestParcel();
-            requestParcel.modelId = modelId;
-            return Utils.convertToGetObservedDeviceResponse(
-                    mProxyFastPairDataProvider
-                            .loadFastPairAntispoofKeyDeviceMetadata(requestParcel));
-        }
-        throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
-    }
-
-    /**
-     * Enrolls an account to Fast Pair.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    public void optIn(Account account) {
-        if (mProxyFastPairDataProvider != null) {
-            FastPairManageAccountRequestParcel requestParcel =
-                    new FastPairManageAccountRequestParcel();
-            requestParcel.account = account;
-            requestParcel.requestType = FastPairDataProviderService.MANAGE_REQUEST_ADD;
-            mProxyFastPairDataProvider.manageFastPairAccount(requestParcel);
-            return;
-        }
-        throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
-    }
-
-    /**
-     * Uploads the device info to Fast Pair account.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    public void upload(Account account, FastPairUploadInfo uploadInfo) {
-        if (mProxyFastPairDataProvider != null) {
-            FastPairManageAccountDeviceRequestParcel requestParcel =
-                    new FastPairManageAccountDeviceRequestParcel();
-            requestParcel.account = account;
-            requestParcel.requestType = FastPairDataProviderService.MANAGE_REQUEST_ADD;
-            requestParcel.accountKeyDeviceMetadata =
-                    Utils.convertToFastPairAccountKeyDeviceMetadata(uploadInfo);
-            mProxyFastPairDataProvider.manageFastPairAccountDevice(requestParcel);
-            return;
-        }
-        throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
-    }
-
-    /**
-     * Get recognized device from bloom filter.
-     */
-    public Data.FastPairDeviceWithAccountKey getRecognizedDevice(BloomFilter bloomFilter,
-            byte[] salt) {
-        return Data.FastPairDeviceWithAccountKey.newBuilder().build();
-    }
-
-    /**
-     * Loads FastPair device accountKeys for a given account, but not other detailed fields.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    public List<Data.FastPairDeviceWithAccountKey> loadFastPairDeviceWithAccountKey(
-            Account account) {
-        return loadFastPairDeviceWithAccountKey(account, new ArrayList<byte[]>(0));
-    }
-
-    /**
-     * Loads FastPair devices for a list of accountKeys of a given account.
-     *
-     * @param account The account of the FastPair devices.
-     * @param deviceAccountKeys The allow list of FastPair devices if it is not empty. Otherwise,
-     *                    the function returns accountKeys of all FastPair devices under the
-     *                    account, without detailed fields.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    public List<Data.FastPairDeviceWithAccountKey> loadFastPairDeviceWithAccountKey(
-            Account account, List<byte[]> deviceAccountKeys) {
-        if (mProxyFastPairDataProvider != null) {
-            FastPairAccountDevicesMetadataRequestParcel requestParcel =
-                    new FastPairAccountDevicesMetadataRequestParcel();
-            requestParcel.account = account;
-            requestParcel.deviceAccountKeys = new ByteArrayParcel[deviceAccountKeys.size()];
-            int i = 0;
-            for (byte[] deviceAccountKey : deviceAccountKeys) {
-                requestParcel.deviceAccountKeys[i] = new ByteArrayParcel();
-                requestParcel.deviceAccountKeys[i].byteArray = deviceAccountKey;
-                i = i + 1;
-            }
-            return Utils.convertToFastPairDevicesWithAccountKey(
-                    mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(requestParcel));
-        }
-        throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
-    }
-
-    /**
-     * Loads FastPair Eligible Accounts.
-     *
-     * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
-     */
-    public List<Account> loadFastPairEligibleAccounts() {
-        if (mProxyFastPairDataProvider != null) {
-            FastPairEligibleAccountsRequestParcel requestParcel =
-                    new FastPairEligibleAccountsRequestParcel();
-            return Utils.convertToAccountList(
-                    mProxyFastPairDataProvider.loadFastPairEligibleAccounts(requestParcel));
-        }
-        throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java
deleted file mode 100644
index f0ade6c..0000000
--- a/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.provider;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
-import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
-import android.nearby.aidl.FastPairManageAccountRequestParcel;
-import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
-import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
-import android.nearby.aidl.IFastPairDataProvider;
-import android.nearby.aidl.IFastPairEligibleAccountsCallback;
-import android.nearby.aidl.IFastPairManageAccountCallback;
-import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider;
-import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider.BoundServiceInfo;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor;
-import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceListener;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Proxy for IFastPairDataProvider implementations.
- */
-public class ProxyFastPairDataProvider implements ServiceListener<BoundServiceInfo> {
-
-    private static final int TIME_OUT_MILLIS = 10000;
-
-    /**
-     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
-     * null.
-     */
-    @Nullable
-    public static ProxyFastPairDataProvider create(Context context, String action) {
-        ProxyFastPairDataProvider proxy = new ProxyFastPairDataProvider(context, action);
-        if (proxy.checkServiceResolves()) {
-            return proxy;
-        } else {
-            return null;
-        }
-    }
-
-    private final ServiceMonitor mServiceMonitor;
-
-    private ProxyFastPairDataProvider(Context context, String action) {
-        // safe to use direct executor since our locks are not acquired in a code path invoked by
-        // our owning provider
-
-        mServiceMonitor = ServiceMonitor.create(context, "FAST_PAIR_DATA_PROVIDER",
-                CurrentUserServiceProvider.create(context, action), this);
-    }
-
-    private boolean checkServiceResolves() {
-        return mServiceMonitor.checkServiceResolves();
-    }
-
-    /**
-     * User service watch to connect to actually services implemented by OEMs.
-     */
-    public void register() {
-        mServiceMonitor.register();
-    }
-
-    // Fast Pair Data Provider doesn't maintain a long running state.
-    // Therefore, it doesn't need setup at bind time.
-    @Override
-    public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
-    }
-
-    // Fast Pair Data Provider doesn't maintain a long running state.
-    // Therefore, it doesn't need tear down at unbind time.
-    @Override
-    public void onUnbind() {
-    }
-
-    /**
-     * Invokes system api loadFastPairEligibleAccounts.
-     *
-     * @return an array of acccounts and their opt in status.
-     */
-    @WorkerThread
-    @Nullable
-    public FastPairEligibleAccountParcel[] loadFastPairEligibleAccounts(
-            FastPairEligibleAccountsRequestParcel requestParcel) {
-        final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
-        final AtomicReference<FastPairEligibleAccountParcel[]> response = new AtomicReference<>();
-        mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
-                IFastPairEligibleAccountsCallback callback =
-                        new IFastPairEligibleAccountsCallback.Stub() {
-                            public void onFastPairEligibleAccountsReceived(
-                                    FastPairEligibleAccountParcel[] accountParcels) {
-                                response.set(accountParcels);
-                                waitForCompletionLatch.countDown();
-                            }
-
-                            public void onError(int code, String message) {
-                                waitForCompletionLatch.countDown();
-                            }
-                        };
-                provider.loadFastPairEligibleAccounts(requestParcel, callback);
-            }
-
-            @Override
-            public void onError() {
-                waitForCompletionLatch.countDown();
-            }
-        });
-        try {
-            waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // skip.
-        }
-        return response.get();
-    }
-
-    /**
-     * Invokes system api manageFastPairAccount to opt in account, or opt out account.
-     */
-    @WorkerThread
-    public void manageFastPairAccount(FastPairManageAccountRequestParcel requestParcel) {
-        final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
-        mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
-                IFastPairManageAccountCallback callback =
-                        new IFastPairManageAccountCallback.Stub() {
-                            public void onSuccess() {
-                                waitForCompletionLatch.countDown();
-                            }
-
-                            public void onError(int code, String message) {
-                                waitForCompletionLatch.countDown();
-                            }
-                        };
-                provider.manageFastPairAccount(requestParcel, callback);
-            }
-
-            @Override
-            public void onError() {
-                waitForCompletionLatch.countDown();
-            }
-        });
-        try {
-            waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // skip.
-        }
-        return;
-    }
-
-    /**
-     * Invokes system api manageFastPairAccountDevice to add or remove a device from a Fast Pair
-     * account.
-     */
-    @WorkerThread
-    public void manageFastPairAccountDevice(
-            FastPairManageAccountDeviceRequestParcel requestParcel) {
-        final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
-        mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
-                IFastPairManageAccountDeviceCallback callback =
-                        new IFastPairManageAccountDeviceCallback.Stub() {
-                            public void onSuccess() {
-                                waitForCompletionLatch.countDown();
-                            }
-
-                            public void onError(int code, String message) {
-                                waitForCompletionLatch.countDown();
-                            }
-                        };
-                provider.manageFastPairAccountDevice(requestParcel, callback);
-            }
-
-            @Override
-            public void onError() {
-                waitForCompletionLatch.countDown();
-            }
-        });
-        try {
-            waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // skip.
-        }
-        return;
-    }
-
-    /**
-     * Invokes system api loadFastPairAntispoofKeyDeviceMetadata.
-     *
-     * @return the Fast Pair AntispoofKeyDeviceMetadata of a given device.
-     */
-    @WorkerThread
-    @Nullable
-    FastPairAntispoofKeyDeviceMetadataParcel loadFastPairAntispoofKeyDeviceMetadata(
-            FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel) {
-        final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
-        final AtomicReference<FastPairAntispoofKeyDeviceMetadataParcel> response =
-                new AtomicReference<>();
-        mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
-                IFastPairAntispoofKeyDeviceMetadataCallback callback =
-                        new IFastPairAntispoofKeyDeviceMetadataCallback.Stub() {
-                            public void onFastPairAntispoofKeyDeviceMetadataReceived(
-                                    FastPairAntispoofKeyDeviceMetadataParcel metadata) {
-                                response.set(metadata);
-                                waitForCompletionLatch.countDown();
-                            }
-
-                            public void onError(int code, String message) {
-                                waitForCompletionLatch.countDown();
-                            }
-                        };
-                provider.loadFastPairAntispoofKeyDeviceMetadata(requestParcel, callback);
-            }
-
-            @Override
-            public void onError() {
-                waitForCompletionLatch.countDown();
-            }
-        });
-        try {
-            waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // skip.
-        }
-        return response.get();
-    }
-
-    /**
-     * Invokes loadFastPairAccountDevicesMetadata.
-     *
-     * @return the metadata of Fast Pair devices that are associated with a given account.
-     */
-    @WorkerThread
-    @Nullable
-    FastPairAccountKeyDeviceMetadataParcel[] loadFastPairAccountDevicesMetadata(
-            FastPairAccountDevicesMetadataRequestParcel requestParcel) {
-        final CountDownLatch waitForCompletionLatch = new CountDownLatch(1);
-        final AtomicReference<FastPairAccountKeyDeviceMetadataParcel[]> response =
-                new AtomicReference<>();
-        mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder);
-                IFastPairAccountDevicesMetadataCallback callback =
-                        new IFastPairAccountDevicesMetadataCallback.Stub() {
-                            public void onFastPairAccountDevicesMetadataReceived(
-                                    FastPairAccountKeyDeviceMetadataParcel[] metadatas) {
-                                response.set(metadatas);
-                                waitForCompletionLatch.countDown();
-                            }
-
-                            public void onError(int code, String message) {
-                                waitForCompletionLatch.countDown();
-                            }
-                        };
-                provider.loadFastPairAccountDevicesMetadata(requestParcel, callback);
-            }
-
-            @Override
-            public void onError() {
-                waitForCompletionLatch.countDown();
-            }
-        });
-        try {
-            waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // skip.
-        }
-        return response.get();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/provider/Utils.java b/nearby/service/java/com/android/server/nearby/provider/Utils.java
deleted file mode 100644
index 0f1c567..0000000
--- a/nearby/service/java/com/android/server/nearby/provider/Utils.java
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.provider;
-
-import android.accounts.Account;
-import android.annotation.Nullable;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairDeviceMetadataParcel;
-import android.nearby.aidl.FastPairDiscoveryItemParcel;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-
-import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
-
-import com.google.protobuf.ByteString;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import service.proto.Cache;
-import service.proto.Data;
-import service.proto.FastPairString.FastPairStrings;
-import service.proto.Rpcs;
-
-/**
- * Utility functions to convert between different data classes.
- */
-class Utils {
-
-    static List<Data.FastPairDeviceWithAccountKey> convertToFastPairDevicesWithAccountKey(
-            @Nullable FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
-        if (metadataParcels == null) {
-            return new ArrayList<Data.FastPairDeviceWithAccountKey>(0);
-        }
-
-        List<Data.FastPairDeviceWithAccountKey> fpDeviceList =
-                new ArrayList<>(metadataParcels.length);
-        for (FastPairAccountKeyDeviceMetadataParcel metadataParcel : metadataParcels) {
-            if (metadataParcel == null) {
-                continue;
-            }
-            Data.FastPairDeviceWithAccountKey.Builder fpDeviceBuilder =
-                    Data.FastPairDeviceWithAccountKey.newBuilder();
-            if (metadataParcel.deviceAccountKey != null) {
-                fpDeviceBuilder.setAccountKey(
-                        ByteString.copyFrom(metadataParcel.deviceAccountKey));
-            }
-            if (metadataParcel.sha256DeviceAccountKeyPublicAddress != null) {
-                fpDeviceBuilder.setSha256AccountKeyPublicAddress(
-                        ByteString.copyFrom(metadataParcel.sha256DeviceAccountKeyPublicAddress));
-            }
-
-            Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder =
-                    Cache.StoredDiscoveryItem.newBuilder();
-
-            if (metadataParcel.discoveryItem != null) {
-                if (metadataParcel.discoveryItem.actionUrl != null) {
-                    storedDiscoveryItemBuilder.setActionUrl(metadataParcel.discoveryItem.actionUrl);
-                }
-                Cache.ResolvedUrlType urlType = Cache.ResolvedUrlType.forNumber(
-                        metadataParcel.discoveryItem.actionUrlType);
-                if (urlType != null) {
-                    storedDiscoveryItemBuilder.setActionUrlType(urlType);
-                }
-                if (metadataParcel.discoveryItem.appName != null) {
-                    storedDiscoveryItemBuilder.setAppName(metadataParcel.discoveryItem.appName);
-                }
-                if (metadataParcel.discoveryItem.authenticationPublicKeySecp256r1 != null) {
-                    storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
-                            ByteString.copyFrom(
-                                    metadataParcel.discoveryItem.authenticationPublicKeySecp256r1));
-                }
-                if (metadataParcel.discoveryItem.description != null) {
-                    storedDiscoveryItemBuilder.setDescription(
-                            metadataParcel.discoveryItem.description);
-                }
-                if (metadataParcel.discoveryItem.deviceName != null) {
-                    storedDiscoveryItemBuilder.setDeviceName(
-                            metadataParcel.discoveryItem.deviceName);
-                }
-                if (metadataParcel.discoveryItem.displayUrl != null) {
-                    storedDiscoveryItemBuilder.setDisplayUrl(
-                            metadataParcel.discoveryItem.displayUrl);
-                }
-                storedDiscoveryItemBuilder.setFirstObservationTimestampMillis(
-                        metadataParcel.discoveryItem.firstObservationTimestampMillis);
-                if (metadataParcel.discoveryItem.iconFifeUrl != null) {
-                    storedDiscoveryItemBuilder.setIconFifeUrl(
-                            metadataParcel.discoveryItem.iconFifeUrl);
-                }
-                if (metadataParcel.discoveryItem.iconPng != null) {
-                    storedDiscoveryItemBuilder.setIconPng(
-                            ByteString.copyFrom(metadataParcel.discoveryItem.iconPng));
-                }
-                if (metadataParcel.discoveryItem.id != null) {
-                    storedDiscoveryItemBuilder.setId(metadataParcel.discoveryItem.id);
-                }
-                storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
-                        metadataParcel.discoveryItem.lastObservationTimestampMillis);
-                if (metadataParcel.discoveryItem.macAddress != null) {
-                    storedDiscoveryItemBuilder.setMacAddress(
-                            metadataParcel.discoveryItem.macAddress);
-                }
-                if (metadataParcel.discoveryItem.packageName != null) {
-                    storedDiscoveryItemBuilder.setPackageName(
-                            metadataParcel.discoveryItem.packageName);
-                }
-                storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis(
-                        metadataParcel.discoveryItem.pendingAppInstallTimestampMillis);
-                storedDiscoveryItemBuilder.setRssi(metadataParcel.discoveryItem.rssi);
-                Cache.StoredDiscoveryItem.State state =
-                        Cache.StoredDiscoveryItem.State.forNumber(
-                                metadataParcel.discoveryItem.state);
-                if (state != null) {
-                    storedDiscoveryItemBuilder.setState(state);
-                }
-                if (metadataParcel.discoveryItem.title != null) {
-                    storedDiscoveryItemBuilder.setTitle(metadataParcel.discoveryItem.title);
-                }
-                if (metadataParcel.discoveryItem.triggerId != null) {
-                    storedDiscoveryItemBuilder.setTriggerId(metadataParcel.discoveryItem.triggerId);
-                }
-                storedDiscoveryItemBuilder.setTxPower(metadataParcel.discoveryItem.txPower);
-            }
-            if (metadataParcel.metadata != null) {
-                FastPairStrings.Builder stringsBuilder = FastPairStrings.newBuilder();
-                if (metadataParcel.metadata.connectSuccessCompanionAppInstalled != null) {
-                    stringsBuilder.setPairingFinishedCompanionAppInstalled(
-                            metadataParcel.metadata.connectSuccessCompanionAppInstalled);
-                }
-                if (metadataParcel.metadata.connectSuccessCompanionAppNotInstalled != null) {
-                    stringsBuilder.setPairingFinishedCompanionAppNotInstalled(
-                            metadataParcel.metadata.connectSuccessCompanionAppNotInstalled);
-                }
-                if (metadataParcel.metadata.failConnectGoToSettingsDescription != null) {
-                    stringsBuilder.setPairingFailDescription(
-                            metadataParcel.metadata.failConnectGoToSettingsDescription);
-                }
-                if (metadataParcel.metadata.initialNotificationDescription != null) {
-                    stringsBuilder.setTapToPairWithAccount(
-                            metadataParcel.metadata.initialNotificationDescription);
-                }
-                if (metadataParcel.metadata.initialNotificationDescriptionNoAccount != null) {
-                    stringsBuilder.setTapToPairWithoutAccount(
-                            metadataParcel.metadata.initialNotificationDescriptionNoAccount);
-                }
-                if (metadataParcel.metadata.initialPairingDescription != null) {
-                    stringsBuilder.setInitialPairingDescription(
-                            metadataParcel.metadata.initialPairingDescription);
-                }
-                if (metadataParcel.metadata.retroactivePairingDescription != null) {
-                    stringsBuilder.setRetroactivePairingDescription(
-                            metadataParcel.metadata.retroactivePairingDescription);
-                }
-                if (metadataParcel.metadata.subsequentPairingDescription != null) {
-                    stringsBuilder.setSubsequentPairingDescription(
-                            metadataParcel.metadata.subsequentPairingDescription);
-                }
-                if (metadataParcel.metadata.waitLaunchCompanionAppDescription != null) {
-                    stringsBuilder.setWaitAppLaunchDescription(
-                            metadataParcel.metadata.waitLaunchCompanionAppDescription);
-                }
-                storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build());
-
-                Cache.FastPairInformation.Builder fpInformationBuilder =
-                        Cache.FastPairInformation.newBuilder();
-                Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
-                        Rpcs.TrueWirelessHeadsetImages.newBuilder();
-                if (metadataParcel.metadata.trueWirelessImageUrlCase != null) {
-                    imagesBuilder.setCaseUrl(metadataParcel.metadata.trueWirelessImageUrlCase);
-                }
-                if (metadataParcel.metadata.trueWirelessImageUrlLeftBud != null) {
-                    imagesBuilder.setLeftBudUrl(
-                            metadataParcel.metadata.trueWirelessImageUrlLeftBud);
-                }
-                if (metadataParcel.metadata.trueWirelessImageUrlRightBud != null) {
-                    imagesBuilder.setRightBudUrl(
-                            metadataParcel.metadata.trueWirelessImageUrlRightBud);
-                }
-                fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build());
-                Rpcs.DeviceType deviceType =
-                        Rpcs.DeviceType.forNumber(metadataParcel.metadata.deviceType);
-                if (deviceType != null) {
-                    fpInformationBuilder.setDeviceType(deviceType);
-                }
-
-                storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build());
-            }
-            fpDeviceBuilder.setDiscoveryItem(storedDiscoveryItemBuilder.build());
-            fpDeviceList.add(fpDeviceBuilder.build());
-        }
-        return fpDeviceList;
-    }
-
-    static List<Account> convertToAccountList(
-            @Nullable FastPairEligibleAccountParcel[] accountParcels) {
-        if (accountParcels == null) {
-            return new ArrayList<Account>(0);
-        }
-        List<Account> accounts = new ArrayList<Account>(accountParcels.length);
-        for (FastPairEligibleAccountParcel parcel : accountParcels) {
-            if (parcel != null && parcel.account != null) {
-                accounts.add(parcel.account);
-            }
-        }
-        return accounts;
-    }
-
-    private static @Nullable Rpcs.Device convertToDevice(
-            FastPairAntispoofKeyDeviceMetadataParcel metadata) {
-
-        Rpcs.Device.Builder deviceBuilder = Rpcs.Device.newBuilder();
-        if (metadata.antispoofPublicKey != null) {
-            deviceBuilder.setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder()
-                    .setPublicKey(ByteString.copyFrom(metadata.antispoofPublicKey))
-                    .build());
-        }
-        if (metadata.deviceMetadata != null) {
-            Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
-                    Rpcs.TrueWirelessHeadsetImages.newBuilder();
-            if (metadata.deviceMetadata.trueWirelessImageUrlLeftBud != null) {
-                imagesBuilder.setLeftBudUrl(metadata.deviceMetadata.trueWirelessImageUrlLeftBud);
-            }
-            if (metadata.deviceMetadata.trueWirelessImageUrlRightBud != null) {
-                imagesBuilder.setRightBudUrl(metadata.deviceMetadata.trueWirelessImageUrlRightBud);
-            }
-            if (metadata.deviceMetadata.trueWirelessImageUrlCase != null) {
-                imagesBuilder.setCaseUrl(metadata.deviceMetadata.trueWirelessImageUrlCase);
-            }
-            deviceBuilder.setTrueWirelessImages(imagesBuilder.build());
-            if (metadata.deviceMetadata.imageUrl != null) {
-                deviceBuilder.setImageUrl(metadata.deviceMetadata.imageUrl);
-            }
-            if (metadata.deviceMetadata.intentUri != null) {
-                deviceBuilder.setIntentUri(metadata.deviceMetadata.intentUri);
-            }
-            if (metadata.deviceMetadata.name != null) {
-                deviceBuilder.setName(metadata.deviceMetadata.name);
-            }
-            Rpcs.DeviceType deviceType =
-                    Rpcs.DeviceType.forNumber(metadata.deviceMetadata.deviceType);
-            if (deviceType != null) {
-                deviceBuilder.setDeviceType(deviceType);
-            }
-            deviceBuilder.setBleTxPower(metadata.deviceMetadata.bleTxPower)
-                    .setTriggerDistance(metadata.deviceMetadata.triggerDistance);
-        }
-
-        return deviceBuilder.build();
-    }
-
-    private static @Nullable ByteString convertToImage(
-            FastPairAntispoofKeyDeviceMetadataParcel metadata) {
-        if (metadata.deviceMetadata == null || metadata.deviceMetadata.image == null) {
-            return null;
-        }
-
-        return ByteString.copyFrom(metadata.deviceMetadata.image);
-    }
-
-    private static @Nullable Rpcs.ObservedDeviceStrings
-            convertToObservedDeviceStrings(FastPairAntispoofKeyDeviceMetadataParcel metadata) {
-        if (metadata.deviceMetadata == null) {
-            return null;
-        }
-
-        Rpcs.ObservedDeviceStrings.Builder stringsBuilder = Rpcs.ObservedDeviceStrings.newBuilder();
-        if (metadata.deviceMetadata.connectSuccessCompanionAppInstalled != null) {
-            stringsBuilder.setConnectSuccessCompanionAppInstalled(
-                    metadata.deviceMetadata.connectSuccessCompanionAppInstalled);
-        }
-        if (metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled != null) {
-            stringsBuilder.setConnectSuccessCompanionAppNotInstalled(
-                    metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled);
-        }
-        if (metadata.deviceMetadata.downloadCompanionAppDescription != null) {
-            stringsBuilder.setDownloadCompanionAppDescription(
-                    metadata.deviceMetadata.downloadCompanionAppDescription);
-        }
-        if (metadata.deviceMetadata.failConnectGoToSettingsDescription != null) {
-            stringsBuilder.setFailConnectGoToSettingsDescription(
-                    metadata.deviceMetadata.failConnectGoToSettingsDescription);
-        }
-        if (metadata.deviceMetadata.initialNotificationDescription != null) {
-            stringsBuilder.setInitialNotificationDescription(
-                    metadata.deviceMetadata.initialNotificationDescription);
-        }
-        if (metadata.deviceMetadata.initialNotificationDescriptionNoAccount != null) {
-            stringsBuilder.setInitialNotificationDescriptionNoAccount(
-                    metadata.deviceMetadata.initialNotificationDescriptionNoAccount);
-        }
-        if (metadata.deviceMetadata.initialPairingDescription != null) {
-            stringsBuilder.setInitialPairingDescription(
-                    metadata.deviceMetadata.initialPairingDescription);
-        }
-        if (metadata.deviceMetadata.openCompanionAppDescription != null) {
-            stringsBuilder.setOpenCompanionAppDescription(
-                    metadata.deviceMetadata.openCompanionAppDescription);
-        }
-        if (metadata.deviceMetadata.retroactivePairingDescription != null) {
-            stringsBuilder.setRetroactivePairingDescription(
-                    metadata.deviceMetadata.retroactivePairingDescription);
-        }
-        if (metadata.deviceMetadata.subsequentPairingDescription != null) {
-            stringsBuilder.setSubsequentPairingDescription(
-                    metadata.deviceMetadata.subsequentPairingDescription);
-        }
-        if (metadata.deviceMetadata.unableToConnectDescription != null) {
-            stringsBuilder.setUnableToConnectDescription(
-                    metadata.deviceMetadata.unableToConnectDescription);
-        }
-        if (metadata.deviceMetadata.unableToConnectTitle != null) {
-            stringsBuilder.setUnableToConnectTitle(
-                    metadata.deviceMetadata.unableToConnectTitle);
-        }
-        if (metadata.deviceMetadata.updateCompanionAppDescription != null) {
-            stringsBuilder.setUpdateCompanionAppDescription(
-                    metadata.deviceMetadata.updateCompanionAppDescription);
-        }
-        if (metadata.deviceMetadata.waitLaunchCompanionAppDescription != null) {
-            stringsBuilder.setWaitLaunchCompanionAppDescription(
-                    metadata.deviceMetadata.waitLaunchCompanionAppDescription);
-        }
-
-        return stringsBuilder.build();
-    }
-
-    static @Nullable Rpcs.GetObservedDeviceResponse
-            convertToGetObservedDeviceResponse(
-                    @Nullable FastPairAntispoofKeyDeviceMetadataParcel metadata) {
-        if (metadata == null) {
-            return null;
-        }
-
-        Rpcs.GetObservedDeviceResponse.Builder responseBuilder =
-                Rpcs.GetObservedDeviceResponse.newBuilder();
-
-        Rpcs.Device device = convertToDevice(metadata);
-        if (device != null) {
-            responseBuilder.setDevice(device);
-        }
-        ByteString image = convertToImage(metadata);
-        if (image != null) {
-            responseBuilder.setImage(image);
-        }
-        Rpcs.ObservedDeviceStrings strings = convertToObservedDeviceStrings(metadata);
-        if (strings != null) {
-            responseBuilder.setStrings(strings);
-        }
-
-        return responseBuilder.build();
-    }
-
-    static @Nullable FastPairAccountKeyDeviceMetadataParcel
-            convertToFastPairAccountKeyDeviceMetadata(
-            @Nullable FastPairUploadInfo uploadInfo) {
-        if (uploadInfo == null) {
-            return null;
-        }
-
-        FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadataParcel =
-                new FastPairAccountKeyDeviceMetadataParcel();
-        if (uploadInfo.getAccountKey() != null) {
-            accountKeyDeviceMetadataParcel.deviceAccountKey =
-                    uploadInfo.getAccountKey().toByteArray();
-        }
-        if (uploadInfo.getSha256AccountKeyPublicAddress() != null) {
-            accountKeyDeviceMetadataParcel.sha256DeviceAccountKeyPublicAddress =
-                    uploadInfo.getSha256AccountKeyPublicAddress().toByteArray();
-        }
-        if (uploadInfo.getStoredDiscoveryItem() != null) {
-            accountKeyDeviceMetadataParcel.metadata =
-                    convertToFastPairDeviceMetadata(uploadInfo.getStoredDiscoveryItem());
-            accountKeyDeviceMetadataParcel.discoveryItem =
-                    convertToFastPairDiscoveryItem(uploadInfo.getStoredDiscoveryItem());
-        }
-
-        return accountKeyDeviceMetadataParcel;
-    }
-
-    private static @Nullable FastPairDiscoveryItemParcel
-            convertToFastPairDiscoveryItem(Cache.StoredDiscoveryItem storedDiscoveryItem) {
-        FastPairDiscoveryItemParcel discoveryItemParcel = new FastPairDiscoveryItemParcel();
-        discoveryItemParcel.actionUrl = storedDiscoveryItem.getActionUrl();
-        discoveryItemParcel.actionUrlType = storedDiscoveryItem.getActionUrlType().getNumber();
-        discoveryItemParcel.appName = storedDiscoveryItem.getAppName();
-        discoveryItemParcel.authenticationPublicKeySecp256r1 =
-                storedDiscoveryItem.getAuthenticationPublicKeySecp256R1().toByteArray();
-        discoveryItemParcel.description = storedDiscoveryItem.getDescription();
-        discoveryItemParcel.deviceName = storedDiscoveryItem.getDeviceName();
-        discoveryItemParcel.displayUrl = storedDiscoveryItem.getDisplayUrl();
-        discoveryItemParcel.firstObservationTimestampMillis =
-                storedDiscoveryItem.getFirstObservationTimestampMillis();
-        discoveryItemParcel.iconFifeUrl = storedDiscoveryItem.getIconFifeUrl();
-        discoveryItemParcel.iconPng = storedDiscoveryItem.getIconPng().toByteArray();
-        discoveryItemParcel.id = storedDiscoveryItem.getId();
-        discoveryItemParcel.lastObservationTimestampMillis =
-                storedDiscoveryItem.getLastObservationTimestampMillis();
-        discoveryItemParcel.macAddress = storedDiscoveryItem.getMacAddress();
-        discoveryItemParcel.packageName = storedDiscoveryItem.getPackageName();
-        discoveryItemParcel.pendingAppInstallTimestampMillis =
-                storedDiscoveryItem.getPendingAppInstallTimestampMillis();
-        discoveryItemParcel.rssi = storedDiscoveryItem.getRssi();
-        discoveryItemParcel.state = storedDiscoveryItem.getState().getNumber();
-        discoveryItemParcel.title = storedDiscoveryItem.getTitle();
-        discoveryItemParcel.triggerId = storedDiscoveryItem.getTriggerId();
-        discoveryItemParcel.txPower = storedDiscoveryItem.getTxPower();
-
-        return discoveryItemParcel;
-    }
-
-    /*  Do we upload these?
-        String downloadCompanionAppDescription =
-             bundle.getString("downloadCompanionAppDescription");
-        String locale = bundle.getString("locale");
-        String openCompanionAppDescription = bundle.getString("openCompanionAppDescription");
-        float triggerDistance = bundle.getFloat("triggerDistance");
-        String unableToConnectDescription = bundle.getString("unableToConnectDescription");
-        String unableToConnectTitle = bundle.getString("unableToConnectTitle");
-        String updateCompanionAppDescription = bundle.getString("updateCompanionAppDescription");
-    */
-    private static @Nullable FastPairDeviceMetadataParcel
-            convertToFastPairDeviceMetadata(Cache.StoredDiscoveryItem storedDiscoveryItem) {
-        FastPairStrings fpStrings = storedDiscoveryItem.getFastPairStrings();
-
-        FastPairDeviceMetadataParcel metadataParcel = new FastPairDeviceMetadataParcel();
-        metadataParcel.connectSuccessCompanionAppInstalled =
-                fpStrings.getPairingFinishedCompanionAppInstalled();
-        metadataParcel.connectSuccessCompanionAppNotInstalled =
-                fpStrings.getPairingFinishedCompanionAppNotInstalled();
-        metadataParcel.failConnectGoToSettingsDescription = fpStrings.getPairingFailDescription();
-        metadataParcel.initialNotificationDescription = fpStrings.getTapToPairWithAccount();
-        metadataParcel.initialNotificationDescriptionNoAccount =
-                fpStrings.getTapToPairWithoutAccount();
-        metadataParcel.initialPairingDescription = fpStrings.getInitialPairingDescription();
-        metadataParcel.retroactivePairingDescription = fpStrings.getRetroactivePairingDescription();
-        metadataParcel.subsequentPairingDescription = fpStrings.getSubsequentPairingDescription();
-        metadataParcel.waitLaunchCompanionAppDescription = fpStrings.getWaitAppLaunchDescription();
-
-        Cache.FastPairInformation fpInformation = storedDiscoveryItem.getFastPairInformation();
-        metadataParcel.trueWirelessImageUrlCase =
-                fpInformation.getTrueWirelessImages().getCaseUrl();
-        metadataParcel.trueWirelessImageUrlLeftBud =
-                fpInformation.getTrueWirelessImages().getLeftBudUrl();
-        metadataParcel.trueWirelessImageUrlRightBud =
-                fpInformation.getTrueWirelessImages().getRightBudUrl();
-        metadataParcel.deviceType = fpInformation.getDeviceType().getNumber();
-
-        return metadataParcel;
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
deleted file mode 100644
index 8bb83e9..0000000
--- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.server.nearby.util;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import service.proto.Cache.ScanFastPairStoreItem;
-import service.proto.Cache.StoredDiscoveryItem;
-import service.proto.FastPairString.FastPairStrings;
-import service.proto.Rpcs.Device;
-import service.proto.Rpcs.GetObservedDeviceResponse;
-import service.proto.Rpcs.ObservedDeviceStrings;
-
-/**
- * Utils class converts different data types {@link ScanFastPairStoreItem},
- * {@link StoredDiscoveryItem} and {@link GetObservedDeviceResponse},
- *
- */
-public final class DataUtils {
-
-    /**
-     * Converts a {@link GetObservedDeviceResponse} to a {@link ScanFastPairStoreItem}.
-     */
-    public static ScanFastPairStoreItem toScanFastPairStoreItem(
-            GetObservedDeviceResponse observedDeviceResponse,
-            @NonNull String bleAddress, @Nullable String account) {
-        Device device = observedDeviceResponse.getDevice();
-        String deviceName = device.getName();
-        return ScanFastPairStoreItem.newBuilder()
-                .setAddress(bleAddress)
-                .setActionUrl(device.getIntentUri())
-                .setDeviceName(deviceName)
-                .setIconPng(observedDeviceResponse.getImage())
-                .setIconFifeUrl(device.getImageUrl())
-                .setAntiSpoofingPublicKey(device.getAntiSpoofingKeyPair().getPublicKey())
-                .setFastPairStrings(getFastPairStrings(observedDeviceResponse, deviceName, account))
-                .build();
-    }
-
-    /**
-     * Prints readable string for a {@link ScanFastPairStoreItem}.
-     */
-    public static String toString(ScanFastPairStoreItem item) {
-        return "ScanFastPairStoreItem=[address:" + item.getAddress()
-                + ", actionUr:" + item.getActionUrl()
-                + ", deviceName:" + item.getDeviceName()
-                + ", iconPng:" + item.getIconPng()
-                + ", iconFifeUrl:" + item.getIconFifeUrl()
-                + ", antiSpoofingKeyPair:" + item.getAntiSpoofingPublicKey()
-                + ", fastPairStrings:" + toString(item.getFastPairStrings())
-                + "]";
-    }
-
-    /**
-     * Prints readable string for a {@link FastPairStrings}
-     */
-    public static String toString(FastPairStrings fastPairStrings) {
-        return "FastPairStrings["
-                + "tapToPairWithAccount=" + fastPairStrings.getTapToPairWithAccount()
-                + ", tapToPairWithoutAccount=" + fastPairStrings.getTapToPairWithoutAccount()
-                + ", initialPairingDescription=" + fastPairStrings.getInitialPairingDescription()
-                + ", pairingFinishedCompanionAppInstalled="
-                + fastPairStrings.getPairingFinishedCompanionAppInstalled()
-                + ", pairingFinishedCompanionAppNotInstalled="
-                + fastPairStrings.getPairingFinishedCompanionAppNotInstalled()
-                + ", subsequentPairingDescription="
-                + fastPairStrings.getSubsequentPairingDescription()
-                + ", retroactivePairingDescription="
-                + fastPairStrings.getRetroactivePairingDescription()
-                + ", waitAppLaunchDescription=" + fastPairStrings.getWaitAppLaunchDescription()
-                + ", pairingFailDescription=" + fastPairStrings.getPairingFailDescription()
-                + "]";
-    }
-
-    private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response,
-            String deviceName, @Nullable String account) {
-        ObservedDeviceStrings strings = response.getStrings();
-        return FastPairStrings.newBuilder()
-                .setTapToPairWithAccount(strings.getInitialNotificationDescription())
-                .setTapToPairWithoutAccount(
-                        strings.getInitialNotificationDescriptionNoAccount())
-                .setInitialPairingDescription(account == null
-                        ? strings.getInitialNotificationDescriptionNoAccount()
-                        : String.format(strings.getInitialPairingDescription(),
-                                deviceName, account))
-                .setPairingFinishedCompanionAppInstalled(
-                        strings.getConnectSuccessCompanionAppInstalled())
-                .setPairingFinishedCompanionAppNotInstalled(
-                        strings.getConnectSuccessCompanionAppNotInstalled())
-                .setSubsequentPairingDescription(strings.getSubsequentPairingDescription())
-                .setRetroactivePairingDescription(strings.getRetroactivePairingDescription())
-                .setWaitAppLaunchDescription(strings.getWaitLaunchCompanionAppDescription())
-                .setPairingFailDescription(strings.getFailConnectGoToSettingsDescription())
-                .build();
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java
deleted file mode 100644
index d397862..0000000
--- a/nearby/service/java/com/android/server/nearby/util/Environment.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.util;
-
-import android.content.ApexEnvironment;
-import android.content.pm.ApplicationInfo;
-import android.os.UserHandle;
-
-import java.io.File;
-
-/**
- * Provides function to make sure the function caller is from the same apex.
- */
-public class Environment {
-    /**
-     * NEARBY apex name.
-     */
-    private static final String NEARBY_APEX_NAME = "com.android.tethering";
-
-    /**
-     * The path where the Nearby apex is mounted.
-     * Current value = "/apex/com.android.tethering"
-     */
-    private static final String NEARBY_APEX_PATH =
-            new File("/apex", NEARBY_APEX_NAME).getAbsolutePath();
-
-    /**
-     * Nearby shared folder.
-     */
-    public static File getNearbyDirectory() {
-        return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME).getDeviceProtectedDataDir();
-    }
-
-    /**
-     * Nearby user specific folder.
-     */
-    public static File getNearbyDirectory(int userId) {
-        return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME)
-                .getCredentialProtectedDataDirForUser(UserHandle.of(userId));
-    }
-
-    /**
-     * Returns true if the app is in the nearby apex, false otherwise.
-     * Checks if the app's path starts with "/apex/com.android.tethering".
-     */
-    public static boolean isAppInNearbyApex(ApplicationInfo appInfo) {
-        return appInfo.sourceDir.startsWith(NEARBY_APEX_PATH);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
deleted file mode 100644
index 6021ff6..0000000
--- a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.server.nearby.util;
-
-import android.annotation.Nullable;
-import android.bluetooth.le.ScanRecord;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import com.android.server.nearby.common.ble.BleFilter;
-import com.android.server.nearby.common.ble.BleRecord;
-
-import java.util.Arrays;
-
-/**
- * Parses Fast Pair information out of {@link BleRecord}s.
- *
- * <p>There are 2 different packet formats that are supported, which is used can be determined by
- * packet length:
- *
- * <p>For 3-byte packets, the full packet is the model ID.
- *
- * <p>For all other packets, the first byte is the header, followed by the model ID, followed by
- * zero or more extra fields. Each field has its own header byte followed by the field value. The
- * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and
- * each extra field header is 0bLLLLTTTT (L = field length, T = field type).
- */
-public class FastPairDecoder {
-
-    private static final int FIELD_TYPE_BLOOM_FILTER = 0;
-    private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1;
-    private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2;
-    private static final int FIELD_TYPE_BATTERY = 3;
-    private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4;
-    public static final int FIELD_TYPE_CONNECTION_STATE = 5;
-    private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6;
-
-
-    /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */
-    private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID =
-            ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB");
-
-    /** The filter you use to scan for Fast Pair BLE advertisements. */
-    public static final BleFilter FILTER =
-            new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID,
-                    new byte[0]).build();
-
-    // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly
-    // without needing worry about signing errors.
-    private static final int HEADER_VERSION_BITMASK = 0b11100000;
-    private static final int HEADER_LENGTH_BITMASK = 0b00011110;
-    private static final int HEADER_VERSION_OFFSET = 5;
-    private static final int HEADER_LENGTH_OFFSET = 1;
-
-    private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000;
-    private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111;
-    private static final int EXTRA_FIELD_LENGTH_OFFSET = 4;
-    private static final int EXTRA_FIELD_TYPE_OFFSET = 0;
-
-    private static final int MIN_ID_LENGTH = 3;
-    private static final int MAX_ID_LENGTH = 14;
-    private static final int HEADER_INDEX = 0;
-    private static final int HEADER_LENGTH = 1;
-    private static final int FIELD_HEADER_LENGTH = 1;
-
-    // Not using java.util.IllegalFormatException because it is unchecked.
-    private static class IllegalFormatException extends Exception {
-        private IllegalFormatException(String message) {
-            super(message);
-        }
-    }
-
-    /**
-     * Gets model id data from broadcast
-     */
-    @Nullable
-    public static byte[] getModelId(@Nullable byte[] serviceData) {
-        if (serviceData == null) {
-            return null;
-        }
-
-        if (serviceData.length >= MIN_ID_LENGTH) {
-            if (serviceData.length == MIN_ID_LENGTH) {
-                // If the length == 3, all bytes are the ID. See flag docs for more about
-                // endianness.
-                return serviceData;
-            } else {
-                // Otherwise, the first byte is a header which contains the length of the big-endian
-                // model ID that follows. The model ID will be trimmed if it contains leading zeros.
-                int idIndex = 1;
-                int end = idIndex + getIdLength(serviceData);
-                while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) {
-                    idIndex++;
-                }
-                return Arrays.copyOfRange(serviceData, idIndex, end);
-            }
-        }
-        return null;
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(BleRecord bleRecord) {
-        return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the FastPair service data array if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getServiceDataArray(ScanRecord scanRecord) {
-        return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-    }
-
-    /** Gets the bloom filter from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilter(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER);
-    }
-
-    /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */
-    @Nullable
-    public static byte[] getBloomFilterSalt(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT);
-    }
-
-    /**
-     * Gets the suppress notification with bloom filter from the extra fields if available,
-     * otherwise returns null.
-     */
-    @Nullable
-    public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
-    }
-
-    /**
-     * Get random resolvableData
-     */
-    @Nullable
-    public static byte[] getRandomResolvableData(byte[] serviceData) {
-        return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA);
-    }
-
-    @Nullable
-    private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) {
-        if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) {
-            return null;
-        }
-        try {
-            return getExtraFields(serviceData).get(fieldId);
-        } catch (IllegalFormatException e) {
-            return null;
-        }
-    }
-
-    /** Gets extra field data at the end of the packet, defined by the extra field header. */
-    private static SparseArray<byte[]> getExtraFields(byte[] serviceData)
-            throws IllegalFormatException {
-        SparseArray<byte[]> extraFields = new SparseArray<>();
-        if (getVersion(serviceData) != 0) {
-            return extraFields;
-        }
-        int headerIndex = getFirstExtraFieldHeaderIndex(serviceData);
-        while (headerIndex < serviceData.length) {
-            int length = getExtraFieldLength(serviceData, headerIndex);
-            int index = headerIndex + FIELD_HEADER_LENGTH;
-            int type = getExtraFieldType(serviceData, headerIndex);
-            int end = index + length;
-            if (extraFields.get(type) == null) {
-                if (end <= serviceData.length) {
-                    extraFields.put(type, Arrays.copyOfRange(serviceData, index, end));
-                } else {
-                    throw new IllegalFormatException(
-                            "Invalid length, " + end + " is longer than service data size "
-                                    + serviceData.length);
-                }
-            }
-            headerIndex = end;
-        }
-        return extraFields;
-    }
-
-    /** Checks whether or not a valid ID is included in the service data packet. */
-    public static boolean hasBeaconIdBytes(BleRecord bleRecord) {
-        byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
-        return checkModelId(serviceData);
-    }
-
-    /** Check whether byte array is FastPair model id or not. */
-    public static boolean checkModelId(@Nullable byte[] scanResult) {
-        return scanResult != null
-                // The 3-byte format has no header byte (all bytes are the ID).
-                && (scanResult.length == MIN_ID_LENGTH
-                // Header byte exists. We support only format version 0. (A different version
-                // indicates
-                // a breaking change in the format.)
-                || (scanResult.length > MIN_ID_LENGTH
-                && getVersion(scanResult) == 0
-                && isIdLengthValid(scanResult)));
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(BleRecord bleRecord) {
-        return (getBloomFilter(getServiceDataArray(bleRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null);
-    }
-
-    /** Checks whether or not bloom filter is included in the service data packet. */
-    public static boolean hasBloomFilter(ScanRecord scanRecord) {
-        return (getBloomFilter(getServiceDataArray(scanRecord)) != null
-                || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null);
-    }
-
-    private static int getVersion(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? 0
-                : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET;
-    }
-
-    private static int getIdLength(byte[] serviceData) {
-        return serviceData.length == MIN_ID_LENGTH
-                ? MIN_ID_LENGTH
-                : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET;
-    }
-
-    private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) {
-        return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData);
-    }
-
-    private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK)
-                >> EXTRA_FIELD_LENGTH_OFFSET;
-    }
-
-    private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) {
-        return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET;
-    }
-
-    private static boolean isIdLengthValid(byte[] serviceData) {
-        int idLength = getIdLength(serviceData);
-        return MIN_ID_LENGTH <= idLength
-                && idLength <= MAX_ID_LENGTH
-                && idLength + HEADER_LENGTH <= serviceData.length;
-    }
-}
-
diff --git a/nearby/service/proto/src/fastpair/cache.proto b/nearby/service/proto/src/fastpair/cache.proto
deleted file mode 100644
index d4c7c3d..0000000
--- a/nearby/service/proto/src/fastpair/cache.proto
+++ /dev/null
@@ -1,427 +0,0 @@
-syntax = "proto3";
-package service.proto;
-import "src/fastpair/rpcs.proto";
-import "src/fastpair/fast_pair_string.proto";
-
-// db information for Fast Pair that gets from server.
-message ServerResponseDbItem {
-  // Device's model id.
-  string model_id = 1;
-
-  // Response was received from the server. Contains data needed to display
-  // FastPair notification such as device name, txPower of device, image used
-  // in the notification, etc.
-  GetObservedDeviceResponse get_observed_device_response = 2;
-
-  // The timestamp that make the server fetch.
-  int64 last_fetch_info_timestamp_millis = 3;
-
-  // Whether the item in the cache is expirable or not (when offline mode this
-  // will be false).
-  bool expirable = 4;
-}
-
-
-// Client side scan result.
-message StoredScanResult {
-  // REQUIRED
-  // Unique ID generated based on scan result
-  string id = 1;
-
-  // REQUIRED
-  NearbyType type = 2;
-
-  // REQUIRED
-  // The most recent all upper case mac associated with this item.
-  // (Mac-to-DiscoveryItem is a many-to-many relationship)
-  string mac_address = 4;
-
-  // Beacon's RSSI value
-  int32 rssi = 10;
-
-  // Beacon's tx power
-  int32 tx_power = 11;
-
-  // The mac address encoded in beacon advertisement. Currently only used by
-  // chromecast.
-  string device_setup_mac = 12;
-
-  // Uptime of the device in minutes. Stops incrementing at 255.
-  int32 uptime_minutes = 13;
-
-  // REQUIRED
-  // Client timestamp when the beacon was first observed in BLE scan.
-  int64 first_observation_timestamp_millis = 14;
-
-  // REQUIRED
-  // Client timestamp when the beacon was last observed in BLE scan.
-  int64 last_observation_timestamp_millis = 15;
-
-  // Deprecated fields.
-  reserved 3, 5, 6, 7, 8, 9;
-}
-
-
-// Data for a DiscoveryItem created from server response and client scan result.
-// Only caching original data from scan result, server response, timestamps
-// and user actions. Do not save generated data in this object.
-// Next ID: 50
-message StoredDiscoveryItem {
-  enum State {
-    // Default unknown state.
-    STATE_UNKNOWN = 0;
-
-    // The item is normal.
-    STATE_ENABLED = 1;
-
-    // The item has been muted by user.
-    STATE_MUTED = 2;
-
-    // The item has been disabled by us (likely temporarily).
-    STATE_DISABLED_BY_SYSTEM = 3;
-  }
-
-  // The status of the item.
-  // TODO(b/204409421) remove enum
-  enum DebugMessageCategory {
-    // Default unknown state.
-    STATUS_UNKNOWN = 0;
-
-    // The item is valid and visible in notification.
-    STATUS_VALID_NOTIFICATION = 1;
-
-    // The item made it to list but not to notification.
-    STATUS_VALID_LIST_VIEW = 2;
-
-    // The item is filtered out on client. Never made it to list view.
-    STATUS_DISABLED_BY_CLIENT = 3;
-
-    // The item is filtered out by server. Never made it to client.
-    STATUS_DISABLED_BY_SERVER = 4;
-  }
-
-  enum ExperienceType {
-    EXPERIENCE_UNKNOWN = 0;
-    EXPERIENCE_GOOD = 1;
-    EXPERIENCE_BAD = 2;
-  }
-
-  // REQUIRED
-  // Offline item: unique ID generated on client.
-  // Online item: unique ID generated on server.
-  string id = 1;
-
-  // REQUIRED
-  // The most recent all upper case mac associated with this item.
-  // (Mac-to-DiscoveryItem is a many-to-many relationship)
-  string mac_address = 4;
-
-  // REQUIRED
-  string action_url = 5;
-
-  // The bluetooth device name from advertisment
-  string device_name = 6;
-
-  // REQUIRED
-  // Item's title
-  string title = 7;
-
-  // Item's description.
-  string description = 8;
-
-  // The URL for display
-  string display_url = 9;
-
-  // REQUIRED
-  // Client timestamp when the beacon was last observed in BLE scan.
-  int64 last_observation_timestamp_millis = 10;
-
-  // REQUIRED
-  // Client timestamp when the beacon was first observed in BLE scan.
-  int64 first_observation_timestamp_millis = 11;
-
-  // REQUIRED
-  // Item's current state. e.g. if the item is blocked.
-  State state = 17;
-
-  // The resolved url type for the action_url.
-  ResolvedUrlType action_url_type = 19;
-
-  // The timestamp when the user is redirected to Play Store after clicking on
-  // the item.
-  int64 pending_app_install_timestamp_millis = 20;
-
-  // Beacon's RSSI value
-  int32 rssi = 22;
-
-  // Beacon's tx power
-  int32 tx_power = 23;
-
-  // Human readable name of the app designated to open the uri
-  // Used in the second line of the notification, "Open in {} app"
-  string app_name = 25;
-
-  // The timestamp when the attachment was created on PBS server. In case there
-  // are duplicate
-  // items with the same scanId/groupID, only show the one with the latest
-  // timestamp.
-  int64 attachment_creation_sec = 28;
-
-  // Package name of the App that owns this item.
-  string package_name = 30;
-
-  // The average star rating of the app.
-  float star_rating = 31;
-
-  // TriggerId identifies the trigger/beacon that is attached with a message.
-  // It's generated from server for online messages to synchronize formatting
-  // across client versions.
-  // Example:
-  // * BLE_UID: 3||deadbeef
-  // * BLE_URL: http://trigger.id
-  // See go/discovery-store-message-and-trigger-id for more details.
-  string trigger_id = 34;
-
-  // Bytes of item icon in PNG format displayed in Discovery item list.
-  bytes icon_png = 36;
-
-  // A FIFE URL of the item icon displayed in Discovery item list.
-  string icon_fife_url = 49;
-
-  // See equivalent field in NearbyItem.
-  bytes authentication_public_key_secp256r1 = 45;
-
-  // See equivalent field in NearbyItem.
-  FastPairInformation fast_pair_information = 46;
-
-  // Companion app detail.
-  CompanionAppDetails companion_detail = 47;
-
-  // Fast pair strings
-  FastPairStrings fast_pair_strings = 48;
-
-  // Deprecated fields.
-  reserved 2, 3, 12, 13, 14, 15, 16, 18, 21, 24, 26, 27, 29, 32, 33, 35, 37, 38, 39, 40, 41, 42, 43, 44;
-}
-enum ResolvedUrlType {
-  RESOLVED_URL_TYPE_UNKNOWN = 0;
-
-  // The url is resolved to a web page that is not a play store app.
-  // This can be considered as the default resolved type when it's
-  // not the other specific types.
-  WEBPAGE = 1;
-
-  // The url is resolved to the Google Play store app
-  // ie. play.google.com/store
-  APP = 2;
-}
-enum DiscoveryAttachmentType {
-  DISCOVERY_ATTACHMENT_TYPE_UNKNOWN = 0;
-
-  // The attachment is posted in the prod namespace (without "-debug")
-  DISCOVERY_ATTACHMENT_TYPE_NORMAL = 1;
-
-  // The attachment is posted in the debug namespace (with "-debug")
-  DISCOVERY_ATTACHMENT_TYPE_DEBUG = 2;
-}
-// Additional information relevant only for Fast Pair devices.
-message FastPairInformation {
-  // When true, Fast Pair will only create a bond with the device and not
-  // attempt to connect any profiles (for example, A2DP or HFP).
-  bool data_only_connection = 1;
-
-  // Additional images that are attached specifically for true wireless Fast
-  // Pair devices.
-  TrueWirelessHeadsetImages true_wireless_images = 3;
-
-  // When true, this device can support assistant function.
-  bool assistant_supported = 4;
-
-  // Features supported by the Fast Pair device.
-  repeated FastPairFeature features = 5;
-
-  // Optional, the name of the company producing this Fast Pair device.
-  string company_name = 6;
-
-  // Optional, the type of device.
-  DeviceType device_type = 7;
-
-  reserved 2;
-}
-
-
-enum NearbyType {
-  NEARBY_TYPE_UNKNOWN = 0;
-  // Proximity Beacon Service (PBS). This is the only type of nearbyItems which
-  // can be customized by 3p and therefore the intents passed should not be
-  // completely trusted. Deprecated already.
-  NEARBY_PROXIMITY_BEACON = 1;
-  // Physical Web URL beacon. Deprecated already.
-  NEARBY_PHYSICAL_WEB = 2;
-  // Chromecast beacon. Used on client-side only.
-  NEARBY_CHROMECAST = 3;
-  // Wear beacon. Used on client-side only.
-  NEARBY_WEAR = 4;
-  // A device (e.g. a Magic Pair device that needs to be set up). The special-
-  // case devices above (e.g. ChromeCast, Wear) might migrate to this type.
-  NEARBY_DEVICE = 6;
-  // Popular apps/urls based on user's current geo-location.
-  NEARBY_POPULAR_HERE = 7;
-
-  reserved 5;
-}
-
-// A locally cached Fast Pair device associating an account key with the
-// bluetooth address of the device.
-message StoredFastPairItem {
-  // The device's public mac address.
-  string mac_address = 1;
-
-  // The account key written to the device.
-  bytes account_key = 2;
-
-  // When user need to update provider name, enable this value to trigger
-  // writing new name to provider.
-  bool need_to_update_provider_name = 3;
-
-  // The retry times to update name into provider.
-  int32 update_name_retries = 4;
-
-  // Latest firmware version from the server.
-  string latest_firmware_version = 5;
-
-  // The firmware version that is on the device.
-  string device_firmware_version = 6;
-
-  // The timestamp from the last time we fetched the firmware version from the
-  // device.
-  int64 last_check_firmware_timestamp_millis = 7;
-
-  // The timestamp from the last time we fetched the firmware version from
-  // server.
-  int64 last_server_query_timestamp_millis = 8;
-
-  // Only allows one bloom filter check process to create gatt connection and
-  // try to read the firmware version value.
-  bool can_read_firmware = 9;
-
-  // Device's model id.
-  string model_id = 10;
-
-  // Features that this Fast Pair device supports.
-  repeated FastPairFeature features = 11;
-
-  // Keeps the stored discovery item in local cache, we can have most
-  // information of fast pair device locally without through footprints, i.e. we
-  // can have most fast pair features locally.
-  StoredDiscoveryItem discovery_item = 12;
-
-  // When true, the latest uploaded event to FMA is connected. We use
-  // it as the previous ACL state when getting the BluetoothAdapter STATE_OFF to
-  // determine if need to upload the disconnect event to FMA.
-  bool fma_state_is_connected = 13;
-
-  // Device's buffer size range.
-  repeated BufferSizeRange buffer_size_range = 18;
-
-  // The additional account key if this device could be associated with multiple
-  // accounts. Notes that for this device, the account_key field is the basic
-  // one which will not be associated with the accounts.
-  repeated bytes additional_account_key = 19;
-
-  // Deprecated fields.
-  reserved 14, 15, 16, 17;
-}
-
-// Contains information about Fast Pair devices stored through our scanner.
-// Next ID: 29
-message ScanFastPairStoreItem {
-  // Device's model id.
-  string model_id = 1;
-
-  // Device's RSSI value
-  int32 rssi = 2;
-
-  // Device's tx power
-  int32 tx_power = 3;
-
-  // Bytes of item icon in PNG format displayed in Discovery item list.
-  bytes icon_png = 4;
-
-  // A FIFE URL of the item icon displayed in Discovery item list.
-  string icon_fife_url = 28;
-
-  // Device name like "Bose QC 35".
-  string device_name = 5;
-
-  // Client timestamp when user last saw Fast Pair device.
-  int64 last_observation_timestamp_millis = 6;
-
-  // Action url after user click the notification.
-  string action_url = 7;
-
-  // Device's bluetooth address.
-  string address = 8;
-
-  // The computed threshold rssi value that would trigger FastPair notifications
-  int32 threshold_rssi = 9;
-
-  // Populated with the contents of the bloom filter in the event that
-  // the scanned device is advertising a bloom filter instead of a model id
-  bytes bloom_filter = 10;
-
-  // Device name from the BLE scan record
-  string ble_device_name = 11;
-
-  // Strings used for the FastPair UI
-  FastPairStrings fast_pair_strings = 12;
-
-  // A key used to authenticate advertising device.
-  // See NearbyItem.authentication_public_key_secp256r1 for more information.
-  bytes anti_spoofing_public_key = 13;
-
-  // When true, Fast Pair will only create a bond with the device and not
-  // attempt to connect any profiles (for example, A2DP or HFP).
-  bool data_only_connection = 14;
-
-  // The type of the manufacturer (first party, third party, etc).
-  int32 manufacturer_type_num = 15;
-
-  // Additional images that are attached specifically for true wireless Fast
-  // Pair devices.
-  TrueWirelessHeadsetImages true_wireless_images = 16;
-
-  // When true, this device can support assistant function.
-  bool assistant_supported = 17;
-
-  // Optional, the name of the company producing this Fast Pair device.
-  string company_name = 18;
-
-  // Features supported by the Fast Pair device.
-  FastPairFeature features = 19;
-
-  // The interaction type that this scan should trigger
-  InteractionType interaction_type = 20;
-
-  // The copy of the advertisement bytes, used to pass along to other
-  // apps that use Fast Pair as the discovery vehicle.
-  bytes full_ble_record = 21;
-
-  // Companion app related information
-  CompanionAppDetails companion_detail = 22;
-
-  // Client timestamp when user first saw Fast Pair device.
-  int64 first_observation_timestamp_millis = 23;
-
-  // The type of the device (wearable, headphones, etc).
-  int32 device_type_num = 24;
-
-  // The type of notification (app launch smart setup, etc).
-  NotificationType notification_type = 25;
-
-  // The customized title.
-  string customized_title = 26;
-
-  // The customized description.
-  string customized_description = 27;
-}
diff --git a/nearby/service/proto/src/fastpair/data.proto b/nearby/service/proto/src/fastpair/data.proto
deleted file mode 100644
index 6f4fadd..0000000
--- a/nearby/service/proto/src/fastpair/data.proto
+++ /dev/null
@@ -1,26 +0,0 @@
-syntax = "proto3";
-
-package service.proto;
-import "src/fastpair/cache.proto";
-
-// A device that has been Fast Paired with.
-message FastPairDeviceWithAccountKey {
-  // The account key which was written to the device after pairing completed.
-  bytes account_key = 1;
-
-  // The stored discovery item which represents the notification that should be
-  // associated with the device. Note, this is stored as a raw byte array
-  // instead of StoredDiscoveryItem because icing only supports proto lite and
-  // StoredDiscoveryItem is handed around as a nano proto in implementation,
-  // which are not compatible with each other.
-  StoredDiscoveryItem discovery_item = 3;
-
-  // SHA256 of "account key + headset's public address", this is used to
-  // identify the paired headset. Because of adding account key to generate the
-  // hash value, it makes the information anonymous, even for the same headset,
-  // different accounts have different values.
-  bytes sha256_account_key_public_address = 4;
-
-  // Deprecated fields.
-  reserved 2;
-}
diff --git a/nearby/service/proto/src/fastpair/fast_pair_string.proto b/nearby/service/proto/src/fastpair/fast_pair_string.proto
deleted file mode 100644
index f318c1a..0000000
--- a/nearby/service/proto/src/fastpair/fast_pair_string.proto
+++ /dev/null
@@ -1,40 +0,0 @@
-syntax = "proto2";
-
-package service.proto;
-
-message FastPairStrings {
-  // Required for initial pairing, used when there is a Google account on the
-  // device
-  optional string tap_to_pair_with_account = 1;
-
-  // Required for initial pairing, used when there is no Google account on the
-  // device
-  optional string tap_to_pair_without_account = 2;
-
-  // Description for initial pairing
-  optional string initial_pairing_description = 3;
-
-  // Description after successfully paired the device with companion app
-  // installed
-  optional string pairing_finished_companion_app_installed = 4;
-
-  // Description after successfully paired the device with companion app not
-  // installed
-  optional string pairing_finished_companion_app_not_installed = 5;
-
-  // Description when phone found the device that associates with user's account
-  // before remind user to pair with new device.
-  optional string subsequent_pairing_description = 6;
-
-  // Description when fast pair finds the user paired with device manually
-  // reminds user to opt the device into cloud.
-  optional string retroactive_pairing_description = 7;
-
-  // Description when user click setup device while device is still pairing
-  optional string wait_app_launch_description = 8;
-
-  // Description when user fail to pair with device
-  optional string pairing_fail_description = 9;
-
-  reserved 10, 11, 12, 13, 14,15, 16, 17, 18;
-}
diff --git a/nearby/service/proto/src/fastpair/rpcs.proto b/nearby/service/proto/src/fastpair/rpcs.proto
deleted file mode 100644
index bce4378..0000000
--- a/nearby/service/proto/src/fastpair/rpcs.proto
+++ /dev/null
@@ -1,301 +0,0 @@
-// RPCs for the Nearby Console service.
-syntax = "proto3";
-
-package service.proto;
-// Response containing an observed device.
-message GetObservedDeviceResponse {
-  // The device from the request.
-  Device device = 1;
-
-  // The image icon that shows in the notification
-  bytes image = 3;
-
-  // Strings to be displayed on notifications during the pairing process.
-  ObservedDeviceStrings strings = 4;
-
-  reserved 2;
-}
-
-message Device {
-  // Output only. The server-generated ID of the device.
-  int64 id = 1;
-
-  // The pantheon project number the device is created under. Only Nearby admins
-  // can change this.
-  int64 project_number = 2;
-
-  // How the notification will be displayed to the user
-  NotificationType notification_type = 3;
-
-  // The image to show on the notification.
-  string image_url = 4;
-
-  // The name of the device.
-  string name = 5;
-
-  // The intent that will be launched via the notification.
-  string intent_uri = 6;
-
-  // The transmit power of the device's BLE chip.
-  int32 ble_tx_power = 7;
-
-  // The distance that the device must be within to show a notification.
-  // If no distance is set, we default to 0.6 meters. Only Nearby admins can
-  // change this.
-  float trigger_distance = 8;
-
-  // Output only. Fast Pair only - The anti-spoofing key pair for the device.
-  AntiSpoofingKeyPair anti_spoofing_key_pair = 9;
-
-  // Output only. The current status of the device.
-  Status status = 10;
-
-
-  // DEPRECATED - check for published_version instead.
-  // Output only.
-  // Whether the device has a different, already published version.
-  bool has_published_version = 12;
-
-  // Fast Pair only - The type of device being registered.
-  DeviceType device_type = 13;
-
-
-  // Fast Pair only - Additional images for true wireless headsets.
-  TrueWirelessHeadsetImages true_wireless_images = 15;
-
-  // Fast Pair only - When true, this device can support assistant function.
-  bool assistant_supported = 16;
-
-  // Output only.
-  // The published version of a device that has been approved to be displayed
-  // as a notification - only populated if the device has a different published
-  // version. (A device that only has a published version would not have this
-  // populated).
-  Device published_version = 17;
-
-  // Fast Pair only - When true, Fast Pair will only create a bond with the
-  // device and not attempt to connect any profiles (for example, A2DP or HFP).
-  bool data_only_connection = 18;
-
-  // Name of the company/brand that will be selling the product.
-  string company_name = 19;
-
-  repeated FastPairFeature features = 20;
-
-  // Name of the device that is displayed on the console.
-  string display_name = 21;
-
-  // How the device will be interacted with by the user when the scan record
-  // is detected.
-  InteractionType interaction_type = 22;
-
-  // Companion app information.
-  CompanionAppDetails companion_detail = 23;
-
-  reserved 11, 14;
-}
-
-
-// Represents the format of the final device notification (which is directly
-// correlated to the action taken by the notification).
-enum NotificationType {
-  // Unspecified notification type.
-  NOTIFICATION_TYPE_UNSPECIFIED = 0;
-  // Notification launches the fast pair intent.
-  // Example Notification Title: "Bose SoundLink II"
-  // Notification Description: "Tap to pair with this device"
-  FAST_PAIR = 1;
-  // Notification launches an app.
-  // Notification Title: "[X]" where X is type/name of the device.
-  // Notification Description: "Tap to setup this device"
-  APP_LAUNCH = 2;
-  // Notification launches for Nearby Setup. The notification title and
-  // description is the same as APP_LAUNCH.
-  NEARBY_SETUP = 3;
-  // Notification launches the fast pair intent, but doesn't include an anti-
-  // spoofing key. The notification title and description is the same as
-  // FAST_PAIR.
-  FAST_PAIR_ONE = 4;
-  // Notification launches Smart Setup on devices.
-  // These notifications are identical to APP_LAUNCH except that they always
-  // launch Smart Setup intents within GMSCore.
-  SMART_SETUP = 5;
-}
-
-// How the device will be interacted with when it is seen.
-enum InteractionType {
-  INTERACTION_TYPE_UNKNOWN = 0;
-  AUTO_LAUNCH = 1;
-  NOTIFICATION = 2;
-}
-
-// Features that can be enabled for a Fast Pair device.
-enum FastPairFeature {
-  FAST_PAIR_FEATURE_UNKNOWN = 0;
-  SILENCE_MODE = 1;
-  WIRELESS_CHARGING = 2;
-  DYNAMIC_BUFFER_SIZE = 3;
-  NO_PERSONALIZED_NAME = 4;
-  EDDYSTONE_TRACKING = 5;
-}
-
-message CompanionAppDetails {
-  // Companion app slice provider's authority.
-  string authority = 1;
-
-  // Companion app certificate value.
-  string certificate_hash = 2;
-
-  // Deprecated fields.
-  reserved 3;
-}
-
-// Additional images for True Wireless Fast Pair devices.
-message TrueWirelessHeadsetImages {
-  // Image URL for the left bud.
-  string left_bud_url = 1;
-
-  // Image URL for the right bud.
-  string right_bud_url = 2;
-
-  // Image URL for the case.
-  string case_url = 3;
-}
-
-// Represents the type of device that is being registered.
-enum DeviceType {
-  DEVICE_TYPE_UNSPECIFIED = 0;
-  HEADPHONES = 1;
-  SPEAKER = 2;
-  WEARABLE = 3;
-  INPUT_DEVICE = 4;
-  AUTOMOTIVE = 5;
-  OTHER = 6;
-  TRUE_WIRELESS_HEADPHONES = 7;
-  WEAR_OS = 8;
-  ANDROID_AUTO = 9;
-}
-
-// An anti-spoofing key pair for a device that allows us to verify the device is
-// broadcasting legitimately.
-message AntiSpoofingKeyPair {
-  // The private key (restricted to only be viewable by trusted clients).
-  bytes private_key = 1;
-
-  // The public key.
-  bytes public_key = 2;
-}
-
-// Various states that a customer-configured device notification can be in.
-// PUBLISHED is the only state that shows notifications to the public.
-message Status {
-  // Status types available for each device.
-  enum StatusType {
-    // Unknown status.
-    TYPE_UNSPECIFIED = 0;
-    // Drafted device.
-    DRAFT = 1;
-    // Submitted and waiting for approval.
-    SUBMITTED = 2;
-    // Fully approved and available for end users.
-    PUBLISHED = 3;
-    // Rejected and not available for end users.
-    REJECTED = 4;
-  }
-
-  // Details about a device that has been rejected.
-  message RejectionDetails {
-    // The reason for the rejection.
-    enum RejectionReason {
-      // Unspecified reason.
-      REASON_UNSPECIFIED = 0;
-      // Name is not valid.
-      NAME = 1;
-      // Image is not valid.
-      IMAGE = 2;
-      // Tests have failed.
-      TESTS = 3;
-      // Other reason.
-      OTHER = 4;
-    }
-
-    // A list of reasons the device was rejected.
-    repeated RejectionReason reasons = 1;
-    // Comment about an OTHER rejection reason.
-    string additional_comment = 2;
-  }
-
-  // The status of the device.
-  StatusType status_type = 1;
-
-  // Accompanies Status.REJECTED.
-  RejectionDetails rejection_details = 2;
-}
-
-// Strings to be displayed in notifications surfaced for a device.
-message ObservedDeviceStrings {
-  // The notification description for when the device is initially discovered.
-  string initial_notification_description = 2;
-
-  // The notification description for when the device is initially discovered
-  // and no account is logged in.
-  string initial_notification_description_no_account = 3;
-
-  // The notification description for once we have finished pairing and the
-  // companion app has been opened. For google assistant devices, this string will point
-  // users to setting up the assistant.
-  string open_companion_app_description = 4;
-
-  // The notification description for once we have finished pairing and the
-  // companion app needs to be updated before use.
-  string update_companion_app_description = 5;
-
-  // The notification description for once we have finished pairing and the
-  // companion app needs to be installed.
-  string download_companion_app_description = 6;
-
-  // The notification title when a pairing fails.
-  string unable_to_connect_title = 7;
-
-  // The notification summary when a pairing fails.
-  string unable_to_connect_description = 8;
-
-  // The description that helps user initially paired with device.
-  string initial_pairing_description = 9;
-
-  // The description that let user open the companion app.
-  string connect_success_companion_app_installed = 10;
-
-  // The description that let user download the companion app.
-  string connect_success_companion_app_not_installed = 11;
-
-  // The description that reminds user there is a paired device nearby.
-  string subsequent_pairing_description = 12;
-
-  // The description that reminds users opt in their device.
-  string retroactive_pairing_description = 13;
-
-  // The description that indicates companion app is about to launch.
-  string wait_launch_companion_app_description = 14;
-
-  // The description that indicates go to bluetooth settings when connection
-  // fail.
-  string fail_connect_go_to_settings_description = 15;
-
-  reserved 1, 16, 17, 18, 19, 20, 21, 22, 23, 24;
-}
-
-// The buffer size range of a Fast Pair devices support dynamic buffer size.
-message BufferSizeRange {
-  // The max buffer size in ms.
-  int32 max_size = 1;
-
-  // The min buffer size in ms.
-  int32 min_size = 2;
-
-  // The default buffer size in ms.
-  int32 default_size = 3;
-
-  // The codec of this buffer size range.
-  int32 codec = 4;
-}
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt
deleted file mode 100644
index af3f75f..0000000
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 android.nearby.integration.privileged
-
-import android.content.Context
-import android.provider.Settings
-import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameters
-
-data class FastPairSettingsFlag(val name: String, val value: Int) {
-    override fun toString() = name
-}
-
-@RunWith(Parameterized::class)
-class FastPairSettingsProviderTest(private val flag: FastPairSettingsFlag) {
-
-    /** Verify privileged app can enable/disable Fast Pair scan. */
-    @Test
-    fun testSettingsFastPairScan_fromPrivilegedApp() {
-        val appContext = ApplicationProvider.getApplicationContext<Context>()
-        val contentResolver = appContext.contentResolver
-
-        Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", flag.value)
-
-        val actualValue = Settings.Secure.getInt(
-                contentResolver, "fast_pair_scan_enabled", /* default value */ -1)
-        assertThat(actualValue).isEqualTo(flag.value)
-    }
-
-    companion object {
-        @JvmStatic
-        @Parameters(name = "{0}Succeed")
-        fun fastPairScanFlags() = listOf(
-            FastPairSettingsFlag(name = "disable", value = 0),
-            FastPairSettingsFlag(name = "enable", value = 1),
-        )
-    }
-}
diff --git a/nearby/tests/integration/ui/Android.bp b/nearby/tests/integration/ui/Android.bp
deleted file mode 100644
index 524c838..0000000
--- a/nearby/tests/integration/ui/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "NearbyIntegrationUiTests",
-    defaults: ["mts-target-sdk-version-current"],
-    sdk_version: "test_current",
-    static_libs: ["NearbyIntegrationUiTestsLib"],
-    test_suites: ["device-tests"],
-}
-
-android_library {
-    name: "NearbyIntegrationUiTestsLib",
-    srcs: ["src/**/*.kt"],
-    sdk_version: "test_current",
-    static_libs: [
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "androidx.test.uiautomator_uiautomator",
-        "junit",
-        "platform-test-rules",
-        "service-nearby-pre-jarjar",
-        "truth-prebuilt",
-    ],
-}
diff --git a/nearby/tests/integration/ui/AndroidManifest.xml b/nearby/tests/integration/ui/AndroidManifest.xml
deleted file mode 100644
index 9aea0c1..0000000
--- a/nearby/tests/integration/ui/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.nearby.integration.ui">
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.nearby.integration.ui"
-        android:label="Nearby Mainline Module Integration UI Tests" />
-
-</manifest>
diff --git a/nearby/tests/integration/ui/AndroidTest.xml b/nearby/tests/integration/ui/AndroidTest.xml
deleted file mode 100644
index 9dfcf7b..0000000
--- a/nearby/tests/integration/ui/AndroidTest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<configuration description="Runs Nearby Mainline Module Integration UI Tests">
-    <!-- Needed for pulling the screen record files. -->
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="test-file-name" value="NearbyIntegrationUiTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="NearbyIntegrationUiTests" />
-    <option name="config-descriptor:metadata" key="mainline-param"
-            value="com.google.android.tethering.next.apex" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.nearby.integration.ui" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="hidden-api-checks" value="false"/>
-        <!-- test-timeout unit is ms, value = 5 min -->
-        <option name="test-timeout" value="300000" />
-    </test>
-
-    <!-- Only run NearbyIntegrationUiTests in MTS if the Nearby Mainline module is installed. -->
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
-    </object>
-
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/data/user/0/android.nearby.integration.ui/files" />
-        <option name="collect-on-run-ended-only" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
deleted file mode 100644
index 658775b..0000000
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 android.nearby.integration.ui
-
-import android.platform.test.rule.ArtifactSaver
-import android.platform.test.rule.ScreenRecordRule
-import android.platform.test.rule.TestWatcher
-import org.junit.Rule
-import org.junit.rules.TestRule
-import org.junit.rules.Timeout
-import org.junit.runner.Description
-
-abstract class BaseUiTest {
-    @get:Rule
-    var mGlobalTimeout: Timeout = Timeout.seconds(100) // Test times out in 1.67 minutes
-
-    @get:Rule
-    val mTestWatcherRule: TestRule = object : TestWatcher() {
-        override fun failed(throwable: Throwable?, description: Description?) {
-            super.failed(throwable, description)
-            ArtifactSaver.onError(description, throwable)
-        }
-    }
-
-    @get:Rule
-    val mScreenRecordRule: TestRule = ScreenRecordRule()
-}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 5a3538e..0000000
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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 android.nearby.integration.ui
-
-import android.content.Context
-import android.os.Bundle
-import android.platform.test.rule.ScreenRecordRule.ScreenRecord
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.server.nearby.common.eventloop.EventLoop
-import com.android.server.nearby.common.locator.Locator
-import com.android.server.nearby.common.locator.LocatorContextWrapper
-import com.android.server.nearby.fastpair.FastPairController
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.AfterClass
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import service.proto.Cache
-import service.proto.FastPairString.FastPairStrings
-import java.time.Clock
-
-/** An instrumented test to check Nearby half sheet UI showed correctly.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.integration.ui.CheckNearbyHalfSheetUiTest \
- * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class CheckNearbyHalfSheetUiTest : BaseUiTest() {
-    private var waitHalfSheetPopupTimeoutMs: Long
-    private var halfSheetTitleText: String
-    private var halfSheetSubtitleText: String
-
-    init {
-        val arguments: Bundle = InstrumentationRegistry.getArguments()
-        waitHalfSheetPopupTimeoutMs = arguments.getLong(
-            WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
-            DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
-        )
-        halfSheetTitleText =
-            arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
-        halfSheetSubtitleText =
-            arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
-
-        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-    }
-
-    /** For multidevice test snippet only. Force overwrites the test arguments. */
-    fun updateTestArguments(
-        waitHalfSheetPopupTimeoutSeconds: Int,
-        halfSheetTitleText: String,
-        halfSheetSubtitleText: String
-    ) {
-        this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
-        this.halfSheetTitleText = halfSheetTitleText
-        this.halfSheetSubtitleText = halfSheetSubtitleText
-    }
-
-    @Before
-    fun setUp() {
-        val appContext = ApplicationProvider.getApplicationContext<Context>()
-        val locator = Locator(appContext).apply {
-            overrideBindingForTest(EventLoop::class.java, EventLoop.newInstance("test"))
-            overrideBindingForTest(
-                FastPairCacheManager::class.java,
-                FastPairCacheManager(appContext)
-            )
-            overrideBindingForTest(FootprintsDeviceManager::class.java, FootprintsDeviceManager())
-            overrideBindingForTest(Clock::class.java, Clock.systemDefaultZone())
-        }
-        val locatorContextWrapper = LocatorContextWrapper(appContext, locator)
-        locator.overrideBindingForTest(
-            FastPairController::class.java,
-            FastPairController(locatorContextWrapper)
-        )
-        val scanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
-            .setDeviceName(DEFAULT_HALF_SHEET_TITLE_TEXT)
-            .setFastPairStrings(
-                FastPairStrings.newBuilder()
-                    .setInitialPairingDescription(DEFAULT_HALF_SHEET_SUBTITLE_TEXT).build()
-            )
-            .build()
-        FastPairHalfSheetManager(locatorContextWrapper).showHalfSheet(scanFastPairStoreItem)
-    }
-
-    @Test
-    @ScreenRecord
-    fun checkNearbyHalfSheetUi() {
-        // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
-        val isConnectButtonShowed = device.wait(
-            Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
-            waitHalfSheetPopupTimeoutMs
-        )
-        assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
-            .that(isConnectButtonShowed).isTrue()
-
-        val halfSheetTitle =
-            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
-        assertThat(halfSheetTitle).isNotNull()
-        assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
-
-        val halfSheetSubtitle =
-            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
-        assertThat(halfSheetSubtitle).isNotNull()
-        assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
-
-        val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
-        assertThat(deviceImage).isNotNull()
-
-        val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
-        assertThat(infoButton).isNotNull()
-    }
-
-    companion object {
-        private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 30 * 1000L
-        private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
-        private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
-                "appear on devices linked with nearby-mainline-fpseeker@google.com"
-        private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
-        private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
-        private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
-        private lateinit var device: UiDevice
-
-        @AfterClass
-        @JvmStatic
-        fun teardownClass() {
-            // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
-            DismissNearbyHalfSheetUiTest().dismissHalfSheet()
-        }
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 52d202a..0000000
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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 android.nearby.integration.ui
-
-import android.platform.test.rule.ScreenRecordRule.ScreenRecord
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to dismiss Nearby half sheet UI.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.integration.ui.DismissNearbyHalfSheetUiTest \
- * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class DismissNearbyHalfSheetUiTest : BaseUiTest() {
-    private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-
-    @Test
-    @ScreenRecord
-    fun dismissHalfSheet() {
-        device.pressHome()
-        device.waitForIdle()
-
-        assertWithMessage("Fail to dismiss Nearby half sheet.").that(
-            device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton)
-        ).isNull()
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
deleted file mode 100644
index 8b19d5c..0000000
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 android.nearby.integration.ui
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY
-import android.content.pm.PackageManager.ResolveInfoFlags
-import android.content.pm.ResolveInfo
-import android.util.Log
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import com.android.server.nearby.fastpair.FastPairManager
-import com.android.server.nearby.util.Environment
-import com.google.common.truth.Truth.assertThat
-
-/** UiMap for Nearby Mainline Half Sheet. */
-object NearbyHalfSheetUiMap {
-    private val PACKAGE_NAME: String = getHalfSheetApkPkgName()
-    private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
-    private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
-    private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
-
-    object DevicePairingFragment {
-        val halfSheetTitle: BySelector =
-            By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
-        val halfSheetSubtitle: BySelector =
-            By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
-        val deviceImage: BySelector =
-            By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
-        val connectButton: BySelector =
-            By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
-        val infoButton: BySelector =
-            By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
-    }
-
-    // Vendors might override HalfSheetUX in their vendor partition, query the package name
-    // instead of hard coding. ex: Google overrides it in vendor/google/modules/TetheringGoogle.
-    fun getHalfSheetApkPkgName(): String {
-        val appContext = ApplicationProvider.getApplicationContext<Context>()
-        val resolveInfos: MutableList<ResolveInfo> =
-            appContext.packageManager.queryIntentActivities(
-                Intent(FastPairManager.ACTION_RESOURCES_APK),
-                ResolveInfoFlags.of(MATCH_SYSTEM_ONLY.toLong())
-            )
-
-        // remove apps that don't live in the nearby apex
-        resolveInfos.removeIf { !Environment.isAppInNearbyApex(it.activityInfo.applicationInfo) }
-
-        assertThat(resolveInfos).hasSize(1)
-
-        val halfSheetApkPkgName: String = resolveInfos[0].activityInfo.applicationInfo.packageName
-        Log.i("NearbyHalfSheetUiMap", "Found half-sheet APK at: $halfSheetApkPkgName")
-        return halfSheetApkPkgName
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 27264b51..0000000
--- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 android.nearby.integration.ui
-
-import android.platform.test.rule.ScreenRecordRule.ScreenRecord
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import org.junit.AfterClass
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.integration.ui.PairByNearbyHalfSheetUiTest \
- * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class PairByNearbyHalfSheetUiTest : BaseUiTest() {
-    init {
-        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-    }
-
-    @Before
-    fun setUp() {
-        CheckNearbyHalfSheetUiTest().apply {
-            setUp()
-            checkNearbyHalfSheetUi()
-        }
-    }
-
-    @Test
-    @ScreenRecord
-    fun clickConnectButton() {
-        val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
-        device.findObject(connectButton).click()
-        device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
-    }
-
-    companion object {
-        private const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
-        private lateinit var device: UiDevice
-
-        @AfterClass
-        @JvmStatic
-        fun teardownClass() {
-            // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
-            device.pressBack()
-            DismissNearbyHalfSheetUiTest().dismissHalfSheet()
-        }
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt
deleted file mode 100644
index c549073..0000000
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 android.nearby.integration.untrusted
-
-import android.content.Context
-import android.content.ContentResolver
-import android.provider.Settings
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.test.assertFailsWith
-
-
-@RunWith(AndroidJUnit4::class)
-class FastPairSettingsProviderTest {
-    private lateinit var contentResolver: ContentResolver
-
-    @Before
-    fun setUp() {
-        contentResolver = ApplicationProvider.getApplicationContext<Context>().contentResolver
-    }
-
-    /** Verify untrusted app can read Fast Pair scan enabled setting. */
-    @Test
-    fun testSettingsFastPairScan_fromUnTrustedApp_readsSucceed() {
-        Settings.Secure.getInt(contentResolver,
-                "fast_pair_scan_enabled", /* default value */ -1)
-    }
-
-    /** Verify untrusted app can't write Fast Pair scan enabled setting. */
-    @Test
-    fun testSettingsFastPairScan_fromUnTrustedApp_writesFailed() {
-        assertFailsWith<SecurityException> {
-            Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", 1)
-        }
-    }
-}
diff --git a/nearby/tests/multidevices/OWNERS b/nearby/tests/multidevices/OWNERS
deleted file mode 100644
index f4dbde2..0000000
--- a/nearby/tests/multidevices/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 1092133
-
-ericth@google.com
-ryancllin@google.com
\ No newline at end of file
diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md
deleted file mode 100644
index 9d086de..0000000
--- a/nearby/tests/multidevices/README.md
+++ /dev/null
@@ -1,155 +0,0 @@
-# Nearby Mainline Fast Pair end-to-end tests
-
-This document refers to the Mainline Fast Pair project source code in the
-packages/modules/Connectivity/nearby. This is not an officially supported Google
-product.
-
-## About the Fast Pair Project
-
-The Connectivity Nearby mainline module is created in the Android T to host
-Better Together related functionality. Fast Pair is one of the main
-functionalities to provide seamless onboarding and integrated experiences for
-peripheral devices (for example, headsets like Google Pixel Buds) in the Nearby
-component.
-
-## Fully automated test
-
-### Prerequisites
-
-The fully automated end-to-end (e2e) tests are host-driven tests (which means
-test logics are in the host test scripts) using Mobly runner in Python. The two
-phones are installed with the test snippet
-`NearbyMultiDevicesClientsSnippets.apk` in the test time to let the host scripts
-control both sides for testing. Here's the overview of the test environment.
-
-Workstation (runs Python test scripts and controls Android devices through USB
-ADB) \
-├── Phone 1: As Fast Pair seeker role, to scan, pair Fast Pair devices nearby \
-└── Phone 2: As Fast Pair provider role, to simulate a Fast Pair device (for
-example, a Bluetooth headset)
-
-Note: These two phones need to be physically within 0.3 m of each other.
-
-### Prepare Phone 1 (Fast Pair seeker role)
-
-This is the phone to scan/pair Fast Pair devices nearby using the Nearby
-Mainline module. Test it by flashing with the Android T ROM.
-
-### Prepare Phone 2 (Fast Pair provider role)
-
-This is the phone to simulate a Fast Pair device (for example, a Bluetooth
-headset). Flash it with a customized ROM with the following changes:
-
-*   Adjust Bluetooth profile configurations. \
-    The Fast Pair provider simulator is an opposite role to the seeker. It needs
-    to enable/disable the following Bluetooth profile:
-    *   Disable A2DP source (bluetooth.profile.a2dp.source.enabled)
-    *   Enable A2DP sink (bluetooth.profile.a2dp.sink.enabled)
-    *   Disable the AVRCP controller (bluetooth.profile.avrcp.controller.enabled)
-    *   Enable the AVRCP target (bluetooth.profile.avrcp.target.enabled)
-    *   Enable the HFP service (bluetooth.profile.hfp.ag.enabled, bluetooth.profile.hfp.hf.enabled)
-
-```makefile
-# The Bluetooth profiles that Fast Pair provider simulator expect to have enabled.
-PRODUCT_PRODUCT_PROPERTIES += \
-    bluetooth.device.default_name=FastPairProviderSimulator \
-    bluetooth.profile.a2dp.source.enabled=false \
-    bluetooth.profile.a2dp.sink.enabled=true \
-    bluetooth.profile.avrcp.controller.enabled=false \
-    bluetooth.profile.avrcp.target.enabled=true \
-    bluetooth.profile.hfp.ag.enabled=true \
-    bluetooth.profile.hfp.hf.enabled=true
-```
-
-*   Adjust Bluetooth TX power limitation in Bluetooth module and disable the
-    Fast Pair in Google Play service (aka GMS)
-
-```shell
-adb root
-adb shell am broadcast \
-  -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' \
-  --es package "com.google.android.gms.nearby" \
-  --es user "\*" \
-  --esa flags "enabled" \
-  --esa types "boolean" \
-  --esa values "false" \
-  com.google.android.gms
-```
-
-### Running tests
-
-To run the tests, enter:
-
-```shell
-atest -v CtsNearbyMultiDevicesTestSuite
-```
-
-## Manual testing the seeker side with headsets
-
-Use this testing with headsets such as Google Pixel buds.
-
-The `FastPairTestDataProviderService.apk` is a run-time configurable Fast Pair
-data provider service (`FastPairDataProviderService`):
-
-`packages/modules/Connectivity/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider`
-
-It has a test data manager(`FastPairTestDataManager`) to receive intent
-broadcasts to add or clear the test data cache (`FastPairTestDataCache`). This
-cache provides the data to return to the Fast Pair module for onXXX calls (for
-example, `onLoadFastPairAntispoofKeyDeviceMetadata`) so you can feed the
-metadata for your device.
-
-Here are some sample uses:
-
-*   Send FastPairAntispoofKeyDeviceMetadata for PixelBuds-A to
-    FastPairTestDataCache \
-    `./fast_pair_data_provider_shell.sh -m=718c17
-    -a=../test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt`
-*   Send FastPairAccountDevicesMetadata for PixelBuds-A to FastPairTestDataCache
-    \
-    `./fast_pair_data_provider_shell.sh
-    -d=../test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt`
-*   Send FastPairAntispoofKeyDeviceMetadata for Provider Simulator to
-    FastPairTestDataCache \
-    `./fast_pair_data_provider_shell.sh -m=00000c
-    -a=../test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt`
-*   Send FastPairAccountDevicesMetadata for Provider Simulator to
-    FastPairTestDataCache \
-    `./fast_pair_data_provider_shell.sh
-    -d=../test_data/fastpair/simulator_account_devicemeta_json.txt`
-*   Clear FastPairTestDataCache \
-    `./fast_pair_data_provider_shell.sh -c`
-
-See
-[host/tool/fast_pair_data_provider_shell.sh](host/tool/fast_pair_data_provider_shell.sh)
-for more documentation.
-
-To install the data provider as system private app, consider remounting the
-system partition:
-
-```
-adb root && adb remount
-```
-
-Push it in:
-
-```
-adb push ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairSeekerDataProvider
-/system/priv-app/
-```
-
-Then reboot:
-
-```
-adb reboot
-```
-
-## Manual testing the seeker side with provider simulator app
-
-The `NearbyFastPairProviderSimulatorApp.apk` is a simple Android app to let you
-control the state of the Fast Pair provider simulator. Install this app on phone
-2 (Fast Pair provider role) to work correctly.
-
-See
-[clients/test_support/fastpair_provider/simulator_app/Android.bp](clients/test_support/fastpair_provider/simulator_app/Android.bp)
-for more documentation.
diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp
deleted file mode 100644
index db6d191..0000000
--- a/nearby/tests/multidevices/clients/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "NearbyMultiDevicesClientsLib",
-    srcs: ["src/**/*.kt"],
-    sdk_version: "test_current",
-    static_libs: [
-        "MoblySnippetHelperLib",
-        "NearbyFastPairProviderLib",
-        "NearbyFastPairSeekerSharedLib",
-        "NearbyIntegrationUiTestsLib",
-        "androidx.test.core",
-        "androidx.test.ext.junit",
-        "kotlin-stdlib",
-        "mobly-snippet-lib",
-        "truth-prebuilt",
-    ],
-}
-
-android_app {
-    name: "NearbyMultiDevicesClientsSnippets",
-    sdk_version: "test_current",
-    certificate: "platform",
-    static_libs: ["NearbyMultiDevicesClientsLib"],
-    optimize: {
-        enabled: true,
-        shrink: false,
-        // Required to avoid class collisions from static and shared linking
-        // of MessageNano.
-        proguard_compatibility: true,
-        proguard_flags_files: ["proguard.flags"],
-    },
-}
diff --git a/nearby/tests/multidevices/clients/AndroidManifest.xml b/nearby/tests/multidevices/clients/AndroidManifest.xml
deleted file mode 100644
index 86c10b2..0000000
--- a/nearby/tests/multidevices/clients/AndroidManifest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nearby.multidevices">
-
-    <uses-feature android:name="android.hardware.bluetooth" />
-    <uses-feature android:name="android.hardware.bluetooth_le" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
-    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
-    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
-    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
-    <application>
-        <meta-data
-            android:name="mobly-log-tag"
-            android:value="NearbyMainlineSnippet" />
-        <meta-data
-            android:name="mobly-snippets"
-            android:value="android.nearby.multidevices.fastpair.seeker.FastPairSeekerSnippet,
-                           android.nearby.multidevices.fastpair.provider.FastPairProviderSimulatorSnippet" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="Nearby Mainline Module Instrumentation Test"
-        android:targetPackage="android.nearby.multidevices" />
-
-    <instrumentation
-        android:name="com.google.android.mobly.snippet.SnippetRunner"
-        android:label="Nearby Mainline Module Mobly Snippet"
-        android:targetPackage="android.nearby.multidevices" />
-
-</manifest>
diff --git a/nearby/tests/multidevices/clients/proguard.flags b/nearby/tests/multidevices/clients/proguard.flags
deleted file mode 100644
index 11938cd..0000000
--- a/nearby/tests/multidevices/clients/proguard.flags
+++ /dev/null
@@ -1,29 +0,0 @@
-# Keep all snippet classes.
--keep class android.nearby.multidevices.** {
-     *;
-}
-
-# Keep AdvertisingSetCallback#onOwnAddressRead callback.
--keep class * extends android.bluetooth.le.AdvertisingSetCallback {
-     *;
-}
-
-# Do not touch Mobly.
--keep class com.google.android.mobly.** {
-  *;
-}
-
-# Keep names for easy debugging.
--dontobfuscate
-
-# Necessary to allow debugging.
--keepattributes *
-
-# By default, proguard leaves all classes in their original package, which
-# needlessly repeats com.google.android.apps.etc.
--repackageclasses ""
-
-# Allows proguard to make private and protected methods and fields public as
-# part of optimization. This lets proguard inline trivial getter/setter
-# methods.
--allowaccessmodification
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
deleted file mode 100644
index 922e950..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.provider
-
-import android.annotation.TargetApi
-import android.content.Context
-import android.nearby.multidevices.fastpair.provider.controller.FastPairProviderSimulatorController
-import android.nearby.multidevices.fastpair.provider.events.ProviderStatusEvents
-import android.os.Build
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.android.mobly.snippet.Snippet
-import com.google.android.mobly.snippet.rpc.AsyncRpc
-import com.google.android.mobly.snippet.rpc.Rpc
-
-/** Expose Mobly RPC methods for Python side to simulate fast pair provider role. */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-class FastPairProviderSimulatorSnippet : Snippet {
-    private val context: Context = InstrumentationRegistry.getInstrumentation().context
-    private val fastPairProviderSimulatorController = FastPairProviderSimulatorController(context)
-
-    /** Sets up the Fast Pair provider simulator. */
-    @AsyncRpc(description = "Sets up FP provider simulator.")
-    fun setupProviderSimulator(callbackId: String) {
-        fastPairProviderSimulatorController.setupProviderSimulator(ProviderStatusEvents(callbackId))
-    }
-
-    /**
-     * Starts model id advertising for scanning and initial pairing.
-     *
-     * @param callbackId the callback ID corresponding to the
-     * [FastPairProviderSimulatorSnippet#startProviderSimulator] call that started the scanning.
-     * @param modelId a 3-byte hex string for seeker side to recognize the device (ex: 0x00000C).
-     * @param antiSpoofingKeyString a public key for registered headsets.
-     */
-    @AsyncRpc(description = "Starts model id advertising for scanning and initial pairing.")
-    fun startModelIdAdvertising(
-        callbackId: String,
-        modelId: String,
-        antiSpoofingKeyString: String
-    ) {
-        fastPairProviderSimulatorController.startModelIdAdvertising(
-            modelId,
-            antiSpoofingKeyString,
-            ProviderStatusEvents(callbackId)
-        )
-    }
-
-    /** Tears down the Fast Pair provider simulator. */
-    @Rpc(description = "Tears down FP provider simulator.")
-    fun teardownProviderSimulator() {
-        fastPairProviderSimulatorController.teardownProviderSimulator()
-    }
-
-    /** Gets BLE mac address of the Fast Pair provider simulator. */
-    @Rpc(description = "Gets BLE mac address of the Fast Pair provider simulator.")
-    fun getBluetoothLeAddress(): String {
-        return fastPairProviderSimulatorController.getProviderSimulatorBleAddress()
-    }
-
-    /** Gets the latest account key received on the Fast Pair provider simulator */
-    @Rpc(description = "Gets the latest account key received on the Fast Pair provider simulator.")
-    fun getLatestReceivedAccountKey(): String? {
-        return fastPairProviderSimulatorController.getLatestReceivedAccountKey()
-    }
-}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
deleted file mode 100644
index a2d2659..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.provider.controller
-
-import android.bluetooth.le.AdvertiseSettings
-import android.content.Context
-import android.nearby.fastpair.provider.FastPairSimulator
-import android.nearby.fastpair.provider.bluetooth.BluetoothController
-import com.google.android.mobly.snippet.util.Log
-import com.google.common.io.BaseEncoding.base64
-
-class FastPairProviderSimulatorController(private val context: Context) :
-    FastPairSimulator.AdvertisingChangedCallback, BluetoothController.EventListener {
-    private lateinit var bluetoothController: BluetoothController
-    private lateinit var eventListener: EventListener
-    private var simulator: FastPairSimulator? = null
-
-    fun setupProviderSimulator(listener: EventListener) {
-        eventListener = listener
-
-        bluetoothController = BluetoothController(context, this)
-        bluetoothController.registerBluetoothStateReceiver()
-        bluetoothController.enableBluetooth()
-        bluetoothController.connectA2DPSinkProfile()
-    }
-
-    fun teardownProviderSimulator() {
-        simulator?.destroy()
-        bluetoothController.unregisterBluetoothStateReceiver()
-    }
-
-    fun startModelIdAdvertising(
-        modelId: String,
-        antiSpoofingKeyString: String,
-        listener: EventListener
-    ) {
-        eventListener = listener
-
-        val antiSpoofingKey = base64().decode(antiSpoofingKeyString)
-        simulator = FastPairSimulator(
-            context, FastPairSimulator.Options.builder(modelId)
-                .setAdvertisingModelId(modelId)
-                .setBluetoothAddress(null)
-                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                .setAdvertisingChangedCallback(this)
-                .setAntiSpoofingPrivateKey(antiSpoofingKey)
-                .setUseRandomSaltForAccountKeyRotation(false)
-                .setDataOnlyConnection(false)
-                .setShowsPasskeyConfirmation(false)
-                .setRemoveAllDevicesDuringPairing(true)
-                .build()
-        )
-    }
-
-    fun getProviderSimulatorBleAddress() = simulator!!.bleAddress!!
-
-    fun getLatestReceivedAccountKey() =
-        simulator!!.accountKey?.let { base64().encode(it.toByteArray()) }
-
-    /**
-     * Called when we change our BLE advertisement.
-     *
-     * @param isAdvertising the advertising status.
-     */
-    override fun onAdvertisingChanged(isAdvertising: Boolean) {
-        Log.i("FastPairSimulator onAdvertisingChanged(isAdvertising: $isAdvertising)")
-        eventListener.onAdvertisingChange(isAdvertising)
-    }
-
-    /** The callback for the first onServiceConnected of A2DP sink profile. */
-    override fun onA2DPSinkProfileConnected() {
-        eventListener.onA2DPSinkProfileConnected()
-    }
-
-    /**
-     * Reports the current bond state of the remote device.
-     *
-     * @param bondState the bond state of the remote device.
-     */
-    override fun onBondStateChanged(bondState: Int) {
-    }
-
-    /**
-     * Reports the current connection state of the remote device.
-     *
-     * @param connectionState the bond state of the remote device.
-     */
-    override fun onConnectionStateChanged(connectionState: Int) {
-    }
-
-    /**
-     * Reports the current scan mode of the local Adapter.
-     *
-     * @param mode the current scan mode of the local Adapter.
-     */
-    override fun onScanModeChange(mode: Int) {
-        eventListener.onScanModeChange(FastPairSimulator.scanModeToString(mode))
-    }
-
-    /** Interface for listening the events from Fast Pair Provider Simulator. */
-    interface EventListener {
-        /** Reports the first onServiceConnected of A2DP sink profile. */
-        fun onA2DPSinkProfileConnected()
-
-        /**
-         * Reports the current scan mode of the local Adapter.
-         *
-         * @param mode the current scan mode in string.
-         */
-        fun onScanModeChange(mode: String)
-
-        /**
-         * Indicates the advertising state of the Fast Pair provider simulator has changed.
-         *
-         * @param isAdvertising the current advertising state, true if advertising otherwise false.
-         */
-        fun onAdvertisingChange(isAdvertising: Boolean)
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt
deleted file mode 100644
index 2addd77..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.provider.events
-
-import android.nearby.multidevices.fastpair.provider.controller.FastPairProviderSimulatorController
-import com.google.android.mobly.snippet.util.postSnippetEvent
-
-/** The Mobly snippet events to report to the Python side. */
-class ProviderStatusEvents(private val callbackId: String) :
-    FastPairProviderSimulatorController.EventListener {
-
-    /** Reports the first onServiceConnected of A2DP sink profile. */
-    override fun onA2DPSinkProfileConnected() {
-        postSnippetEvent(callbackId, "onA2DPSinkProfileConnected") {}
-    }
-
-    /**
-     * Indicates the Bluetooth scan mode of the Fast Pair provider simulator has changed.
-     *
-     * @param mode the current scan mode in String mapping by [FastPairSimulator#scanModeToString].
-     */
-    override fun onScanModeChange(mode: String) {
-        postSnippetEvent(callbackId, "onScanModeChange") { putString("mode", mode) }
-    }
-
-    /**
-     * Indicates the advertising state of the Fast Pair provider simulator has changed.
-     *
-     * @param isAdvertising the current advertising state, true if advertising otherwise false.
-     */
-    override fun onAdvertisingChange(isAdvertising: Boolean) {
-        postSnippetEvent(callbackId, "onAdvertisingChange") {
-            putBoolean("isAdvertising", isAdvertising)
-        }
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
deleted file mode 100644
index a2c39f7..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.seeker
-
-import android.content.Context
-import android.nearby.FastPairDeviceMetadata
-import android.nearby.NearbyManager
-import android.nearby.ScanCallback
-import android.nearby.ScanRequest
-import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
-import android.nearby.integration.ui.CheckNearbyHalfSheetUiTest
-import android.nearby.integration.ui.DismissNearbyHalfSheetUiTest
-import android.nearby.integration.ui.PairByNearbyHalfSheetUiTest
-import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
-import android.nearby.multidevices.fastpair.seeker.events.PairingCallbackEvents
-import android.nearby.multidevices.fastpair.seeker.events.ScanCallbackEvents
-import android.provider.Settings
-import androidx.test.core.app.ApplicationProvider
-import com.google.android.mobly.snippet.Snippet
-import com.google.android.mobly.snippet.rpc.AsyncRpc
-import com.google.android.mobly.snippet.rpc.Rpc
-import com.google.android.mobly.snippet.util.Log
-
-/** Expose Mobly RPC methods for Python side to test fast pair seeker role. */
-class FastPairSeekerSnippet : Snippet {
-    private val appContext = ApplicationProvider.getApplicationContext<Context>()
-    private val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
-    private val fastPairTestDataManager = FastPairTestDataManager(appContext)
-    private lateinit var scanCallback: ScanCallback
-
-    /**
-     * Starts scanning as a Fast Pair seeker to find provider devices.
-     *
-     * @param callbackId the callback ID corresponding to the {@link FastPairSeekerSnippet#startScan}
-     * call that started the scanning.
-     */
-    @AsyncRpc(description = "Starts scanning as Fast Pair seeker to find provider devices.")
-    fun startScan(callbackId: String) {
-        val scanRequest = ScanRequest.Builder()
-            .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
-            .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
-            .setBleEnabled(true)
-            .build()
-        scanCallback = ScanCallbackEvents(callbackId)
-
-        Log.i("Start Fast Pair scanning via BLE...")
-        nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
-    }
-
-    /** Stops the Fast Pair seeker scanning. */
-    @Rpc(description = "Stops the Fast Pair seeker scanning.")
-    fun stopScan() {
-        Log.i("Stop Fast Pair scanning.")
-        nearbyManager.stopScan(scanCallback)
-    }
-
-    /** Waits and asserts the HalfSheet showed for Fast Pair pairing.
-     *
-     * @param modelId the expected model id to be associated with the HalfSheet.
-     * @param timeout the number of seconds to wait before giving up.
-     */
-    @Rpc(description = "Waits the HalfSheet showed for Fast Pair pairing.")
-    fun waitAndAssertHalfSheetShowed(modelId: String, timeout: Int) {
-        Log.i("Waits and asserts the HalfSheet showed for Fast Pair model $modelId.")
-
-        val deviceMetadata: FastPairDeviceMetadata =
-            fastPairTestDataManager.testDataCache.getFastPairDeviceMetadata(modelId)
-                ?: throw IllegalArgumentException(
-                    "Can't find $modelId-FastPairAntispoofKeyDeviceMetadata pair in " +
-                            "FastPairTestDataCache."
-                )
-        val deviceName = deviceMetadata.name!!
-        val initialPairingDescriptionTemplateText = deviceMetadata.initialPairingDescription!!
-
-        CheckNearbyHalfSheetUiTest().apply {
-            updateTestArguments(
-                waitHalfSheetPopupTimeoutSeconds = timeout,
-                halfSheetTitleText = deviceName,
-                halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
-                    deviceName,
-                    FAKE_TEST_ACCOUNT_NAME
-                )
-            )
-            checkNearbyHalfSheetUi()
-        }
-    }
-
-    /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
-     *
-     * @param modelId a string of model id to be associated with.
-     * @param json a string of FastPairAntispoofKeyDeviceMetadata JSON object.
-     */
-    @Rpc(
-        description =
-        "Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache."
-    )
-    fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) {
-        Log.i("Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.")
-        fastPairTestDataManager.sendAntispoofKeyDeviceMetadata(modelId, json)
-    }
-
-    /** Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.
-     *
-     * @param json a string of FastPairAccountKeyDeviceMetadata JSON array.
-     */
-    @Rpc(description = "Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.")
-    fun putAccountKeyDeviceMetadata(json: String) {
-        Log.i("Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.")
-        fastPairTestDataManager.sendAccountKeyDeviceMetadataJsonArray(json)
-    }
-
-    /** Dumps all FastPairAccountKeyDeviceMetadata from the test data cache. */
-    @Rpc(description = "Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.")
-    fun dumpAccountKeyDeviceMetadata(): String {
-        Log.i("Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.")
-        return fastPairTestDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson()
-    }
-
-    /** Writes into {@link Settings} whether Fast Pair scan is enabled.
-     *
-     * @param enable whether the Fast Pair scan should be enabled.
-     */
-    @Rpc(description = "Writes into Settings whether Fast Pair scan is enabled.")
-    fun setFastPairScanEnabled(enable: Boolean) {
-        Log.i("Writes into Settings whether Fast Pair scan is enabled.")
-        // TODO(b/228406038): Change back to use NearbyManager.setFastPairScanEnabled once un-hide.
-        val resolver = appContext.contentResolver
-        Settings.Secure.putInt(resolver, "fast_pair_scan_enabled", if (enable) 1 else 0)
-    }
-
-    /** Dismisses the half sheet UI if showed. */
-    @Rpc(description = "Dismisses the half sheet UI if showed.")
-    fun dismissHalfSheet() {
-        Log.i("Dismisses the half sheet UI if showed.")
-
-        DismissNearbyHalfSheetUiTest().dismissHalfSheet()
-    }
-
-    /** Starts pairing by interacting with half sheet UI.
-     *
-     * @param callbackId the callback ID corresponding to the
-     * {@link FastPairSeekerSnippet#startPairing} call that started the pairing.
-     */
-    @AsyncRpc(description = "Starts pairing by interacting with half sheet UI.")
-    fun startPairing(callbackId: String) {
-        Log.i("Starts pairing by interacting with half sheet UI.")
-
-        PairByNearbyHalfSheetUiTest().clickConnectButton()
-        fastPairTestDataManager.registerDataReceiveListener(PairingCallbackEvents(callbackId))
-    }
-
-    /** Invokes when the snippet runner shutting down. */
-    override fun shutdown() {
-        super.shutdown()
-
-        Log.i("Resets the Fast Pair test data cache.")
-        fastPairTestDataManager.unregisterDataReceiveListener()
-        fastPairTestDataManager.sendResetCache()
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt
deleted file mode 100644
index 239ac61..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.seeker.data
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE
-import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.DATA_JSON_STRING_KEY
-import android.nearby.fastpair.seeker.DATA_MODEL_ID_STRING_KEY
-import android.nearby.fastpair.seeker.FastPairTestDataCache
-import android.util.Log
-
-/** Manage local FastPairTestDataCache and send to/sync from the remote cache in data provider. */
-class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() {
-    val testDataCache = FastPairTestDataCache()
-    var listener: EventListener? = null
-
-    /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into local and remote cache.
-     *
-     * @param modelId a string of model id to be associated with.
-     * @param json a string of FastPairAntispoofKeyDeviceMetadata JSON object.
-     */
-    fun sendAntispoofKeyDeviceMetadata(modelId: String, json: String) {
-        Intent().also { intent ->
-            intent.action = ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
-            intent.putExtra(DATA_MODEL_ID_STRING_KEY, modelId)
-            intent.putExtra(DATA_JSON_STRING_KEY, json)
-            context.sendBroadcast(intent)
-        }
-        testDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
-    }
-
-    /** Puts account key device metadata array to local and remote cache.
-     *
-     * @param json a string of FastPairAccountKeyDeviceMetadata JSON array.
-     */
-    fun sendAccountKeyDeviceMetadataJsonArray(json: String) {
-        Intent().also { intent ->
-            intent.action = ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
-            intent.putExtra(DATA_JSON_STRING_KEY, json)
-            context.sendBroadcast(intent)
-        }
-        testDataCache.putAccountKeyDeviceMetadataJsonArray(json)
-    }
-
-    /** Clears local and remote cache. */
-    fun sendResetCache() {
-        context.sendBroadcast(Intent(ACTION_RESET_TEST_DATA_CACHE))
-        testDataCache.reset()
-    }
-
-    /**
-     * Callback method for receiving Intent broadcast from FastPairTestDataProvider.
-     *
-     * See [BroadcastReceiver#onReceive].
-     *
-     * @param context the Context in which the receiver is running.
-     * @param intent the Intent being received.
-     */
-    override fun onReceive(context: Context, intent: Intent) {
-        when (intent.action) {
-            ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA -> {
-                Log.d(TAG, "ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA received!")
-                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
-                testDataCache.putAccountKeyDeviceMetadataJsonObject(json)
-                listener?.onManageFastPairAccountDevice(json)
-            }
-            else -> Log.d(TAG, "Unknown action received!")
-        }
-    }
-
-    fun registerDataReceiveListener(listener: EventListener) {
-        this.listener = listener
-        val bondStateFilter = IntentFilter(ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA)
-        context.registerReceiver(this, bondStateFilter)
-    }
-
-    fun unregisterDataReceiveListener() {
-        this.listener = null
-        context.unregisterReceiver(this)
-    }
-
-    /** Interface for listening the data receive from the remote cache in data provider. */
-    interface EventListener {
-        /** Reports a FastPairAccountKeyDeviceMetadata write into the cache.
-         *
-         * @param json the FastPairAccountKeyDeviceMetadata as JSON object string.
-         */
-        fun onManageFastPairAccountDevice(json: String)
-    }
-
-    companion object {
-        private const val TAG = "FastPairTestDataManager"
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt
deleted file mode 100644
index 19de1d9..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.seeker.events
-
-import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
-import com.google.android.mobly.snippet.util.postSnippetEvent
-
-/** The Mobly snippet events to report to the Python side. */
-class PairingCallbackEvents(private val callbackId: String) :
-    FastPairTestDataManager.EventListener {
-
-    /** Reports a FastPairAccountKeyDeviceMetadata write into the cache.
-     *
-     * @param json the FastPairAccountKeyDeviceMetadata as JSON object string.
-     */
-    override fun onManageFastPairAccountDevice(json: String) {
-        postSnippetEvent(callbackId, "onManageAccountDevice") {
-            putString("accountDeviceJsonString", json)
-        }
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt
deleted file mode 100644
index 363355f..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.seeker.events
-
-import android.nearby.NearbyDevice
-import android.nearby.ScanCallback
-import com.google.android.mobly.snippet.util.postSnippetEvent
-
-/** The Mobly snippet events to report to the Python side. */
-class ScanCallbackEvents(private val callbackId: String) : ScanCallback {
-
-    override fun onDiscovered(device: NearbyDevice) {
-        postSnippetEvent(callbackId, "onDiscovered") {
-            putString("device", device.toString())
-        }
-    }
-
-    override fun onUpdated(device: NearbyDevice) {
-        postSnippetEvent(callbackId, "onUpdated") {
-            putString("device", device.toString())
-        }
-    }
-
-    override fun onLost(device: NearbyDevice) {
-        postSnippetEvent(callbackId, "onLost") {
-            putString("device", device.toString())
-        }
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
deleted file mode 100644
index b406776..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "NearbyFastPairSeekerSharedLib",
-    srcs: ["shared/**/*.kt"],
-    sdk_version: "test_current",
-    static_libs: [
-        // TODO(b/228406038): Remove "framework-nearby-static" once Fast Pair system APIs add back.
-        "framework-nearby-static",
-        "gson",
-        "guava",
-    ],
-}
-
-android_library {
-    name: "NearbyFastPairSeekerDataProviderLib",
-    srcs: ["src/**/*.kt"],
-    sdk_version: "test_current",
-    static_libs: ["NearbyFastPairSeekerSharedLib"],
-}
-
-android_app {
-    name: "NearbyFastPairSeekerDataProvider",
-    sdk_version: "test_current",
-    certificate: "platform",
-    static_libs: ["NearbyFastPairSeekerDataProviderLib"],
-    optimize: {
-        enabled: true,
-        shrink: true,
-        proguard_flags_files: ["proguard.flags"],
-    },
-}
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml
deleted file mode 100644
index 1d62f04..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nearby.fastpair.seeker.dataprovider">
-
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-
-    <application>
-        <!-- Fast Pair Data Provider Service which acts as an "overlay" to the
-             framework Fast Pair Data Provider. Only supported on Android T and later.
-             All overlays are protected from non-system access via WRITE_SECURE_SETTINGS.
-             Must stay in the same process as Nearby Discovery Service.
-        -->
-        <service
-            android:name=".FastPairTestDataProviderService"
-            android:exported="true"
-            android:permission="android.permission.WRITE_SECURE_SETTINGS"
-            android:visibleToInstantApps="true">
-            <intent-filter>
-                <action android:name="android.nearby.action.FAST_PAIR_DATA_PROVIDER" />
-            </intent-filter>
-
-            <meta-data
-                android:name="instantapps.clients.allowed"
-                android:value="true" />
-            <meta-data
-                android:name="serviceVersion"
-                android:value="1" />
-        </service>
-    </application>
-
-</manifest>
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags
deleted file mode 100644
index 15debab..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags
+++ /dev/null
@@ -1,19 +0,0 @@
-# Keep all receivers/service classes.
--keep class android.nearby.fastpair.seeker.** {
-     *;
-}
-
-# Keep names for easy debugging.
--dontobfuscate
-
-# Necessary to allow debugging.
--keepattributes *
-
-# By default, proguard leaves all classes in their original package, which
-# needlessly repeats com.google.android.apps.etc.
--repackageclasses ""
-
-# Allows proguard to make private and protected methods and fields public as
-# part of optimization. This lets proguard inline trivial getter/setter
-# methods.
--allowaccessmodification
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt
deleted file mode 100644
index 6070140..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 android.nearby.fastpair.seeker
-
-const val FAKE_TEST_ACCOUNT_NAME = "nearby-mainline-fpseeker@google.com"
-
-const val ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA =
-    "android.nearby.fastpair.seeker.action.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA"
-const val ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA =
-    "android.nearby.fastpair.seeker.action.ACCOUNT_KEY_DEVICE_METADATA"
-const val ACTION_RESET_TEST_DATA_CACHE = "android.nearby.fastpair.seeker.action.RESET"
-const val ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA =
-    "android.nearby.fastpair.seeker.action.WRITE_ACCOUNT_KEY_DEVICE_METADATA"
-
-const val DATA_JSON_STRING_KEY = "json"
-const val DATA_MODEL_ID_STRING_KEY = "modelId"
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
deleted file mode 100644
index 4fb8832..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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 android.nearby.fastpair.seeker
-
-import android.nearby.FastPairAccountKeyDeviceMetadata
-import android.nearby.FastPairAntispoofKeyDeviceMetadata
-import android.nearby.FastPairDeviceMetadata
-import android.nearby.FastPairDiscoveryItem
-import com.google.common.io.BaseEncoding
-import com.google.gson.GsonBuilder
-import com.google.gson.annotations.SerializedName
-
-/** Manage a cache of Fast Pair test data for testing. */
-class FastPairTestDataCache {
-    private val gson = GsonBuilder().disableHtmlEscaping().create()
-    private val accountKeyDeviceMetadataList = mutableListOf<FastPairAccountKeyDeviceMetadata>()
-    private val antispoofKeyDeviceMetadataDataMap =
-        mutableMapOf<String, FastPairAntispoofKeyDeviceMetadataData>()
-
-    fun putAccountKeyDeviceMetadataJsonArray(json: String) {
-        accountKeyDeviceMetadataList +=
-            gson.fromJson(json, Array<FastPairAccountKeyDeviceMetadataData>::class.java)
-                .map { it.toFastPairAccountKeyDeviceMetadata() }
-    }
-
-    fun putAccountKeyDeviceMetadataJsonObject(json: String) {
-        accountKeyDeviceMetadataList +=
-            gson.fromJson(json, FastPairAccountKeyDeviceMetadataData::class.java)
-                .toFastPairAccountKeyDeviceMetadata()
-    }
-
-    fun putAccountKeyDeviceMetadata(accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata) {
-        accountKeyDeviceMetadataList += accountKeyDeviceMetadata
-    }
-
-    fun getAccountKeyDeviceMetadataList(): List<FastPairAccountKeyDeviceMetadata> =
-        accountKeyDeviceMetadataList.toList()
-
-    fun dumpAccountKeyDeviceMetadataAsJson(metadata: FastPairAccountKeyDeviceMetadata): String =
-        gson.toJson(FastPairAccountKeyDeviceMetadataData(metadata))
-
-    fun dumpAccountKeyDeviceMetadataListAsJson(): String =
-        gson.toJson(accountKeyDeviceMetadataList.map { FastPairAccountKeyDeviceMetadataData(it) })
-
-    fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) {
-        antispoofKeyDeviceMetadataDataMap[modelId] =
-            gson.fromJson(json, FastPairAntispoofKeyDeviceMetadataData::class.java)
-    }
-
-    fun getAntispoofKeyDeviceMetadata(modelId: String): FastPairAntispoofKeyDeviceMetadata? {
-        return antispoofKeyDeviceMetadataDataMap[modelId]?.toFastPairAntispoofKeyDeviceMetadata()
-    }
-
-    fun getFastPairDeviceMetadata(modelId: String): FastPairDeviceMetadata? =
-        antispoofKeyDeviceMetadataDataMap[modelId]?.deviceMeta?.toFastPairDeviceMetadata()
-
-    fun reset() {
-        accountKeyDeviceMetadataList.clear()
-        antispoofKeyDeviceMetadataDataMap.clear()
-    }
-
-    data class FastPairAccountKeyDeviceMetadataData(
-        @SerializedName("account_key") val accountKey: String?,
-        @SerializedName("sha256_account_key_public_address") val accountKeyPublicAddress: String?,
-        @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?,
-        @SerializedName("fast_pair_discovery_item") val discoveryItem: FastPairDiscoveryItemData?
-    ) {
-        constructor(meta: FastPairAccountKeyDeviceMetadata) : this(
-            accountKey = meta.deviceAccountKey?.base64Encode(),
-            accountKeyPublicAddress = meta.sha256DeviceAccountKeyPublicAddress?.base64Encode(),
-            deviceMeta = meta.fastPairDeviceMetadata?.let { FastPairDeviceMetadataData(it) },
-            discoveryItem = meta.fastPairDiscoveryItem?.let { FastPairDiscoveryItemData(it) }
-        )
-
-        fun toFastPairAccountKeyDeviceMetadata(): FastPairAccountKeyDeviceMetadata {
-            return FastPairAccountKeyDeviceMetadata.Builder()
-                .setDeviceAccountKey(accountKey?.base64Decode())
-                .setSha256DeviceAccountKeyPublicAddress(accountKeyPublicAddress?.base64Decode())
-                .setFastPairDeviceMetadata(deviceMeta?.toFastPairDeviceMetadata())
-                .setFastPairDiscoveryItem(discoveryItem?.toFastPairDiscoveryItem())
-                .build()
-        }
-    }
-
-    data class FastPairAntispoofKeyDeviceMetadataData(
-        @SerializedName("anti_spoofing_public_key_str") val antispoofPublicKey: String?,
-        @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?
-    ) {
-        fun toFastPairAntispoofKeyDeviceMetadata(): FastPairAntispoofKeyDeviceMetadata {
-            return FastPairAntispoofKeyDeviceMetadata.Builder()
-                .setAntispoofPublicKey(antispoofPublicKey?.base64Decode())
-                .setFastPairDeviceMetadata(deviceMeta?.toFastPairDeviceMetadata())
-                .build()
-        }
-    }
-
-    data class FastPairDeviceMetadataData(
-        @SerializedName("ble_tx_power") val bleTxPower: Int,
-        @SerializedName("connect_success_companion_app_installed") val compAppInstalled: String?,
-        @SerializedName("connect_success_companion_app_not_installed") val comAppNotIns: String?,
-        @SerializedName("device_type") val deviceType: Int,
-        @SerializedName("download_companion_app_description") val downloadComApp: String?,
-        @SerializedName("fail_connect_go_to_settings_description") val failConnectDes: String?,
-        @SerializedName("image_url") val imageUrl: String?,
-        @SerializedName("initial_notification_description") val initNotification: String?,
-        @SerializedName("initial_notification_description_no_account") val initNoAccount: String?,
-        @SerializedName("initial_pairing_description") val initialPairingDescription: String?,
-        @SerializedName("intent_uri") val intentUri: String?,
-        @SerializedName("name") val name: String?,
-        @SerializedName("open_companion_app_description") val openCompanionAppDescription: String?,
-        @SerializedName("retroactive_pairing_description") val retroactivePairingDes: String?,
-        @SerializedName("subsequent_pairing_description") val subsequentPairingDescription: String?,
-        @SerializedName("trigger_distance") val triggerDistance: Double,
-        @SerializedName("case_url") val trueWirelessImageUrlCase: String?,
-        @SerializedName("left_bud_url") val trueWirelessImageUrlLeftBud: String?,
-        @SerializedName("right_bud_url") val trueWirelessImageUrlRightBud: String?,
-        @SerializedName("unable_to_connect_description") val unableToConnectDescription: String?,
-        @SerializedName("unable_to_connect_title") val unableToConnectTitle: String?,
-        @SerializedName("update_companion_app_description") val updateCompAppDes: String?,
-        @SerializedName("wait_launch_companion_app_description") val waitLaunchCompApp: String?
-    ) {
-        constructor(meta: FastPairDeviceMetadata) : this(
-            bleTxPower = meta.bleTxPower,
-            compAppInstalled = meta.connectSuccessCompanionAppInstalled,
-            comAppNotIns = meta.connectSuccessCompanionAppNotInstalled,
-            deviceType = meta.deviceType,
-            downloadComApp = meta.downloadCompanionAppDescription,
-            failConnectDes = meta.failConnectGoToSettingsDescription,
-            imageUrl = meta.imageUrl,
-            initNotification = meta.initialNotificationDescription,
-            initNoAccount = meta.initialNotificationDescriptionNoAccount,
-            initialPairingDescription = meta.initialPairingDescription,
-            intentUri = meta.intentUri,
-            name = meta.name,
-            openCompanionAppDescription = meta.openCompanionAppDescription,
-            retroactivePairingDes = meta.retroactivePairingDescription,
-            subsequentPairingDescription = meta.subsequentPairingDescription,
-            triggerDistance = meta.triggerDistance.toDouble(),
-            trueWirelessImageUrlCase = meta.trueWirelessImageUrlCase,
-            trueWirelessImageUrlLeftBud = meta.trueWirelessImageUrlLeftBud,
-            trueWirelessImageUrlRightBud = meta.trueWirelessImageUrlRightBud,
-            unableToConnectDescription = meta.unableToConnectDescription,
-            unableToConnectTitle = meta.unableToConnectTitle,
-            updateCompAppDes = meta.updateCompanionAppDescription,
-            waitLaunchCompApp = meta.waitLaunchCompanionAppDescription
-        )
-
-        fun toFastPairDeviceMetadata(): FastPairDeviceMetadata {
-            return FastPairDeviceMetadata.Builder()
-                .setBleTxPower(bleTxPower)
-                .setConnectSuccessCompanionAppInstalled(compAppInstalled)
-                .setConnectSuccessCompanionAppNotInstalled(comAppNotIns)
-                .setDeviceType(deviceType)
-                .setDownloadCompanionAppDescription(downloadComApp)
-                .setFailConnectGoToSettingsDescription(failConnectDes)
-                .setImageUrl(imageUrl)
-                .setInitialNotificationDescription(initNotification)
-                .setInitialNotificationDescriptionNoAccount(initNoAccount)
-                .setInitialPairingDescription(initialPairingDescription)
-                .setIntentUri(intentUri)
-                .setName(name)
-                .setOpenCompanionAppDescription(openCompanionAppDescription)
-                .setRetroactivePairingDescription(retroactivePairingDes)
-                .setSubsequentPairingDescription(subsequentPairingDescription)
-                .setTriggerDistance(triggerDistance.toFloat())
-                .setTrueWirelessImageUrlCase(trueWirelessImageUrlCase)
-                .setTrueWirelessImageUrlLeftBud(trueWirelessImageUrlLeftBud)
-                .setTrueWirelessImageUrlRightBud(trueWirelessImageUrlRightBud)
-                .setUnableToConnectDescription(unableToConnectDescription)
-                .setUnableToConnectTitle(unableToConnectTitle)
-                .setUpdateCompanionAppDescription(updateCompAppDes)
-                .setWaitLaunchCompanionAppDescription(waitLaunchCompApp)
-                .build()
-        }
-    }
-
-    data class FastPairDiscoveryItemData(
-        @SerializedName("action_url") val actionUrl: String?,
-        @SerializedName("action_url_type") val actionUrlType: Int,
-        @SerializedName("app_name") val appName: String?,
-        @SerializedName("authentication_public_key_secp256r1") val authenticationPublicKey: String?,
-        @SerializedName("description") val description: String?,
-        @SerializedName("device_name") val deviceName: String?,
-        @SerializedName("display_url") val displayUrl: String?,
-        @SerializedName("first_observation_timestamp_millis") val firstObservationMs: Long,
-        @SerializedName("icon_fife_url") val iconFfeUrl: String?,
-        @SerializedName("icon_png") val iconPng: String?,
-        @SerializedName("id") val id: String?,
-        @SerializedName("last_observation_timestamp_millis") val lastObservationMs: Long,
-        @SerializedName("mac_address") val macAddress: String?,
-        @SerializedName("package_name") val packageName: String?,
-        @SerializedName("pending_app_install_timestamp_millis") val pendingAppInstallMs: Long,
-        @SerializedName("rssi") val rssi: Int,
-        @SerializedName("state") val state: Int,
-        @SerializedName("title") val title: String?,
-        @SerializedName("trigger_id") val triggerId: String?,
-        @SerializedName("tx_power") val txPower: Int
-    ) {
-        constructor(item: FastPairDiscoveryItem) : this(
-            actionUrl = item.actionUrl,
-            actionUrlType = item.actionUrlType,
-            appName = item.appName,
-            authenticationPublicKey = item.authenticationPublicKeySecp256r1?.base64Encode(),
-            description = item.description,
-            deviceName = item.deviceName,
-            displayUrl = item.displayUrl,
-            firstObservationMs = item.firstObservationTimestampMillis,
-            iconFfeUrl = item.iconFfeUrl,
-            iconPng = item.iconPng?.base64Encode(),
-            id = item.id,
-            lastObservationMs = item.lastObservationTimestampMillis,
-            macAddress = item.macAddress,
-            packageName = item.packageName,
-            pendingAppInstallMs = item.pendingAppInstallTimestampMillis,
-            rssi = item.rssi,
-            state = item.state,
-            title = item.title,
-            triggerId = item.triggerId,
-            txPower = item.txPower
-        )
-
-        fun toFastPairDiscoveryItem(): FastPairDiscoveryItem {
-            return FastPairDiscoveryItem.Builder()
-                .setActionUrl(actionUrl)
-                .setActionUrlType(actionUrlType)
-                .setAppName(appName)
-                .setAuthenticationPublicKeySecp256r1(authenticationPublicKey?.base64Decode())
-                .setDescription(description)
-                .setDeviceName(deviceName)
-                .setDisplayUrl(displayUrl)
-                .setFirstObservationTimestampMillis(firstObservationMs)
-                .setIconFfeUrl(iconFfeUrl)
-                .setIconPng(iconPng?.base64Decode())
-                .setId(id)
-                .setLastObservationTimestampMillis(lastObservationMs)
-                .setMacAddress(macAddress)
-                .setPackageName(packageName)
-                .setPendingAppInstallTimestampMillis(pendingAppInstallMs)
-                .setRssi(rssi)
-                .setState(state)
-                .setTitle(title)
-                .setTriggerId(triggerId)
-                .setTxPower(txPower)
-                .build()
-        }
-    }
-}
-
-private fun String.base64Decode(): ByteArray = BaseEncoding.base64().decode(this)
-
-private fun ByteArray.base64Encode(): String = BaseEncoding.base64().encode(this)
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt
deleted file mode 100644
index e924da1..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 android.nearby.fastpair.seeker.data
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.nearby.FastPairAccountKeyDeviceMetadata
-import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE
-import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.DATA_JSON_STRING_KEY
-import android.nearby.fastpair.seeker.DATA_MODEL_ID_STRING_KEY
-import android.nearby.fastpair.seeker.FastPairTestDataCache
-import android.util.Log
-
-/** Manage local FastPairTestDataCache and receive/update the remote cache in test snippet. */
-class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() {
-    val testDataCache = FastPairTestDataCache()
-
-    /** Writes a FastPairAccountKeyDeviceMetadata into local and remote cache.
-     *
-     * @param accountKeyDeviceMetadata the FastPairAccountKeyDeviceMetadata to write.
-     * @return a json object string of the accountKeyDeviceMetadata.
-     */
-    fun writeAccountKeyDeviceMetadata(
-        accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata
-    ): String {
-        testDataCache.putAccountKeyDeviceMetadata(accountKeyDeviceMetadata)
-
-        val json =
-            testDataCache.dumpAccountKeyDeviceMetadataAsJson(accountKeyDeviceMetadata)
-        Intent().also { intent ->
-            intent.action = ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA
-            intent.putExtra(DATA_JSON_STRING_KEY, json)
-            context.sendBroadcast(intent)
-        }
-        return json
-    }
-
-    /**
-     * Callback method for receiving Intent broadcast from test snippet.
-     *
-     * See [BroadcastReceiver#onReceive].
-     *
-     * @param context the Context in which the receiver is running.
-     * @param intent the Intent being received.
-     */
-    override fun onReceive(context: Context, intent: Intent) {
-        when (intent.action) {
-            ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -> {
-                Log.d(TAG, "ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA received!")
-                val modelId = intent.getStringExtra(DATA_MODEL_ID_STRING_KEY)!!
-                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
-                testDataCache.putAntispoofKeyDeviceMetadata(modelId, json)
-            }
-            ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -> {
-                Log.d(TAG, "ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA received!")
-                val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!!
-                testDataCache.putAccountKeyDeviceMetadataJsonArray(json)
-            }
-            ACTION_RESET_TEST_DATA_CACHE -> {
-                Log.d(TAG, "ACTION_RESET_TEST_DATA_CACHE received!")
-                testDataCache.reset()
-            }
-            else -> Log.d(TAG, "Unknown action received!")
-        }
-    }
-
-    companion object {
-        private const val TAG = "FastPairTestDataManager"
-    }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
deleted file mode 100644
index aec1379..0000000
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 android.nearby.fastpair.seeker.dataprovider
-
-import android.accounts.Account
-import android.content.IntentFilter
-import android.nearby.FastPairDataProviderService
-import android.nearby.FastPairEligibleAccount
-import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE
-import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA
-import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
-import android.nearby.fastpair.seeker.data.FastPairTestDataManager
-import android.util.Log
-
-/**
- * Fast Pair Test Data Provider Service entry point for platform overlay.
- */
-class FastPairTestDataProviderService : FastPairDataProviderService(TAG) {
-    private lateinit var testDataManager: FastPairTestDataManager
-
-    override fun onCreate() {
-        Log.d(TAG, "onCreate()")
-        testDataManager = FastPairTestDataManager(this)
-
-        val bondStateFilter = IntentFilter(ACTION_RESET_TEST_DATA_CACHE).apply {
-            addAction(ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA)
-            addAction(ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA)
-        }
-        registerReceiver(testDataManager, bondStateFilter)
-    }
-
-    override fun onDestroy() {
-        Log.d(TAG, "onDestroy()")
-        unregisterReceiver(testDataManager)
-
-        super.onDestroy()
-    }
-
-    override fun onLoadFastPairAntispoofKeyDeviceMetadata(
-        request: FastPairAntispoofKeyDeviceMetadataRequest,
-        callback: FastPairAntispoofKeyDeviceMetadataCallback
-    ) {
-        val requestedModelId = request.modelId.bytesToStringLowerCase()
-        Log.d(TAG, "onLoadFastPairAntispoofKeyDeviceMetadata(modelId: $requestedModelId)")
-
-        val fastPairAntispoofKeyDeviceMetadata =
-            testDataManager.testDataCache.getAntispoofKeyDeviceMetadata(requestedModelId)
-        if (fastPairAntispoofKeyDeviceMetadata != null) {
-            callback.onFastPairAntispoofKeyDeviceMetadataReceived(
-                fastPairAntispoofKeyDeviceMetadata
-            )
-        } else {
-            Log.d(TAG, "No metadata available for $requestedModelId!")
-            callback.onError(ERROR_CODE_BAD_REQUEST, "No metadata available for $requestedModelId")
-        }
-    }
-
-    override fun onLoadFastPairAccountDevicesMetadata(
-        request: FastPairAccountDevicesMetadataRequest,
-        callback: FastPairAccountDevicesMetadataCallback
-    ) {
-        val requestedAccount = request.account
-        val requestedAccountKeys = request.deviceAccountKeys
-        Log.d(
-            TAG, "onLoadFastPairAccountDevicesMetadata(" +
-                    "account: $requestedAccount, accountKeys:$requestedAccountKeys)"
-        )
-        Log.d(TAG, testDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson())
-
-        callback.onFastPairAccountDevicesMetadataReceived(
-            testDataManager.testDataCache.getAccountKeyDeviceMetadataList()
-        )
-    }
-
-    override fun onLoadFastPairEligibleAccounts(
-        request: FastPairEligibleAccountsRequest,
-        callback: FastPairEligibleAccountsCallback
-    ) {
-        Log.d(TAG, "onLoadFastPairEligibleAccounts()")
-        callback.onFastPairEligibleAccountsReceived(ELIGIBLE_ACCOUNTS_TEST_CONSTANT)
-    }
-
-    override fun onManageFastPairAccount(
-        request: FastPairManageAccountRequest,
-        callback: FastPairManageActionCallback
-    ) {
-        val requestedAccount = request.account
-        val requestType = request.requestType
-        Log.d(TAG, "onManageFastPairAccount(account: $requestedAccount, requestType: $requestType)")
-
-        callback.onSuccess()
-    }
-
-    override fun onManageFastPairAccountDevice(
-        request: FastPairManageAccountDeviceRequest,
-        callback: FastPairManageActionCallback
-    ) {
-        val requestedAccount = request.account
-        val requestType = request.requestType
-        val requestTypeString = if (requestType == MANAGE_REQUEST_ADD) "Add" else "Remove"
-        val requestedAccountKeyDeviceMetadata = request.accountKeyDeviceMetadata
-        Log.d(
-            TAG,
-            "onManageFastPairAccountDevice(requestedAccount: $requestedAccount, " +
-                    "requestType: $requestTypeString,"
-        )
-
-        val requestedAccountKeyDeviceMetadataInJson =
-            testDataManager.writeAccountKeyDeviceMetadata(requestedAccountKeyDeviceMetadata)
-        Log.d(TAG, "requestedAccountKeyDeviceMetadata: $requestedAccountKeyDeviceMetadataInJson)")
-
-        callback.onSuccess()
-    }
-
-    companion object {
-        private const val TAG = "FastPairTestDataProviderService"
-        private val ELIGIBLE_ACCOUNTS_TEST_CONSTANT = listOf(
-            FastPairEligibleAccount.Builder()
-                .setAccount(Account(FAKE_TEST_ACCOUNT_NAME, "FakeTestAccount"))
-                .setOptIn(true)
-                .build()
-        )
-
-        private fun ByteArray.bytesToStringLowerCase(): String =
-            joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp
deleted file mode 100644
index 298c9dc..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "NearbyFastPairProviderLib",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
-    ],
-    sdk_version: "core_platform",
-    libs: [
-        // order matters: classes in framework-bluetooth are resolved before framework, meaning
-        // @hide APIs in framework-bluetooth are resolved before @SystemApi stubs in framework
-        "framework-bluetooth.impl",
-        "framework",
-
-        // if sdk_version="" this gets automatically included, but here we need to add manually.
-        "framework-res",
-    ],
-    static_libs: [
-        "NearbyFastPairProviderLiteProtos",
-        "androidx.core_core",
-        "androidx.test.core",
-        "error_prone_annotations",
-        "fast-pair-lite-protos",
-        "framework-annotations-lib",
-        "guava",
-        "kotlin-stdlib",
-        "nearby-common-lib",
-    ],
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml
deleted file mode 100644
index 400a434..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nearby.fastpair.provider">
-
-    <uses-feature android:name="android.hardware.bluetooth" />
-    <uses-feature android:name="android.hardware.bluetooth_le" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
-    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
-    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
-    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
-
-</manifest>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp
deleted file mode 100644
index 7ae43e5..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
-    name: "NearbyFastPairProviderLiteProtos",
-    proto: {
-        type: "lite",
-        canonical_path_from_root: false,
-    },
-    sdk_version: "system_current",
-    min_sdk_version: "30",
-    srcs: ["*.proto"],
-}
-
-
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto
deleted file mode 100644
index 54db34a..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto
+++ /dev/null
@@ -1,85 +0,0 @@
-syntax = "proto2";
-
-package android.nearby.fastpair.provider;
-
-option java_package = "android.nearby.fastpair.provider";
-option java_outer_classname = "EventStreamProtocol";
-
-enum EventGroup {
-  UNSPECIFIED = 0;
-  BLUETOOTH = 1;
-  LOGGING = 2;
-  DEVICE = 3;
-  DEVICE_ACTION = 4;
-  DEVICE_CONFIGURATION = 5;
-  DEVICE_CAPABILITY_SYNC = 6;
-  SMART_AUDIO_SOURCE_SWITCHING = 7;
-  ACKNOWLEDGEMENT = 255;
-}
-
-enum BluetoothEventCode {
-  BLUETOOTH_UNSPECIFIED = 0;
-  BLUETOOTH_ENABLE_SILENCE_MODE = 1;
-  BLUETOOTH_DISABLE_SILENCE_MODE = 2;
-}
-
-enum LoggingEventCode {
-  LOG_UNSPECIFIED = 0;
-  LOG_FULL = 1;
-  LOG_SAVE_TO_BUFFER = 2;
-}
-
-enum DeviceEventCode {
-  DEVICE_UNSPECIFIED = 0;
-  DEVICE_MODEL_ID = 1;
-  DEVICE_BLE_ADDRESS = 2;
-  DEVICE_BATTERY_INFO = 3;
-  ACTIVE_COMPONENTS_REQUEST = 5;
-  ACTIVE_COMPONENTS_RESPONSE = 6;
-  DEVICE_CAPABILITY = 7;
-  PLATFORM_TYPE = 8;
-  FIRMWARE_VERSION = 9;
-  SECTION_NONCE = 10;
-}
-
-enum DeviceActionEventCode {
-  DEVICE_ACTION_UNSPECIFIED = 0;
-  DEVICE_ACTION_RING = 1;
-}
-
-enum DeviceConfigurationEventCode {
-  CONFIGURATION_UNSPECIFIED = 0;
-  CONFIGURATION_BUFFER_SIZE = 1;
-}
-
-enum DeviceCapabilitySyncEventCode {
-  REQUEST_UNSPECIFIED = 0;
-  REQUEST_CAPABILITY_UPDATE = 1;
-  CONFIGURABLE_BUFFER_SIZE_RANGE = 2;
-}
-
-enum AcknowledgementEventCode {
-  ACKNOWLEDGEMENT_UNSPECIFIED = 0;
-  ACKNOWLEDGEMENT_ACK = 1;
-  ACKNOWLEDGEMENT_NAK = 2;
-}
-
-enum PlatformType {
-  PLATFORM_TYPE_UNKNOWN = 0;
-  ANDROID = 1;
-}
-
-enum SassEventCode {
-  EVENT_UNSPECIFIED = 0;
-  EVENT_GET_CAPABILITY_OF_SASS = 0x10;
-  EVENT_NOTIFY_CAPABILITY_OF_SASS = 0x11;
-  EVENT_SET_MULTI_POINT_STATE = 0x12;
-  EVENT_SWITCH_AUDIO_SOURCE_BETWEEN_CONNECTED_DEVICES = 0x30;
-  EVENT_SWITCH_BACK = 0x31;
-  EVENT_NOTIFY_MULTIPOINT_SWITCH_EVENT = 0x32;
-  EVENT_GET_CONNECTION_STATUS = 0x33;
-  EVENT_NOTIFY_CONNECTION_STATUS = 0x34;
-  EVENT_SASS_INITIATED_CONNECTION = 0x40;
-  EVENT_INDICATE_IN_USE_ACCOUNT_KEY = 0x41;
-  EVENT_SET_CUSTOM_DATA = 0x42;
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp
deleted file mode 100644
index 125c34e..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Build and install NearbyFastPairProviderSimulatorApp to your phone:
-// m NearbyFastPairProviderSimulatorApp
-// adb root
-// adb remount && adb reboot (make first time remount work)
-//
-// adb root
-// adb remount
-// adb push ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairProviderSimulatorApp /system/app/
-// adb reboot
-// Grant all permissions requested to NearbyFastPairProviderSimulatorApp before launching it.
-android_app {
-    name: "NearbyFastPairProviderSimulatorApp",
-    sdk_version: "test_current",
-    // Sign with "platform" certificate for accessing Bluetooth @SystemAPI
-    certificate: "platform",
-    static_libs: ["NearbyFastPairProviderSimulatorLib"],
-    optimize: {
-        enabled: true,
-        shrink: true,
-        proguard_flags_files: ["proguard.flags"],
-    },
-}
-
-android_library {
-    name: "NearbyFastPairProviderSimulatorLib",
-    sdk_version: "test_current",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "NearbyFastPairProviderLib",
-        "NearbyFastPairProviderLiteProtos",
-        "NearbyFastPairProviderSimulatorLiteProtos",
-        "androidx.annotation_annotation",
-        "error_prone_annotations",
-        "fast-pair-lite-protos",
-    ],
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml
deleted file mode 100644
index 8880b11..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nearby.fastpair.provider.simulator.app" >
-
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
-
-    <application
-        android:allowBackup="true"
-        android:label="@string/app_name" >
-        <activity
-            android:name=".MainActivity"
-            android:windowSoftInputMode="stateHidden"
-            android:screenOrientation="portrait"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags
deleted file mode 100644
index 0827c60..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags
+++ /dev/null
@@ -1,19 +0,0 @@
-# Keep AdvertisingSetCallback#onOwnAddressRead callback.
--keep class * extends android.bluetooth.le.AdvertisingSetCallback {
-     *;
-}
-
-# Keep names for easy debugging.
--dontobfuscate
-
-# Necessary to allow debugging.
--keepattributes *
-
-# By default, proguard leaves all classes in their original package, which
-# needlessly repeats com.google.android.apps.etc.
--repackageclasses ""
-
-# Allows proguard to make private and protected methods and fields public as
-# part of optimization. This lets proguard inline trivial getter/setter
-# methods.
--allowaccessmodification
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp
deleted file mode 100644
index e964800..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
-    name: "NearbyFastPairProviderSimulatorLiteProtos",
-    proto: {
-        type: "lite",
-        canonical_path_from_root: false,
-    },
-    sdk_version: "system_current",
-    min_sdk_version: "30",
-    srcs: ["*.proto"],
-}
-
-
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto
deleted file mode 100644
index 9b17fda..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto
+++ /dev/null
@@ -1,110 +0,0 @@
-syntax = "proto2";
-
-package android.nearby.fastpair.provider.simulator;
-
-option java_package = "android.nearby.fastpair.provider.simulator";
-option java_outer_classname = "SimulatorStreamProtocol";
-
-// Used by remote devices to control simulator behaviors.
-message Command {
-  // Type of this command.
-  required Code code = 1;
-
-  // Required for SHOW_BATTERY.
-  optional BatteryInfo battery_info = 2;
-
-  enum Code {
-    // Request for simulator's acknowledge message.
-    POLLING = 0;
-
-    // Reset and clear bluetooth state.
-    RESET = 1;
-
-    // Present battery information in the advertisement.
-    SHOW_BATTERY = 2;
-
-    // Remove battery information in the advertisement.
-    HIDE_BATTERY = 3;
-
-    // Request for BR/EDR address.
-    REQUEST_BLUETOOTH_ADDRESS_PUBLIC = 4;
-
-    // Request for BLE address.
-    REQUEST_BLUETOOTH_ADDRESS_BLE = 5;
-
-    // Request for account key.
-    REQUEST_ACCOUNT_KEY = 6;
-  }
-
-  // Battery information for true wireless headsets.
-  // https://devsite.googleplex.com/nearby/fast-pair/early-access/spec#BatteryNotification
-  message BatteryInfo {
-    // Show or hide the battery UI notification.
-    optional bool suppress_notification = 1;
-    repeated BatteryValue battery_values = 2;
-
-    // Advertised battery level data.
-    message BatteryValue {
-      // The charging flag.
-      required bool charging = 1;
-
-      // Battery level from 0 to 100.
-      required uint32 level = 2;
-    }
-  }
-}
-
-// Notify the remote devices when states are changed or response the command on
-// the simulator.
-message Event {
-  // Type of this event.
-  required Code code = 1;
-
-  // Required for BLUETOOTH_STATE_BOND.
-  optional int32 bond_state = 2;
-
-  // Required for BLUETOOTH_STATE_CONNECTION.
-  optional int32 connection_state = 3;
-
-  // Required for BLUETOOTH_STATE_SCAN_MODE.
-  optional int32 scan_mode = 4;
-
-  // Required for BLUETOOTH_ADDRESS_PUBLIC.
-  optional string public_address = 5;
-
-  // Required for BLUETOOTH_ADDRESS_BLE.
-  optional string ble_address = 6;
-
-  // Required for BLUETOOTH_ALIAS_NAME.
-  optional string alias_name = 7;
-
-  // Required for REQUEST_ACCOUNT_KEY.
-  optional bytes account_key = 8;
-
-  enum Code {
-    // Response the polling.
-    ACKNOWLEDGE = 0;
-
-    // Notify the event android.bluetooth.device.action.BOND_STATE_CHANGED
-    BLUETOOTH_STATE_BOND = 1;
-
-    // Notify the event
-    // android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED
-    BLUETOOTH_STATE_CONNECTION = 2;
-
-    // Notify the event android.bluetooth.adapter.action.SCAN_MODE_CHANGED
-    BLUETOOTH_STATE_SCAN_MODE = 3;
-
-    // Notify the current BR/EDR address
-    BLUETOOTH_ADDRESS_PUBLIC = 4;
-
-    // Notify the current BLE address
-    BLUETOOTH_ADDRESS_BLE = 5;
-
-    // Notify the event android.bluetooth.device.action.ALIAS_CHANGED
-    BLUETOOTH_ALIAS_NAME = 6;
-
-    // Response the REQUEST_ACCOUNT_KEY.
-    ACCOUNT_KEY = 7;
-  }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml
deleted file mode 100644
index b7e85eb..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml
+++ /dev/null
@@ -1,190 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:layout_margin="16dp"
-    android:keepScreenOn="true"
-    tools:context=".MainActivity">
-
-    <TextView
-        android:id="@+id/bluetooth_address_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="14dp"
-        android:textStyle="bold"
-        android:padding="8dp"/>
-
-    <TextView
-        android:id="@+id/device_name_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="14dp"
-        android:textStyle="bold"
-        android:padding="8dp"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="8dp"
-        android:orientation="horizontal">
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textSize="14dp"
-                android:textStyle="bold"
-                android:text="Model ID:"/>
-            <Spinner
-                android:id="@+id/model_id_spinner"
-                android:textSize="14dp"
-                android:textStyle="bold"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1" />
-        <TextView
-            android:id="@+id/tx_power_text_view"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:textSize="14dp"
-            android:textStyle="bold" />
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/anti_spoofing_private_key_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="14dp"
-        android:textStyle="bold"
-        android:padding="8dp"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:id="@+id/is_advertising_text_view"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:textSize="14dp"
-            android:textStyle="bold"
-            android:padding="8dp"/>
-        <TextView
-            android:id="@+id/scan_mode_text_view"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:textSize="14dp"
-            android:textStyle="bold"
-            android:padding="8dp"/>
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/remote_device_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="14dp"
-        android:textStyle="bold"
-        android:padding="8dp"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:id="@+id/is_paired_text_view"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:textSize="14dp"
-            android:textStyle="bold"
-            android:padding="8dp"/>
-        <TextView
-            android:id="@+id/is_connected_text_view"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:textSize="14dp"
-            android:textStyle="bold"
-            android:padding="8dp"/>
-    </LinearLayout>
-
-    <Button
-        android:id="@+id/reset_button"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Reset"
-        android:onClick="onResetButtonClicked"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginBottom="8dp"
-        android:orientation="horizontal"
-        android:layout_gravity="center_vertical">
-
-      <Spinner
-          android:id="@+id/event_stream_spinner"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"/>
-
-      <Button
-          android:id="@+id/send_event_message_button"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="Send Event Message"
-          android:onClick="onSendEventStreamMessageButtonClicked"/>
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="8dp"
-        android:orientation="horizontal"
-        android:layout_gravity="center_vertical">
-        <Switch
-            android:id="@+id/fail_switch"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Force Fail" />
-        <Switch
-            android:id="@+id/app_launch_switch"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Trigger app launch"
-            android:paddingLeft="8dp"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/adv_options"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginBottom="8dp"
-        android:orientation="horizontal"
-        android:layout_gravity="center_vertical">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginRight="8dp"
-            android:textColor="@android:color/black"
-            android:text="adv options"/>
-
-        <Spinner
-            android:id="@+id/adv_option_spinner"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
-
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="bottom"
-        android:scrollbars="vertical"/>
-</LinearLayout>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml
deleted file mode 100644
index 980b057..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="16dp">
-
-  <EditText
-      android:id="@+id/userInputDialog"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:hint="@string/firmware_input_hint"
-      android:inputType="text" />
-
-</LinearLayout>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml
deleted file mode 100644
index f225522..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:context=".MainActivity">
-
-  <item
-      android:id="@+id/sign_out_menu_item"
-      android:title="Sign out"/>
-  <item
-      android:id="@+id/reset_account_keys_menu_item"
-      android:title="Reset Account Keys"/>
-  <item
-      android:id="@+id/reset_device_name_menu_item"
-      android:title="Reset Device Name"/>
-  <item
-    android:id="@+id/set_firmware_version"
-    android:title="Set Firmware Version"/>
-  <item
-      android:id="@+id/set_simulator_capability"
-      android:title="Set Simulator Capability"/>
-  <item
-    android:id="@+id/use_new_gatt_characteristics_id"
-    android:checkable="true"
-    android:checked="false"
-    android:title="Use new GATT characteristics id"/>
-</menu>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml
deleted file mode 100644
index 47c8224..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
-    <!-- Default screen margins, per the Android Design guidelines. -->
-    <dimen name="activity_horizontal_margin">16dp</dimen>
-    <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml
deleted file mode 100644
index 5123038..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<resources>
-    <string name="app_name">Fast Pair Provider Simulator</string>
-    <string-array name="adv_options">
-        <item>0: No battery info</item>
-        <item>1: Show L(⬆) + R(⬆) + C(⬆)</item>
-        <item>2: Show L + R + C(unknown)</item>
-        <item>3: Show L(low 10) + R(low 9) + C(low 25)</item>
-        <item>4: Suppress battery w/o level changes</item>
-        <item>5: Suppress L(low 10) + R(11) + C</item>
-        <item>6: Suppress L(low ⬆) + R(low ⬆) + C(low 10)</item>
-        <item>7: Suppress L(low ⬆) + R(low ⬆) + C(low ⬆)</item>
-        <item>8: Show subsequent pairing notification</item>
-        <item>9: Suppress subsequent pairing notification</item>
-    </string-array>
-    <string-array name="event_stream_options">
-        <item>OHD event</item>
-        <item>Log event</item>
-        <item>Battery event</item>
-    </string-array>
-    <string name="firmware_dialog_title">Firmware version number</string>
-    <string name="firmware_input_hint">Type in version number</string>
-    <string name="passkey_dialog_title">Passkey needed</string>
-    <string name="passkey_input_hint">Type in passkey</string>
-    <!-- Passkey confirmation dialog title. [CHAR_LIMIT=NONE]-->
-    <string name="confirm_passkey">Confirm passkey</string>
-    <string name="model_id_progress_title">Get models from server</string>
-
-    <!-- Fast Pair Simulator: pair one device only. -->
-    <string name="fast_pair_simulator" translatable="false">Fast Pair Simulator</string>
-
-</resources>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java
deleted file mode 100644
index 4db8560..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.app;
-
-import android.util.Log;
-
-import com.google.common.util.concurrent.FutureCallback;
-
-/** Wrapper for {@link FutureCallback} to prevent the memory linkage. */
-public abstract class FutureCallbackWrapper<T> implements FutureCallback<T> {
-    private static final String TAG = FutureCallback.class.getSimpleName();
-
-    public static FutureCallbackWrapper<Void> createRegisterCallback(MainActivity activity) {
-        String id = activity.mRemoteDeviceId;
-        return new FutureCallbackWrapper<Void>() {
-            @Override
-            public void onSuccess(Void result) {
-                Log.d(TAG, String.format("%s was registered", id));
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                Log.w(TAG, String.format("Failed to register %s", id), t);
-            }
-        };
-    }
-
-    public static FutureCallbackWrapper<Void> createDefaultIOCallback(MainActivity activity) {
-        String id = activity.mRemoteDeviceId;
-        return new FutureCallbackWrapper<Void>() {
-            @Override
-            public void onSuccess(Void result) {
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                Log.w(TAG, String.format("IO stream error on %s", id), t);
-            }
-        };
-    }
-
-    public static FutureCallbackWrapper<Void> createDestroyCallback() {
-        return new FutureCallbackWrapper<Void>() {
-            @Override
-            public void onSuccess(Void result) {
-                Log.d(TAG, "remote devices manager is destroyed");
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                Log.w(TAG, "Failed to destroy remote devices manager", t);
-            }
-        };
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
deleted file mode 100644
index 75fafb0..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
+++ /dev/null
@@ -1,1040 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.app;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_BOND;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_CONNECTION;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_SCAN_MODE;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.io.BaseEncoding.base64;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.le.AdvertiseSettings;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.graphics.Color;
-import android.nearby.fastpair.provider.EventStreamProtocol.EventGroup;
-import android.nearby.fastpair.provider.FastPairSimulator;
-import android.nearby.fastpair.provider.FastPairSimulator.BatteryValue;
-import android.nearby.fastpair.provider.FastPairSimulator.KeyInputCallback;
-import android.nearby.fastpair.provider.FastPairSimulator.PasskeyEventCallback;
-import android.nearby.fastpair.provider.bluetooth.BluetoothController;
-import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event;
-import android.nearby.fastpair.provider.simulator.testing.RemoteDevice;
-import android.nearby.fastpair.provider.simulator.testing.RemoteDevicesManager;
-import android.nearby.fastpair.provider.simulator.testing.StreamIOHandlerFactory;
-import android.nearby.fastpair.provider.utils.Logger;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.method.ScrollingMovementMethod;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.Switch;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.core.util.Consumer;
-
-import com.google.common.base.Ascii;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-import com.google.errorprone.annotations.FormatMethod;
-import com.google.protobuf.ByteString;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.Executors;
-
-import service.proto.Rpcs.AntiSpoofingKeyPair;
-import service.proto.Rpcs.Device;
-import service.proto.Rpcs.DeviceType;
-
-/**
- * Simulates a Fast Pair device (e.g. a headset).
- *
- * <p>See README in this directory, and {http://go/fast-pair-spec}.
- */
-@SuppressLint("SetTextI18n")
-public class MainActivity extends Activity {
-    public static final String TAG = "FastPairProviderSimulatorApp";
-    private final Logger mLogger = new Logger(TAG);
-
-    /** Device has a display and the ability to input Yes/No. */
-    private static final int IO_CAPABILITY_IO = 1;
-
-    /** Device only has a keyboard for entry but no display. */
-    private static final int IO_CAPABILITY_IN = 2;
-
-    /** Device has no Input or Output capability. */
-    private static final int IO_CAPABILITY_NONE = 3;
-
-    /** Device has a display and a full keyboard. */
-    private static final int IO_CAPABILITY_KBDISP = 4;
-
-    private static final String SHARED_PREFS_NAME =
-            "android.nearby.fastpair.provider.simulator.app";
-    private static final String EXTRA_MODEL_ID = "MODEL_ID";
-    private static final String EXTRA_BLUETOOTH_ADDRESS = "BLUETOOTH_ADDRESS";
-    private static final String EXTRA_TX_POWER_LEVEL = "TX_POWER_LEVEL";
-    private static final String EXTRA_FIRMWARE_VERSION = "FIRMWARE_VERSION";
-    private static final String EXTRA_SUPPORT_DYNAMIC_SIZE = "SUPPORT_DYNAMIC_SIZE";
-    private static final String EXTRA_USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION =
-            "USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION";
-    private static final String EXTRA_REMOTE_DEVICE_ID = "REMOTE_DEVICE_ID";
-    private static final String EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID =
-            "USE_NEW_GATT_CHARACTERISTICS_ID";
-    public static final String EXTRA_REMOVE_ALL_DEVICES_DURING_PAIRING =
-            "REMOVE_ALL_DEVICES_DURING_PAIRING";
-    private static final String KEY_ACCOUNT_NAME = "ACCOUNT_NAME";
-    private static final String[] PERMISSIONS =
-            new String[]{permission.BLUETOOTH, permission.BLUETOOTH_ADMIN, permission.GET_ACCOUNTS};
-    private static final int LIGHT_GREEN = 0xFFC8FFC8;
-    private static final String ANTI_SPOOFING_KEY_LABEL = "Anti-spoofing key";
-
-    private static final ImmutableMap<String, String> ANTI_SPOOFING_PRIVATE_KEY_MAP =
-            new ImmutableMap.Builder<String, String>()
-                    .put("361A2E", "/1rMqyJRGeOK6vkTNgM70xrytxdKg14mNQkITeusK20=")
-                    .put("00000D", "03/MAmUPTGNsN+2iA/1xASXoPplDh3Ha5/lk2JgEBx4=")
-                    .put("00000C", "Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE=")
-                    // BLE only devices
-                    .put("49426D", "I5QFOJW0WWFgKKZiwGchuseXsq/p9RN/aYtNsGEVGT0=")
-                    .put("01E5CE", "FbHt8STpHJDd4zFQFjimh4Zt7IU94U28MOEIXgUEeCw=")
-                    .put("8D13B9", "mv++LcJB1n0mbLNGWlXCv/8Gb6aldctrJC4/Ma/Q3Rg=")
-                    .put("9AB0F6", "9eKQNwJUr5vCg0c8rtOXkJcWTAsBmmvEKSgXIqAd50Q=")
-                    // Android Auto
-                    .put("8E083D", "hGQeREDKM/H1834zWMmTIe0Ap4Zl5igThgE62OtdcKA=")
-                    .buildOrThrow();
-
-    private static final Uri REMOTE_DEVICE_INPUT_STREAM_URI =
-            Uri.fromFile(new File("/data/local/nearby/tmp/read.pipe"));
-
-    private static final Uri REMOTE_DEVICE_OUTPUT_STREAM_URI =
-            Uri.fromFile(new File("/data/local/nearby/tmp/write.pipe"));
-
-    private static final String MODEL_ID_DEFAULT = "00000C";
-
-    private static final String MODEL_ID_APP_LAUNCH = "60EB56";
-
-    private static final int MODEL_ID_LENGTH = 6;
-
-    private BluetoothController mBluetoothController;
-    private final BluetoothController.EventListener mEventListener =
-            new BluetoothController.EventListener() {
-
-                @Override
-                public void onBondStateChanged(int bondState) {
-                    sendEventToRemoteDevice(
-                            Event.newBuilder().setCode(BLUETOOTH_STATE_BOND).setBondState(
-                                    bondState));
-                    updateStatusView();
-                }
-
-                @Override
-                public void onConnectionStateChanged(int connectionState) {
-                    sendEventToRemoteDevice(
-                            Event.newBuilder()
-                                    .setCode(BLUETOOTH_STATE_CONNECTION)
-                                    .setConnectionState(connectionState));
-                    updateStatusView();
-                }
-
-                @Override
-                public void onScanModeChange(int mode) {
-                    sendEventToRemoteDevice(
-                            Event.newBuilder().setCode(BLUETOOTH_STATE_SCAN_MODE).setScanMode(
-                                    mode));
-                    updateStatusView();
-                }
-
-                @Override
-                public void onA2DPSinkProfileConnected() {
-                    reset();
-                }
-            };
-
-    @Nullable
-    private FastPairSimulator mFastPairSimulator;
-    @Nullable
-    private AlertDialog mInputPasskeyDialog;
-    private Switch mFailSwitch;
-    private Switch mAppLaunchSwitch;
-    private Spinner mAdvOptionSpinner;
-    private Spinner mEventStreamSpinner;
-    private EventGroup mEventGroup;
-    private SharedPreferences mSharedPreferences;
-    private Spinner mModelIdSpinner;
-    private final RemoteDevicesManager mRemoteDevicesManager = new RemoteDevicesManager();
-    @Nullable
-    private RemoteDeviceListener mInputStreamListener;
-    @Nullable
-    String mRemoteDeviceId;
-    private final Map<String, Device> mModelsMap = new LinkedHashMap<>();
-    private boolean mRemoveAllDevicesDuringPairing = true;
-
-    void sendEventToRemoteDevice(Event.Builder eventBuilder) {
-        if (mRemoteDeviceId == null) {
-            return;
-        }
-
-        mLogger.log("Send data to output stream: %s", eventBuilder.getCode().getNumber());
-        mRemoteDevicesManager.writeDataToRemoteDevice(
-                mRemoteDeviceId,
-                eventBuilder.build().toByteString(),
-                FutureCallbackWrapper.createDefaultIOCallback(this));
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.activity_main);
-
-        mSharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
-
-        mRemoveAllDevicesDuringPairing =
-                getIntent().getBooleanExtra(EXTRA_REMOVE_ALL_DEVICES_DURING_PAIRING, true);
-
-        mFailSwitch = findViewById(R.id.fail_switch);
-        mFailSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
-            if (mFastPairSimulator != null) {
-                mFastPairSimulator.setShouldFailPairing(isChecked);
-            }
-        });
-
-        mAppLaunchSwitch = findViewById(R.id.app_launch_switch);
-        mAppLaunchSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> reset());
-
-        mAdvOptionSpinner = findViewById(R.id.adv_option_spinner);
-        mEventStreamSpinner = findViewById(R.id.event_stream_spinner);
-        ArrayAdapter<CharSequence> advOptionAdapter =
-                ArrayAdapter.createFromResource(
-                        this, R.array.adv_options, android.R.layout.simple_spinner_item);
-        ArrayAdapter<CharSequence> eventStreamAdapter =
-                ArrayAdapter.createFromResource(
-                        this, R.array.event_stream_options, android.R.layout.simple_spinner_item);
-        advOptionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mAdvOptionSpinner.setAdapter(advOptionAdapter);
-        mEventStreamSpinner.setAdapter(eventStreamAdapter);
-        mAdvOptionSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> adapterView, View view, int position,
-                    long id) {
-                startAdvertisingBatteryInformationBasedOnOption(position);
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> adapterView) {
-            }
-        });
-        mEventStreamSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> parent, View view, int position,
-                    long id) {
-                switch (EventGroup.forNumber(position + 1)) {
-                    case BLUETOOTH:
-                        mEventGroup = EventGroup.BLUETOOTH;
-                        break;
-                    case LOGGING:
-                        mEventGroup = EventGroup.LOGGING;
-                        break;
-                    case DEVICE:
-                        mEventGroup = EventGroup.DEVICE;
-                        break;
-                    default:
-                        // fall through
-                }
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> parent) {
-            }
-        });
-        setupModelIdSpinner();
-        setupRemoteDevices();
-        if (checkPermissions(PERMISSIONS)) {
-            mBluetoothController = new BluetoothController(this, mEventListener);
-            mBluetoothController.registerBluetoothStateReceiver();
-            mBluetoothController.enableBluetooth();
-            mBluetoothController.connectA2DPSinkProfile();
-
-            if (mSharedPreferences.getString(KEY_ACCOUNT_NAME, "").isEmpty()) {
-                putFixedModelLocal();
-                resetModelIdSpinner();
-                reset();
-            }
-        } else {
-            requestPermissions(PERMISSIONS, 0 /* requestCode */);
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.menu, menu);
-        menu.findItem(R.id.use_new_gatt_characteristics_id).setChecked(
-                getFromIntentOrPrefs(
-                        EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, /* defaultValue= */ false));
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == R.id.sign_out_menu_item) {
-            recreate();
-            return true;
-        } else if (item.getItemId() == R.id.reset_account_keys_menu_item) {
-            resetAccountKeys();
-            return true;
-        } else if (item.getItemId() == R.id.reset_device_name_menu_item) {
-            resetDeviceName();
-            return true;
-        } else if (item.getItemId() == R.id.set_firmware_version) {
-            setFirmware();
-            return true;
-        } else if (item.getItemId() == R.id.set_simulator_capability) {
-            setSimulatorCapability();
-            return true;
-        } else if (item.getItemId() == R.id.use_new_gatt_characteristics_id) {
-            if (!item.isChecked()) {
-                item.setChecked(true);
-                mSharedPreferences.edit()
-                        .putBoolean(EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, true).apply();
-            } else {
-                item.setChecked(false);
-                mSharedPreferences.edit()
-                        .putBoolean(EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, false).apply();
-            }
-            reset();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    private void setFirmware() {
-        View firmwareInputView =
-                LayoutInflater.from(getApplicationContext()).inflate(R.layout.user_input_dialog,
-                        null);
-        EditText userInputDialogEditText = firmwareInputView.findViewById(R.id.userInputDialog);
-        new AlertDialog.Builder(MainActivity.this)
-                .setView(firmwareInputView)
-                .setCancelable(false)
-                .setPositiveButton(android.R.string.ok, (dialogBox, id) -> {
-                    String input = userInputDialogEditText.getText().toString();
-                    mSharedPreferences.edit().putString(EXTRA_FIRMWARE_VERSION,
-                            input).apply();
-                    reset();
-                })
-                .setNegativeButton(android.R.string.cancel, null)
-                .setTitle(R.string.firmware_dialog_title)
-                .show();
-    }
-
-    private void setSimulatorCapability() {
-        String[] capabilityKeys = new String[]{EXTRA_SUPPORT_DYNAMIC_SIZE};
-        String[] capabilityNames = new String[]{"Dynamic Buffer Size"};
-        // Default values.
-        boolean[] capabilitySelected = new boolean[]{false};
-        // Get from preferences if exist.
-        for (int i = 0; i < capabilityKeys.length; i++) {
-            capabilitySelected[i] =
-                    mSharedPreferences.getBoolean(capabilityKeys[i], capabilitySelected[i]);
-        }
-
-        new AlertDialog.Builder(MainActivity.this)
-                .setMultiChoiceItems(
-                        capabilityNames,
-                        capabilitySelected,
-                        (dialog, which, isChecked) -> capabilitySelected[which] = isChecked)
-                .setCancelable(false)
-                .setPositiveButton(
-                        android.R.string.ok,
-                        (dialogBox, id) -> {
-                            for (int i = 0; i < capabilityKeys.length; i++) {
-                                mSharedPreferences
-                                        .edit()
-                                        .putBoolean(capabilityKeys[i], capabilitySelected[i])
-                                        .apply();
-                            }
-                            setCapabilityToSimulator();
-                        })
-                .setNegativeButton(android.R.string.cancel, null)
-                .setTitle("Simulator Capability")
-                .show();
-    }
-
-    private void setCapabilityToSimulator() {
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.setDynamicBufferSize(
-                    getFromIntentOrPrefs(EXTRA_SUPPORT_DYNAMIC_SIZE, false));
-        }
-    }
-
-    private static String getModelIdString(long id) {
-        String result = Ascii.toUpperCase(Long.toHexString(id));
-        while (result.length() < MODEL_ID_LENGTH) {
-            result = "0" + result;
-        }
-        return result;
-    }
-
-    private void putFixedModelLocal() {
-        mModelsMap.put(
-                "00000C",
-                Device.newBuilder()
-                        .setId(12)
-                        .setAntiSpoofingKeyPair(AntiSpoofingKeyPair.newBuilder().build())
-                        .setDeviceType(DeviceType.HEADPHONES)
-                        .build());
-    }
-
-    private void setupModelIdSpinner() {
-        mModelIdSpinner = findViewById(R.id.model_id_spinner);
-
-        ArrayAdapter<String> modelIdAdapter =
-                new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
-        modelIdAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mModelIdSpinner.setAdapter(modelIdAdapter);
-        resetModelIdSpinner();
-        mModelIdSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> parent, View view, int position,
-                    long id) {
-                setModelId(mModelsMap.keySet().toArray(new String[0])[position]);
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> adapterView) {
-            }
-        });
-    }
-
-    private void setupRemoteDevices() {
-        if (Strings.isNullOrEmpty(getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID))) {
-            mLogger.log("Can't get remote device id");
-            return;
-        }
-        mRemoteDeviceId = getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID);
-        mInputStreamListener = new RemoteDeviceListener(this);
-
-        try {
-            mRemoteDevicesManager.registerRemoteDevice(
-                    mRemoteDeviceId,
-                    new RemoteDevice(
-                            mRemoteDeviceId,
-                            StreamIOHandlerFactory.createStreamIOHandler(
-                                    StreamIOHandlerFactory.Type.LOCAL_FILE,
-                                    REMOTE_DEVICE_INPUT_STREAM_URI,
-                                    REMOTE_DEVICE_OUTPUT_STREAM_URI),
-                            mInputStreamListener));
-        } catch (IOException e) {
-            mLogger.log(e, "Failed to create stream IO handler");
-        }
-    }
-
-    @SuppressWarnings({"unchecked", "rawtypes"})
-    @UiThread
-    private void resetModelIdSpinner() {
-        ArrayAdapter adapter = (ArrayAdapter) mModelIdSpinner.getAdapter();
-        if (adapter == null) {
-            return;
-        }
-
-        adapter.clear();
-        if (!mModelsMap.isEmpty()) {
-            for (String modelId : mModelsMap.keySet()) {
-                adapter.add(modelId + "-" + mModelsMap.get(modelId).getName());
-            }
-            mModelIdSpinner.setEnabled(true);
-            int newPos = getPositionFromModelId(getModelId());
-            if (newPos < 0) {
-                String newModelId = mModelsMap.keySet().iterator().next();
-                Toast.makeText(this,
-                        "Can't find Model ID " + getModelId() + " from console, reset it to "
-                                + newModelId, Toast.LENGTH_SHORT).show();
-                setModelId(newModelId);
-                newPos = 0;
-            }
-            mModelIdSpinner.setSelection(newPos, /* animate= */ false);
-        } else {
-            mModelIdSpinner.setEnabled(false);
-        }
-    }
-
-    private String getModelId() {
-        return getFromIntentOrPrefs(EXTRA_MODEL_ID, MODEL_ID_DEFAULT).toUpperCase(Locale.US);
-    }
-
-    private boolean setModelId(String modelId) {
-        String validModelId = getValidModelId(modelId);
-        if (TextUtils.isEmpty(validModelId)) {
-            mLogger.log("Can't do setModelId because inputted modelId is invalid!");
-            return false;
-        }
-
-        if (getModelId().equals(validModelId)) {
-            return false;
-        }
-        mSharedPreferences.edit().putString(EXTRA_MODEL_ID, validModelId).apply();
-        reset();
-        return true;
-    }
-
-    @Nullable
-    private static String getValidModelId(String modelId) {
-        if (TextUtils.isEmpty(modelId) || modelId.length() < MODEL_ID_LENGTH) {
-            return null;
-        }
-
-        return modelId.substring(0, MODEL_ID_LENGTH).toUpperCase(Locale.US);
-    }
-
-    private int getPositionFromModelId(String modelId) {
-        int i = 0;
-        for (String id : mModelsMap.keySet()) {
-            if (id.equals(modelId)) {
-                return i;
-            }
-            i++;
-        }
-        return -1;
-    }
-
-    private void resetAccountKeys() {
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.resetAccountKeys();
-            mFastPairSimulator.startAdvertising();
-        }
-    }
-
-    private void resetDeviceName() {
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.resetDeviceName();
-        }
-    }
-
-    /** Called via activity_main.xml */
-    public void onResetButtonClicked(View view) {
-        reset();
-    }
-
-    /** Called via activity_main.xml */
-    public void onSendEventStreamMessageButtonClicked(View view) {
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.sendEventStreamMessageToRfcommDevices(mEventGroup);
-        }
-    }
-
-    void reset() {
-        Button resetButton = findViewById(R.id.reset_button);
-        if (mModelsMap.isEmpty() || !resetButton.isEnabled()) {
-            return;
-        }
-        resetButton.setText("Resetting...");
-        resetButton.setEnabled(false);
-        mModelIdSpinner.setEnabled(false);
-        mAppLaunchSwitch.setEnabled(false);
-
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.stopAdvertising();
-
-            if (mBluetoothController.getRemoteDevice() != null) {
-                if (mRemoveAllDevicesDuringPairing) {
-                    mFastPairSimulator.removeBond(mBluetoothController.getRemoteDevice());
-                }
-                mBluetoothController.clearRemoteDevice();
-            }
-            // To be safe, also unpair from all phones (this covers the case where you kill +
-            // relaunch the
-            // simulator while paired).
-            if (mRemoveAllDevicesDuringPairing) {
-                mFastPairSimulator.disconnectAllBondedDevices();
-            }
-            // Sometimes a device will still be connected even though it's not bonded. :( Clear
-            // that too.
-            BluetoothProfile profileProxy = mBluetoothController.getA2DPSinkProfileProxy();
-            for (BluetoothDevice device : profileProxy.getConnectedDevices()) {
-                mFastPairSimulator.disconnect(profileProxy, device);
-            }
-        }
-        updateStatusView();
-
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.destroy();
-        }
-        TextView textView = (TextView) findViewById(R.id.text_view);
-        textView.setText("");
-        textView.setMovementMethod(new ScrollingMovementMethod());
-
-        String modelId = getModelId();
-
-        String txPower = getFromIntentOrPrefs(EXTRA_TX_POWER_LEVEL, "HIGH");
-        updateStringStatusView(R.id.tx_power_text_view, "TxPower", txPower);
-
-        String bluetoothAddress = getFromIntentOrPrefs(EXTRA_BLUETOOTH_ADDRESS, "");
-
-        String firmwareVersion = getFromIntentOrPrefs(EXTRA_FIRMWARE_VERSION, "1.1");
-        try {
-            Preconditions.checkArgument(base16().decode(bluetoothAddress).length == 6);
-        } catch (IllegalArgumentException e) {
-            mLogger.log("Invalid BLUETOOTH_ADDRESS extra (%s), using default.", bluetoothAddress);
-            bluetoothAddress = null;
-        }
-        final String finalBluetoothAddress = bluetoothAddress;
-
-        updateStringStatusView(
-                R.id.anti_spoofing_private_key_text_view, ANTI_SPOOFING_KEY_LABEL, "Loading...");
-
-        boolean useRandomSaltForAccountKeyRotation =
-                getFromIntentOrPrefs(EXTRA_USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION, false);
-
-        Executors.newSingleThreadExecutor().execute(() -> {
-            // Fetch the anti-spoofing key corresponding to this model ID (if it
-            // exists).
-            // The account must have Project Viewer permission for the project
-            // that owns
-            // the model ID (normally discoverer-test or discoverer-devices).
-            byte[] antiSpoofingKey = getAntiSpoofingKey(modelId);
-            String antiSpoofingKeyString;
-            Device device = mModelsMap.get(modelId);
-            if (antiSpoofingKey != null) {
-                antiSpoofingKeyString = base64().encode(antiSpoofingKey);
-            } else {
-                if (mSharedPreferences.getString(KEY_ACCOUNT_NAME, "").isEmpty()) {
-                    antiSpoofingKeyString = "Can't fetch, no account";
-                } else {
-                    if (device == null) {
-                        antiSpoofingKeyString = String.format(Locale.US,
-                                "Can't find model %s from console", modelId);
-                    } else if (!device.hasAntiSpoofingKeyPair()) {
-                        antiSpoofingKeyString = String.format(Locale.US,
-                                "Can't find AntiSpoofingKeyPair for model %s", modelId);
-                    } else if (device.getAntiSpoofingKeyPair().getPrivateKey().isEmpty()) {
-                        antiSpoofingKeyString = String.format(Locale.US,
-                                "Can't find privateKey for model %s", modelId);
-                    } else {
-                        antiSpoofingKeyString = "Unknown error";
-                    }
-                }
-            }
-
-            int desiredIoCapability = getIoCapabilityFromModelId(modelId);
-
-            mBluetoothController.setIoCapability(desiredIoCapability);
-
-            runOnUiThread(() -> {
-                updateStringStatusView(
-                        R.id.anti_spoofing_private_key_text_view,
-                        ANTI_SPOOFING_KEY_LABEL,
-                        antiSpoofingKeyString);
-                FastPairSimulator.Options option = FastPairSimulator.Options.builder(modelId)
-                        .setAdvertisingModelId(
-                                mAppLaunchSwitch.isChecked() ? MODEL_ID_APP_LAUNCH : modelId)
-                        .setBluetoothAddress(finalBluetoothAddress)
-                        .setTxPowerLevel(toTxPowerLevel(txPower))
-                        .setAdvertisingChangedCallback(isAdvertising -> updateStatusView())
-                        .setAntiSpoofingPrivateKey(antiSpoofingKey)
-                        .setUseRandomSaltForAccountKeyRotation(useRandomSaltForAccountKeyRotation)
-                        .setDataOnlyConnection(device != null && device.getDataOnlyConnection())
-                        .setShowsPasskeyConfirmation(
-                                device.getDeviceType().equals(DeviceType.ANDROID_AUTO))
-                        .setRemoveAllDevicesDuringPairing(mRemoveAllDevicesDuringPairing)
-                        .build();
-                Logger textViewLogger = new Logger(FastPairSimulator.TAG) {
-
-                    @FormatMethod
-                    public void log(@Nullable Throwable exception, String message,
-                            Object... objects) {
-                        super.log(exception, message, objects);
-
-                        String exceptionMessage = (exception == null) ? ""
-                                : " - " + exception.getMessage();
-                        final String finalMessage =
-                                String.format(message, objects) + exceptionMessage;
-
-                        textView.post(() -> {
-                            String newText =
-                                    textView.getText() + "\n\n" + finalMessage;
-                            textView.setText(newText);
-                        });
-                    }
-                };
-                mFastPairSimulator =
-                        new FastPairSimulator(this, option, textViewLogger);
-                mFastPairSimulator.setFirmwareVersion(firmwareVersion);
-                mFailSwitch.setChecked(
-                        mFastPairSimulator.getShouldFailPairing());
-                mAdvOptionSpinner.setSelection(0);
-                setCapabilityToSimulator();
-
-                updateStringStatusView(R.id.bluetooth_address_text_view,
-                        "Bluetooth address",
-                        mFastPairSimulator.getBluetoothAddress());
-
-                updateStringStatusView(R.id.device_name_text_view,
-                        "Device name",
-                        mFastPairSimulator.getDeviceName());
-
-                resetButton.setText("Reset");
-                resetButton.setEnabled(true);
-                mModelIdSpinner.setEnabled(true);
-                mAppLaunchSwitch.setEnabled(true);
-                mFastPairSimulator.setDeviceNameCallback(deviceName ->
-                        updateStringStatusView(
-                                R.id.device_name_text_view,
-                                "Device name", deviceName));
-
-                if (desiredIoCapability == IO_CAPABILITY_IN
-                        || device.getDeviceType().equals(DeviceType.ANDROID_AUTO)) {
-                    mFastPairSimulator.setPasskeyEventCallback(mPasskeyEventCallback);
-                }
-                if (mInputStreamListener != null) {
-                    mInputStreamListener.setFastPairSimulator(mFastPairSimulator);
-                }
-            });
-        });
-    }
-
-    private int getIoCapabilityFromModelId(String modelId) {
-        Device device = mModelsMap.get(modelId);
-        if (device == null) {
-            return IO_CAPABILITY_NONE;
-        } else {
-            if (getAntiSpoofingKey(modelId) == null) {
-                return IO_CAPABILITY_NONE;
-            } else {
-                switch (device.getDeviceType()) {
-                    case INPUT_DEVICE:
-                        return IO_CAPABILITY_IN;
-
-                    case DEVICE_TYPE_UNSPECIFIED:
-                        return IO_CAPABILITY_NONE;
-
-                    // Treats wearable to IO_CAPABILITY_KBDISP for simulator because there seems
-                    // no suitable
-                    // type.
-                    case WEARABLE:
-                        return IO_CAPABILITY_KBDISP;
-
-                    default:
-                        return IO_CAPABILITY_IO;
-                }
-            }
-        }
-    }
-
-    @Nullable
-    ByteString getAccontKey() {
-        if (mFastPairSimulator == null) {
-            return null;
-        }
-        return mFastPairSimulator.getAccountKey();
-    }
-
-    @Nullable
-    private byte[] getAntiSpoofingKey(String modelId) {
-        Device device = mModelsMap.get(modelId);
-        if (device != null
-                && device.hasAntiSpoofingKeyPair()
-                && !device.getAntiSpoofingKeyPair().getPrivateKey().isEmpty()) {
-            return base64().decode(device.getAntiSpoofingKeyPair().getPrivateKey().toStringUtf8());
-        } else if (ANTI_SPOOFING_PRIVATE_KEY_MAP.containsKey(modelId)) {
-            return base64().decode(ANTI_SPOOFING_PRIVATE_KEY_MAP.get(modelId));
-        } else {
-            return null;
-        }
-    }
-
-    private final PasskeyEventCallback mPasskeyEventCallback = new PasskeyEventCallback() {
-        @Override
-        public void onPasskeyRequested(KeyInputCallback keyInputCallback) {
-            showInputPasskeyDialog(keyInputCallback);
-        }
-
-        @Override
-        public void onPasskeyConfirmation(int passkey, Consumer<Boolean> isConfirmed) {
-            showConfirmPasskeyDialog(passkey, isConfirmed);
-        }
-
-        @Override
-        public void onRemotePasskeyReceived(int passkey) {
-            if (mInputPasskeyDialog == null) {
-                return;
-            }
-
-            EditText userInputDialogEditText = mInputPasskeyDialog.findViewById(
-                    R.id.userInputDialog);
-            if (userInputDialogEditText == null) {
-                return;
-            }
-
-            userInputDialogEditText.setText(String.format("%d", passkey));
-        }
-    };
-
-    private void showInputPasskeyDialog(KeyInputCallback keyInputCallback) {
-        if (mInputPasskeyDialog == null) {
-            View userInputView =
-                    LayoutInflater.from(getApplicationContext()).inflate(R.layout.user_input_dialog,
-                            null);
-            EditText userInputDialogEditText = userInputView.findViewById(R.id.userInputDialog);
-            userInputDialogEditText.setHint(R.string.passkey_input_hint);
-            userInputDialogEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
-            mInputPasskeyDialog = new AlertDialog.Builder(MainActivity.this)
-                    .setView(userInputView)
-                    .setCancelable(false)
-                    .setPositiveButton(
-                            android.R.string.ok,
-                            (DialogInterface dialogBox, int id) -> {
-                                String input = userInputDialogEditText.getText().toString();
-                                keyInputCallback.onKeyInput(Integer.parseInt(input));
-                            })
-                    .setNegativeButton(android.R.string.cancel, /* listener= */ null)
-                    .setTitle(R.string.passkey_dialog_title)
-                    .create();
-        }
-        if (!mInputPasskeyDialog.isShowing()) {
-            mInputPasskeyDialog.show();
-        }
-    }
-
-    private void showConfirmPasskeyDialog(int passkey, Consumer<Boolean> isConfirmed) {
-        runOnUiThread(() -> new AlertDialog.Builder(MainActivity.this)
-                .setCancelable(false)
-                .setTitle(R.string.confirm_passkey)
-                .setMessage(String.valueOf(passkey))
-                .setPositiveButton(android.R.string.ok,
-                        (d, w) -> isConfirmed.accept(true))
-                .setNegativeButton(android.R.string.cancel,
-                        (d, w) -> isConfirmed.accept(false))
-                .create()
-                .show());
-    }
-
-    @UiThread
-    private void updateStringStatusView(int id, String name, String value) {
-        ((TextView) findViewById(id)).setText(name + ": " + value);
-    }
-
-    @UiThread
-    private void updateStatusView() {
-        TextView remoteDeviceTextView = (TextView) findViewById(R.id.remote_device_text_view);
-        remoteDeviceTextView.setBackgroundColor(
-                mBluetoothController.getRemoteDevice() != null ? LIGHT_GREEN : Color.LTGRAY);
-        String remoteDeviceString = mBluetoothController.getRemoteDeviceAsString();
-        remoteDeviceTextView.setText("Remote device: " + remoteDeviceString);
-
-        updateBooleanStatusView(
-                R.id.is_advertising_text_view,
-                "BLE advertising",
-                mFastPairSimulator != null && mFastPairSimulator.isAdvertising());
-
-        updateStringStatusView(
-                R.id.scan_mode_text_view,
-                "Mode",
-                FastPairSimulator.scanModeToString(mBluetoothController.getScanMode()));
-
-        boolean isPaired = mBluetoothController.isPaired();
-        updateBooleanStatusView(R.id.is_paired_text_view, "Paired", isPaired);
-
-        updateBooleanStatusView(
-                R.id.is_connected_text_view, "Connected", mBluetoothController.isConnected());
-    }
-
-    @UiThread
-    private void updateBooleanStatusView(int id, String name, boolean value) {
-        TextView view = (TextView) findViewById(id);
-        view.setBackgroundColor(value ? LIGHT_GREEN : Color.LTGRAY);
-        view.setText(name + ": " + (value ? "Yes" : "No"));
-    }
-
-    private String getFromIntentOrPrefs(String key, String defaultValue) {
-        Bundle extras = getIntent().getExtras();
-        extras = extras != null ? extras : new Bundle();
-        SharedPreferences prefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
-        String value = extras.getString(key, prefs.getString(key, defaultValue));
-        if (value == null) {
-            prefs.edit().remove(key).apply();
-        } else {
-            prefs.edit().putString(key, value).apply();
-        }
-        return value;
-    }
-
-    private boolean getFromIntentOrPrefs(String key, boolean defaultValue) {
-        Bundle extras = getIntent().getExtras();
-        extras = extras != null ? extras : new Bundle();
-        SharedPreferences prefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
-        boolean value = extras.getBoolean(key, prefs.getBoolean(key, defaultValue));
-        prefs.edit().putBoolean(key, value).apply();
-        return value;
-    }
-
-    private static int toTxPowerLevel(String txPowerLevelString) {
-        switch (txPowerLevelString.toUpperCase()) {
-            case "3":
-            case "HIGH":
-                return AdvertiseSettings.ADVERTISE_TX_POWER_HIGH;
-            case "2":
-            case "MEDIUM":
-                return AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM;
-            case "1":
-            case "LOW":
-                return AdvertiseSettings.ADVERTISE_TX_POWER_LOW;
-            case "0":
-            case "ULTRA_LOW":
-                return AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW;
-            default:
-                throw new IllegalArgumentException(
-                        "Unexpected TxPower="
-                                + txPowerLevelString
-                                + ", please provide HIGH, MEDIUM, LOW, or ULTRA_LOW.");
-        }
-    }
-
-    private boolean checkPermissions(String[] permissions) {
-        for (String permission : permissions) {
-            if (checkSelfPermission(permission) != PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    protected void onDestroy() {
-        mRemoteDevicesManager.destroy();
-
-        if (mFastPairSimulator != null) {
-            mFastPairSimulator.destroy();
-            mBluetoothController.unregisterBluetoothStateReceiver();
-        }
-
-        // Recover the IO capability.
-        mBluetoothController.setIoCapability(IO_CAPABILITY_IO);
-
-        super.onDestroy();
-    }
-
-    @Override
-    public void onRequestPermissionsResult(
-            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        // Relaunch this activity.
-        recreate();
-    }
-
-    void startAdvertisingBatteryInformationBasedOnOption(int option) {
-        if (mFastPairSimulator == null) {
-            return;
-        }
-
-        // Option 0 is "No battery info", it means simulator will not pack battery information when
-        // advertising. For the others with battery info, since we are simulating the Presto's
-        // behavior,
-        // there will always be three battery values.
-        switch (option) {
-            case 0:
-                // Option "0: No battery info"
-                mFastPairSimulator.clearBatteryValues();
-                break;
-            case 1:
-                // Option "1: Show L(⬆) + R(⬆) + C(⬆)"
-                mFastPairSimulator.setSuppressBatteryNotification(false);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(true, 60),
-                        new BatteryValue(true, 61),
-                        new BatteryValue(true, 62));
-                break;
-            case 2:
-                // Option "2: Show L + R + C(unknown)"
-                mFastPairSimulator.setSuppressBatteryNotification(false);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(false, 70),
-                        new BatteryValue(false, 71),
-                        new BatteryValue(false, -1));
-                break;
-            case 3:
-                // Option "3: Show L(low 10) + R(low 9) + C(low 25)"
-                mFastPairSimulator.setSuppressBatteryNotification(false);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(false, 10),
-                        new BatteryValue(false, 9),
-                        new BatteryValue(false, 25));
-                break;
-            case 4:
-                // Option "4: Suppress battery w/o level changes"
-                // Just change the suppress bit and keep the battery values the same as before.
-                mFastPairSimulator.setSuppressBatteryNotification(true);
-                break;
-            case 5:
-                // Option "5: Suppress L(low 10) + R(11) + C"
-                mFastPairSimulator.setSuppressBatteryNotification(true);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(false, 10),
-                        new BatteryValue(false, 11),
-                        new BatteryValue(false, 82));
-                break;
-            case 6:
-                // Option "6: Suppress L(low ⬆) + R(low ⬆) + C(low 10)"
-                mFastPairSimulator.setSuppressBatteryNotification(true);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(true, 10),
-                        new BatteryValue(true, 9),
-                        new BatteryValue(false, 10));
-                break;
-            case 7:
-                // Option "7: Suppress L(low ⬆) + R(low ⬆) + C(low ⬆)"
-                mFastPairSimulator.setSuppressBatteryNotification(true);
-                mFastPairSimulator.setBatteryValues(new BatteryValue(true, 10),
-                        new BatteryValue(true, 9),
-                        new BatteryValue(true, 25));
-                break;
-            case 8:
-                // Option "8: Show subsequent pairing notification"
-                mFastPairSimulator.setSuppressSubsequentPairingNotification(false);
-                break;
-            case 9:
-                // Option "9: Suppress subsequent pairing notification"
-                mFastPairSimulator.setSuppressSubsequentPairingNotification(true);
-                break;
-            default:
-                // Unknown option, do nothing.
-                return;
-        }
-
-        mFastPairSimulator.startAdvertising();
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java
deleted file mode 100644
index fac8cb5..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.app;
-
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.ACCOUNT_KEY;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.ACKNOWLEDGE;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_ADDRESS_BLE;
-import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_ADDRESS_PUBLIC;
-
-import android.nearby.fastpair.provider.FastPairSimulator;
-import android.nearby.fastpair.provider.FastPairSimulator.BatteryValue;
-import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Command;
-import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Command.BatteryInfo;
-import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event;
-import android.nearby.fastpair.provider.simulator.testing.InputStreamListener;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-
-/** Listener for input stream of the remote device. */
-public class RemoteDeviceListener implements InputStreamListener {
-    private static final String TAG = RemoteDeviceListener.class.getSimpleName();
-
-    private final MainActivity mMainActivity;
-    @Nullable
-    private FastPairSimulator mFastPairSimulator;
-
-    public RemoteDeviceListener(MainActivity mainActivity) {
-        this.mMainActivity = mainActivity;
-    }
-
-    @Override
-    public void onInputData(ByteString byteString) {
-        Command command;
-        try {
-            command = Command.parseFrom(byteString);
-        } catch (InvalidProtocolBufferException e) {
-            Log.w(TAG, String.format("%s input data is not a Command",
-                    mMainActivity.mRemoteDeviceId), e);
-            return;
-        }
-
-        mMainActivity.runOnUiThread(() -> {
-            Log.d(TAG, String.format("%s new command %s",
-                    mMainActivity.mRemoteDeviceId, command.getCode()));
-            switch (command.getCode()) {
-                case POLLING:
-                    mMainActivity.sendEventToRemoteDevice(
-                            Event.newBuilder().setCode(ACKNOWLEDGE));
-                    break;
-                case RESET:
-                    mMainActivity.reset();
-                    break;
-                case SHOW_BATTERY:
-                    onShowBattery(command.getBatteryInfo());
-                    break;
-                case HIDE_BATTERY:
-                    onHideBattery();
-                    break;
-                case REQUEST_BLUETOOTH_ADDRESS_BLE:
-                    onRequestBleAddress();
-                    break;
-                case REQUEST_BLUETOOTH_ADDRESS_PUBLIC:
-                    onRequestPublicAddress();
-                    break;
-                case REQUEST_ACCOUNT_KEY:
-                    ByteString accountKey = mMainActivity.getAccontKey();
-                    if (accountKey == null) {
-                        break;
-                    }
-                    mMainActivity.sendEventToRemoteDevice(
-                            Event.newBuilder().setCode(ACCOUNT_KEY)
-                                    .setAccountKey(accountKey));
-                    break;
-            }
-        });
-    }
-
-    @Override
-    public void onClose() {
-        Log.d(TAG, String.format("%s input stream is closed", mMainActivity.mRemoteDeviceId));
-    }
-
-    void setFastPairSimulator(FastPairSimulator fastPairSimulator) {
-        this.mFastPairSimulator = fastPairSimulator;
-    }
-
-    private void onShowBattery(@Nullable BatteryInfo batteryInfo) {
-        if (mFastPairSimulator == null || batteryInfo == null) {
-            Log.w(TAG, "skip showing battery");
-            return;
-        }
-
-        if (batteryInfo.getBatteryValuesCount() != 3) {
-            Log.w(TAG, String.format("skip showing battery: count is not valid %d",
-                    batteryInfo.getBatteryValuesCount()));
-            return;
-        }
-
-        Log.d(TAG, String.format("Show battery %s", batteryInfo));
-
-        if (batteryInfo.hasSuppressNotification()) {
-            mFastPairSimulator.setSuppressBatteryNotification(
-                    batteryInfo.getSuppressNotification());
-        }
-        mFastPairSimulator.setBatteryValues(
-                convertFrom(batteryInfo.getBatteryValues(0)),
-                convertFrom(batteryInfo.getBatteryValues(1)),
-                convertFrom(batteryInfo.getBatteryValues(2)));
-        mFastPairSimulator.startAdvertising();
-    }
-
-    private void onHideBattery() {
-        if (mFastPairSimulator == null) {
-            return;
-        }
-
-        mFastPairSimulator.clearBatteryValues();
-        mFastPairSimulator.startAdvertising();
-    }
-
-    private void onRequestBleAddress() {
-        if (mFastPairSimulator == null) {
-            return;
-        }
-
-        mMainActivity.sendEventToRemoteDevice(
-                Event.newBuilder()
-                        .setCode(BLUETOOTH_ADDRESS_BLE)
-                        .setBleAddress(mFastPairSimulator.getBleAddress()));
-    }
-
-    private void onRequestPublicAddress() {
-        if (mFastPairSimulator == null) {
-            return;
-        }
-
-        mMainActivity.sendEventToRemoteDevice(
-                Event.newBuilder()
-                        .setCode(BLUETOOTH_ADDRESS_PUBLIC)
-                        .setPublicAddress(mFastPairSimulator.getBluetoothAddress()));
-    }
-
-    private static BatteryValue convertFrom(BatteryInfo.BatteryValue batteryValue) {
-        return new BatteryValue(batteryValue.getCharging(), batteryValue.getLevel());
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java
deleted file mode 100644
index b29225a..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-import com.google.protobuf.ByteString;
-
-/** Listener for input stream. */
-public interface InputStreamListener {
-
-    /** Called when new data {@code byteString} is read from the input stream. */
-    void onInputData(ByteString byteString);
-
-    /** Called when the input stream is closed. */
-    void onClose();
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java
deleted file mode 100644
index cf8b022..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.io.BaseEncoding.base16;
-
-import android.net.Uri;
-
-import androidx.annotation.Nullable;
-
-import com.google.protobuf.ByteString;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-
-/**
- * Opens the {@code inputUri} and {@code outputUri} as local files and provides reading/writing
- * data operations.
- *
- * To support bluetooth testing on real devices, the named pipes are created as local files and the
- * pipe data are transferred via usb cable, then (1) the peripheral device writes {@code Event} to
- * the output stream and reads {@code Command} from the input stream (2) the central devices write
- * {@code Command} to the output stream and read {@code Event} from the input stream.
- *
- * The {@code Event} and {@code Command} are special protocols which are defined at
- * simulator_stream_protocol.proto.
- */
-public class LocalFileStreamIOHandler implements StreamIOHandler {
-
-    private static final int MAX_IO_DATA_LENGTH_BYTE = 65535;
-
-    private final String mInputPath;
-    private final String mOutputPath;
-
-    LocalFileStreamIOHandler(Uri inputUri, Uri outputUri) throws IOException {
-        if (!isFileExists(inputUri.getPath())) {
-            throw new FileNotFoundException("Input path is not exists.");
-        }
-        if (!isFileExists(outputUri.getPath())) {
-            throw new FileNotFoundException("Output path is not exists.");
-        }
-
-        this.mInputPath = inputUri.getPath();
-        this.mOutputPath = outputUri.getPath();
-    }
-
-    /**
-     * Reads a {@code ByteString} from the input stream. The input stream must be opened before
-     * calling this method.
-     */
-    @Override
-    public ByteString read() throws IOException {
-        try (InputStreamReader inputStream = new InputStreamReader(
-                new FileInputStream(mInputPath))) {
-            int size = inputStream.read();
-            if (size == 0) {
-                throw new IOException(String.format("Missing data size %d", size));
-            }
-
-            if (size > MAX_IO_DATA_LENGTH_BYTE) {
-                throw new IOException("Exceed the maximum data length when reading.");
-            }
-
-            char[] data = new char[size];
-            int count = inputStream.read(data);
-            if (count != size) {
-                throw new IOException(
-                        String.format("Expected size was %s but got %s", size, count));
-            }
-
-            return ByteString.copyFrom(base16().decode(new String(data)));
-        }
-    }
-
-    /**
-     * Writes a {@code output} into the output stream. The output stream must be opened before
-     * calling this method.
-     */
-    @Override
-    public void write(ByteString output) throws IOException {
-        checkArgument(output.size() > 0, "Output data is empty.");
-
-        if (output.size() > MAX_IO_DATA_LENGTH_BYTE) {
-            throw new IOException("Exceed the maximum data length when writing.");
-        }
-
-        try (OutputStreamWriter outputStream =
-                     new OutputStreamWriter(new FileOutputStream(mOutputPath))) {
-            String base16Output = base16().encode(output.toByteArray());
-            outputStream.write(base16Output.length());
-            outputStream.write(base16Output);
-        }
-    }
-
-    private static boolean isFileExists(@Nullable String path) {
-        return path != null && new File(path).exists();
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java
deleted file mode 100644
index 11ec9cb..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-/** Represents a remote device and provides a {@link StreamIOHandler} to communicate with it. */
-public class RemoteDevice {
-    private final String mId;
-    private final StreamIOHandler mStreamIOHandler;
-    private final InputStreamListener mInputStreamListener;
-
-    public RemoteDevice(
-            String id, StreamIOHandler streamIOHandler, InputStreamListener inputStreamListener) {
-        this.mId = id;
-        this.mStreamIOHandler = streamIOHandler;
-        this.mInputStreamListener = inputStreamListener;
-    }
-
-    /** The id used by this device. */
-    public String getId() {
-        return mId;
-    }
-
-    /** The handler processes input and output data channels. */
-    public StreamIOHandler getStreamIOHandler() {
-        return mStreamIOHandler;
-    }
-
-    /** Listener for the input stream. */
-    public InputStreamListener getInputStreamListener() {
-        return mInputStreamListener;
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java
deleted file mode 100644
index 02260c2..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import android.util.Log;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.protobuf.ByteString;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-
-/**
- * Manages the IO streams with remote devices.
- *
- * <p>The caller must invoke {@link #registerRemoteDevice} before starting to communicate with the
- * remote device, and invoke {@link #unregisterRemoteDevice} after finishing tasks. If this instance
- * is not used anymore, the caller need to invoke {@link #destroy} to release all resources.
- *
- * <p>All of the methods are thread-safe.
- */
-public class RemoteDevicesManager {
-    private static final String TAG = "RemoteDevicesManager";
-
-    private final Map<String, RemoteDevice> mRemoteDeviceMap = new HashMap<>();
-    private final ListeningExecutorService mBackgroundExecutor =
-            MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
-    private final ListeningExecutorService mListenInputStreamExecutors =
-            MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
-    private final Map<String, ListenableFuture<Void>> mListeningTaskMap = new HashMap<>();
-
-    /**
-     * Opens input and output data streams for {@code remoteDevice} in the background and notifies
-     * the
-     * open result via {@code callback}, and assigns a dedicated executor to listen the input data
-     * stream if data streams are opened successfully. The dedicated executor will invoke the
-     * {@code
-     * remoteDevice.inputStreamListener().onInputData()} directly if the new data exists in the
-     * input
-     * stream and invoke the {@code remoteDevice.inputStreamListener().onClose()} if the input
-     * stream
-     * is closed.
-     */
-    public synchronized void registerRemoteDevice(String id, RemoteDevice remoteDevice) {
-        checkState(mRemoteDeviceMap.put(id, remoteDevice) == null,
-                "The %s is already registered", id);
-        startListeningInputStreamTask(remoteDevice);
-    }
-
-    /**
-     * Closes the data streams for specific remote device {@code id} in the background and notifies
-     * the result via {@code callback}.
-     */
-    public synchronized void unregisterRemoteDevice(String id) {
-        RemoteDevice remoteDevice = mRemoteDeviceMap.remove(id);
-        checkState(remoteDevice != null, "The %s is not registered", id);
-        if (mListeningTaskMap.containsKey(id)) {
-            mListeningTaskMap.remove(id).cancel(/* mayInterruptIfRunning= */ true);
-        }
-    }
-
-    /** Closes all data streams of registered remote devices and stop all background tasks. */
-    public synchronized void destroy() {
-        mRemoteDeviceMap.clear();
-        mListeningTaskMap.clear();
-        mListenInputStreamExecutors.shutdownNow();
-    }
-
-    /**
-     * Writes {@code data} into the output data stream of specific remote device {@code id} in the
-     * background and notifies the result via {@code callback}.
-     */
-    public synchronized void writeDataToRemoteDevice(
-            String id, ByteString data, FutureCallback<Void> callback) {
-        RemoteDevice remoteDevice = mRemoteDeviceMap.get(id);
-        checkState(remoteDevice != null, "The %s is not registered", id);
-
-        runInBackground(() -> {
-            remoteDevice.getStreamIOHandler().write(data);
-            return null;
-        }, callback);
-    }
-
-    private void runInBackground(Callable<Void> callable, FutureCallback<Void> callback) {
-        Futures.addCallback(
-                mBackgroundExecutor.submit(callable), callback, MoreExecutors.directExecutor());
-    }
-
-    private void startListeningInputStreamTask(RemoteDevice remoteDevice) {
-        ListenableFuture<Void> listenFuture = mListenInputStreamExecutors.submit(() -> {
-            Log.i(TAG, "Start listening " + remoteDevice.getId());
-            while (true) {
-                ByteString data;
-                try {
-                    data = remoteDevice.getStreamIOHandler().read();
-                } catch (IOException | IllegalStateException e) {
-                    break;
-                }
-                remoteDevice.getInputStreamListener().onInputData(data);
-            }
-        }, /* result= */ null);
-        Futures.addCallback(listenFuture, new FutureCallback<Void>() {
-            @Override
-            public void onSuccess(Void result) {
-                Log.i(TAG, "Stop listening " + remoteDevice.getId());
-                remoteDevice.getInputStreamListener().onClose();
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                Log.w(TAG, "Stop listening " + remoteDevice.getId() + ", cause: " + t);
-                remoteDevice.getInputStreamListener().onClose();
-            }
-        }, MoreExecutors.directExecutor());
-        mListeningTaskMap.put(remoteDevice.getId(), listenFuture);
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java
deleted file mode 100644
index d5fdb9e..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-import com.google.protobuf.ByteString;
-
-import java.io.IOException;
-
-/**
- * Opens input and output data channels, then provides read and write operations to the data
- * channels.
- */
-public interface StreamIOHandler {
-    /**
-     * Reads stream data from the input channel.
-     *
-     * @return a protocol buffer contains the input message
-     * @throws IOException errors occur when reading the input stream
-     */
-    ByteString read() throws IOException;
-
-    /**
-     * Writes stream data to the output channel.
-     *
-     * @param output a protocol buffer contains the output message
-     * @throws IOException errors occur when writing the output message to output stream
-     */
-    void write(ByteString output) throws IOException;
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java
deleted file mode 100644
index 24cfe56..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.testing;
-
-import android.net.Uri;
-
-import java.io.IOException;
-
-/** A simple factory creating {@link StreamIOHandler} according to {@link Type}. */
-public class StreamIOHandlerFactory {
-
-    /** Types for creating {@link StreamIOHandler}. */
-    public enum Type {
-
-        /**
-         * A {@link StreamIOHandler} accepts local file uris and provides reading/writing file
-         * operations.
-         */
-        LOCAL_FILE
-    }
-
-    /** Creates an instance of {@link StreamIOHandler}. */
-    public static StreamIOHandler createStreamIOHandler(Type type, Uri input, Uri output)
-            throws IOException {
-        if (type.equals(Type.LOCAL_FILE)) {
-            return new LocalFileStreamIOHandler(input, output);
-        }
-        throw new IllegalArgumentException(String.format("Can't support %s", type));
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java
deleted file mode 100644
index 95c077b..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider;
-
-import androidx.annotation.Nullable;
-
-/** Helper for advertising Fast Pair data. */
-public interface FastPairAdvertiser {
-
-    void startAdvertising(@Nullable byte[] serviceData);
-
-    void stopAdvertising();
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
deleted file mode 100644
index 0d5563e..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
+++ /dev/null
@@ -1,2391 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider;
-
-import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
-import static android.bluetooth.BluetoothAdapter.STATE_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.bluetooth.BluetoothDevice.ERROR;
-import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ;
-import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE;
-import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_INDICATE;
-import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
-import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ;
-import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
-import static android.nearby.fastpair.provider.bluetooth.BluetoothManager.wrap;
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.CONNECTED;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt;
-import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.A2DP_SINK_SERVICE_UUID;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID;
-import static com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.SECTION_NONCE_LENGTH;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.primitives.Bytes.concat;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass.Device.Major;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.le.AdvertiseSettings;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.nearby.fastpair.provider.EventStreamProtocol.AcknowledgementEventCode;
-import android.nearby.fastpair.provider.EventStreamProtocol.DeviceActionEventCode;
-import android.nearby.fastpair.provider.EventStreamProtocol.DeviceCapabilitySyncEventCode;
-import android.nearby.fastpair.provider.EventStreamProtocol.DeviceConfigurationEventCode;
-import android.nearby.fastpair.provider.EventStreamProtocol.DeviceEventCode;
-import android.nearby.fastpair.provider.EventStreamProtocol.EventGroup;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig.ServiceConfig;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection.Notifier;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerHelper;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServlet;
-import android.nearby.fastpair.provider.bluetooth.RfcommServer;
-import android.nearby.fastpair.provider.crypto.Crypto;
-import android.nearby.fastpair.provider.crypto.E2eeCalculator;
-import android.nearby.fastpair.provider.utils.Logger;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.util.Consumer;
-
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
-import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption;
-import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress;
-import com.android.server.nearby.common.bluetooth.fastpair.Bytes.Value;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.BeaconActionsCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.PasskeyCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BrHandoverDataCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.ControlPointCharacteristic;
-import com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange;
-import com.android.server.nearby.common.bluetooth.fastpair.Ltv;
-import com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder;
-import com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder;
-
-import com.google.common.base.Ascii;
-import com.google.common.primitives.Bytes;
-import com.google.protobuf.ByteString;
-
-import java.lang.reflect.Method;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Simulates a Fast Pair device (e.g. a headset).
- *
- * <p>Note: There are two deviations from the spec:
- *
- * <ul>
- *   <li>Instead of using the public address when in pairing mode (discoverable), it always uses the
- *       random private address (RPA), because that's how stock Android works. To work around this,
- *       it implements the BR/EDR Handover profile (which is no longer part of the Fast Pair spec)
- *       when simulating a keyless device (i.e. Fast Pair 1.0), which allows the phone to ask for
- *       the public address. When there is an anti-spoofing key, i.e. Fast Pair 2.0, the public
- *       address is delivered via the Key-based Pairing handshake. b/79374759 tracks fixing this.
- *   <li>The simulator always identifies its device capabilities as Keyboard/Display, even when
- *       simulating a keyless (Fast Pair 1.0) device that should identify as NoInput/NoOutput.
- *       b/79377125 tracks fixing this.
- * </ul>
- *
- * @see {http://go/fast-pair-2-spec}
- */
-public class FastPairSimulator {
-    public static final String TAG = "FastPairSimulator";
-    private final Logger mLogger;
-
-    private static final int BECOME_DISCOVERABLE_TIMEOUT_SEC = 3;
-
-    private static final int SCAN_MODE_REFRESH_SEC = 30;
-
-    /**
-     * Headphones. Generated by
-     * http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html
-     */
-    private static final Value CLASS_OF_DEVICE =
-            new Value(base16().decode("200418"), ByteOrder.BIG_ENDIAN);
-
-    private static final byte[] SUPPORTED_SERVICES_LTV = new Ltv(
-            TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE,
-            toBytes(ByteOrder.LITTLE_ENDIAN, A2DP_SINK_SERVICE_UUID)
-    ).getBytes();
-    private static final byte[] TDS_CONTROL_POINT_RESPONSE_PARAMETER =
-            Bytes.concat(new byte[]{BLUETOOTH_SIG_ORGANIZATION_ID}, SUPPORTED_SERVICES_LTV);
-
-    private static final String SIMULATOR_FAKE_BLE_ADDRESS = "11:22:33:44:55:66";
-
-    private static final long ADVERTISING_REFRESH_DELAY_1_MIN = TimeUnit.MINUTES.toMillis(1);
-
-    /**
-     * The size of account key filter in bytes is (1.2*n + 3), n represents the size of account key,
-     * see https://developers.google.com/nearby/fast-pair/spec#advertising_when_not_discoverable.
-     * However we'd like to advertise something else, so we could only afford 8 account keys.
-     *
-     * <ul>
-     *   <li>BLE flags: 3 bytes
-     *   <li>TxPower: 3 bytes
-     *   <li>FastPair: max 25 bytes
-     *       <ul>
-     *         <li>FastPair service data: 4 bytes
-     *         <li>Flags: 1 byte
-     *         <li>Account key filter: max 14 bytes (1 byte: length + type, 13 bytes: max 8 account
-     *             keys)
-     *         <li>Salt: 2 bytes
-     *         <li>Battery: 4 bytes
-     *       </ul>
-     * </ul>
-     */
-    private String mDeviceFirmwareVersion = "1.1.0";
-
-    private byte[] mSessionNonce;
-
-    private boolean mUseLogFullEvent = true;
-
-    private enum ResultCode {
-        SUCCESS((byte) 0x00),
-        OP_CODE_NOT_SUPPORTED((byte) 0x01),
-        INVALID_PARAMETER((byte) 0x02),
-        UNSUPPORTED_ORGANIZATION_ID((byte) 0x03),
-        OPERATION_FAILED((byte) 0x04);
-
-        private final byte mByteValue;
-
-        ResultCode(byte byteValue) {
-            this.mByteValue = byteValue;
-        }
-    }
-
-    private enum TransportState {
-        OFF((byte) 0x00),
-        ON((byte) 0x01),
-        TEMPORARILY_UNAVAILABLE((byte) 0x10);
-
-        private final byte mByteValue;
-
-        TransportState(byte byteValue) {
-            this.mByteValue = byteValue;
-        }
-    }
-
-    private final Context mContext;
-    private final Options mOptions;
-    private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
-    // No thread pool: Only used in test app (outside gmscore) and in javatests/.../gmscore/.
-    private final ScheduledExecutorService mExecutor =
-            Executors.newSingleThreadScheduledExecutor(); // exempt
-    private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (mShouldFailPairing) {
-                mLogger.log("Pairing disabled by test app switch");
-                return;
-            }
-            if (mIsDestroyed) {
-                // Sometimes this receiver does not successfully unregister in destroy()
-                // which causes events to occur after the simulator is stopped, so ignore
-                // those events.
-                mLogger.log("Intent received after simulator destroyed, ignoring");
-                return;
-            }
-            BluetoothDevice device = intent.getParcelableExtra(
-                    BluetoothDevice.EXTRA_DEVICE);
-            switch (intent.getAction()) {
-                case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED:
-                    if (isDiscoverable()) {
-                        mIsDiscoverableLatch.countDown();
-                    }
-                    break;
-                case BluetoothDevice.ACTION_PAIRING_REQUEST:
-                    int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
-                            ERROR);
-                    int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
-                    mLogger.log(
-                            "Pairing request, variant=%d, key=%s", variant,
-                            key == ERROR ? "(none)" : key);
-
-                    // Prevent Bluetooth Settings from getting the pairing request.
-                    abortBroadcast();
-
-                    mPairingDevice = device;
-                    if (mSecret == null) {
-                        // We haven't done the handshake over GATT to agree on the shared
-                        // secret. For now, just accept anyway (so we can still simulate
-                        // old 1.0 model IDs).
-                        mLogger.log("No handshake, auto-accepting anyway.");
-                        setPasskeyConfirmation(true);
-                    } else if (variant
-                            == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
-                        // Store the passkey. And check it, since there's a race (see
-                        // method for why). Usually this check is a no-op and we'll get
-                        // the passkey later over GATT.
-                        mLocalPasskey = key;
-                        checkPasskey();
-                    } else if (variant == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
-                        if (mPasskeyEventCallback != null) {
-                            mPasskeyEventCallback.onPasskeyRequested(
-                                    FastPairSimulator.this::enterPassKey);
-                        } else {
-                            mLogger.log("passkeyEventCallback is not set!");
-                            enterPassKey(key);
-                        }
-                    } else if (variant == BluetoothDevice.PAIRING_VARIANT_CONSENT) {
-                        setPasskeyConfirmation(true);
-
-                    } else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) {
-                        if (mPasskeyEventCallback != null) {
-                            mPasskeyEventCallback.onPasskeyRequested(
-                                    (int pin) -> {
-                                        byte[] newPin = convertPinToBytes(
-                                                String.format(Locale.ENGLISH, "%d", pin));
-                                        mPairingDevice.setPin(newPin);
-                                    });
-                        }
-                    } else {
-                        // Reject the pairing request if it's not using the Numeric
-                        // Comparison (aka Passkey Confirmation) method.
-                        setPasskeyConfirmation(false);
-                    }
-                    break;
-                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
-                    int bondState =
-                            intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
-                                    BluetoothDevice.BOND_NONE);
-                    mLogger.log("Bond state to %s changed to %d", device, bondState);
-                    switch (bondState) {
-                        case BluetoothDevice.BOND_BONDING:
-                            // If we've started bonding, we shouldn't be advertising.
-                            mAdvertiser.stopAdvertising();
-                            // Not discoverable anymore, but still connectable.
-                            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
-                            break;
-                        case BluetoothDevice.BOND_BONDED:
-                            // Once bonded, advertise the account keys.
-                            mAdvertiser.startAdvertising(accountKeysServiceData());
-                            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
-
-                            // If it is subsequent pair, we need to add paired device here.
-                            if (mIsSubsequentPair
-                                    && mSecret != null
-                                    && mSecret.length == AES_BLOCK_LENGTH) {
-                                addAccountKey(mSecret, mPairingDevice);
-                            }
-                            break;
-                        case BluetoothDevice.BOND_NONE:
-                            // If the bonding process fails, we should be advertising again.
-                            mAdvertiser.startAdvertising(getServiceData());
-                            break;
-                        default:
-                            break;
-                    }
-                    break;
-                case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
-                    mLogger.log(
-                            "Connection state to %s changed to %d",
-                            device,
-                            intent.getIntExtra(
-                                    BluetoothAdapter.EXTRA_CONNECTION_STATE,
-                                    BluetoothAdapter.STATE_DISCONNECTED));
-                    break;
-                case BluetoothAdapter.ACTION_STATE_CHANGED:
-                    int state = intent.getIntExtra(EXTRA_STATE, -1);
-                    mLogger.log("Bluetooth adapter state=%s", state);
-                    switch (state) {
-                        case STATE_ON:
-                            startRfcommServer();
-                            break;
-                        case STATE_OFF:
-                            stopRfcommServer();
-                            break;
-                        default: // fall out
-                    }
-                    break;
-                default:
-                    mLogger.log(new IllegalArgumentException(intent.toString()),
-                            "Received unexpected intent");
-                    break;
-            }
-        }
-    };
-
-    @Nullable
-    private byte[] convertPinToBytes(@Nullable String pin) {
-        if (TextUtils.isEmpty(pin)) {
-            return null;
-        }
-        byte[] pinBytes;
-        pinBytes = pin.getBytes(StandardCharsets.UTF_8);
-        if (pinBytes.length <= 0 || pinBytes.length > 16) {
-            return null;
-        }
-        return pinBytes;
-    }
-
-    private final NotifiableGattServlet mPasskeyServlet =
-            new NotifiableGattServlet() {
-                @Override
-                // Simulating deprecated API {@code PasskeyCharacteristic.ID} for testing.
-                @SuppressWarnings("deprecation")
-                public BluetoothGattCharacteristic getBaseCharacteristic() {
-                    return new BluetoothGattCharacteristic(
-                            PasskeyCharacteristic.CUSTOM_128_BIT_UUID,
-                            PROPERTY_WRITE | PROPERTY_INDICATE,
-                            PERMISSION_WRITE);
-                }
-
-                @Override
-                public void write(
-                        BluetoothGattServerConnection connection, int offset, byte[] value) {
-                    mLogger.log("Got value from passkey servlet: %s", base16().encode(value));
-                    if (mSecret == null) {
-                        mLogger.log("Ignoring write to passkey characteristic, no pairing secret.");
-                        return;
-                    }
-
-                    try {
-                        mRemotePasskey = PasskeyCharacteristic.decrypt(
-                                PasskeyCharacteristic.Type.SEEKER, mSecret, value);
-                        if (mPasskeyEventCallback != null) {
-                            mPasskeyEventCallback.onRemotePasskeyReceived(mRemotePasskey);
-                        }
-                        checkPasskey();
-                    } catch (GeneralSecurityException e) {
-                        mLogger.log(
-                                "Decrypting passkey value %s failed using key %s",
-                                base16().encode(value), base16().encode(mSecret));
-                    }
-                }
-            };
-
-    private final NotifiableGattServlet mDeviceNameServlet =
-            new NotifiableGattServlet() {
-                @Override
-                // Simulating deprecated API {@code NameCharacteristic.ID} for testing.
-                @SuppressWarnings("deprecation")
-                BluetoothGattCharacteristic getBaseCharacteristic() {
-                    return new BluetoothGattCharacteristic(
-                            NameCharacteristic.CUSTOM_128_BIT_UUID,
-                            PROPERTY_WRITE | PROPERTY_INDICATE,
-                            PERMISSION_WRITE);
-                }
-
-                @Override
-                public void write(
-                        BluetoothGattServerConnection connection, int offset, byte[] value) {
-                    mLogger.log("Got value from device naming servlet: %s", base16().encode(value));
-                    if (mSecret == null) {
-                        mLogger.log("Ignoring write to name characteristic, no pairing secret.");
-                        return;
-                    }
-                    // Parse the device name from seeker to write name into provider.
-                    mLogger.log("Got name byte array size = %d", value.length);
-                    try {
-                        String decryptedDeviceName =
-                                NamingEncoder.decodeNamingPacket(mSecret, value);
-                        if (decryptedDeviceName != null) {
-                            setDeviceName(decryptedDeviceName.getBytes(StandardCharsets.UTF_8));
-                            mLogger.log("write device name = %s", decryptedDeviceName);
-                        }
-                    } catch (GeneralSecurityException e) {
-                        mLogger.log(e, "Failed to decrypt device name.");
-                    }
-                    // For testing to make sure we get the new provider name from simulator.
-                    if (mWriteNameCountDown != null) {
-                        mLogger.log("finish count down latch to write device name.");
-                        mWriteNameCountDown.countDown();
-                    }
-                }
-            };
-
-    private Value mBluetoothAddress;
-    private final FastPairAdvertiser mAdvertiser;
-    private final Map<String, BluetoothGattServerHelper> mBluetoothGattServerHelpers =
-            new HashMap<>();
-    private CountDownLatch mIsDiscoverableLatch = new CountDownLatch(1);
-    private ScheduledFuture<?> mRevertDiscoverableFuture;
-    private boolean mShouldFailPairing = false;
-    private boolean mIsDestroyed = false;
-    private boolean mIsAdvertising;
-    @Nullable
-    private String mBleAddress;
-    private BluetoothDevice mPairingDevice;
-    private int mLocalPasskey;
-    private int mRemotePasskey;
-    @Nullable
-    private byte[] mSecret;
-    @Nullable
-    private byte[] mAccountKey; // The latest account key added.
-    // The first account key added. Eddystone treats that account as the owner of the device.
-    @Nullable
-    private byte[] mOwnerAccountKey;
-    @Nullable
-    private PasskeyConfirmationCallback mPasskeyConfirmationCallback;
-    @Nullable
-    private DeviceNameCallback mDeviceNameCallback;
-    @Nullable
-    private PasskeyEventCallback mPasskeyEventCallback;
-    private final List<BatteryValue> mBatteryValues;
-    private boolean mSuppressBatteryNotification = false;
-    private boolean mSuppressSubsequentPairingNotification = false;
-    HandshakeRequest mHandshakeRequest;
-    @Nullable
-    private CountDownLatch mWriteNameCountDown;
-    private final RfcommServer mRfcommServer = new RfcommServer();
-    private boolean mSupportDynamicBufferSize = false;
-    private NotifiableGattServlet mBeaconActionsServlet;
-    private final FastPairSimulatorDatabase mFastPairSimulatorDatabase;
-    private boolean mIsSubsequentPair = false;
-
-    /** Sets the flag for failing paring for debug purpose. */
-    public void setShouldFailPairing(boolean shouldFailPairing) {
-        this.mShouldFailPairing = shouldFailPairing;
-    }
-
-    /** Gets the flag for failing paring for debug purpose. */
-    public boolean getShouldFailPairing() {
-        return mShouldFailPairing;
-    }
-
-    /** Clear the battery values, then no battery information is packed when advertising. */
-    public void clearBatteryValues() {
-        mBatteryValues.clear();
-    }
-
-    /** Sets the battery items which will be included in the advertisement packet. */
-    public void setBatteryValues(BatteryValue... batteryValues) {
-        this.mBatteryValues.clear();
-        Collections.addAll(this.mBatteryValues, batteryValues);
-    }
-
-    /** Sets whether the battery advertisement packet is within suppress type or not. */
-    public void setSuppressBatteryNotification(boolean suppressBatteryNotification) {
-        this.mSuppressBatteryNotification = suppressBatteryNotification;
-    }
-
-    /** Sets whether the account key data is within suppress type or not. */
-    public void setSuppressSubsequentPairingNotification(boolean isSuppress) {
-        mSuppressSubsequentPairingNotification = isSuppress;
-    }
-
-    /** Calls this to start advertising after some values are changed. */
-    public void startAdvertising() {
-        mAdvertiser.startAdvertising(getServiceData());
-    }
-
-    /** Send Event Message on to rfcomm connected devices. */
-    public void sendEventStreamMessageToRfcommDevices(EventGroup eventGroup) {
-        // Send fake log when event code is logging and type is not using Log_Full event.
-        if (eventGroup == EventGroup.LOGGING && !mUseLogFullEvent) {
-            mRfcommServer.sendFakeEventStreamLoggingMessage(
-                    getDeviceName()
-                            + " "
-                            + getBleAddress()
-                            + " send log at "
-                            + new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
-                            .format(Calendar.getInstance().getTime()));
-        } else {
-            mRfcommServer.sendFakeEventStreamMessage(eventGroup);
-        }
-    }
-
-    public void setUseLogFullEvent(boolean useLogFullEvent) {
-        this.mUseLogFullEvent = useLogFullEvent;
-    }
-
-    /** An optional way to get advertising status updates. */
-    public interface AdvertisingChangedCallback {
-        /**
-         * Called when we change our BLE advertisement.
-         *
-         * @param isAdvertising the advertising status.
-         */
-        void onAdvertisingChanged(boolean isAdvertising);
-    }
-
-    /** A way for tests to get callbacks when passkey confirmation is invoked. */
-    public interface PasskeyConfirmationCallback {
-        void onPasskeyConfirmation(boolean confirm);
-    }
-
-    /** A way for simulator UI update to get callback when device name is changed. */
-    public interface DeviceNameCallback {
-        void onNameChanged(String deviceName);
-    }
-
-    /**
-     * Callback when there comes a passkey input request from BT service, or receiving remote
-     * device's passkey.
-     */
-    public interface PasskeyEventCallback {
-        void onPasskeyRequested(KeyInputCallback keyInputCallback);
-
-        void onRemotePasskeyReceived(int passkey);
-
-        default void onPasskeyConfirmation(int passkey, Consumer<Boolean> isConfirmed) {
-        }
-    }
-
-    /** Options for the simulator. */
-    public static class Options {
-        private final String mModelId;
-
-        // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key.
-        private final String mAdvertisingModelId;
-
-        @Nullable
-        private final String mBluetoothAddress;
-
-        @Nullable
-        private final String mBleAddress;
-
-        private final boolean mDataOnlyConnection;
-
-        private final int mTxPowerLevel;
-
-        private final boolean mEnableNameCharacteristic;
-
-        private final AdvertisingChangedCallback mAdvertisingChangedCallback;
-
-        private final boolean mIncludeTransportDataDescriptor;
-
-        @Nullable
-        private final byte[] mAntiSpoofingPrivateKey;
-
-        private final boolean mUseRandomSaltForAccountKeyRotation;
-
-        private final boolean mBecomeDiscoverable;
-
-        private final boolean mShowsPasskeyConfirmation;
-
-        private final boolean mEnableBeaconActionsCharacteristic;
-
-        private final boolean mRemoveAllDevicesDuringPairing;
-
-        @Nullable
-        private final ByteString mEddystoneIdentityKey;
-
-        private Options(
-                String modelId,
-                String advertisingModelId,
-                @Nullable String bluetoothAddress,
-                @Nullable String bleAddress,
-                boolean dataOnlyConnection,
-                int txPowerLevel,
-                boolean enableNameCharacteristic,
-                AdvertisingChangedCallback advertisingChangedCallback,
-                boolean includeTransportDataDescriptor,
-                @Nullable byte[] antiSpoofingPrivateKey,
-                boolean useRandomSaltForAccountKeyRotation,
-                boolean becomeDiscoverable,
-                boolean showsPasskeyConfirmation,
-                boolean enableBeaconActionsCharacteristic,
-                boolean removeAllDevicesDuringPairing,
-                @Nullable ByteString eddystoneIdentityKey) {
-            this.mModelId = modelId;
-            this.mAdvertisingModelId = advertisingModelId;
-            this.mBluetoothAddress = bluetoothAddress;
-            this.mBleAddress = bleAddress;
-            this.mDataOnlyConnection = dataOnlyConnection;
-            this.mTxPowerLevel = txPowerLevel;
-            this.mEnableNameCharacteristic = enableNameCharacteristic;
-            this.mAdvertisingChangedCallback = advertisingChangedCallback;
-            this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor;
-            this.mAntiSpoofingPrivateKey = antiSpoofingPrivateKey;
-            this.mUseRandomSaltForAccountKeyRotation = useRandomSaltForAccountKeyRotation;
-            this.mBecomeDiscoverable = becomeDiscoverable;
-            this.mShowsPasskeyConfirmation = showsPasskeyConfirmation;
-            this.mEnableBeaconActionsCharacteristic = enableBeaconActionsCharacteristic;
-            this.mRemoveAllDevicesDuringPairing = removeAllDevicesDuringPairing;
-            this.mEddystoneIdentityKey = eddystoneIdentityKey;
-        }
-
-        public String getModelId() {
-            return mModelId;
-        }
-
-        // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key.
-        public String getAdvertisingModelId() {
-            return mAdvertisingModelId;
-        }
-
-        @Nullable
-        public String getBluetoothAddress() {
-            return mBluetoothAddress;
-        }
-
-        @Nullable
-        public String getBleAddress() {
-            return mBleAddress;
-        }
-
-        public boolean getDataOnlyConnection() {
-            return mDataOnlyConnection;
-        }
-
-        public int getTxPowerLevel() {
-            return mTxPowerLevel;
-        }
-
-        public boolean getEnableNameCharacteristic() {
-            return mEnableNameCharacteristic;
-        }
-
-        public AdvertisingChangedCallback getAdvertisingChangedCallback() {
-            return mAdvertisingChangedCallback;
-        }
-
-        public boolean getIncludeTransportDataDescriptor() {
-            return mIncludeTransportDataDescriptor;
-        }
-
-        @Nullable
-        public byte[] getAntiSpoofingPrivateKey() {
-            return mAntiSpoofingPrivateKey;
-        }
-
-        public boolean getUseRandomSaltForAccountKeyRotation() {
-            return mUseRandomSaltForAccountKeyRotation;
-        }
-
-        public boolean getBecomeDiscoverable() {
-            return mBecomeDiscoverable;
-        }
-
-        public boolean getShowsPasskeyConfirmation() {
-            return mShowsPasskeyConfirmation;
-        }
-
-        public boolean getEnableBeaconActionsCharacteristic() {
-            return mEnableBeaconActionsCharacteristic;
-        }
-
-        public boolean getRemoveAllDevicesDuringPairing() {
-            return mRemoveAllDevicesDuringPairing;
-        }
-
-        @Nullable
-        public ByteString getEddystoneIdentityKey() {
-            return mEddystoneIdentityKey;
-        }
-
-        /** Converts an instance to a builder. */
-        public Builder toBuilder() {
-            return new Options.Builder(this);
-        }
-
-        /** Constructs a builder. */
-        public static Builder builder() {
-            return new Options.Builder();
-        }
-
-        /** @param modelId Must be a 3-byte hex string. */
-        public static Builder builder(String modelId) {
-            return new Options.Builder()
-                    .setModelId(Ascii.toUpperCase(modelId))
-                    .setAdvertisingModelId(Ascii.toUpperCase(modelId))
-                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                    .setAdvertisingChangedCallback(isAdvertising -> {
-                    })
-                    .setIncludeTransportDataDescriptor(true)
-                    .setUseRandomSaltForAccountKeyRotation(false)
-                    .setEnableNameCharacteristic(true)
-                    .setDataOnlyConnection(false)
-                    .setBecomeDiscoverable(true)
-                    .setShowsPasskeyConfirmation(false)
-                    .setEnableBeaconActionsCharacteristic(true)
-                    .setRemoveAllDevicesDuringPairing(true);
-        }
-
-        /** A builder for {@link Options}. */
-        public static class Builder {
-
-            private String mModelId;
-
-            // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key.
-            private String mAdvertisingModelId;
-
-            @Nullable
-            private String mBluetoothAddress;
-
-            @Nullable
-            private String mBleAddress;
-
-            private boolean mDataOnlyConnection;
-
-            private int mTxPowerLevel;
-
-            private boolean mEnableNameCharacteristic;
-
-            private AdvertisingChangedCallback mAdvertisingChangedCallback;
-
-            private boolean mIncludeTransportDataDescriptor;
-
-            @Nullable
-            private byte[] mAntiSpoofingPrivateKey;
-
-            private boolean mUseRandomSaltForAccountKeyRotation;
-
-            private boolean mBecomeDiscoverable;
-
-            private boolean mShowsPasskeyConfirmation;
-
-            private boolean mEnableBeaconActionsCharacteristic;
-
-            private boolean mRemoveAllDevicesDuringPairing;
-
-            @Nullable
-            private ByteString mEddystoneIdentityKey;
-
-            private Builder() {
-            }
-
-            private Builder(Options option) {
-                this.mModelId = option.mModelId;
-                this.mAdvertisingModelId = option.mAdvertisingModelId;
-                this.mBluetoothAddress = option.mBluetoothAddress;
-                this.mBleAddress = option.mBleAddress;
-                this.mDataOnlyConnection = option.mDataOnlyConnection;
-                this.mTxPowerLevel = option.mTxPowerLevel;
-                this.mEnableNameCharacteristic = option.mEnableNameCharacteristic;
-                this.mAdvertisingChangedCallback = option.mAdvertisingChangedCallback;
-                this.mIncludeTransportDataDescriptor = option.mIncludeTransportDataDescriptor;
-                this.mAntiSpoofingPrivateKey = option.mAntiSpoofingPrivateKey;
-                this.mUseRandomSaltForAccountKeyRotation =
-                        option.mUseRandomSaltForAccountKeyRotation;
-                this.mBecomeDiscoverable = option.mBecomeDiscoverable;
-                this.mShowsPasskeyConfirmation = option.mShowsPasskeyConfirmation;
-                this.mEnableBeaconActionsCharacteristic = option.mEnableBeaconActionsCharacteristic;
-                this.mRemoveAllDevicesDuringPairing = option.mRemoveAllDevicesDuringPairing;
-                this.mEddystoneIdentityKey = option.mEddystoneIdentityKey;
-            }
-
-            /**
-             * Must be one of the {@code ADVERTISE_TX_POWER_*} levels in {@link AdvertiseSettings}.
-             * Default is HIGH.
-             */
-            public Builder setTxPowerLevel(int txPowerLevel) {
-                this.mTxPowerLevel = txPowerLevel;
-                return this;
-            }
-
-            /**
-             * Must be a 6-byte hex string (optionally with colons).
-             * Default is this device's BT MAC.
-             */
-            public Builder setBluetoothAddress(@Nullable String bluetoothAddress) {
-                this.mBluetoothAddress = bluetoothAddress;
-                return this;
-            }
-
-            public Builder setBleAddress(@Nullable String bleAddress) {
-                this.mBleAddress = bleAddress;
-                return this;
-            }
-
-            /** A boolean to decide if enable name characteristic as simulator characteristic. */
-            public Builder setEnableNameCharacteristic(boolean enable) {
-                this.mEnableNameCharacteristic = enable;
-                return this;
-            }
-
-            /** @see AdvertisingChangedCallback */
-            public Builder setAdvertisingChangedCallback(
-                    AdvertisingChangedCallback advertisingChangedCallback) {
-                this.mAdvertisingChangedCallback = advertisingChangedCallback;
-                return this;
-            }
-
-            public Builder setDataOnlyConnection(boolean dataOnlyConnection) {
-                this.mDataOnlyConnection = dataOnlyConnection;
-                return this;
-            }
-
-            /**
-             * Set whether to include the Transport Data descriptor, which has the list of supported
-             * profiles. This is required by the spec, but if we can't get it, we recover gracefully
-             * by assuming support for one of {A2DP, Headset}. Default is true.
-             */
-            public Builder setIncludeTransportDataDescriptor(
-                    boolean includeTransportDataDescriptor) {
-                this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor;
-                return this;
-            }
-
-            public Builder setAntiSpoofingPrivateKey(@Nullable byte[] antiSpoofingPrivateKey) {
-                this.mAntiSpoofingPrivateKey = antiSpoofingPrivateKey;
-                return this;
-            }
-
-            public Builder setUseRandomSaltForAccountKeyRotation(
-                    boolean useRandomSaltForAccountKeyRotation) {
-                this.mUseRandomSaltForAccountKeyRotation = useRandomSaltForAccountKeyRotation;
-                return this;
-            }
-
-            // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key.
-            public Builder setAdvertisingModelId(String modelId) {
-                this.mAdvertisingModelId = modelId;
-                return this;
-            }
-
-            public Builder setBecomeDiscoverable(boolean becomeDiscoverable) {
-                this.mBecomeDiscoverable = becomeDiscoverable;
-                return this;
-            }
-
-            public Builder setShowsPasskeyConfirmation(boolean showsPasskeyConfirmation) {
-                this.mShowsPasskeyConfirmation = showsPasskeyConfirmation;
-                return this;
-            }
-
-            public Builder setEnableBeaconActionsCharacteristic(
-                    boolean enableBeaconActionsCharacteristic) {
-                this.mEnableBeaconActionsCharacteristic = enableBeaconActionsCharacteristic;
-                return this;
-            }
-
-            public Builder setRemoveAllDevicesDuringPairing(boolean removeAllDevicesDuringPairing) {
-                this.mRemoveAllDevicesDuringPairing = removeAllDevicesDuringPairing;
-                return this;
-            }
-
-            /**
-             * Non-public because this is required to create a builder. See
-             * {@link Options#builder}.
-             */
-            public Builder setModelId(String modelId) {
-                this.mModelId = modelId;
-                return this;
-            }
-
-            public Builder setEddystoneIdentityKey(@Nullable ByteString eddystoneIdentityKey) {
-                this.mEddystoneIdentityKey = eddystoneIdentityKey;
-                return this;
-            }
-
-            // Custom builder in order to normalize properties. go/autovalue/builders-howto
-            public Options build() {
-                return new Options(
-                        Ascii.toUpperCase(mModelId),
-                        Ascii.toUpperCase(mAdvertisingModelId),
-                        mBluetoothAddress,
-                        mBleAddress,
-                        mDataOnlyConnection,
-                        mTxPowerLevel,
-                        mEnableNameCharacteristic,
-                        mAdvertisingChangedCallback,
-                        mIncludeTransportDataDescriptor,
-                        mAntiSpoofingPrivateKey,
-                        mUseRandomSaltForAccountKeyRotation,
-                        mBecomeDiscoverable,
-                        mShowsPasskeyConfirmation,
-                        mEnableBeaconActionsCharacteristic,
-                        mRemoveAllDevicesDuringPairing,
-                        mEddystoneIdentityKey);
-            }
-        }
-    }
-
-    public FastPairSimulator(Context context, Options options) {
-        this(context, options, new Logger(TAG));
-    }
-
-    public FastPairSimulator(Context context, Options options, Logger logger) {
-        this.mContext = context;
-        this.mOptions = options;
-        this.mLogger = logger;
-
-        this.mBatteryValues = new ArrayList<>();
-
-        String bluetoothAddress =
-                !TextUtils.isEmpty(options.getBluetoothAddress())
-                        ? options.getBluetoothAddress()
-                        : Settings.Secure.getString(context.getContentResolver(),
-                                "bluetooth_address");
-        if (bluetoothAddress == null && VERSION.SDK_INT >= VERSION_CODES.O) {
-            // Requires a modified Android O build for access to bluetoothAdapter.getAddress().
-            // See http://google3/java/com/google/location/nearby/apps/fastpair/simulator/README.md.
-            bluetoothAddress = mBluetoothAdapter.getAddress();
-        }
-        this.mBluetoothAddress =
-                new Value(BluetoothAddress.decode(bluetoothAddress), ByteOrder.BIG_ENDIAN);
-        this.mBleAddress = options.getBleAddress();
-        this.mAdvertiser = new OreoFastPairAdvertiser(this);
-
-        mFastPairSimulatorDatabase = new FastPairSimulatorDatabase(context);
-
-        byte[] deviceName = getDeviceNameInBytes();
-        mLogger.log(
-                "Provider default device name is %s",
-                deviceName != null ? new String(deviceName, StandardCharsets.UTF_8) : null);
-
-        if (mOptions.getDataOnlyConnection()) {
-            // To get BLE address, we need to start advertising first, and then
-            // {@code#setBleAddress} will be called with BLE address.
-            mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
-        } else {
-            // Make this so that the simulator doesn't start automatically.
-            // This is tricky since the simulator is used in our integ tests as well.
-            start(mBleAddress != null ? mBleAddress : bluetoothAddress);
-        }
-    }
-
-    public void start(String address) {
-        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
-        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
-
-        BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
-        BluetoothGattServerHelper bluetoothGattServerHelper =
-                new BluetoothGattServerHelper(mContext, wrap(bluetoothManager));
-        mBluetoothGattServerHelpers.put(address, bluetoothGattServerHelper);
-
-        if (mOptions.getBecomeDiscoverable()) {
-            try {
-                becomeDiscoverable();
-            } catch (InterruptedException | TimeoutException e) {
-                mLogger.log(e, "Error becoming discoverable");
-            }
-        }
-
-        mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
-        startGattServer(bluetoothGattServerHelper);
-        startRfcommServer();
-        scheduleAdvertisingRefresh();
-    }
-
-    /**
-     * Regenerate service data on a fixed interval.
-     * This causes the bloom filter to be refreshed and a different salt to be used for rotation.
-     */
-    @SuppressWarnings("FutureReturnValueIgnored")
-    private void scheduleAdvertisingRefresh() {
-        mExecutor.scheduleAtFixedRate(() -> {
-            if (mIsAdvertising) {
-                mAdvertiser.startAdvertising(getServiceData());
-            }
-        }, ADVERTISING_REFRESH_DELAY_1_MIN, ADVERTISING_REFRESH_DELAY_1_MIN, TimeUnit.MILLISECONDS);
-    }
-
-    public void destroy() {
-        try {
-            mLogger.log("Destroying simulator");
-            mIsDestroyed = true;
-            mContext.unregisterReceiver(mBroadcastReceiver);
-            mAdvertiser.stopAdvertising();
-            for (BluetoothGattServerHelper helper : mBluetoothGattServerHelpers.values()) {
-                helper.close();
-            }
-            stopRfcommServer();
-            mDeviceNameCallback = null;
-            mExecutor.shutdownNow();
-        } catch (IllegalArgumentException ignored) {
-            // Happens if you haven't given us permissions yet, so we didn't register the receiver.
-        }
-    }
-
-    public boolean isDestroyed() {
-        return mIsDestroyed;
-    }
-
-    @Nullable
-    public String getBluetoothAddress() {
-        return BluetoothAddress.encode(mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN));
-    }
-
-    public boolean isAdvertising() {
-        return mIsAdvertising;
-    }
-
-    public void setIsAdvertising(boolean isAdvertising) {
-        if (this.mIsAdvertising != isAdvertising) {
-            this.mIsAdvertising = isAdvertising;
-            mOptions.getAdvertisingChangedCallback().onAdvertisingChanged(isAdvertising);
-        }
-    }
-
-    public void stopAdvertising() {
-        mAdvertiser.stopAdvertising();
-    }
-
-    public void setBleAddress(String bleAddress) {
-        this.mBleAddress = bleAddress;
-        if (mOptions.getDataOnlyConnection()) {
-            mBluetoothAddress = new Value(BluetoothAddress.decode(bleAddress),
-                    ByteOrder.BIG_ENDIAN);
-            start(bleAddress);
-        }
-        // When BLE address changes, needs to send BLE address to the client again.
-        sendDeviceBleAddress(bleAddress);
-
-        // If we are advertising something other than the model id (e.g. the bloom filter), restart
-        // the advertisement so that it is updated with the new address.
-        if (isAdvertising() && !isDiscoverable()) {
-            mAdvertiser.startAdvertising(getServiceData());
-        }
-    }
-
-    @Nullable
-    public String getBleAddress() {
-        return mBleAddress;
-    }
-
-    // This method is only for testing to make test block until write name success or time out.
-    @VisibleForTesting
-    public void setCountDownLatchToWriteName(CountDownLatch countDownLatch) {
-        mLogger.log("Set up count down latch to write device name.");
-        mWriteNameCountDown = countDownLatch;
-    }
-
-    public boolean areBeaconActionsNotificationsEnabled() {
-        return mBeaconActionsServlet.areNotificationsEnabled();
-    }
-
-    private abstract class NotifiableGattServlet extends BluetoothGattServlet {
-        private final Map<BluetoothGattServerConnection, Notifier> mConnections = new HashMap<>();
-
-        abstract BluetoothGattCharacteristic getBaseCharacteristic();
-
-        @Override
-        public BluetoothGattCharacteristic getCharacteristic() {
-            // Enabling indication requires the Client Characteristic Configuration descriptor.
-            BluetoothGattCharacteristic characteristic = getBaseCharacteristic();
-            characteristic.addDescriptor(
-                    new BluetoothGattDescriptor(
-                            Constants.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID,
-                            BluetoothGattDescriptor.PERMISSION_READ
-                                    | BluetoothGattDescriptor.PERMISSION_WRITE));
-            return characteristic;
-        }
-
-        @Override
-        public void enableNotification(BluetoothGattServerConnection connection, Notifier notifier)
-                throws BluetoothGattException {
-            mLogger.log("Registering notifier for %s", getCharacteristic());
-            mConnections.put(connection, notifier);
-        }
-
-        @Override
-        public void disableNotification(BluetoothGattServerConnection connection, Notifier notifier)
-                throws BluetoothGattException {
-            mLogger.log("Removing notifier for %s", getCharacteristic());
-            mConnections.remove(connection);
-        }
-
-        boolean areNotificationsEnabled() {
-            return !mConnections.isEmpty();
-        }
-
-        void sendNotification(byte[] data) {
-            if (mConnections.isEmpty()) {
-                mLogger.log("Not sending notify as no notifier registered");
-                return;
-            }
-            // Needs to be on a separate thread to avoid deadlocking and timing out (waits for a
-            // callback from OS, which happens on the main thread).
-            mExecutor.execute(
-                    () -> {
-                        for (Map.Entry<BluetoothGattServerConnection, Notifier> entry :
-                                mConnections.entrySet()) {
-                            try {
-                                mLogger.log("Sending notify %s to %s",
-                                        getCharacteristic(),
-                                        entry.getKey().getDevice().getAddress());
-                                entry.getValue().notify(data);
-                            } catch (BluetoothException e) {
-                                mLogger.log(
-                                        e,
-                                        "Failed to notify (indicate) result of %s to %s",
-                                        getCharacteristic(),
-                                        entry.getKey().getDevice().getAddress());
-                            }
-                        }
-                    });
-        }
-    }
-
-    private void startRfcommServer() {
-        mRfcommServer.setRequestHandler(this::handleRfcommServerRequest);
-        mRfcommServer.setStateMonitor(state -> {
-            mLogger.log("RfcommServer is in %s state", state);
-            if (CONNECTED.equals(state)) {
-                sendModelId();
-                sendDeviceBleAddress(mBleAddress);
-                sendFirmwareVersion();
-                sendSessionNonce();
-            }
-        });
-        mRfcommServer.start();
-    }
-
-    private void handleRfcommServerRequest(int eventGroup, int eventCode, byte[] data) {
-        switch (eventGroup) {
-            case EventGroup.DEVICE_VALUE:
-                if (data == null) {
-                    break;
-                }
-
-                String deviceValue = base16().encode(data);
-                if (eventCode == DeviceEventCode.DEVICE_CAPABILITY_VALUE) {
-                    mLogger.log("Received phone capability: %s", deviceValue);
-                } else if (eventCode == DeviceEventCode.PLATFORM_TYPE_VALUE) {
-                    mLogger.log("Received platform type: %s", deviceValue);
-                }
-                break;
-            case EventGroup.DEVICE_ACTION_VALUE:
-                if (eventCode == DeviceActionEventCode.DEVICE_ACTION_RING_VALUE) {
-                    mLogger.log("receive device action with ring value, data = %d",
-                            data[0]);
-                    sendDeviceRingActionResponse();
-                    // Simulate notifying the seeker that the ringing has stopped due
-                    // to user interaction (such as tapping the bud).
-                    mUiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction,
-                            5000);
-                }
-                break;
-            case EventGroup.DEVICE_CONFIGURATION_VALUE:
-                if (eventCode == DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE) {
-                    mLogger.log(
-                            "receive device action with buffer size value, data = %s",
-                            base16().encode(data));
-                    sendSetBufferActionResponse(data);
-                }
-                break;
-            case EventGroup.DEVICE_CAPABILITY_SYNC_VALUE:
-                if (eventCode == DeviceCapabilitySyncEventCode.REQUEST_CAPABILITY_UPDATE_VALUE) {
-                    mLogger.log("receive device capability update request.");
-                    sendCapabilitySync();
-                }
-                break;
-            default: // fall out
-                break;
-        }
-    }
-
-    private void stopRfcommServer() {
-        mRfcommServer.stop();
-        mRfcommServer.setRequestHandler(null);
-        mRfcommServer.setStateMonitor(null);
-    }
-
-    private void sendModelId() {
-        mLogger.log("Send model ID to the client");
-        mRfcommServer.send(
-                EventGroup.DEVICE_VALUE,
-                DeviceEventCode.DEVICE_MODEL_ID_VALUE,
-                modelIdServiceData(/* forAdvertising= */ false));
-    }
-
-    private void sendDeviceBleAddress(String bleAddress) {
-        mLogger.log("Send BLE address (%s) to the client", bleAddress);
-        if (bleAddress != null) {
-            mRfcommServer.send(
-                    EventGroup.DEVICE_VALUE,
-                    DeviceEventCode.DEVICE_BLE_ADDRESS_VALUE,
-                    BluetoothAddress.decode(bleAddress));
-        }
-    }
-
-    private void sendFirmwareVersion() {
-        mLogger.log("Send Firmware Version (%s) to the client", mDeviceFirmwareVersion);
-        mRfcommServer.send(
-                EventGroup.DEVICE_VALUE,
-                DeviceEventCode.FIRMWARE_VERSION_VALUE,
-                mDeviceFirmwareVersion.getBytes());
-    }
-
-    private void sendSessionNonce() {
-        mLogger.log("Send SessionNonce (%s) to the client", mDeviceFirmwareVersion);
-        SecureRandom secureRandom = new SecureRandom();
-        mSessionNonce = new byte[SECTION_NONCE_LENGTH];
-        secureRandom.nextBytes(mSessionNonce);
-        mRfcommServer.send(
-                EventGroup.DEVICE_VALUE, DeviceEventCode.SECTION_NONCE_VALUE, mSessionNonce);
-    }
-
-    private void sendDeviceRingActionResponse() {
-        mLogger.log("Send device ring action response to the client");
-        mRfcommServer.send(
-                EventGroup.ACKNOWLEDGEMENT_VALUE,
-                AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
-                new byte[]{
-                        EventGroup.DEVICE_ACTION_VALUE,
-                        DeviceActionEventCode.DEVICE_ACTION_RING_VALUE
-                });
-    }
-
-    private void sendSetBufferActionResponse(byte[] data) {
-        boolean hmacPassed = false;
-        for (ByteString accountKey : getAccountKeys()) {
-            try {
-                if (MessageStreamHmacEncoder.verifyHmac(
-                        accountKey.toByteArray(), mSessionNonce, data)) {
-                    hmacPassed = true;
-                    mLogger.log("Buffer size data matches account key %s",
-                            base16().encode(accountKey.toByteArray()));
-                    break;
-                }
-            } catch (GeneralSecurityException e) {
-                // Ignore.
-            }
-        }
-        if (hmacPassed) {
-            mLogger.log("Send buffer size action response %s to the client", base16().encode(data));
-            mRfcommServer.send(
-                    EventGroup.ACKNOWLEDGEMENT_VALUE,
-                    AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
-                    new byte[]{
-                            EventGroup.DEVICE_CONFIGURATION_VALUE,
-                            DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE,
-                            data[0],
-                            data[1],
-                            data[2]
-                    });
-        } else {
-            mLogger.log("No matched account key for sendSetBufferActionResponse");
-        }
-    }
-
-    private void sendCapabilitySync() {
-        mLogger.log("Send capability sync to the client");
-        if (mSupportDynamicBufferSize) {
-            mLogger.log("Send dynamic buffer size range to the client");
-            mRfcommServer.send(
-                    EventGroup.DEVICE_CAPABILITY_SYNC_VALUE,
-                    DeviceCapabilitySyncEventCode.CONFIGURABLE_BUFFER_SIZE_RANGE_VALUE,
-                    new byte[]{
-                            0x00, 0x01, (byte) 0xf4, 0x00, 0x64, 0x00, (byte) 0xc8,
-                            0x01, 0x00, (byte) 0xff, 0x00, 0x01, 0x00, (byte) 0x88,
-                            0x02, 0x01, (byte) 0xff, 0x01, 0x01, 0x01, (byte) 0x88,
-                            0x03, 0x02, (byte) 0xff, 0x02, 0x01, 0x02, (byte) 0x88,
-                            0x04, 0x03, (byte) 0xff, 0x03, 0x01, 0x03, (byte) 0x88
-                    });
-        }
-    }
-
-    private void sendDeviceRingStoppedAction() {
-        mLogger.log("Sending device ring stopped action to the client");
-        mRfcommServer.send(
-                EventGroup.DEVICE_ACTION_VALUE,
-                DeviceActionEventCode.DEVICE_ACTION_RING_VALUE,
-                // Additional data for stopping ringing on all components.
-                new byte[]{0x00});
-    }
-
-    private void startGattServer(BluetoothGattServerHelper helper) {
-        BluetoothGattServlet tdsControlPointServlet =
-                new NotifiableGattServlet() {
-                    @Override
-                    public BluetoothGattCharacteristic getBaseCharacteristic() {
-                        return new BluetoothGattCharacteristic(ControlPointCharacteristic.ID,
-                                PROPERTY_WRITE | PROPERTY_INDICATE, PERMISSION_WRITE);
-                    }
-
-                    @Override
-                    public void write(
-                            BluetoothGattServerConnection connection, int offset, byte[] value)
-                            throws BluetoothGattException {
-                        mLogger.log("Requested TDS Control Point write, value=%s",
-                                base16().encode(value));
-
-                        ResultCode resultCode = checkTdsControlPointRequest(value);
-                        if (resultCode == ResultCode.SUCCESS) {
-                            try {
-                                becomeDiscoverable();
-                            } catch (TimeoutException | InterruptedException e) {
-                                mLogger.log(e, "Failed to become discoverable");
-                                resultCode = ResultCode.OPERATION_FAILED;
-                            }
-                        }
-
-                        mLogger.log("Request complete, resultCode=%s", resultCode);
-
-                        mLogger.log("Sending TDS Control Point response indication");
-                        sendNotification(
-                                Bytes.concat(
-                                        new byte[]{
-                                                getTdsControlPointOpCode(value),
-                                                resultCode.mByteValue,
-                                        },
-                                        resultCode == ResultCode.SUCCESS
-                                                ? TDS_CONTROL_POINT_RESPONSE_PARAMETER
-                                                : new byte[0]));
-                    }
-                };
-
-        BluetoothGattServlet brHandoverDataServlet =
-                new BluetoothGattServlet() {
-
-                    @Override
-                    public BluetoothGattCharacteristic getCharacteristic() {
-                        return new BluetoothGattCharacteristic(BrHandoverDataCharacteristic.ID,
-                                PROPERTY_READ, PERMISSION_READ);
-                    }
-
-                    @Override
-                    public byte[] read(BluetoothGattServerConnection connection, int offset) {
-                        return Bytes.concat(
-                                new byte[]{BrHandoverDataCharacteristic.BR_EDR_FEATURES},
-                                mBluetoothAddress.getBytes(ByteOrder.LITTLE_ENDIAN),
-                                CLASS_OF_DEVICE.getBytes(ByteOrder.LITTLE_ENDIAN));
-                    }
-                };
-
-        BluetoothGattServlet bluetoothSigServlet =
-                new BluetoothGattServlet() {
-
-                    @Override
-                    public BluetoothGattCharacteristic getCharacteristic() {
-                        BluetoothGattCharacteristic characteristic =
-                                new BluetoothGattCharacteristic(
-                                        TransportDiscoveryService.BluetoothSigDataCharacteristic.ID,
-                                        0 /* no properties */,
-                                        0 /* no permissions */);
-
-                        if (mOptions.getIncludeTransportDataDescriptor()) {
-                            characteristic.addDescriptor(
-                                    new BluetoothGattDescriptor(
-                                            TransportDiscoveryService.BluetoothSigDataCharacteristic
-                                                    .BrTransportBlockDataDescriptor.ID,
-                                            BluetoothGattDescriptor.PERMISSION_READ));
-                        }
-                        return characteristic;
-                    }
-
-                    @Override
-                    public byte[] readDescriptor(
-                            BluetoothGattServerConnection connection,
-                            BluetoothGattDescriptor descriptor,
-                            int offset)
-                            throws BluetoothGattException {
-                        return transportDiscoveryData();
-                    }
-                };
-
-        BluetoothGattServlet accountKeyServlet =
-                new BluetoothGattServlet() {
-                    @Override
-                    // Simulating deprecated API {@code AccountKeyCharacteristic.ID} for testing.
-                    @SuppressWarnings("deprecation")
-                    public BluetoothGattCharacteristic getCharacteristic() {
-                        return new BluetoothGattCharacteristic(
-                                AccountKeyCharacteristic.CUSTOM_128_BIT_UUID,
-                                PROPERTY_WRITE,
-                                PERMISSION_WRITE);
-                    }
-
-                    @Override
-                    public void write(
-                            BluetoothGattServerConnection connection, int offset, byte[] value) {
-                        mLogger.log("Got value from account key servlet: %s",
-                                base16().encode(value));
-                        try {
-                            addAccountKey(AesEcbSingleBlockEncryption.decrypt(mSecret, value),
-                                    mPairingDevice);
-                        } catch (GeneralSecurityException e) {
-                            mLogger.log(e, "Failed to decrypt account key.");
-                        }
-                        mUiThreadHandler.post(
-                                () -> mAdvertiser.startAdvertising(accountKeysServiceData()));
-                    }
-                };
-
-        BluetoothGattServlet firmwareVersionServlet =
-                new BluetoothGattServlet() {
-                    @Override
-                    public BluetoothGattCharacteristic getCharacteristic() {
-                        return new BluetoothGattCharacteristic(
-                                FirmwareVersionCharacteristic.ID, PROPERTY_READ, PERMISSION_READ);
-                    }
-
-                    @Override
-                    public byte[] read(BluetoothGattServerConnection connection, int offset) {
-                        return mDeviceFirmwareVersion.getBytes();
-                    }
-                };
-
-        BluetoothGattServlet keyBasedPairingServlet =
-                new NotifiableGattServlet() {
-                    @Override
-                    // Simulating deprecated API {@code KeyBasedPairingCharacteristic.ID} for
-                    // testing.
-                    @SuppressWarnings("deprecation")
-                    public BluetoothGattCharacteristic getBaseCharacteristic() {
-                        return new BluetoothGattCharacteristic(
-                                KeyBasedPairingCharacteristic.CUSTOM_128_BIT_UUID,
-                                PROPERTY_WRITE | PROPERTY_INDICATE,
-                                PERMISSION_WRITE);
-                    }
-
-                    @Override
-                    public void write(
-                            BluetoothGattServerConnection connection, int offset, byte[] value) {
-                        mLogger.log("Requesting key based pairing handshake, value=%s",
-                                base16().encode(value));
-
-                        mSecret = null;
-                        byte[] seekerPublicAddress = null;
-                        if (value.length == AES_BLOCK_LENGTH) {
-
-                            for (ByteString key : getAccountKeys()) {
-                                byte[] candidateSecret = key.toByteArray();
-                                try {
-                                    seekerPublicAddress = handshake(candidateSecret, value);
-                                    mSecret = candidateSecret;
-                                    mIsSubsequentPair = true;
-                                    break;
-                                } catch (GeneralSecurityException e) {
-                                    mLogger.log(e, "Failed to decrypt with %s",
-                                            base16().encode(candidateSecret));
-                                }
-                            }
-                        } else if (value.length == AES_BLOCK_LENGTH + PUBLIC_KEY_LENGTH
-                                && mOptions.getAntiSpoofingPrivateKey() != null) {
-                            try {
-                                byte[] encryptedRequest = Arrays.copyOf(value, AES_BLOCK_LENGTH);
-                                byte[] receivedPublicKey =
-                                        Arrays.copyOfRange(value, AES_BLOCK_LENGTH, value.length);
-                                byte[] candidateSecret =
-                                        EllipticCurveDiffieHellmanExchange.create(
-                                                        mOptions.getAntiSpoofingPrivateKey())
-                                                .generateSecret(receivedPublicKey);
-                                seekerPublicAddress = handshake(candidateSecret, encryptedRequest);
-                                mSecret = candidateSecret;
-                            } catch (Exception e) {
-                                mLogger.log(
-                                        e,
-                                        "Failed to decrypt with anti-spoofing private key %s",
-                                        base16().encode(mOptions.getAntiSpoofingPrivateKey()));
-                            }
-                        } else {
-                            mLogger.log("Packet length invalid, %d", value.length);
-                            return;
-                        }
-
-                        if (mSecret == null) {
-                            mLogger.log("Couldn't find a usable key to decrypt with.");
-                            return;
-                        }
-
-                        mLogger.log("Found valid decryption key, %s", base16().encode(mSecret));
-                        byte[] salt = new byte[9];
-                        new Random().nextBytes(salt);
-                        try {
-                            byte[] data = concat(
-                                    new byte[]{KeyBasedPairingCharacteristic.Response.TYPE},
-                                    mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN), salt);
-                            byte[] encryptedAddress = encrypt(mSecret, data);
-                            mLogger.log(
-                                    "Sending handshake response %s with size %d",
-                                    base16().encode(encryptedAddress), encryptedAddress.length);
-                            sendNotification(encryptedAddress);
-
-                            // Notify seeker for NameCharacteristic to get provider device name
-                            // when seeker request device name flag is true.
-                            if (mOptions.getEnableNameCharacteristic()
-                                    && mHandshakeRequest.requestDeviceName()) {
-                                byte[] encryptedResponse =
-                                        getDeviceNameInBytes() != null ? createEncryptedDeviceName()
-                                                : new byte[0];
-                                mLogger.log(
-                                        "Sending device name response %s with size %d",
-                                        base16().encode(encryptedResponse),
-                                        encryptedResponse.length);
-                                mDeviceNameServlet.sendNotification(encryptedResponse);
-                            }
-
-                            // Disconnects the current connection to allow the following pairing
-                            // request. Needs to be on a separate thread to avoid deadlocking and
-                            // timing out (waits for a callback from OS, which happens on this
-                            // thread).
-                            //
-                            // Note: The spec does not require you to disconnect from other
-                            // devices at this point.
-                            // If headphones support multiple simultaneous connections, they
-                            // should stay connected. But Android fails to pair with the new
-                            // device if we don't first disconnect from any other device.
-                            mLogger.log("Skip remove bond, value=%s",
-                                    mOptions.getRemoveAllDevicesDuringPairing());
-                            if (mOptions.getRemoveAllDevicesDuringPairing()
-                                    && mHandshakeRequest.getType()
-                                    == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
-                                    && !mHandshakeRequest.requestRetroactivePair()) {
-                                mExecutor.execute(() -> disconnectAllBondedDevices());
-                            }
-
-                            if (mHandshakeRequest.getType()
-                                    == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
-                                    && mHandshakeRequest.requestProviderInitialBonding()) {
-                                // Run on executor to ensure it doesn't happen until after the
-                                // notify (which tells the remote device what address to expect).
-                                String seekerPublicAddressString =
-                                        BluetoothAddress.encode(seekerPublicAddress);
-                                mExecutor.execute(() -> {
-                                    mLogger.log("Sending pairing request to %s",
-                                            seekerPublicAddressString);
-                                    mBluetoothAdapter.getRemoteDevice(
-                                            seekerPublicAddressString).createBond();
-                                });
-                            }
-                        } catch (GeneralSecurityException e) {
-                            mLogger.log(e, "Failed to notify of static mac address");
-                        }
-                    }
-
-                    @Nullable
-                    private byte[] handshake(byte[] key, byte[] encryptedPairingRequest)
-                            throws GeneralSecurityException {
-                        mHandshakeRequest = new HandshakeRequest(key, encryptedPairingRequest);
-
-                        byte[] decryptedAddress = mHandshakeRequest.getVerificationData();
-                        if (mBleAddress != null
-                                && Arrays.equals(decryptedAddress,
-                                BluetoothAddress.decode(mBleAddress))
-                                || Arrays.equals(decryptedAddress,
-                                mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN))) {
-                            mLogger.log("Address matches: %s", base16().encode(decryptedAddress));
-                        } else {
-                            throw new GeneralSecurityException(
-                                    "Address (BLE or BR/EDR) is not correct: "
-                                            + base16().encode(decryptedAddress)
-                                            + ", "
-                                            + mBleAddress
-                                            + ", "
-                                            + getBluetoothAddress());
-                        }
-
-                        switch (mHandshakeRequest.getType()) {
-                            case KEY_BASED_PAIRING_REQUEST:
-                                return handleKeyBasedPairingRequest(mHandshakeRequest);
-                            case ACTION_OVER_BLE:
-                                return handleActionOverBleRequest(mHandshakeRequest);
-                            case UNKNOWN:
-                                // continue to throw the exception;
-                        }
-                        throw new GeneralSecurityException(
-                                "Type is not correct: " + mHandshakeRequest.getType());
-                    }
-
-                    @Nullable
-                    private byte[] handleKeyBasedPairingRequest(HandshakeRequest handshakeRequest)
-                            throws GeneralSecurityException {
-                        if (handshakeRequest.requestDiscoverable()) {
-                            mLogger.log("Requested discoverability");
-                            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
-                        }
-
-                        mLogger.log(
-                                "KeyBasedPairing: initialBonding=%s, requestDeviceName=%s, "
-                                        + "retroactivePair=%s",
-                                handshakeRequest.requestProviderInitialBonding(),
-                                handshakeRequest.requestDeviceName(),
-                                handshakeRequest.requestRetroactivePair());
-
-                        byte[] seekerPublicAddress = null;
-                        if (handshakeRequest.requestProviderInitialBonding()
-                                || handshakeRequest.requestRetroactivePair()) {
-                            seekerPublicAddress = handshakeRequest.getSeekerPublicAddress();
-                            mLogger.log(
-                                    "Seeker sends BR/EDR address %s to provider",
-                                    BluetoothAddress.encode(seekerPublicAddress));
-                        }
-
-                        if (handshakeRequest.requestRetroactivePair()) {
-                            if (mBluetoothAdapter.getRemoteDevice(
-                                    seekerPublicAddress).getBondState()
-                                    != BluetoothDevice.BOND_BONDED) {
-                                throw new GeneralSecurityException(
-                                        "Address (BR/EDR) is not bonded: "
-                                                + BluetoothAddress.encode(seekerPublicAddress));
-                            }
-                        }
-
-                        return seekerPublicAddress;
-                    }
-
-                    @Nullable
-                    private byte[] handleActionOverBleRequest(HandshakeRequest handshakeRequest) {
-                        // TODO(wollohchou): implement action over ble request.
-                        if (handshakeRequest.requestDeviceAction()) {
-                            mLogger.log("Requesting action over BLE, device action");
-                        } else if (handshakeRequest.requestFollowedByAdditionalData()) {
-                            mLogger.log(
-                                    "Requesting action over BLE, followed by additional data, "
-                                            + "type:%s",
-                                    handshakeRequest.getAdditionalDataType());
-                        } else {
-                            mLogger.log("Requesting action over BLE");
-                        }
-                        return null;
-                    }
-
-                    /**
-                     * @return The encrypted device name from provider for seeker to use.
-                     */
-                    private byte[] createEncryptedDeviceName() throws GeneralSecurityException {
-                        byte[] deviceName = getDeviceNameInBytes();
-                        String providerName = new String(deviceName, StandardCharsets.UTF_8);
-                        mLogger.log(
-                                "Sending handshake response for device name %s with size %d",
-                                providerName, deviceName.length);
-                        return NamingEncoder.encodeNamingPacket(mSecret, providerName);
-                    }
-                };
-
-        mBeaconActionsServlet =
-                new NotifiableGattServlet() {
-                    private static final int GATT_ERROR_UNAUTHENTICATED = 0x80;
-                    private static final int GATT_ERROR_INVALID_VALUE = 0x81;
-                    private static final int NONCE_LENGTH = 8;
-                    private static final int ONE_TIME_AUTH_KEY_OFFSET = 2;
-                    private static final int ONE_TIME_AUTH_KEY_LENGTH = 8;
-                    private static final int IDENTITY_KEY_LENGTH = 32;
-                    private static final byte TRANSMISSION_POWER = 0;
-
-                    private final SecureRandom mRandom = new SecureRandom();
-                    private final MessageDigest mSha256;
-                    @Nullable
-                    private byte[] mLastNonce;
-                    @Nullable
-                    private ByteString mIdentityKey = mOptions.getEddystoneIdentityKey();
-
-                    {
-                        try {
-                            mSha256 = MessageDigest.getInstance("SHA-256");
-                            mSha256.reset();
-                        } catch (NoSuchAlgorithmException e) {
-                            throw new IllegalStateException(
-                                    "System missing SHA-256 implementation.", e);
-                        }
-                    }
-
-                    @Override
-                    // Simulating deprecated API {@code BeaconActionsCharacteristic.ID} for testing.
-                    @SuppressWarnings("deprecation")
-                    public BluetoothGattCharacteristic getBaseCharacteristic() {
-                        return new BluetoothGattCharacteristic(
-                                BeaconActionsCharacteristic.CUSTOM_128_BIT_UUID,
-                                PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY,
-                                PERMISSION_READ | PERMISSION_WRITE);
-                    }
-
-                    @Override
-                    public byte[] read(BluetoothGattServerConnection connection, int offset) {
-                        mLastNonce = new byte[NONCE_LENGTH];
-                        mRandom.nextBytes(mLastNonce);
-                        return mLastNonce;
-                    }
-
-                    @Override
-                    public void write(
-                            BluetoothGattServerConnection connection, int offset, byte[] value)
-                            throws BluetoothGattException {
-                        mLogger.log("Got value from beacon actions servlet: %s",
-                                base16().encode(value));
-                        if (value.length == 0) {
-                            mLogger.log("Packet length invalid, %d", value.length);
-                            throw new BluetoothGattException("Packet length invalid",
-                                    GATT_ERROR_INVALID_VALUE);
-                        }
-                        switch (value[0]) {
-                            case BeaconActionType.READ_BEACON_PARAMETERS:
-                                handleReadBeaconParameters(value);
-                                break;
-                            case BeaconActionType.READ_PROVISIONING_STATE:
-                                handleReadProvisioningState(value);
-                                break;
-                            case BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY:
-                                handleSetEphemeralIdentityKey(value);
-                                break;
-                            case BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY:
-                            case BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY:
-                            case BeaconActionType.RING:
-                            case BeaconActionType.READ_RINGING_STATE:
-                                throw new BluetoothGattException(
-                                        "Unimplemented beacon action",
-                                        BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-                            default:
-                                throw new BluetoothGattException(
-                                        "Unknown beacon action",
-                                        BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-                        }
-                    }
-
-                    private boolean verifyAccountKeyToken(byte[] value, boolean ownerOnly)
-                            throws BluetoothGattException {
-                        if (value.length < ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET) {
-                            mLogger.log("Packet length invalid, %d", value.length);
-                            throw new BluetoothGattException(
-                                    "Packet length invalid", GATT_ERROR_INVALID_VALUE);
-                        }
-                        byte[] hashedAccountKey =
-                                Arrays.copyOfRange(
-                                        value,
-                                        ONE_TIME_AUTH_KEY_OFFSET,
-                                        ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET);
-                        if (mLastNonce == null) {
-                            throw new BluetoothGattException(
-                                    "Nonce wasn't set", GATT_ERROR_UNAUTHENTICATED);
-                        }
-                        if (ownerOnly) {
-                            ByteString accountKey = getOwnerAccountKey();
-                            if (accountKey != null) {
-                                mSha256.update(accountKey.toByteArray());
-                                mSha256.update(mLastNonce);
-                                return Arrays.equals(
-                                        hashedAccountKey,
-                                        Arrays.copyOf(mSha256.digest(), ONE_TIME_AUTH_KEY_LENGTH));
-                            }
-                        } else {
-                            Set<ByteString> accountKeys = getAccountKeys();
-                            for (ByteString accountKey : accountKeys) {
-                                mSha256.update(accountKey.toByteArray());
-                                mSha256.update(mLastNonce);
-                                if (Arrays.equals(
-                                        hashedAccountKey,
-                                        Arrays.copyOf(mSha256.digest(),
-                                                ONE_TIME_AUTH_KEY_LENGTH))) {
-                                    return true;
-                                }
-                            }
-                        }
-                        return false;
-                    }
-
-                    private int getBeaconClock() {
-                        return (int) TimeUnit.MILLISECONDS.toSeconds(SystemClock.elapsedRealtime());
-                    }
-
-                    private ByteString fromBytes(byte... bytes) {
-                        return ByteString.copyFrom(bytes);
-                    }
-
-                    private byte[] intToByteArray(int value) {
-                        byte[] data = new byte[4];
-                        data[3] = (byte) value;
-                        data[2] = (byte) (value >>> 8);
-                        data[1] = (byte) (value >>> 16);
-                        data[0] = (byte) (value >>> 24);
-                        return data;
-                    }
-
-                    private void handleReadBeaconParameters(byte[] value)
-                            throws BluetoothGattException {
-                        if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) {
-                            throw new BluetoothGattException(
-                                    "failed to authenticate account key",
-                                    GATT_ERROR_UNAUTHENTICATED);
-                        }
-                        sendNotification(
-                                fromBytes(
-                                        (byte) BeaconActionType.READ_BEACON_PARAMETERS,
-                                        (byte) 5 /* data length */,
-                                        TRANSMISSION_POWER)
-                                        .concat(ByteString.copyFrom(
-                                                intToByteArray(getBeaconClock())))
-                                        .toByteArray());
-                    }
-
-                    private void handleReadProvisioningState(byte[] value)
-                            throws BluetoothGattException {
-                        if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) {
-                            throw new BluetoothGattException(
-                                    "failed to authenticate account key",
-                                    GATT_ERROR_UNAUTHENTICATED);
-                        }
-                        byte flags = 0;
-                        if (verifyAccountKeyToken(value, /* ownerOnly= */ true)) {
-                            flags |= (byte) (1 << 1);
-                        }
-                        if (mIdentityKey == null) {
-                            sendNotification(
-                                    fromBytes(
-                                            (byte) BeaconActionType.READ_PROVISIONING_STATE,
-                                            (byte) 1 /* data length */,
-                                            flags)
-                                            .toByteArray());
-                        } else {
-                            flags |= (byte) 1;
-                            sendNotification(
-                                    fromBytes(
-                                            (byte) BeaconActionType.READ_PROVISIONING_STATE,
-                                            (byte) 21 /* data length */,
-                                            flags)
-                                            .concat(
-                                                    E2eeCalculator.computeE2eeEid(
-                                                            mIdentityKey, /* exponent= */ 10,
-                                                            getBeaconClock()))
-                                            .toByteArray());
-                        }
-                    }
-
-                    private void handleSetEphemeralIdentityKey(byte[] value)
-                            throws BluetoothGattException {
-                        if (!verifyAccountKeyToken(value, /* ownerOnly= */ true)) {
-                            throw new BluetoothGattException(
-                                    "failed to authenticate owner account key",
-                                    GATT_ERROR_UNAUTHENTICATED);
-                        }
-                        if (value.length
-                                != ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET
-                                + IDENTITY_KEY_LENGTH) {
-                            mLogger.log("Packet length invalid, %d", value.length);
-                            throw new BluetoothGattException("Packet length invalid",
-                                    GATT_ERROR_INVALID_VALUE);
-                        }
-                        if (mIdentityKey != null) {
-                            throw new BluetoothGattException(
-                                    "Device is already provisioned as Eddystone",
-                                    GATT_ERROR_UNAUTHENTICATED);
-                        }
-                        mIdentityKey = Crypto.aesEcbNoPaddingDecrypt(
-                                ByteString.copyFrom(mOwnerAccountKey),
-                                ByteString.copyFrom(value)
-                                        .substring(ONE_TIME_AUTH_KEY_LENGTH
-                                                + ONE_TIME_AUTH_KEY_OFFSET));
-                    }
-                };
-
-        ServiceConfig fastPairServiceConfig =
-                new ServiceConfig()
-                        .addCharacteristic(accountKeyServlet)
-                        .addCharacteristic(keyBasedPairingServlet)
-                        .addCharacteristic(mPasskeyServlet)
-                        .addCharacteristic(firmwareVersionServlet);
-        if (mOptions.getEnableBeaconActionsCharacteristic()) {
-            fastPairServiceConfig.addCharacteristic(mBeaconActionsServlet);
-        }
-
-        BluetoothGattServerConfig config =
-                new BluetoothGattServerConfig()
-                        .addService(
-                                TransportDiscoveryService.ID,
-                                new ServiceConfig()
-                                        .addCharacteristic(tdsControlPointServlet)
-                                        .addCharacteristic(brHandoverDataServlet)
-                                        .addCharacteristic(bluetoothSigServlet))
-                        .addService(
-                                FastPairService.ID,
-                                mOptions.getEnableNameCharacteristic()
-                                        ? fastPairServiceConfig.addCharacteristic(
-                                        mDeviceNameServlet)
-                                        : fastPairServiceConfig);
-
-        mLogger.log(
-                "Starting GATT server, support name characteristic %b",
-                mOptions.getEnableNameCharacteristic());
-        try {
-            helper.open(config);
-        } catch (BluetoothException e) {
-            mLogger.log(e, "Error starting GATT server");
-        }
-    }
-
-    /** Callback for passkey/pin input. */
-    public interface KeyInputCallback {
-        void onKeyInput(int key);
-    }
-
-    public void enterPassKey(int passkey) {
-        mLogger.log("enterPassKey called with passkey %d.", passkey);
-        mPairingDevice.setPairingConfirmation(true);
-    }
-
-    private void checkPasskey() {
-        // There's a race between the PAIRING_REQUEST broadcast from the OS giving us the local
-        // passkey, and the remote passkey received over GATT. Skip the check until we have both.
-        if (mLocalPasskey == 0 || mRemotePasskey == 0) {
-            mLogger.log(
-                    "Skipping passkey check, missing local (%s) or remote (%s).",
-                    mLocalPasskey, mRemotePasskey);
-            return;
-        }
-
-        // Regardless of whether it matches, send our (encrypted) passkey to the seeker.
-        sendPasskeyToRemoteDevice(mLocalPasskey);
-
-        mLogger.log("Checking localPasskey %s == remotePasskey %s", mLocalPasskey, mRemotePasskey);
-        boolean passkeysMatched = mLocalPasskey == mRemotePasskey;
-        if (mOptions.getShowsPasskeyConfirmation() && passkeysMatched
-                && mPasskeyEventCallback != null) {
-            mLogger.log("callbacks the UI for passkey confirmation.");
-            mPasskeyEventCallback.onPasskeyConfirmation(mLocalPasskey,
-                    this::setPasskeyConfirmation);
-        } else {
-            setPasskeyConfirmation(passkeysMatched);
-        }
-    }
-
-    private void sendPasskeyToRemoteDevice(int passkey) {
-        try {
-            mPasskeyServlet.sendNotification(
-                    PasskeyCharacteristic.encrypt(
-                            PasskeyCharacteristic.Type.PROVIDER, mSecret, passkey));
-        } catch (GeneralSecurityException e) {
-            mLogger.log(e, "Failed to encrypt passkey response.");
-        }
-    }
-
-    public void setFirmwareVersion(String versionNumber) {
-        mDeviceFirmwareVersion = versionNumber;
-    }
-
-    public void setDynamicBufferSize(boolean support) {
-        if (mSupportDynamicBufferSize != support) {
-            mSupportDynamicBufferSize = support;
-            sendCapabilitySync();
-        }
-    }
-
-    @VisibleForTesting
-    void setPasskeyConfirmationCallback(PasskeyConfirmationCallback callback) {
-        this.mPasskeyConfirmationCallback = callback;
-    }
-
-    public void setDeviceNameCallback(DeviceNameCallback callback) {
-        this.mDeviceNameCallback = callback;
-    }
-
-    public void setPasskeyEventCallback(PasskeyEventCallback passkeyEventCallback) {
-        this.mPasskeyEventCallback = passkeyEventCallback;
-    }
-
-    private void setPasskeyConfirmation(boolean confirm) {
-        mPairingDevice.setPairingConfirmation(confirm);
-        if (mPasskeyConfirmationCallback != null) {
-            mPasskeyConfirmationCallback.onPasskeyConfirmation(confirm);
-        }
-        mLocalPasskey = 0;
-        mRemotePasskey = 0;
-    }
-
-    private void becomeDiscoverable() throws InterruptedException, TimeoutException {
-        setDiscoverable(true);
-    }
-
-    public void cancelDiscovery() throws InterruptedException, TimeoutException {
-        setDiscoverable(false);
-    }
-
-    private void setDiscoverable(boolean discoverable)
-            throws InterruptedException, TimeoutException {
-        mIsDiscoverableLatch = new CountDownLatch(1);
-        setScanMode(discoverable ? SCAN_MODE_CONNECTABLE_DISCOVERABLE : SCAN_MODE_CONNECTABLE);
-        // If we're already discoverable, count down the latch right away. Otherwise,
-        // we'll get a broadcast when we successfully become discoverable.
-        if (isDiscoverable()) {
-            mIsDiscoverableLatch.countDown();
-        }
-        if (mIsDiscoverableLatch.await(BECOME_DISCOVERABLE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
-            mLogger.log("Successfully became switched discoverable mode %s", discoverable);
-        } else {
-            throw new TimeoutException();
-        }
-    }
-
-    private void setScanMode(int scanMode) {
-        if (mRevertDiscoverableFuture != null) {
-            mRevertDiscoverableFuture.cancel(false /* may interrupt if running */);
-        }
-
-        mLogger.log("Setting scan mode to %s", scanModeToString(scanMode));
-        try {
-            Method method = mBluetoothAdapter.getClass().getMethod("setScanMode", Integer.TYPE);
-            method.invoke(mBluetoothAdapter, scanMode);
-
-            if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-                mRevertDiscoverableFuture =
-                        mExecutor.schedule(() -> setScanMode(SCAN_MODE_CONNECTABLE),
-                                SCAN_MODE_REFRESH_SEC, TimeUnit.SECONDS);
-            }
-        } catch (Exception e) {
-            mLogger.log(e, "Error setting scan mode to %d", scanMode);
-        }
-    }
-
-    public static String scanModeToString(int scanMode) {
-        switch (scanMode) {
-            case SCAN_MODE_CONNECTABLE_DISCOVERABLE:
-                return "DISCOVERABLE";
-            case SCAN_MODE_CONNECTABLE:
-                return "CONNECTABLE";
-            case SCAN_MODE_NONE:
-                return "NOT CONNECTABLE";
-            default:
-                return "UNKNOWN(" + scanMode + ")";
-        }
-    }
-
-    private ResultCode checkTdsControlPointRequest(byte[] request) {
-        if (request.length < 2) {
-            mLogger.log(
-                    new IllegalArgumentException(), "Expected length >= 2 for %s",
-                    base16().encode(request));
-            return ResultCode.INVALID_PARAMETER;
-        }
-        byte opCode = getTdsControlPointOpCode(request);
-        if (opCode != ControlPointCharacteristic.ACTIVATE_TRANSPORT_OP_CODE) {
-            mLogger.log(
-                    new IllegalArgumentException(),
-                    "Expected Activate Transport op code (0x01), got %d",
-                    opCode);
-            return ResultCode.OP_CODE_NOT_SUPPORTED;
-        }
-        if (request[1] != BLUETOOTH_SIG_ORGANIZATION_ID) {
-            mLogger.log(
-                    new IllegalArgumentException(),
-                    "Expected Bluetooth SIG organization ID (0x01), got %d",
-                    request[1]);
-            return ResultCode.UNSUPPORTED_ORGANIZATION_ID;
-        }
-        return ResultCode.SUCCESS;
-    }
-
-    private static byte getTdsControlPointOpCode(byte[] request) {
-        return request.length < 1 ? 0x00 : request[0];
-    }
-
-    private boolean isDiscoverable() {
-        return mBluetoothAdapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
-    }
-
-    private byte[] modelIdServiceData(boolean forAdvertising) {
-        // Note: This used to be little-endian but is now big-endian. See b/78229467 for details.
-        byte[] modelIdPacket =
-                base16().decode(
-                        forAdvertising ? mOptions.getAdvertisingModelId() : mOptions.getModelId());
-        if (!mBatteryValues.isEmpty()) {
-            // If we are going to advertise battery values with the packet, then switch to the
-            // non-3-byte model ID format.
-            modelIdPacket = concat(new byte[]{0b00000110}, modelIdPacket);
-        }
-        return modelIdPacket;
-    }
-
-    private byte[] accountKeysServiceData() {
-        try {
-            return concat(new byte[]{0x00}, generateBloomFilterFields());
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("Unable to build bloom filter.", e);
-        }
-    }
-
-    private byte[] transportDiscoveryData() {
-        byte[] transportData = SUPPORTED_SERVICES_LTV;
-        return Bytes.concat(
-                new byte[]{BLUETOOTH_SIG_ORGANIZATION_ID},
-                new byte[]{tdsFlags(isDiscoverable() ? TransportState.ON : TransportState.OFF)},
-                new byte[]{(byte) transportData.length},
-                transportData);
-    }
-
-    private byte[] generateBloomFilterFields() throws NoSuchAlgorithmException {
-        Set<ByteString> accountKeys = getAccountKeys();
-        if (accountKeys.isEmpty()) {
-            return new byte[0];
-        }
-        BloomFilter bloomFilter =
-                new BloomFilter(
-                        new byte[(int) (1.2 * accountKeys.size()) + 3],
-                        new FastPairBloomFilterHasher());
-        String address = mBleAddress == null ? SIMULATOR_FAKE_BLE_ADDRESS : mBleAddress;
-
-        // Simulator supports Central Address Resolution characteristic, so when paired, the BLE
-        // address in Seeker will be resolved to BR/EDR address. This caused Seeker fails on
-        // checking the bloom filter due to different address is used for salting. In order to
-        // let battery values notification be shown on paired device, we use random salt to
-        // workaround it.
-        boolean advertisingBatteryValues = !mBatteryValues.isEmpty();
-        byte[] salt;
-        if (mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues) {
-            salt = new byte[1];
-            new SecureRandom().nextBytes(salt);
-            mLogger.log("Using random salt %s for bloom filter", base16().encode(salt));
-        } else {
-            salt = BluetoothAddress.decode(address);
-            mLogger.log("Using address %s for bloom filter", address);
-        }
-
-        // To prevent tampering, account filter shall be slightly modified to include battery data
-        // when the battery values are included in the advertisement. Normally, when building the
-        // account filter, a value V is produce by combining the account key with a salt. Instead,
-        // when battery values are also being advertised, it be constructed as follows:
-        // - the first 16 bytes are account key.
-        // - the next bytes are the salt.
-        // - the remaining bytes are the battery data.
-        byte[] saltAndBatteryData =
-                advertisingBatteryValues ? concat(salt, generateBatteryData()) : salt;
-
-        for (ByteString accountKey : accountKeys) {
-            bloomFilter.add(concat(accountKey.toByteArray(), saltAndBatteryData));
-        }
-        byte[] packet = generateAccountKeyData(bloomFilter);
-        return mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues
-                // Create a header with length 1 and type 1 for a random salt.
-                ? concat(packet, createField((byte) 0x11, salt))
-                // Exclude the salt from the packet, BLE address will be assumed by the client.
-                : packet;
-    }
-
-    /**
-     * Creates a new field for the packet.
-     *
-     * The header is formatted 0xLLLLTTTT where LLLL is the
-     * length of the field and TTTT is the type (0 for bloom filter, 1 for salt).
-     */
-    private byte[] createField(byte header, byte[] value) {
-        return concat(new byte[]{header}, value);
-    }
-
-    public int getTxPower() {
-        return mOptions.getTxPowerLevel();
-    }
-
-    @Nullable
-    byte[] getServiceData() {
-        byte[] packet =
-                isDiscoverable()
-                        ? modelIdServiceData(/* forAdvertising= */ true)
-                        : !getAccountKeys().isEmpty() ? accountKeysServiceData() : null;
-        return addBatteryValues(packet);
-    }
-
-    @Nullable
-    private byte[] addBatteryValues(byte[] packet) {
-        if (mBatteryValues.isEmpty() || packet == null) {
-            return packet;
-        }
-
-        return concat(packet, generateBatteryData());
-    }
-
-    private byte[] generateBatteryData() {
-        // Byte 0: Battery length and type, first 4 bits are the number of battery values, second
-        // 4 are the type.
-        // Byte 1 - length: Battery values, the first bit is charging status, the remaining bits are
-        // the actual value between 0 and 100, or -1 for unknown.
-        byte[] batteryData = new byte[mBatteryValues.size() + 1];
-        batteryData[0] = (byte) (mBatteryValues.size() << 4
-                | (mSuppressBatteryNotification ? 0b0100 : 0b0011));
-
-        int batteryValueIndex = 1;
-        for (BatteryValue batteryValue : mBatteryValues) {
-            batteryData[batteryValueIndex++] =
-                    (byte)
-                            ((batteryValue.mCharging ? 0b10000000 : 0b00000000)
-                                    | (0b01111111 & batteryValue.mLevel));
-        }
-
-        return batteryData;
-    }
-
-    private byte[] generateAccountKeyData(BloomFilter bloomFilter) {
-        // Byte 0: length and type, first 4 bits are the length of bloom filter, second 4 are the
-        // type which indicating the subsequent pairing notification is suppressed or not.
-        // The following bytes are the data of bloom filter.
-        byte[] filterBytes = bloomFilter.asBytes();
-        byte lengthAndType = (byte) (filterBytes.length << 4
-                | (mSuppressSubsequentPairingNotification ? 0b0010 : 0b0000));
-        mLogger.log(
-                "Generate bloom filter with suppress subsequent pairing notification:%b",
-                mSuppressSubsequentPairingNotification);
-        return createField(lengthAndType, filterBytes);
-    }
-
-    /** Disconnects all bonded devices. */
-    public void disconnectAllBondedDevices() {
-        for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
-            if (device.getBluetoothClass().getMajorDeviceClass() == Major.PHONE) {
-                removeBond(device);
-            }
-        }
-    }
-
-    public void disconnect(BluetoothProfile profile, BluetoothDevice device) {
-        device.disconnect();
-    }
-
-    public void removeBond(BluetoothDevice device) {
-        device.removeBond();
-    }
-
-    public void resetAccountKeys() {
-        mFastPairSimulatorDatabase.setAccountKeys(new HashSet<>());
-        mFastPairSimulatorDatabase.setFastPairSeekerDevices(new HashSet<>());
-        mAccountKey = null;
-        mOwnerAccountKey = null;
-        mLogger.log("Remove all account keys");
-    }
-
-    public void addAccountKey(byte[] key) {
-        addAccountKey(key, /* device= */ null);
-    }
-
-    private void addAccountKey(byte[] key, @Nullable BluetoothDevice device) {
-        mAccountKey = key;
-        if (mOwnerAccountKey == null) {
-            mOwnerAccountKey = key;
-        }
-
-        mFastPairSimulatorDatabase.addAccountKey(key);
-        mFastPairSimulatorDatabase.addFastPairSeekerDevice(device, key);
-        mLogger.log("Add account key: key=%s, device=%s", base16().encode(key), device);
-    }
-
-    private Set<ByteString> getAccountKeys() {
-        return mFastPairSimulatorDatabase.getAccountKeys();
-    }
-
-    /** Get the latest account key. */
-    @Nullable
-    public ByteString getAccountKey() {
-        if (mAccountKey == null) {
-            return null;
-        }
-        return ByteString.copyFrom(mAccountKey);
-    }
-
-    /** Get the owner account key (the first account key registered). */
-    @Nullable
-    public ByteString getOwnerAccountKey() {
-        if (mOwnerAccountKey == null) {
-            return null;
-        }
-        return ByteString.copyFrom(mOwnerAccountKey);
-    }
-
-    public void resetDeviceName() {
-        mFastPairSimulatorDatabase.setLocalDeviceName(null);
-        // Trigger simulator to update device name text view.
-        if (mDeviceNameCallback != null) {
-            mDeviceNameCallback.onNameChanged(getDeviceName());
-        }
-    }
-
-    // This method is used in test case with default name in provider.
-    public void setDeviceName(String deviceName) {
-        setDeviceName(deviceName.getBytes(StandardCharsets.UTF_8));
-    }
-
-    private void setDeviceName(@Nullable byte[] deviceName) {
-        mFastPairSimulatorDatabase.setLocalDeviceName(deviceName);
-
-        mLogger.log("Save device name : %s", getDeviceName());
-        // Trigger simulator to update device name text view.
-        if (mDeviceNameCallback != null) {
-            mDeviceNameCallback.onNameChanged(getDeviceName());
-        }
-    }
-
-    @Nullable
-    private byte[] getDeviceNameInBytes() {
-        return mFastPairSimulatorDatabase.getLocalDeviceName();
-    }
-
-    @Nullable
-    public String getDeviceName() {
-        String providerDeviceName =
-                getDeviceNameInBytes() != null
-                        ? new String(getDeviceNameInBytes(), StandardCharsets.UTF_8)
-                        : null;
-        mLogger.log("get device name = %s", providerDeviceName);
-        return providerDeviceName;
-    }
-
-    /**
-     * Bit index: Description - Value
-     *
-     * <ul>
-     *   <li>0-1: Role - 0b10 (Provider only)
-     *   <li>2: Transport Data Incomplete: 0 (false)
-     *   <li>3-4: Transport State (0b00: Off, 0b01: On, 0b10: Temporarily Unavailable)
-     *   <li>5-7: Reserved for future use
-     * </ul>
-     */
-    private static byte tdsFlags(TransportState transportState) {
-        return (byte) (0b00000010 & (transportState.mByteValue << 3));
-    }
-
-    /** Detailed information about battery value. */
-    public static class BatteryValue {
-        boolean mCharging;
-
-        // The range is 0 ~ 100, and -1 represents the battery level is unknown.
-        int mLevel;
-
-        public BatteryValue(boolean charging, int level) {
-            this.mCharging = charging;
-            this.mLevel = level;
-        }
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
deleted file mode 100644
index cbe39ff..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider;
-
-import static com.google.common.io.BaseEncoding.base16;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import androidx.annotation.Nullable;
-
-import com.google.protobuf.ByteString;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.StringTokenizer;
-
-/** Stores fast pair related information for each paired device */
-public class FastPairSimulatorDatabase {
-
-    private static final String SHARED_PREF_NAME =
-            "android.nearby.fastpair.provider.fastpairsimulator";
-    private static final String KEY_DEVICE_NAME = "DEVICE_NAME";
-    private static final String KEY_ACCOUNT_KEYS = "ACCOUNT_KEYS";
-    private static final int MAX_NUMBER_OF_ACCOUNT_KEYS = 8;
-
-    // [for SASS]
-    private static final String KEY_FAST_PAIR_SEEKER_DEVICE = "FAST_PAIR_SEEKER_DEVICE";
-
-    private final SharedPreferences mSharedPreferences;
-
-    public FastPairSimulatorDatabase(Context context) {
-        mSharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
-    }
-
-    /** Adds single account key. */
-    public void addAccountKey(byte[] accountKey) {
-        if (mSharedPreferences == null) {
-            return;
-        }
-
-        Set<ByteString> accountKeys = new HashSet<>(getAccountKeys());
-        if (accountKeys.size() >= MAX_NUMBER_OF_ACCOUNT_KEYS) {
-            Set<ByteString> removedKeys = new HashSet<>();
-            int removedCount = accountKeys.size() - MAX_NUMBER_OF_ACCOUNT_KEYS + 1;
-            for (ByteString key : accountKeys) {
-                if (removedKeys.size() == removedCount) {
-                    break;
-                }
-                removedKeys.add(key);
-            }
-
-            accountKeys.removeAll(removedKeys);
-        }
-
-        // Just make sure the newest key will not be removed.
-        accountKeys.add(ByteString.copyFrom(accountKey));
-        setAccountKeys(accountKeys);
-    }
-
-    /** Sets account keys, overrides all. */
-    public void setAccountKeys(Set<ByteString> accountKeys) {
-        if (mSharedPreferences == null) {
-            return;
-        }
-
-        Set<String> keys = new HashSet<>();
-        for (ByteString item : accountKeys) {
-            keys.add(base16().encode(item.toByteArray()));
-        }
-
-        mSharedPreferences.edit().putStringSet(KEY_ACCOUNT_KEYS, keys).apply();
-    }
-
-    /** Gets all account keys. */
-    public Set<ByteString> getAccountKeys() {
-        if (mSharedPreferences == null) {
-            return new HashSet<>();
-        }
-
-        Set<String> keys = mSharedPreferences.getStringSet(KEY_ACCOUNT_KEYS, new HashSet<>());
-        Set<ByteString> accountKeys = new HashSet<>();
-        // Add new account keys one by one.
-        for (String key : keys) {
-            accountKeys.add(ByteString.copyFrom(base16().decode(key)));
-        }
-
-        return accountKeys;
-    }
-
-    /** Sets local device name. */
-    public void setLocalDeviceName(byte[] deviceName) {
-        if (mSharedPreferences == null) {
-            return;
-        }
-
-        String humanReadableName = deviceName != null ? new String(deviceName, UTF_8) : null;
-        if (humanReadableName == null) {
-            mSharedPreferences.edit().remove(KEY_DEVICE_NAME).apply();
-        } else {
-            mSharedPreferences.edit().putString(KEY_DEVICE_NAME, humanReadableName).apply();
-        }
-    }
-
-    /** Gets local device name. */
-    @Nullable
-    public byte[] getLocalDeviceName() {
-        if (mSharedPreferences == null) {
-            return null;
-        }
-
-        String deviceName = mSharedPreferences.getString(KEY_DEVICE_NAME, null);
-        return deviceName != null ? deviceName.getBytes(UTF_8) : null;
-    }
-
-    /**
-     * [for SASS] Adds seeker device info. <a
-     * href="http://go/smart-audio-source-switching-design">Sass design doc</a>
-     */
-    public void addFastPairSeekerDevice(@Nullable BluetoothDevice device, byte[] accountKey) {
-        if (mSharedPreferences == null) {
-            return;
-        }
-
-        if (device == null) {
-            return;
-        }
-
-        // When hitting size limitation, choose the existing items to delete.
-        Set<FastPairSeekerDevice> fastPairSeekerDevices = getFastPairSeekerDevices();
-        if (fastPairSeekerDevices.size() > MAX_NUMBER_OF_ACCOUNT_KEYS) {
-            int removedCount = fastPairSeekerDevices.size() - MAX_NUMBER_OF_ACCOUNT_KEYS + 1;
-            Set<FastPairSeekerDevice> removedFastPairDevices = new HashSet<>();
-            for (FastPairSeekerDevice fastPairDevice : fastPairSeekerDevices) {
-                if (removedFastPairDevices.size() == removedCount) {
-                    break;
-                }
-                removedFastPairDevices.add(fastPairDevice);
-            }
-            fastPairSeekerDevices.removeAll(removedFastPairDevices);
-        }
-
-        fastPairSeekerDevices.add(new FastPairSeekerDevice(device, accountKey));
-        setFastPairSeekerDevices(fastPairSeekerDevices);
-    }
-
-    /** [for SASS] Sets all seeker device info, overrides all. */
-    public void setFastPairSeekerDevices(Set<FastPairSeekerDevice> fastPairSeekerDeviceSet) {
-        if (mSharedPreferences == null) {
-            return;
-        }
-
-        Set<String> rawStringSet = new HashSet<>();
-        for (FastPairSeekerDevice item : fastPairSeekerDeviceSet) {
-            rawStringSet.add(item.toRawString());
-        }
-
-        mSharedPreferences.edit().putStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, rawStringSet).apply();
-    }
-
-    /** [for SASS] Gets all seeker device info. */
-    public Set<FastPairSeekerDevice> getFastPairSeekerDevices() {
-        if (mSharedPreferences == null) {
-            return new HashSet<>();
-        }
-
-        Set<FastPairSeekerDevice> fastPairSeekerDevices = new HashSet<>();
-        Set<String> rawStringSet =
-                mSharedPreferences.getStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, new HashSet<>());
-        for (String rawString : rawStringSet) {
-            FastPairSeekerDevice fastPairDevice = FastPairSeekerDevice.fromRawString(rawString);
-            if (fastPairDevice == null) {
-                continue;
-            }
-            fastPairSeekerDevices.add(fastPairDevice);
-        }
-
-        return fastPairSeekerDevices;
-    }
-
-    /** Defines data structure for the paired Fast Pair device. */
-    public static class FastPairSeekerDevice {
-        private static final int INDEX_DEVICE = 0;
-        private static final int INDEX_ACCOUNT_KEY = 1;
-
-        private final BluetoothDevice mDevice;
-        private final byte[] mAccountKey;
-
-        private FastPairSeekerDevice(BluetoothDevice device, byte[] accountKey) {
-            this.mDevice = device;
-            this.mAccountKey = accountKey;
-        }
-
-        public BluetoothDevice getBluetoothDevice() {
-            return mDevice;
-        }
-
-        public byte[] getAccountKey() {
-            return mAccountKey;
-        }
-
-        public String toRawString() {
-            return String.format("%s,%s", mDevice, base16().encode(mAccountKey));
-        }
-
-        /** Decodes the raw string if possible. */
-        @Nullable
-        public static FastPairSeekerDevice fromRawString(String rawString) {
-            BluetoothDevice device = null;
-            byte[] accountKey = null;
-            int step = INDEX_DEVICE;
-
-            StringTokenizer tokenizer = new StringTokenizer(rawString, ",");
-            while (tokenizer.hasMoreElements()) {
-                boolean shouldStop = false;
-                String token = tokenizer.nextToken();
-                switch (step) {
-                    case INDEX_DEVICE:
-                        try {
-                            device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(token);
-                        } catch (IllegalArgumentException e) {
-                            device = null;
-                        }
-                        break;
-                    case INDEX_ACCOUNT_KEY:
-                        accountKey = base16().decode(token);
-                        if (accountKey.length != 16) {
-                            accountKey = null;
-                        }
-                        break;
-                    default:
-                        shouldStop = true;
-                }
-
-                if (shouldStop) {
-                    break;
-                }
-                step++;
-            }
-            if (device != null && accountKey != null) {
-                return new FastPairSeekerDevice(device, accountKey);
-            }
-            return null;
-        }
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java
deleted file mode 100644
index 9cfffd8..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.decrypt;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR;
-import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-/**
- * A wrapper for Fast Pair Provider to access decoded handshake request from the Seeker.
- *
- * @see {go/fast-pair-early-spec-handshake}
- */
-public class HandshakeRequest {
-
-    /**
-     * 16 bytes data: 1-byte for type, 1-byte for flags, 6-bytes for provider's BLE address, 8 bytes
-     * optional data.
-     *
-     * @see {go/fast-pair-spec-handshake-message1}
-     */
-    private final byte[] mDecryptedMessage;
-
-    /** Enumerates the handshake message types. */
-    public enum Type {
-        KEY_BASED_PAIRING_REQUEST(Request.TYPE_KEY_BASED_PAIRING_REQUEST),
-        ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE),
-        UNKNOWN((byte) 0xFF);
-
-        private final byte mValue;
-
-        Type(byte type) {
-            mValue = type;
-        }
-
-        public byte getValue() {
-            return mValue;
-        }
-
-        public static Type valueOf(byte value) {
-            for (Type type : Type.values()) {
-                if (type.getValue() == value) {
-                    return type;
-                }
-            }
-            return UNKNOWN;
-        }
-    }
-
-    public HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)
-            throws GeneralSecurityException {
-        mDecryptedMessage = decrypt(key, encryptedPairingRequest);
-    }
-
-    /**
-     * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based
-     * Pairing Request and 0x10 for Action Request.
-     */
-    public Type getType() {
-        return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]);
-    }
-
-    /**
-     * Gets verification data of this handshake request.
-     * Currently, we use Provider's BLE address.
-     */
-    public byte[] getVerificationData() {
-        return Arrays.copyOfRange(
-                mDecryptedMessage,
-                Request.VERIFICATION_DATA_INDEX,
-                Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH);
-    }
-
-    /** Gets Seeker's public address of the handshake request. */
-    public byte[] getSeekerPublicAddress() {
-        return Arrays.copyOfRange(
-                mDecryptedMessage,
-                Request.SEEKER_PUBLIC_ADDRESS_INDEX,
-                Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH);
-    }
-
-    /** Checks whether the Seeker request discoverability from flags byte. */
-    public boolean requestDiscoverable() {
-        return (getFlags() & REQUEST_DISCOVERABLE) != 0;
-    }
-
-    /**
-     * Checks whether the Seeker requests that the Provider shall initiate bonding from flags byte.
-     */
-    public boolean requestProviderInitialBonding() {
-        return (getFlags() & PROVIDER_INITIATES_BONDING) != 0;
-    }
-
-    /** Checks whether the Seeker requests that the Provider shall notify the existing name. */
-    public boolean requestDeviceName() {
-        return (getFlags() & REQUEST_DEVICE_NAME) != 0;
-    }
-
-    /** Checks whether this is for retroactively writing account key. */
-    public boolean requestRetroactivePair() {
-        return (getFlags() & REQUEST_RETROACTIVE_PAIR) != 0;
-    }
-
-    /** Gets the flags of this handshake request. */
-    private byte getFlags() {
-        return mDecryptedMessage[Request.FLAGS_INDEX];
-    }
-
-    /** Checks whether the Seeker requests a device action. */
-    public boolean requestDeviceAction() {
-        return (getFlags() & DEVICE_ACTION) != 0;
-    }
-
-    /**
-     * Checks whether the Seeker requests an action which will be followed by an additional data
-     * .
-     */
-    public boolean requestFollowedByAdditionalData() {
-        return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0;
-    }
-
-    /** Gets the {@link AdditionalDataType} of this handshake request. */
-    @AdditionalDataType
-    public int getAdditionalDataType() {
-        if (!requestFollowedByAdditionalData()
-                || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
-            return AdditionalDataType.UNKNOWN;
-        }
-        return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
deleted file mode 100644
index bc0cdfe..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider;
-
-import static com.google.common.io.BaseEncoding.base16;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.AdvertisingSet;
-import android.bluetooth.le.AdvertisingSetCallback;
-import android.bluetooth.le.AdvertisingSetParameters;
-import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.nearby.fastpair.provider.utils.Logger;
-import android.os.ParcelUuid;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Locale;
-
-/** Fast Pair advertiser taking advantage of new Android Oreo advertising features. */
-public final class OreoFastPairAdvertiser implements FastPairAdvertiser {
-    private static final String TAG = "OreoFastPairAdvertiser";
-    private final Logger mLogger = new Logger(TAG);
-
-    private final FastPairSimulator mSimulator;
-    private final BluetoothLeAdvertiser mAdvertiser;
-    private final AdvertisingSetCallback mAdvertisingSetCallback;
-    private AdvertisingSet mAdvertisingSet;
-
-    public OreoFastPairAdvertiser(FastPairSimulator simulator) {
-        this.mSimulator = simulator;
-        this.mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
-        this.mAdvertisingSetCallback = new AdvertisingSetCallback() {
-
-            @Override
-            public void onAdvertisingSetStarted(
-                    AdvertisingSet set, int txPower, int status) {
-                if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
-                    mLogger.log("Advertising succeeded, advertising at %s dBm", txPower);
-                    simulator.setIsAdvertising(true);
-                    mAdvertisingSet = set;
-                    mAdvertisingSet.getOwnAddress();
-                } else {
-                    mLogger.log(
-                            new IllegalStateException(),
-                            "Advertising failed, error code=%d", status);
-                }
-            }
-
-            @Override
-            public void onAdvertisingDataSet(AdvertisingSet set, int status) {
-                if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
-                    mLogger.log(
-                            new IllegalStateException(),
-                            "Updating advertisement failed, error code=%d",
-                            status);
-                    stopAdvertising();
-                }
-            }
-
-            // Callback for AdvertisingSet.getOwnAddress().
-            @Override
-            public void onOwnAddressRead(
-                    AdvertisingSet set, int addressType, String address) {
-                if (!address.equals(simulator.getBleAddress())) {
-                    mLogger.log(
-                            "Read own BLE address=%s at %s",
-                            address,
-                            new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
-                                    .format(Calendar.getInstance().getTime()));
-                    // Implicitly start the advertising once BLE address callback arrived.
-                    simulator.setBleAddress(address);
-                }
-            }
-        };
-    }
-
-    @Override
-    public void startAdvertising(@Nullable byte[] serviceData) {
-        // To be informed that BLE address is rotated, we need to polling query it asynchronously.
-        if (mAdvertisingSet != null) {
-            mAdvertisingSet.getOwnAddress();
-        }
-
-        if (mSimulator.isDestroyed()) {
-            return;
-        }
-
-        if (serviceData == null) {
-            mLogger.log("Service data is null, stop advertising");
-            stopAdvertising();
-            return;
-        }
-
-        AdvertiseData data =
-                new AdvertiseData.Builder()
-                        .addServiceData(new ParcelUuid(FastPairService.ID), serviceData)
-                        .setIncludeTxPowerLevel(true)
-                        .build();
-
-        mLogger.log("Advertising FE2C service data=%s", base16().encode(serviceData));
-
-        if (mAdvertisingSet != null) {
-            mAdvertisingSet.setAdvertisingData(data);
-            return;
-        }
-
-        stopAdvertising();
-        AdvertisingSetParameters parameters =
-                new AdvertisingSetParameters.Builder()
-                        .setLegacyMode(true)
-                        .setConnectable(true)
-                        .setScannable(true)
-                        .setInterval(AdvertisingSetParameters.INTERVAL_LOW)
-                        .setTxPowerLevel(convertAdvertiseSettingsTxPower(mSimulator.getTxPower()))
-                        .build();
-        mAdvertiser.startAdvertisingSet(parameters, data, null, null, null,
-                mAdvertisingSetCallback);
-    }
-
-    private static int convertAdvertiseSettingsTxPower(int txPower) {
-        switch (txPower) {
-            case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
-                return AdvertisingSetParameters.TX_POWER_ULTRA_LOW;
-            case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
-                return AdvertisingSetParameters.TX_POWER_LOW;
-            case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
-                return AdvertisingSetParameters.TX_POWER_MEDIUM;
-            default:
-                return AdvertisingSetParameters.TX_POWER_HIGH;
-        }
-    }
-
-    @Override
-    public void stopAdvertising() {
-        if (mSimulator.isDestroyed()) {
-            return;
-        }
-
-        mAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback);
-        mAdvertisingSet = null;
-        mSimulator.setIsAdvertising(false);
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
deleted file mode 100644
index 345e8d2..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth
-
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothProfile
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.nearby.fastpair.provider.FastPairSimulator
-import android.nearby.fastpair.provider.utils.Logger
-import android.os.SystemClock
-import android.provider.Settings
-
-/** Controls the local Bluetooth adapter for Fast Pair testing. */
-class BluetoothController(
-    private val context: Context,
-    private val listener: EventListener
-) : BroadcastReceiver() {
-    private val mLogger = Logger(TAG)
-    private val bluetoothAdapter: BluetoothAdapter =
-        (context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter!!
-    private var remoteDevice: BluetoothDevice? = null
-    private var remoteDeviceConnectionState: Int = BluetoothAdapter.STATE_DISCONNECTED
-    private var a2dpSinkProxy: BluetoothProfile? = null
-
-    /** Turns on the local Bluetooth adapter */
-    fun enableBluetooth() {
-        if (!bluetoothAdapter.isEnabled) {
-            bluetoothAdapter.enable()
-            waitForBluetoothState(BluetoothAdapter.STATE_ON)
-        }
-    }
-
-    /**
-     * Sets the Input/Output capability of the device for classic Bluetooth operations.
-     * Note: In order to let changes take effect, this method will make sure the Bluetooth stack is
-     * restarted by blocking calling thread.
-     *
-     * @param ioCapabilityClassic One of {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_NONE},
-     * ```
-     *     {@link #IO_CAPABILITY_KBDISP} or more in {@link BluetoothAdapter}.
-     */
-    fun setIoCapability(ioCapabilityClassic: Int) {
-        bluetoothAdapter.ioCapability = ioCapabilityClassic
-
-        // Toggling airplane mode on/off to restart Bluetooth stack and reset the BLE.
-        try {
-            Settings.Global.putInt(
-                context.contentResolver,
-                Settings.Global.AIRPLANE_MODE_ON,
-                TURN_AIRPLANE_MODE_ON
-            )
-        } catch (expectedOnNonCustomAndroid: SecurityException) {
-            mLogger.log(
-                expectedOnNonCustomAndroid,
-                "Requires custom Android to toggle airplane mode"
-            )
-            // Fall back to turn off Bluetooth.
-            bluetoothAdapter.disable()
-        }
-        waitForBluetoothState(BluetoothAdapter.STATE_OFF)
-        try {
-            Settings.Global.putInt(
-                context.contentResolver,
-                Settings.Global.AIRPLANE_MODE_ON,
-                TURN_AIRPLANE_MODE_OFF
-            )
-        } catch (expectedOnNonCustomAndroid: SecurityException) {
-            mLogger.log(
-                expectedOnNonCustomAndroid,
-                "SecurityException while toggled airplane mode."
-            )
-        } finally {
-            // Double confirm that Bluetooth is turned on.
-            bluetoothAdapter.enable()
-        }
-        waitForBluetoothState(BluetoothAdapter.STATE_ON)
-    }
-
-    /** Registers this Bluetooth state change receiver. */
-    fun registerBluetoothStateReceiver() {
-        val bondStateFilter =
-            IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED).apply {
-                addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
-                addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
-            }
-        context.registerReceiver(
-            this,
-            bondStateFilter,
-            /* broadcastPermission= */ null,
-            /* scheduler= */ null
-        )
-    }
-
-    /** Unregisters this Bluetooth state change receiver. */
-    fun unregisterBluetoothStateReceiver() {
-        context.unregisterReceiver(this)
-    }
-
-    /** Clears current remote device. */
-    fun clearRemoteDevice() {
-        remoteDevice = null
-    }
-
-    /** Gets current remote device. */
-    fun getRemoteDevice(): BluetoothDevice? = remoteDevice
-
-    /** Gets current remote device as string. */
-    fun getRemoteDeviceAsString(): String = remoteDevice?.remoteDeviceToString() ?: "none"
-
-    /** Connects the Bluetooth A2DP sink profile service. */
-    fun connectA2DPSinkProfile() {
-        // Get the A2DP proxy before continuing with initialization.
-        bluetoothAdapter.getProfileProxy(
-            context,
-            object : BluetoothProfile.ServiceListener {
-                override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
-                    // When Bluetooth turns off and then on again, this is called again. But we only care
-                    // the first time. There doesn't seem to be a way to unregister our listener.
-                    if (a2dpSinkProxy == null) {
-                        a2dpSinkProxy = proxy
-                        listener.onA2DPSinkProfileConnected()
-                    }
-                }
-
-                override fun onServiceDisconnected(profile: Int) {}
-            },
-            BluetoothProfile.A2DP_SINK
-        )
-    }
-
-    /** Get the current Bluetooth scan mode of the local Bluetooth adapter. */
-    fun getScanMode(): Int = bluetoothAdapter.scanMode
-
-    /** Return true if the remote device is connected to the local adapter. */
-    fun isConnected(): Boolean = remoteDeviceConnectionState == BluetoothAdapter.STATE_CONNECTED
-
-    /** Return true if the remote device is bonded (paired) to the local adapter. */
-    fun isPaired(): Boolean = bluetoothAdapter.bondedDevices.contains(remoteDevice)
-
-    /** Gets the A2DP sink profile proxy. */
-    fun getA2DPSinkProfileProxy(): BluetoothProfile? = a2dpSinkProxy
-
-    /**
-     * Callback method for receiving Intent broadcast of Bluetooth state.
-     *
-     * See [BroadcastReceiver#onReceive].
-     *
-     * @param context the Context in which the receiver is running.
-     * @param intent the Intent being received.
-     */
-    override fun onReceive(context: Context, intent: Intent) {
-        when (intent.action) {
-            BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
-                // After a device starts bonding, we only pay attention to intents about that device.
-                val device =
-                    intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
-                val bondState =
-                    intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
-                remoteDevice =
-                    when (bondState) {
-                        BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_BONDED -> device
-                        BluetoothDevice.BOND_NONE -> null
-                        else -> remoteDevice
-                    }
-                mLogger.log(
-                    "ACTION_BOND_STATE_CHANGED, the bound state of " +
-                            "the remote device (%s) change to %s.",
-                    remoteDevice?.remoteDeviceToString(),
-                    bondState.bondStateToString()
-                )
-                listener.onBondStateChanged(bondState)
-            }
-            BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED -> {
-                remoteDeviceConnectionState =
-                    intent.getIntExtra(
-                        BluetoothAdapter.EXTRA_CONNECTION_STATE,
-                        BluetoothAdapter.STATE_DISCONNECTED
-                    )
-                mLogger.log(
-                    "ACTION_CONNECTION_STATE_CHANGED, the new connectionState: %s",
-                    remoteDeviceConnectionState
-                )
-                listener.onConnectionStateChanged(remoteDeviceConnectionState)
-            }
-            BluetoothAdapter.ACTION_SCAN_MODE_CHANGED -> {
-                val scanMode =
-                    intent.getIntExtra(
-                        BluetoothAdapter.EXTRA_SCAN_MODE,
-                        BluetoothAdapter.SCAN_MODE_NONE
-                    )
-                mLogger.log(
-                    "ACTION_SCAN_MODE_CHANGED, the new scanMode: %s",
-                    FastPairSimulator.scanModeToString(scanMode)
-                )
-                listener.onScanModeChange(scanMode)
-            }
-            else -> {}
-        }
-    }
-
-    private fun waitForBluetoothState(state: Int) {
-        while (bluetoothAdapter.state != state) {
-            SystemClock.sleep(1000)
-        }
-    }
-
-    private fun BluetoothDevice.remoteDeviceToString(): String = "${this.name}-${this.address}"
-
-    private fun Int.bondStateToString(): String =
-        when (this) {
-            BluetoothDevice.BOND_NONE -> "BOND_NONE"
-            BluetoothDevice.BOND_BONDING -> "BOND_BONDING"
-            BluetoothDevice.BOND_BONDED -> "BOND_BONDED"
-            else -> "BOND_ERROR"
-        }
-
-    /** Interface for listening the events from Bluetooth controller. */
-    interface EventListener {
-        /** The callback for the first onServiceConnected of A2DP sink profile. */
-        fun onA2DPSinkProfileConnected()
-
-        /**
-         * Reports the current bond state of the remote device.
-         *
-         * @param bondState the bond state of the remote device.
-         */
-        fun onBondStateChanged(bondState: Int)
-
-        /**
-         * Reports the current connection state of the remote device.
-         *
-         * @param connectionState the bond state of the remote device.
-         */
-        fun onConnectionStateChanged(connectionState: Int)
-
-        /**
-         * Reports the current scan mode of the local Adapter.
-         *
-         * @param mode the current scan mode of the local Adapter.
-         */
-        fun onScanModeChange(mode: Int)
-    }
-
-    companion object {
-        private const val TAG = "BluetoothController"
-
-        private const val TURN_AIRPLANE_MODE_OFF = 0
-        private const val TURN_AIRPLANE_MODE_ON = 1
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java
deleted file mode 100644
index 3cacd55..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattService;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.BluetoothConsts;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.UUID;
-
-/** Configuration of a GATT server. */
-@TargetApi(18)
-public class BluetoothGattServerConfig {
-    private final Map<UUID, ServiceConfig> mServiceConfigs = new HashMap<UUID, ServiceConfig>();
-
-    @Nullable
-    private BluetoothGattServerHelper.Listener mServerlistener = null;
-
-    public BluetoothGattServerConfig addService(UUID uuid, ServiceConfig serviceConfig) {
-        mServiceConfigs.put(uuid, serviceConfig);
-        return this;
-    }
-
-    public BluetoothGattServerConfig setServerConnectionListener(
-            BluetoothGattServerHelper.Listener listener) {
-        mServerlistener = listener;
-        return this;
-    }
-
-    @Nullable
-    public BluetoothGattServerHelper.Listener getServerListener() {
-        return mServerlistener;
-    }
-
-    /**
-     * Adds a service and a characteristic to indicate that the server has dynamic services.
-     * This is a workaround for b/21587710.
-     * TODO(lingjunl): remove them when b/21587710 is fixed.
-     */
-    public BluetoothGattServerConfig addSelfDefinedDynamicService() {
-        ServiceConfig serviceConfig = new ServiceConfig().addCharacteristic(
-                new BluetoothGattServlet() {
-                    @Override
-                    public BluetoothGattCharacteristic getCharacteristic() {
-                        return new BluetoothGattCharacteristic(
-                                BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC,
-                                BluetoothGattCharacteristic.PROPERTY_READ,
-                                BluetoothGattCharacteristic.PERMISSION_READ);
-                    }
-                });
-        return addService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE, serviceConfig);
-    }
-
-    public List<BluetoothGattService> getBluetoothGattServices() {
-        List<BluetoothGattService> result = new ArrayList<BluetoothGattService>();
-        for (Entry<UUID, ServiceConfig> serviceEntry : mServiceConfigs.entrySet()) {
-            UUID serviceUuid = serviceEntry.getKey();
-            ServiceConfig serviceConfig = serviceEntry.getValue();
-            if (serviceUuid == null || serviceConfig == null) {
-                // This is not supposed to happen
-                throw new IllegalStateException();
-            }
-            BluetoothGattService gattService = new BluetoothGattService(serviceUuid,
-                    BluetoothGattService.SERVICE_TYPE_PRIMARY);
-            for (Entry<BluetoothGattCharacteristic, BluetoothGattServlet> servletEntry :
-                    serviceConfig.getServlets().entrySet()) {
-                BluetoothGattCharacteristic characteristic = servletEntry.getKey();
-                if (characteristic == null) {
-                    // This is not supposed to happen
-                    throw new IllegalStateException();
-                }
-                gattService.addCharacteristic(characteristic);
-            }
-            result.add(gattService);
-        }
-        return result;
-    }
-
-    public List<UUID> getAdvertisedUuids() {
-        List<UUID> result = new ArrayList<UUID>();
-        for (Entry<UUID, ServiceConfig> serviceEntry : mServiceConfigs.entrySet()) {
-            UUID serviceUuid = serviceEntry.getKey();
-            ServiceConfig serviceConfig = serviceEntry.getValue();
-            if (serviceUuid == null || serviceConfig == null) {
-                // This is not supposed to happen
-                throw new IllegalStateException();
-            }
-            if (serviceConfig.isAdvertised()) {
-                result.add(serviceUuid);
-            }
-        }
-        return result;
-    }
-
-    public Map<BluetoothGattCharacteristic, BluetoothGattServlet> getServlets() {
-        Map<BluetoothGattCharacteristic, BluetoothGattServlet> result =
-                new HashMap<BluetoothGattCharacteristic, BluetoothGattServlet>();
-        for (ServiceConfig serviceConfig : mServiceConfigs.values()) {
-            result.putAll(serviceConfig.getServlets());
-        }
-        return result;
-    }
-
-    /** Configuration of a GATT service. */
-    public static class ServiceConfig {
-        private final Map<BluetoothGattCharacteristic, BluetoothGattServlet> mServlets =
-                new HashMap<BluetoothGattCharacteristic, BluetoothGattServlet>();
-        private boolean mAdvertise = false;
-
-        public ServiceConfig addCharacteristic(BluetoothGattServlet servlet) {
-            mServlets.put(servlet.getCharacteristic(), servlet);
-            return this;
-        }
-
-        public ServiceConfig setAdvertise(boolean advertise) {
-            mAdvertise = advertise;
-            return this;
-        }
-
-        public Map<BluetoothGattCharacteristic, BluetoothGattServlet> getServlets() {
-            return mServlets;
-        }
-
-        public boolean isAdvertised() {
-            return mAdvertise;
-        }
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java
deleted file mode 100644
index fae6951..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.ReservedUuids;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-import com.google.common.io.BaseEncoding;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Connection to a bluetooth LE device over Gatt.
- */
-@TargetApi(18)
-public class BluetoothGattServerConnection implements Closeable {
-    @SuppressWarnings("unused")
-    private static final String TAG = BluetoothGattServerConnection.class.getSimpleName();
-
-    /** See {@link BluetoothGattDescriptor#DISABLE_NOTIFICATION_VALUE}. */
-    private static final short DISABLE_NOTIFICATION_VALUE = 0x0000;
-
-    /** See {@link BluetoothGattDescriptor#ENABLE_NOTIFICATION_VALUE}. */
-    private static final short ENABLE_NOTIFICATION_VALUE = 0x0001;
-
-    /** See {@link BluetoothGattDescriptor#ENABLE_INDICATION_VALUE}. */
-    private static final short ENABLE_INDICATION_VALUE = 0x0002;
-
-    /** Default MTU when value is unknown. */
-    public static final int DEFAULT_MTU = 23;
-
-    @VisibleForTesting
-    static final long OPERATION_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
-
-    /** Notification types as defined by the BLE spec vol 4, sec G, part 3.3.3.3 */
-    public enum NotificationType {
-        NOTIFICATION,
-        INDICATION
-    }
-
-    /** BT operation types that can be in flight. */
-    public enum OperationType {
-        SEND_NOTIFICATION
-    }
-
-    private final Map<ScopedKey, Object> mContextValues = new HashMap<ScopedKey, Object>();
-    private final List<Listener> mCloseListeners = new ArrayList<Listener>();
-
-    private final BluetoothGattServerHelper mBluetoothGattServerHelper;
-    private final BluetoothDevice mBluetoothDevice;
-
-    @VisibleForTesting
-    BluetoothOperationExecutor mBluetoothOperationScheduler =
-            new BluetoothOperationExecutor(1);
-
-    /** Stores pending writes. For each UUID, we store an offset and a byte[] of data. */
-    @VisibleForTesting
-    final Map<BluetoothGattServlet, SortedMap<Integer, byte[]>> mQueuedCharacteristicWrites =
-            new HashMap<BluetoothGattServlet, SortedMap<Integer, byte[]>>();
-
-    @VisibleForTesting
-    final Map<BluetoothGattCharacteristic, Notifier> mRegisteredNotifications =
-            new HashMap<BluetoothGattCharacteristic, Notifier>();
-
-    private final Map<BluetoothGattCharacteristic, BluetoothGattServlet> mServlets;
-
-    public BluetoothGattServerConnection(
-            BluetoothGattServerHelper bluetoothGattServerHelper,
-            BluetoothDevice device,
-            BluetoothGattServerConfig serverConfig) {
-        mBluetoothGattServerHelper = bluetoothGattServerHelper;
-        mBluetoothDevice = device;
-        mServlets = serverConfig.getServlets();
-    }
-
-    public void setContextValue(Object scope, String key, @Nullable Object value) {
-        mContextValues.put(new ScopedKey(scope, key), value);
-    }
-
-    @Nullable
-    public Object getContextValue(Object scope, String key) {
-        return mContextValues.get(new ScopedKey(scope, key));
-    }
-
-    public BluetoothDevice getDevice() {
-        return mBluetoothDevice;
-    }
-
-    public int getMtu() {
-        return DEFAULT_MTU;
-    }
-
-    public int getMaxDataPacketSize() {
-        // Per BT specs (3.2.9), only MTU - 3 bytes can be used to transmit data
-        return getMtu() - 3;
-    }
-
-    public void addCloseListener(Listener listener) {
-        synchronized (mCloseListeners) {
-            mCloseListeners.add(listener);
-        }
-    }
-
-    public void removeCloseListener(Listener listener) {
-        synchronized (mCloseListeners) {
-            mCloseListeners.remove(listener);
-        }
-    }
-
-    private BluetoothGattServlet getServlet(BluetoothGattCharacteristic characteristic)
-            throws BluetoothGattException {
-        BluetoothGattServlet servlet = mServlets.get(characteristic);
-        if (servlet == null) {
-            throw new BluetoothGattException(
-                    String.format("No handler registered for characteristic %s.",
-                            characteristic.getUuid()),
-                    BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-        }
-        return servlet;
-    }
-
-    public byte[] readCharacteristic(int offset, BluetoothGattCharacteristic characteristic)
-            throws BluetoothGattException {
-        return getServlet(characteristic).read(this, offset);
-    }
-
-    public void writeCharacteristic(BluetoothGattCharacteristic characteristic,
-            boolean preparedWrite,
-            int offset, byte[] value) throws BluetoothGattException {
-        Log.d(TAG, String.format(
-                "Received %d bytes at offset %d on %s from device %s, prepareWrite=%s.",
-                value.length,
-                offset,
-                BluetoothGattUtils.toString(characteristic),
-                mBluetoothDevice,
-                preparedWrite));
-        BluetoothGattServlet servlet = getServlet(characteristic);
-        if (preparedWrite) {
-            SortedMap<Integer, byte[]> bytePackets = mQueuedCharacteristicWrites.get(servlet);
-            if (bytePackets == null) {
-                bytePackets = new TreeMap<Integer, byte[]>();
-                mQueuedCharacteristicWrites.put(servlet, bytePackets);
-            }
-            bytePackets.put(offset, value);
-            return;
-        }
-
-        Log.d(TAG, servlet.toString());
-        servlet.write(this, offset, value);
-    }
-
-    public byte[] readDescriptor(int offset, BluetoothGattDescriptor descriptor)
-            throws BluetoothGattException {
-        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
-        if (characteristic == null) {
-            throw new BluetoothGattException(String.format(
-                    "Descriptor %s not associated with a characteristics!",
-                    BluetoothGattUtils.toString(descriptor)), BluetoothGatt.GATT_FAILURE);
-        }
-        return getServlet(characteristic).readDescriptor(this, descriptor, offset);
-    }
-
-    public void writeDescriptor(
-            BluetoothGattDescriptor descriptor,
-            boolean preparedWrite,
-            int offset,
-            byte[] value) throws BluetoothGattException {
-        Log.d(TAG, String.format(
-                "Received %d bytes at offset %d on %s from device %s, prepareWrite=%s.",
-                value.length,
-                offset,
-                BluetoothGattUtils.toString(descriptor),
-                mBluetoothDevice,
-                preparedWrite));
-        if (preparedWrite) {
-            throw new BluetoothGattException(
-                    String.format("Prepare write not supported for descriptor %s.", descriptor),
-                    BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-        }
-
-        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
-        if (characteristic == null) {
-            throw new BluetoothGattException(String.format(
-                    "Descriptor %s not associated with a characteristics!",
-                    BluetoothGattUtils.toString(descriptor)), BluetoothGatt.GATT_FAILURE);
-        }
-        BluetoothGattServlet servlet = getServlet(characteristic);
-        if (descriptor.getUuid().equals(
-                ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION)) {
-            handleCharacteristicConfigurationChange(characteristic, servlet, offset, value);
-            return;
-        }
-        servlet.writeDescriptor(this, descriptor, offset, value);
-    }
-
-    private void handleCharacteristicConfigurationChange(
-            final BluetoothGattCharacteristic characteristic, BluetoothGattServlet servlet,
-            int offset,
-            byte[] value)
-            throws BluetoothGattException {
-        if (offset != 0) {
-            throw new BluetoothGattException(String.format(
-                    "Offset should be 0 when changing the client characteristic config: %d.",
-                    offset),
-                    BluetoothGatt.GATT_INVALID_OFFSET);
-        }
-        if (value.length != 2) {
-            throw new BluetoothGattException(String.format(
-                    "Value 0x%s is undefined for the client characteristic config",
-                    BaseEncoding.base16().encode(value)),
-                    BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH);
-        }
-
-        boolean notificationRegistered = mRegisteredNotifications.containsKey(characteristic);
-        Notifier notifier;
-        switch (toShort(value)) {
-            case ENABLE_NOTIFICATION_VALUE:
-                if (!notificationRegistered) {
-                    notifier = new Notifier() {
-                        @Override
-                        public void notify(byte[] data) throws BluetoothException {
-                            sendNotification(characteristic, NotificationType.NOTIFICATION, data);
-                        }
-                    };
-                    mRegisteredNotifications.put(characteristic, notifier);
-                    servlet.enableNotification(this, notifier);
-                }
-                break;
-            case ENABLE_INDICATION_VALUE:
-                if (!notificationRegistered) {
-                    notifier = new Notifier() {
-                        @Override
-                        public void notify(byte[] data) throws BluetoothException {
-                            sendNotification(characteristic, NotificationType.INDICATION, data);
-                        }
-                    };
-                    mRegisteredNotifications.put(characteristic, notifier);
-                    servlet.enableNotification(this, notifier);
-                }
-                break;
-            case DISABLE_NOTIFICATION_VALUE:
-                // Note: this disables notifications or indications.
-                if (notificationRegistered) {
-                    notifier = mRegisteredNotifications.remove(characteristic);
-                    if (notifier == null) {
-                        return; // this is not supposed to happen
-                    }
-                    servlet.disableNotification(this, notifier);
-                }
-                break;
-            default:
-                throw new BluetoothGattException(String.format(
-                        "Value 0x%s is undefined for the client characteristic config",
-                        BaseEncoding.base16().encode(value)),
-                        BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-        }
-    }
-
-    private static short toShort(byte[] value) {
-        Preconditions.checkNotNull(value);
-        Preconditions.checkArgument(value.length == 2, "Length should be 2 bytes.");
-
-        return (short) ((value[0] & 0x00FF) | (value[1] << 8));
-    }
-
-    public void executeWrite(boolean execute) throws BluetoothGattException {
-        if (!execute) {
-            mQueuedCharacteristicWrites.clear();
-            return;
-        }
-
-        try {
-            for (Entry<BluetoothGattServlet, SortedMap<Integer, byte[]>> queuedWrite :
-                    mQueuedCharacteristicWrites.entrySet()) {
-                BluetoothGattServlet servlet = queuedWrite.getKey();
-                SortedMap<Integer, byte[]> chunks = queuedWrite.getValue();
-                if (servlet == null || chunks == null) {
-                    // This is not supposed to happen
-                    throw new IllegalStateException();
-                }
-                assembleByteChunksAndHandle(servlet, chunks);
-            }
-        } finally {
-            mQueuedCharacteristicWrites.clear();
-        }
-    }
-
-    /**
-     * Assembles the specified queued writes and calls the provided write handler on the assembled
-     * chunks. Tries to assemble all the chunks into one write request. For example, if the content
-     * of byteChunks is:
-     * <code>
-     * offset data_size
-     * 0       10
-     * 10        1
-     * 11        5
-     * </code>
-     *
-     * then this method would call <code>writeHandler.onWrite(0, byte[16])</code>
-     *
-     * However, if all the chunks cannot be assembled into a continuous byte[], then onWrite() will
-     * be called multiple times with the largest continuous chunks. For example, if the content of
-     * byteChunks is:
-     * <code>
-     * offset data_size
-     * 10       12
-     * 30        5
-     * 35        9
-     * </code>
-     *
-     * then this method would call <code>writeHandler.onWrite(10, byte[12)</code> and
-     * <code>writeHandler.onWrite(30, byte[14]).
-     */
-    private void assembleByteChunksAndHandle(BluetoothGattServlet servlet,
-            SortedMap<Integer, byte[]> byteChunks) throws BluetoothGattException {
-        ByteArrayOutputStream assembledRequest = new ByteArrayOutputStream();
-        Integer startWritingAtOffset = 0;
-
-        while (!byteChunks.isEmpty()) {
-            Integer offset = byteChunks.firstKey();
-
-            if (offset.intValue() < startWritingAtOffset + assembledRequest.size()) {
-                throw new BluetoothGattException(
-                        "Expected offset of at least " + assembledRequest.size()
-                                + ", but got offset " + offset, BluetoothGatt.GATT_INVALID_OFFSET);
-            }
-
-            // If we have a hole, then write what we've already assembled and start assembling a new
-            // long write
-            if (offset.intValue() > startWritingAtOffset + assembledRequest.size()) {
-                servlet.write(this, startWritingAtOffset.intValue(),
-                        assembledRequest.toByteArray());
-                startWritingAtOffset = offset;
-                assembledRequest.reset();
-            }
-
-            try {
-                byte[] dataChunk = byteChunks.remove(offset);
-                if (dataChunk == null) {
-                    // This is not supposed to happen
-                    throw new IllegalStateException();
-                }
-                assembledRequest.write(dataChunk);
-            } catch (IOException e) {
-                throw new BluetoothGattException("Error assembling request",
-                        BluetoothGatt.GATT_FAILURE);
-            }
-        }
-
-        // If there is anything to write, write it
-        if (assembledRequest.size() > 0) {
-            Preconditions.checkNotNull(startWritingAtOffset); // should never be null at this point
-            servlet.write(this, startWritingAtOffset.intValue(), assembledRequest.toByteArray());
-        }
-    }
-
-    private void sendNotification(final BluetoothGattCharacteristic characteristic,
-            final NotificationType notificationType, final byte[] data)
-            throws BluetoothException {
-        mBluetoothOperationScheduler.execute(
-                new Operation<Void>(OperationType.SEND_NOTIFICATION) {
-                    @Override
-                    public void run() throws BluetoothException {
-                        mBluetoothGattServerHelper.sendNotification(mBluetoothDevice,
-                                characteristic,
-                                data,
-                                notificationType == NotificationType.INDICATION ? true : false);
-                    }
-                },
-                OPERATION_TIMEOUT);
-    }
-
-    @Override
-    public void close() throws IOException {
-        try {
-            mBluetoothGattServerHelper.closeConnection(mBluetoothDevice);
-        } catch (BluetoothException e) {
-            throw new IOException("Failed to close connection", e);
-        }
-    }
-
-    public void notifyNotificationSent(int status) {
-        mBluetoothOperationScheduler.notifyCompletion(
-                new Operation<Void>(OperationType.SEND_NOTIFICATION), status);
-    }
-
-    public void onClose() {
-        synchronized (mCloseListeners) {
-            for (Listener listener : mCloseListeners) {
-                listener.onClose();
-            }
-        }
-    }
-
-    /** Scope/key pair to use to reference contextual values. */
-    private static class ScopedKey {
-        private final Object mScope;
-        private final String mKey;
-
-        ScopedKey(Object scope, String key) {
-            mScope = scope;
-            mKey = key;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (!(o instanceof ScopedKey)) {
-                return false;
-            }
-            ScopedKey other = (ScopedKey) o;
-            return other.mScope.equals(mScope) && other.mKey.equals(mKey);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(mScope, mKey);
-        }
-    }
-
-    /** Listener to be notified when the connection closes. */
-    public interface Listener {
-        void onClose();
-    }
-
-    /** Notifier to notify data over notification or indication. */
-    public interface Notifier {
-        void notify(byte[] data) throws BluetoothException;
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java
deleted file mode 100644
index 9339e14..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.os.Build.VERSION_CODES;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.testability.VersionProvider;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServer;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServerCallback;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper for simplifying operations on {@link BluetoothGattServer}.
- */
-@TargetApi(18)
-public class BluetoothGattServerHelper {
-    private static final String TAG = BluetoothGattServerHelper.class.getSimpleName();
-
-    @VisibleForTesting
-    static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
-    private static final int MAX_PARALLEL_OPERATIONS = 5;
-
-    /** BT operation types that can be in flight. */
-    public enum OperationType {
-        ADD_SERVICE,
-        CLOSE_CONNECTION,
-        START_ADVERTISING
-    }
-
-    private final Object mOperationLock = new Object();
-    @VisibleForTesting
-    final BluetoothGattServerCallback mGattServerCallback =
-            new GattServerCallback();
-    @VisibleForTesting
-    BluetoothOperationExecutor mBluetoothOperationScheduler =
-            new BluetoothOperationExecutor(MAX_PARALLEL_OPERATIONS);
-
-    private final Context mContext;
-    private final BluetoothManager mBluetoothManager;
-    private final VersionProvider mVersionProvider;
-
-    @Nullable
-    @VisibleForTesting
-    volatile BluetoothGattServerConfig mServerConfig = null;
-
-    @Nullable
-    @VisibleForTesting
-    volatile BluetoothGattServer mBluetoothGattServer = null;
-
-    @VisibleForTesting
-    final ConcurrentMap<BluetoothDevice, BluetoothGattServerConnection>
-            mConnections = new ConcurrentHashMap<BluetoothDevice, BluetoothGattServerConnection>();
-
-    public BluetoothGattServerHelper(Context context, BluetoothManager bluetoothManager) {
-        this(
-                Preconditions.checkNotNull(context),
-                Preconditions.checkNotNull(bluetoothManager),
-                new VersionProvider()
-        );
-    }
-
-    @VisibleForTesting
-    BluetoothGattServerHelper(
-            Context context, BluetoothManager bluetoothManager, VersionProvider versionProvider) {
-        mContext = context;
-        mBluetoothManager = bluetoothManager;
-        mVersionProvider = versionProvider;
-    }
-
-    @Nullable
-    public BluetoothGattServerConfig getConfig() {
-        return mServerConfig;
-    }
-
-    public void open(final BluetoothGattServerConfig gattServerConfig) throws BluetoothException {
-        synchronized (mOperationLock) {
-            Preconditions.checkState(mBluetoothGattServer == null, "Gatt server is already open.");
-            final BluetoothGattServer server =
-                    mBluetoothManager.openGattServer(mContext, mGattServerCallback);
-            if (server == null) {
-                throw new BluetoothException(
-                        "Failed to open the GATT server, openGattServer returned null.");
-            }
-
-            try {
-                for (final BluetoothGattService service :
-                        gattServerConfig.getBluetoothGattServices()) {
-                    if (service == null) {
-                        continue;
-                    }
-                    mBluetoothOperationScheduler.execute(
-                            new Operation<Void>(OperationType.ADD_SERVICE, service) {
-                                @Override
-                                public void run() throws BluetoothException {
-                                    boolean success = server.addService(service);
-                                    if (!success) {
-                                        throw new BluetoothException("Fails on adding service");
-                                    }
-                                }
-                            }, OPERATION_TIMEOUT_MILLIS);
-                }
-                mBluetoothGattServer = server;
-                mServerConfig = gattServerConfig;
-            } catch (BluetoothException e) {
-                server.close();
-                throw e;
-            }
-        }
-    }
-
-    public boolean isOpen() {
-        synchronized (mOperationLock) {
-            return mBluetoothGattServer != null;
-        }
-    }
-
-    public void close() {
-        synchronized (mOperationLock) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            bluetoothGattServer.close();
-            mBluetoothGattServer = null;
-        }
-    }
-
-    private BluetoothGattServerConnection getConnectionByDevice(BluetoothDevice device)
-            throws BluetoothGattException {
-        BluetoothGattServerConnection bluetoothLeConnection = mConnections.get(device);
-        if (bluetoothLeConnection == null) {
-            throw new BluetoothGattException(
-                    String.format("Received operation on an unknown device: %s", device),
-                    BluetoothGatt.GATT_FAILURE);
-        }
-        return bluetoothLeConnection;
-    }
-
-    public void sendNotification(
-            BluetoothDevice device,
-            BluetoothGattCharacteristic characteristic,
-            byte[] data,
-            boolean confirm)
-            throws BluetoothException {
-        Log.d(TAG, String.format("Sending a %s of %d bytes on characteristics %s on device %s.",
-                confirm ? "indication" : "notification",
-                data.length,
-                characteristic.getUuid(),
-                device));
-        synchronized (mOperationLock) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                throw new BluetoothException("Server is not open.");
-            }
-            BluetoothGattCharacteristic clonedCharacteristic =
-                    BluetoothGattUtils.clone(characteristic);
-            clonedCharacteristic.setValue(data);
-            bluetoothGattServer.notifyCharacteristicChanged(device, clonedCharacteristic, confirm);
-        }
-    }
-
-    public void closeConnection(final BluetoothDevice bluetoothDevice) throws BluetoothException {
-        final BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-        if (bluetoothGattServer == null) {
-            throw new BluetoothException("Server is not open.");
-        }
-        int connectionSate =
-                mBluetoothManager.getConnectionState(bluetoothDevice, BluetoothProfile.GATT);
-        if (connectionSate != BluetoothGatt.STATE_CONNECTED) {
-            return;
-        }
-        mBluetoothOperationScheduler.execute(
-                new Operation<Void>(OperationType.CLOSE_CONNECTION) {
-                    @Override
-                    public void run() throws BluetoothException {
-                        bluetoothGattServer.cancelConnection(bluetoothDevice);
-                    }
-                },
-                OPERATION_TIMEOUT_MILLIS);
-    }
-
-    private class GattServerCallback extends BluetoothGattServerCallback {
-        @Override
-        public void onServiceAdded(int status, BluetoothGattService service) {
-            mBluetoothOperationScheduler.notifyCompletion(
-                    new Operation<Void>(OperationType.ADD_SERVICE, service), status);
-        }
-
-        @Override
-        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
-            BluetoothGattServerConfig serverConfig = mServerConfig;
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            BluetoothGattServerConnection bluetoothLeConnection;
-            if (serverConfig == null || bluetoothGattServer == null) {
-                return;
-            }
-            switch (newState) {
-                case BluetoothGattServer.STATE_CONNECTED:
-                    if (status != BluetoothGatt.GATT_SUCCESS) {
-                        Log.e(TAG, String.format("Connection to %s failed: %s", device,
-                                BluetoothGattUtils.getMessageForStatusCode(status)));
-                        return;
-                    }
-                    Log.i(TAG, String.format("Connected to device %s.", device));
-                    if (mConnections.containsKey(device)) {
-                        Log.w(TAG, String.format("A connection is already open with device %s. "
-                                + "Keeping existing one.", device));
-                        return;
-                    }
-
-                    BluetoothGattServerConnection connection = new BluetoothGattServerConnection(
-                            BluetoothGattServerHelper.this,
-                            device,
-                            serverConfig);
-                    if (serverConfig.getServerListener() != null) {
-                        serverConfig.getServerListener().onConnection(connection);
-                    }
-                    mConnections.put(device, connection);
-
-                    // By default, Android disconnects active GATT server connection if the
-                    // advertisement is
-                    // stop (or sometime stopScanning also disconnect, see b/62667394). Asking
-                    // the server to
-                    // reverse connect will tell Android to keep the connection open.
-                    // Code handling connect() on Android OS is: btif_gatt_server.c
-                    // Note: for Android < P, unknown type devices don't connect. See b/62827460.
-                    //       for Android P+, unknown type devices always use LE to connect (see
-                    //       code)
-                    // Note: for Android < N, dual mode devices always connect using BT classic,
-                    // so connect()
-                    //       should *NOT* be called for those devices. See b/29819614.
-                    if (mVersionProvider.getSdkInt() >= VERSION_CODES.N
-                            || device.getType() != BluetoothDevice.DEVICE_TYPE_DUAL) {
-                        boolean success = bluetoothGattServer.connect(device, /* autoConnect */
-                                false);
-                        if (!success) {
-                            Log.w(TAG, String.format(
-                                    "Keeping connection open on stop advertising failed for "
-                                            + "device %s.",
-                                    device));
-                        }
-                    }
-                    break;
-                case BluetoothGattServer.STATE_DISCONNECTED:
-                    if (status != BluetoothGatt.GATT_SUCCESS) {
-                        Log.w(TAG, String.format(
-                                "Disconnection from %s error: %s. Proceeding anyway.",
-                                device, BluetoothGattUtils.getMessageForStatusCode(status)));
-                    }
-                    bluetoothLeConnection = mConnections.remove(device);
-                    if (bluetoothLeConnection != null) {
-                        // Disconnect the server, required after connecting to it.
-                        bluetoothGattServer.cancelConnection(device);
-                        bluetoothLeConnection.onClose();
-                    }
-                    mBluetoothOperationScheduler.notifyCompletion(
-                            new Operation<Void>(OperationType.CLOSE_CONNECTION), status);
-                    break;
-                default:
-                    Log.e(TAG, String.format("Unexpected connection state: %d", newState));
-            }
-        }
-
-        @Override
-        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
-                BluetoothGattCharacteristic characteristic) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            try {
-                byte[] value =
-                        getConnectionByDevice(device).readCharacteristic(offset, characteristic);
-                bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
-                        offset,
-                        value);
-            } catch (BluetoothGattException e) {
-                Log.e(TAG,
-                        String.format(
-                                "Could not read  %s on device %s at offset %d",
-                                BluetoothGattUtils.toString(characteristic),
-                                device,
-                                offset),
-                        e);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, e.getGattErrorCode(), offset, null);
-            }
-        }
-
-        @Override
-        public void onCharacteristicWriteRequest(BluetoothDevice device,
-                int requestId,
-                BluetoothGattCharacteristic characteristic,
-                boolean preparedWrite,
-                boolean responseNeeded,
-                int offset,
-                byte[] value) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            try {
-                getConnectionByDevice(device).writeCharacteristic(characteristic,
-                        preparedWrite,
-                        offset,
-                        value);
-                if (responseNeeded) {
-                    bluetoothGattServer.sendResponse(
-                            device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
-                }
-            } catch (BluetoothGattException e) {
-                Log.e(TAG,
-                        String.format(
-                                "Could not write %s on device %s at offset %d",
-                                BluetoothGattUtils.toString(characteristic),
-                                device,
-                                offset),
-                        e);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, e.getGattErrorCode(), offset, null);
-            }
-        }
-
-        @Override
-        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
-                BluetoothGattDescriptor descriptor) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            try {
-                byte[] value = getConnectionByDevice(device).readDescriptor(offset, descriptor);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
-            } catch (BluetoothGattException e) {
-                Log.e(TAG, String.format(
-                                "Could not read %s on device %s at %d",
-                                BluetoothGattUtils.toString(descriptor),
-                                device,
-                                offset),
-                        e);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, e.getGattErrorCode(), offset, null);
-            }
-        }
-
-        @Override
-        public void onDescriptorWriteRequest(BluetoothDevice device,
-                int requestId,
-                BluetoothGattDescriptor descriptor,
-                boolean preparedWrite,
-                boolean responseNeeded,
-                int offset,
-                byte[] value) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            try {
-                getConnectionByDevice(device)
-                        .writeDescriptor(descriptor, preparedWrite, offset, value);
-                if (responseNeeded) {
-                    bluetoothGattServer.sendResponse(
-                            device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
-                }
-                Log.d(TAG, "Operation onDescriptorWriteRequest successful.");
-            } catch (BluetoothGattException e) {
-                Log.e(TAG,
-                        String.format(
-                                "Could not write %s on device %s at %d",
-                                BluetoothGattUtils.toString(descriptor),
-                                device,
-                                offset),
-                        e);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, e.getGattErrorCode(), offset, null);
-            }
-        }
-
-        @Override
-        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
-            BluetoothGattServer bluetoothGattServer = mBluetoothGattServer;
-            if (bluetoothGattServer == null) {
-                return;
-            }
-            try {
-                getConnectionByDevice(device).executeWrite(execute);
-                bluetoothGattServer.sendResponse(
-                        device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
-            } catch (BluetoothGattException e) {
-                Log.e(TAG, "Could not execute write.", e);
-                bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), 0, null);
-            }
-        }
-
-        @Override
-        public void onNotificationSent(BluetoothDevice device, int status) {
-            Log.d(TAG,
-                    String.format("Received onNotificationSent for device %s with status %s",
-                            device, status));
-            try {
-                getConnectionByDevice(device).notifyNotificationSent(status);
-            } catch (BluetoothGattException e) {
-                Log.e(TAG, "An error occurred when receiving onNotificationSent: " + e);
-            }
-        }
-    }
-
-    /** Listener for {@link BluetoothGattServerHelper}'s events. */
-    public interface Listener {
-        /** Called when a new connection to the server is established. */
-        void onConnection(BluetoothGattServerConnection connection);
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java
deleted file mode 100644
index e25e223..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection.Notifier;
-
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-
-/** Servlet to handle GATT operations on a characteristic. */
-@TargetApi(18)
-public abstract class BluetoothGattServlet {
-    public byte[] read(BluetoothGattServerConnection connection,
-            @SuppressWarnings("unused") int offset) throws BluetoothGattException {
-        throw new BluetoothGattException("Read not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public void write(BluetoothGattServerConnection connection,
-            @SuppressWarnings("unused") int offset, @SuppressWarnings("unused") byte[] value)
-            throws BluetoothGattException {
-        throw new BluetoothGattException("Write not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public byte[] readDescriptor(BluetoothGattServerConnection connection,
-            BluetoothGattDescriptor descriptor, @SuppressWarnings("unused") int offset)
-            throws BluetoothGattException {
-        throw new BluetoothGattException("Read not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public void writeDescriptor(BluetoothGattServerConnection connection,
-            BluetoothGattDescriptor descriptor,
-            @SuppressWarnings("unused") int offset, @SuppressWarnings("unused") byte[] value)
-            throws BluetoothGattException {
-        throw new BluetoothGattException("Write not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public void enableNotification(BluetoothGattServerConnection connection, Notifier notifier)
-            throws BluetoothGattException {
-        throw new BluetoothGattException("Notification not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public void disableNotification(BluetoothGattServerConnection connection, Notifier notifier)
-            throws BluetoothGattException {
-        throw new BluetoothGattException("Notification not supported.",
-                BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
-    }
-
-    public abstract BluetoothGattCharacteristic getCharacteristic();
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java
deleted file mode 100644
index 7ac26ee..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nearby.fastpair.provider.bluetooth;
-
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-
-/**
- * Utils for Gatt profile.
- */
-public class BluetoothGattUtils {
-
-    /**
-     * Returns a string message for a BluetoothGatt status codes.
-     */
-    public static String getMessageForStatusCode(int statusCode) {
-        switch (statusCode) {
-            case BluetoothGatt.GATT_SUCCESS:
-                return "GATT_SUCCESS";
-            case BluetoothGatt.GATT_FAILURE:
-                return "GATT_FAILURE";
-            case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
-                return "GATT_INSUFFICIENT_AUTHENTICATION";
-            case BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION:
-                return "GATT_INSUFFICIENT_AUTHORIZATION";
-            case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION:
-                return "GATT_INSUFFICIENT_ENCRYPTION";
-            case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
-                return "GATT_INVALID_ATTRIBUTE_LENGTH";
-            case BluetoothGatt.GATT_INVALID_OFFSET:
-                return "GATT_INVALID_OFFSET";
-            case BluetoothGatt.GATT_READ_NOT_PERMITTED:
-                return "GATT_READ_NOT_PERMITTED";
-            case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
-                return "GATT_REQUEST_NOT_SUPPORTED";
-            case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
-                return "GATT_WRITE_NOT_PERMITTED";
-            case BluetoothGatt.GATT_CONNECTION_CONGESTED:
-                return "GATT_CONNECTION_CONGESTED";
-            default:
-                return "Unknown error code";
-        }
-    }
-
-    /** Clones a {@link BluetoothGattCharacteristic} so the value can be changed thread-safely. */
-    public static BluetoothGattCharacteristic clone(BluetoothGattCharacteristic characteristic)
-            throws BluetoothException {
-        BluetoothGattCharacteristic result = new BluetoothGattCharacteristic(
-                characteristic.getUuid(),
-                characteristic.getProperties(), characteristic.getPermissions());
-        try {
-            Field instanceIdField = BluetoothGattCharacteristic.class.getDeclaredField("mInstance");
-            Field serviceField = BluetoothGattCharacteristic.class.getDeclaredField("mService");
-            Field descriptorField = BluetoothGattCharacteristic.class.getDeclaredField(
-                    "mDescriptors");
-            instanceIdField.setAccessible(true);
-            serviceField.setAccessible(true);
-            descriptorField.setAccessible(true);
-            instanceIdField.set(result, instanceIdField.get(characteristic));
-            serviceField.set(result, serviceField.get(characteristic));
-            descriptorField.set(result, descriptorField.get(characteristic));
-            byte[] value = characteristic.getValue();
-            if (value != null) {
-                result.setValue(Arrays.copyOf(value, value.length));
-            }
-            result.setWriteType(characteristic.getWriteType());
-        } catch (NoSuchFieldException e) {
-            throw new BluetoothException("Cannot clone characteristic.", e);
-        } catch (IllegalAccessException e) {
-            throw new BluetoothException("Cannot clone characteristic.", e);
-        } catch (IllegalArgumentException e) {
-            throw new BluetoothException("Cannot clone characteristic.", e);
-        }
-        return result;
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattDescriptor}. */
-    public static String toString(@Nullable BluetoothGattDescriptor descriptor) {
-        if (descriptor == null) {
-            return "null descriptor";
-        }
-        return String.format("descriptor %s on %s",
-                descriptor.getUuid(),
-                toString(descriptor.getCharacteristic()));
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattCharacteristic}. */
-    public static String toString(@Nullable BluetoothGattCharacteristic characteristic) {
-        if (characteristic == null) {
-            return "null characteristic";
-        }
-        return String.format("characteristic %s on %s",
-                characteristic.getUuid(),
-                toString(characteristic.getService()));
-    }
-
-    /** Creates a user-readable string from a {@link BluetoothGattService}. */
-    public static String toString(@Nullable BluetoothGattService service) {
-        if (service == null) {
-            return "null service";
-        }
-        return String.format("service %s", service.getUuid());
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java
deleted file mode 100644
index bf241f1..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServer;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServerCallback;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Mockable wrapper of {@link android.bluetooth.BluetoothManager}.
- */
-public class BluetoothManager {
-
-    private android.bluetooth.BluetoothManager mWrappedInstance;
-
-    private BluetoothManager(android.bluetooth.BluetoothManager instance) {
-        mWrappedInstance = instance;
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothManager#openGattServer(Context,
-     * android.bluetooth.BluetoothGattServerCallback)}.
-     */
-    @Nullable
-    public BluetoothGattServer openGattServer(Context context,
-            BluetoothGattServerCallback callback) {
-        return BluetoothGattServer.wrap(
-                mWrappedInstance.openGattServer(context, callback.unwrap()));
-    }
-
-    /**
-     * See {@link android.bluetooth.BluetoothManager#getConnectionState(
-     *android.bluetooth.BluetoothDevice, int)}.
-     */
-    public int getConnectionState(BluetoothDevice device, int profile) {
-        return mWrappedInstance.getConnectionState(device.unwrap(), profile);
-    }
-
-    /** See {@link android.bluetooth.BluetoothManager#getConnectedDevices(int)}. */
-    public List<BluetoothDevice> getConnectedDevices(int profile) {
-        List<android.bluetooth.BluetoothDevice> devices = mWrappedInstance.getConnectedDevices(
-                profile);
-        List<BluetoothDevice> wrappedDevices = new ArrayList<>(devices.size());
-        for (android.bluetooth.BluetoothDevice device : devices) {
-            wrappedDevices.add(BluetoothDevice.wrap(device));
-        }
-        return wrappedDevices;
-    }
-
-    /** See {@link android.bluetooth.BluetoothManager#getAdapter()}. */
-    public BluetoothAdapter getAdapter() {
-        return BluetoothAdapter.wrap(mWrappedInstance.getAdapter());
-    }
-
-    public static BluetoothManager wrap(android.bluetooth.BluetoothManager bluetoothManager) {
-        return new BluetoothManager(bluetoothManager);
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java
deleted file mode 100644
index 9ed95ac..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.bluetooth;
-
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.ACCEPTING;
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.CONNECTED;
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.RESTARTING;
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.STARTING;
-import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.STOPPED;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothServerSocket;
-import android.bluetooth.BluetoothSocket;
-import android.nearby.fastpair.provider.EventStreamProtocol;
-import android.nearby.fastpair.provider.utils.Logger;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * Listens for a rfcomm client to connect and supports both sending messages to the client and
- * receiving messages from the client.
- */
-public class RfcommServer {
-    private static final String TAG = "RfcommServer";
-    private final Logger mLogger = new Logger(TAG);
-
-    private static final String FAST_PAIR_RFCOMM_SERVICE_NAME = "FastPairServer";
-    public static final UUID FAST_PAIR_RFCOMM_UUID =
-            UUID.fromString("df21fe2c-2515-4fdb-8886-f12c4d67927c");
-
-    /** A single thread executor where all state checks are performed. */
-    private final ExecutorService mControllerExecutor = Executors.newSingleThreadExecutor();
-
-    private final ExecutorService mSendMessageExecutor = Executors.newSingleThreadExecutor();
-    private final ExecutorService mReceiveMessageExecutor = Executors.newSingleThreadExecutor();
-
-    @Nullable
-    private BluetoothServerSocket mServerSocket;
-    @Nullable
-    private BluetoothSocket mSocket;
-
-    private State mState = STOPPED;
-    private boolean mIsStopRequested = false;
-
-    @Nullable
-    private RequestHandler mRequestHandler;
-
-    @Nullable
-    private CountDownLatch mCountDownLatch;
-    @Nullable
-    private StateMonitor mStateMonitor;
-
-    /**
-     * Manages RfcommServer status.
-     *
-     * <pre>{@code
-     *      +------------------------------------------------+
-     *      +-------------------------------+                |
-     *      v                               |                |
-     * +---------+    +----------+    +-----+-----+    +-----+-----+
-     * | STOPPED +--> | STARTING +--> | ACCEPTING +--> | CONNECTED |
-     * +---------+    +-----+----+    +-------+---+    +-----+-----+
-     *      ^               |             ^   v              |
-     *      +---------------+         +---+--------+         |
-     *                                | RESTARTING | <-------+
-     *                                +------------+
-     * }</pre>
-     *
-     * If Stop action is not requested, the server will restart forever. Otherwise, go stopped.
-     */
-    public enum State {
-        STOPPED,
-        STARTING,
-        RESTARTING,
-        ACCEPTING,
-        CONNECTED,
-    }
-
-    /** Starts the rfcomm server. */
-    public void start() {
-        runInControllerExecutor(this::startServer);
-    }
-
-    private void startServer() {
-        log("Start RfcommServer");
-
-        if (!mState.equals(STOPPED)) {
-            log("Server is not stopped, skip start request.");
-            return;
-        }
-        updateState(STARTING);
-        mIsStopRequested = false;
-
-        startAccept();
-    }
-
-    private void restartServer() {
-        log("Restart RfcommServer");
-        updateState(RESTARTING);
-        startAccept();
-    }
-
-    private void startAccept() {
-        try {
-            // Gets server socket in controller thread for stop() API.
-            mServerSocket =
-                    BluetoothAdapter.getDefaultAdapter()
-                            .listenUsingRfcommWithServiceRecord(
-                                    FAST_PAIR_RFCOMM_SERVICE_NAME, FAST_PAIR_RFCOMM_UUID);
-        } catch (IOException e) {
-            log("Create service record failed, stop server");
-            stopServer();
-            return;
-        }
-
-        updateState(ACCEPTING);
-        new Thread(() -> accept(mServerSocket)).start();
-    }
-
-    private void accept(BluetoothServerSocket serverSocket) {
-        triggerCountdownLatch();
-
-        try {
-            BluetoothSocket socket = serverSocket.accept();
-            serverSocket.close();
-
-            runInControllerExecutor(() -> startListen(socket));
-        } catch (IOException e) {
-            log("IOException when accepting new connection");
-            runInControllerExecutor(() -> handleAcceptException(serverSocket));
-        }
-    }
-
-    private void handleAcceptException(BluetoothServerSocket serverSocket) {
-        if (mIsStopRequested) {
-            stopServer();
-        } else {
-            closeServerSocket(serverSocket);
-            restartServer();
-        }
-    }
-
-    private void startListen(BluetoothSocket bluetoothSocket) {
-        if (mIsStopRequested) {
-            closeSocket(bluetoothSocket);
-            stopServer();
-            return;
-        }
-
-        updateState(CONNECTED);
-        // Sets method parameter to global socket for stop() API.
-        this.mSocket = bluetoothSocket;
-        new Thread(() -> listen(bluetoothSocket)).start();
-    }
-
-    private void listen(BluetoothSocket bluetoothSocket) {
-        triggerCountdownLatch();
-
-        try {
-            DataInputStream dataInputStream = new DataInputStream(bluetoothSocket.getInputStream());
-            while (true) {
-                int eventGroup = dataInputStream.readUnsignedByte();
-                int eventCode = dataInputStream.readUnsignedByte();
-                int additionalLength = dataInputStream.readUnsignedShort();
-
-                byte[] data = new byte[additionalLength];
-                if (additionalLength > 0) {
-                    int count = 0;
-                    do {
-                        count += dataInputStream.read(data, count, additionalLength - count);
-                    } while (count < additionalLength);
-                }
-
-                if (mRequestHandler != null) {
-                    // In order not to block listening thread, use different thread to dispatch
-                    // message.
-                    mReceiveMessageExecutor.execute(
-                            () -> {
-                                mRequestHandler.handleRequest(eventGroup, eventCode, data);
-                                triggerCountdownLatch();
-                            });
-                }
-            }
-        } catch (IOException e) {
-            log(
-                    String.format(
-                            "IOException when listening to %s",
-                            bluetoothSocket.getRemoteDevice().getAddress()));
-            runInControllerExecutor(() -> handleListenException(bluetoothSocket));
-        }
-    }
-
-    private void handleListenException(BluetoothSocket bluetoothSocket) {
-        if (mIsStopRequested) {
-            stopServer();
-        } else {
-            closeSocket(bluetoothSocket);
-            restartServer();
-        }
-    }
-
-    public void sendFakeEventStreamMessage(EventStreamProtocol.EventGroup eventGroup) {
-        switch (eventGroup) {
-            case BLUETOOTH:
-                send(EventStreamProtocol.EventGroup.BLUETOOTH_VALUE,
-                        EventStreamProtocol.BluetoothEventCode.BLUETOOTH_ENABLE_SILENCE_MODE_VALUE,
-                        new byte[0]);
-                break;
-            case LOGGING:
-                send(EventStreamProtocol.EventGroup.LOGGING_VALUE,
-                        EventStreamProtocol.LoggingEventCode.LOG_FULL_VALUE,
-                        new byte[0]);
-                break;
-            case DEVICE:
-                send(EventStreamProtocol.EventGroup.DEVICE_VALUE,
-                        EventStreamProtocol.DeviceEventCode.DEVICE_BATTERY_INFO_VALUE,
-                        new byte[]{0x11, 0x12, 0x13});
-                break;
-            default: // fall out
-        }
-    }
-
-    public void sendFakeEventStreamLoggingMessage(@Nullable String logContent) {
-        send(EventStreamProtocol.EventGroup.LOGGING_VALUE,
-                EventStreamProtocol.LoggingEventCode.LOG_SAVE_TO_BUFFER_VALUE,
-                logContent != null ? logContent.getBytes(UTF_8) : new byte[0]);
-    }
-
-    public void send(int eventGroup, int eventCode, byte[] data) {
-        runInControllerExecutor(
-                () -> {
-                    if (!CONNECTED.equals(mState)) {
-                        log("Server is not in CONNECTED state, skip send request");
-                        return;
-                    }
-                    BluetoothSocket bluetoothSocket = this.mSocket;
-                    mSendMessageExecutor.execute(() -> {
-                        String address = bluetoothSocket.getRemoteDevice().getAddress();
-                        try {
-                            DataOutputStream dataOutputStream =
-                                    new DataOutputStream(bluetoothSocket.getOutputStream());
-                            dataOutputStream.writeByte(eventGroup);
-                            dataOutputStream.writeByte(eventCode);
-                            dataOutputStream.writeShort(data.length);
-                            if (data.length > 0) {
-                                dataOutputStream.write(data);
-                            }
-                            dataOutputStream.flush();
-                            log(
-                                    String.format(
-                                            "Send message to %s: %s, %s, %s.",
-                                            address, eventGroup, eventCode, data.length));
-                        } catch (IOException e) {
-                            log(
-                                    String.format(
-                                            "Failed to send message to %s: %s, %s, %s.",
-                                            address, eventGroup, eventCode, data.length),
-                                    e);
-                        }
-                    });
-                });
-    }
-
-    /** Stops the rfcomm server. */
-    public void stop() {
-        runInControllerExecutor(() -> {
-            log("Stop RfcommServer");
-
-            if (STOPPED.equals(mState)) {
-                log("Server is stopped, skip stop request.");
-                return;
-            }
-
-            if (mIsStopRequested) {
-                log("Stop is already requested, skip stop request.");
-                return;
-            }
-            mIsStopRequested = true;
-
-            if (ACCEPTING.equals(mState)) {
-                closeServerSocket(mServerSocket);
-            }
-
-            if (CONNECTED.equals(mState)) {
-                closeSocket(mSocket);
-            }
-        });
-    }
-
-    private void stopServer() {
-        updateState(STOPPED);
-        triggerCountdownLatch();
-    }
-
-    private void updateState(State newState) {
-        log(String.format("Change state from %s to %s", mState, newState));
-        if (mStateMonitor != null) {
-            mStateMonitor.onStateChanged(newState);
-        }
-        mState = newState;
-    }
-
-    private void closeServerSocket(BluetoothServerSocket serverSocket) {
-        try {
-            if (serverSocket != null) {
-                log(String.format("Close server socket: %s", serverSocket));
-                serverSocket.close();
-            }
-        } catch (IOException | NullPointerException e) {
-            // NullPointerException is used to skip robolectric test failure.
-            // In unit test, different virtual devices are set up in different threads, calling
-            // ServerSocket.close() in wrong thread will result in NullPointerException since there
-            // is no corresponding service record.
-            // TODO(hylo): Remove NullPointerException when the solution is submitted to test cases.
-            log("Failed to stop server", e);
-        }
-    }
-
-    private void closeSocket(BluetoothSocket socket) {
-        try {
-            if (socket != null && socket.isConnected()) {
-                log(String.format("Close socket: %s", socket.getRemoteDevice().getAddress()));
-                socket.close();
-            }
-        } catch (IOException e) {
-            log(String.format("IOException when close socket %s",
-                    socket.getRemoteDevice().getAddress()));
-        }
-    }
-
-    private void runInControllerExecutor(Runnable runnable) {
-        mControllerExecutor.execute(runnable);
-    }
-
-    private void log(String message) {
-        mLogger.log("Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
-    }
-
-    private void log(String message, Throwable e) {
-        mLogger.log(e, "Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
-    }
-
-    private void triggerCountdownLatch() {
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    /** Interface to handle incoming request from clients. */
-    public interface RequestHandler {
-        void handleRequest(int eventGroup, int eventCode, byte[] data);
-    }
-
-    public void setRequestHandler(@Nullable RequestHandler requestHandler) {
-        this.mRequestHandler = requestHandler;
-    }
-
-    /** A state monitor to send signal when state is changed. */
-    public interface StateMonitor {
-        void onStateChanged(State state);
-    }
-
-    public void setStateMonitor(@Nullable StateMonitor stateMonitor) {
-        this.mStateMonitor = stateMonitor;
-    }
-
-    @VisibleForTesting
-    void setCountDownLatch(@Nullable CountDownLatch countDownLatch) {
-        this.mCountDownLatch = countDownLatch;
-    }
-
-    @VisibleForTesting
-    void setIsStopRequested(boolean isStopRequested) {
-        this.mIsStopRequested = isStopRequested;
-    }
-
-    @VisibleForTesting
-    void simulateAcceptIOException() {
-        runInControllerExecutor(() -> {
-            if (ACCEPTING.equals(mState)) {
-                closeServerSocket(mServerSocket);
-            }
-        });
-    }
-
-    @VisibleForTesting
-    void simulateListenIOException() {
-        runInControllerExecutor(() -> {
-            if (CONNECTED.equals(mState)) {
-                closeSocket(mSocket);
-            }
-        });
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java
deleted file mode 100644
index 0aa4f6e..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.crypto;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-
-import android.annotation.SuppressLint;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.protobuf.ByteString;
-
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-
-import javax.crypto.Cipher;
-import javax.crypto.spec.SecretKeySpec;
-
-/** Cryptography utilities for ephemeral IDs. */
-public final class Crypto {
-    private static final int AES_BLOCK_SIZE = 16;
-    private static final ImmutableSet<Integer> VALID_AES_KEY_SIZES = ImmutableSet.of(16, 24, 32);
-    private static final String AES_ECB_NOPADDING_ENCRYPTION_ALGO = "AES/ECB/NoPadding";
-    private static final String AES_ENCRYPTION_ALGO = "AES";
-
-    /** Encrypts the provided data with the provided key using the AES/ECB/NoPadding algorithm. */
-    public static ByteString aesEcbNoPaddingEncrypt(ByteString key, ByteString data) {
-        return aesEcbOperation(key, data, Cipher.ENCRYPT_MODE);
-    }
-
-    /** Decrypts the provided data with the provided key using the AES/ECB/NoPadding algorithm. */
-    public static ByteString aesEcbNoPaddingDecrypt(ByteString key, ByteString data) {
-        return aesEcbOperation(key, data, Cipher.DECRYPT_MODE);
-    }
-
-    @SuppressLint("GetInstance")
-    private static ByteString aesEcbOperation(ByteString key, ByteString data, int operation) {
-        checkArgument(VALID_AES_KEY_SIZES.contains(key.size()));
-        checkArgument(data.size() % AES_BLOCK_SIZE == 0);
-        try {
-            Cipher aesCipher = Cipher.getInstance(AES_ECB_NOPADDING_ENCRYPTION_ALGO);
-            SecretKeySpec secretKeySpec = new SecretKeySpec(key.toByteArray(), AES_ENCRYPTION_ALGO);
-            aesCipher.init(operation, secretKeySpec);
-            ByteBuffer output = ByteBuffer.allocate(data.size());
-            checkState(aesCipher.doFinal(data.asReadOnlyByteBuffer(), output) == data.size());
-            output.rewind();
-            return ByteString.copyFrom(output);
-        } catch (GeneralSecurityException e) {
-            // Should never happen.
-            throw new AssertionError(e);
-        }
-    }
-
-    private Crypto() {
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java
deleted file mode 100644
index 794c19d..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.crypto;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Verify;
-import com.google.common.primitives.Bytes;
-import com.google.common.primitives.Ints;
-import com.google.protobuf.ByteString;
-
-import java.math.BigInteger;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.EllipticCurve;
-import java.util.Collections;
-
-/** Provides methods for calculating E2EE EIDs and E2E encryption/decryption based on E2EE EIDs. */
-public final class E2eeCalculator {
-
-    private static final byte[] TEMP_KEY_PADDING_1 =
-            Bytes.toArray(Collections.nCopies(11, (byte) 0xFF));
-    private static final byte[] TEMP_KEY_PADDING_2 = new byte[11];
-    private static final ECParameterSpec CURVE_SPEC = getCurveSpec();
-    private static final BigInteger P = ((ECFieldFp) CURVE_SPEC.getCurve().getField()).getP();
-    private static final BigInteger TWO = new BigInteger("2");
-    private static final BigInteger THREE = new BigInteger("3");
-    private static final int E2EE_EID_IDENTITY_KEY_SIZE = 32;
-    private static final int E2EE_EID_SIZE = 20;
-
-    /**
-     * Computes the E2EE EID value for the given device clock based time. Note that Eddystone
-     * beacons start advertising the new EID at a random time within the window, therefore the
-     * currently advertised EID for beacon time <em>t</em> may be either
-     * {@code computeE2eeEid(eik, k, t)} or {@code computeE2eeEid(eik, k, t - (1 << k))}.
-     *
-     * <p>The E2EE EID computation is based on https://goto.google.com/e2ee-eid-computation.
-     *
-     * @param identityKey        the beacon's 32-byte Eddystone E2EE identity key
-     * @param exponent           rotation period exponent as configured on the beacon, must be in
-     *                           range the [0,15]
-     * @param deviceClockSeconds the value of the beacon's 32-bit seconds time counter (treated as
-     *                           an unsigned value)
-     * @return E2EE EID value.
-     */
-    public static ByteString computeE2eeEid(
-            ByteString identityKey, int exponent, int deviceClockSeconds) {
-        return computePublicKey(computePrivateKey(identityKey, exponent, deviceClockSeconds));
-    }
-
-    private static ByteString computePublicKey(BigInteger privateKey) {
-        return getXCoordinateBytes(toPoint(privateKey));
-    }
-
-    private static BigInteger computePrivateKey(
-            ByteString identityKey, int exponent, int deviceClockSeconds) {
-        Preconditions.checkArgument(
-                Preconditions.checkNotNull(identityKey).size() == E2EE_EID_IDENTITY_KEY_SIZE);
-        Preconditions.checkArgument(exponent >= 0 && exponent < 16);
-
-        byte[] exponentByte = new byte[]{(byte) exponent};
-        byte[] paddedCounter = Ints.toByteArray((deviceClockSeconds >>> exponent) << exponent);
-        byte[] data =
-                Bytes.concat(
-                        TEMP_KEY_PADDING_1,
-                        exponentByte,
-                        paddedCounter,
-                        TEMP_KEY_PADDING_2,
-                        exponentByte,
-                        paddedCounter);
-
-        byte[] rTag =
-                Crypto.aesEcbNoPaddingEncrypt(identityKey, ByteString.copyFrom(data)).toByteArray();
-        return new BigInteger(1, rTag).mod(CURVE_SPEC.getOrder());
-    }
-
-    private static ECPoint toPoint(BigInteger privateKey) {
-        return multiplyPoint(CURVE_SPEC.getGenerator(), privateKey);
-    }
-
-    private static ByteString getXCoordinateBytes(ECPoint point) {
-        byte[] unalignedBytes = point.getAffineX().toByteArray();
-
-        // The unalignedBytes may have length < 32 if the leading E2EE EID bytes are zero, or
-        // it may be E2EE_EID_SIZE + 1 if the leading bit is 1, in which case the first byte is
-        // always zero.
-        Verify.verify(
-                unalignedBytes.length <= E2EE_EID_SIZE
-                        || (unalignedBytes.length == E2EE_EID_SIZE + 1 && unalignedBytes[0] == 0));
-
-        byte[] bytes;
-        if (unalignedBytes.length < E2EE_EID_SIZE) {
-            bytes = new byte[E2EE_EID_SIZE];
-            System.arraycopy(
-                    unalignedBytes, 0, bytes, bytes.length - unalignedBytes.length,
-                    unalignedBytes.length);
-        } else if (unalignedBytes.length == E2EE_EID_SIZE + 1) {
-            bytes = new byte[E2EE_EID_SIZE];
-            System.arraycopy(unalignedBytes, 1, bytes, 0, E2EE_EID_SIZE);
-        } else { // unalignedBytes.length ==  GattE2EE_EID_SIZE
-            bytes = unalignedBytes;
-        }
-        return ByteString.copyFrom(bytes);
-    }
-
-    /** Returns a secp160r1 curve spec. */
-    private static ECParameterSpec getCurveSpec() {
-        final BigInteger p = new BigInteger("ffffffffffffffffffffffffffffffff7fffffff", 16);
-        final BigInteger n = new BigInteger("0100000000000000000001f4c8f927aed3ca752257", 16);
-        final BigInteger a = new BigInteger("ffffffffffffffffffffffffffffffff7ffffffc", 16);
-        final BigInteger b = new BigInteger("1c97befc54bd7a8b65acf89f81d4d4adc565fa45", 16);
-        final BigInteger gx = new BigInteger("4a96b5688ef573284664698968c38bb913cbfc82", 16);
-        final BigInteger gy = new BigInteger("23a628553168947d59dcc912042351377ac5fb32", 16);
-        final int h = 1;
-        ECFieldFp fp = new ECFieldFp(p);
-        EllipticCurve spec = new EllipticCurve(fp, a, b);
-        ECPoint g = new ECPoint(gx, gy);
-        return new ECParameterSpec(spec, g, n, h);
-    }
-
-    /** Returns the scalar multiplication result of k*p in Fp. */
-    private static ECPoint multiplyPoint(ECPoint p, BigInteger k) {
-        ECPoint r = ECPoint.POINT_INFINITY;
-        ECPoint s = p;
-        BigInteger kModP = k.mod(P);
-        int length = kModP.bitLength();
-        for (int i = 0; i <= length - 1; i++) {
-            if (kModP.mod(TWO).byteValue() == 1) {
-                r = addPoint(r, s);
-            }
-            s = doublePoint(s);
-            kModP = kModP.divide(TWO);
-        }
-        return r;
-    }
-
-    /** Returns the point addition r+s in Fp. */
-    private static ECPoint addPoint(ECPoint r, ECPoint s) {
-        if (r.equals(s)) {
-            return doublePoint(r);
-        } else if (r.equals(ECPoint.POINT_INFINITY)) {
-            return s;
-        } else if (s.equals(ECPoint.POINT_INFINITY)) {
-            return r;
-        }
-        BigInteger slope =
-                r.getAffineY()
-                        .subtract(s.getAffineY())
-                        .multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(P))
-                        .mod(P);
-        BigInteger x =
-                slope.modPow(TWO, P).subtract(r.getAffineX()).subtract(s.getAffineX()).mod(P);
-        BigInteger y = s.getAffineY().negate().mod(P);
-        y = y.add(slope.multiply(s.getAffineX().subtract(x))).mod(P);
-        return new ECPoint(x, y);
-    }
-
-    /** Returns the point doubling 2*r in Fp. */
-    private static ECPoint doublePoint(ECPoint r) {
-        if (r.equals(ECPoint.POINT_INFINITY)) {
-            return r;
-        }
-        BigInteger slope = r.getAffineX().pow(2).multiply(THREE);
-        slope = slope.add(CURVE_SPEC.getCurve().getA());
-        slope = slope.multiply(r.getAffineY().multiply(TWO).modInverse(P));
-        BigInteger x = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(P);
-        BigInteger y =
-                r.getAffineY().negate().add(slope.multiply(r.getAffineX().subtract(x))).mod(P);
-        return new ECPoint(x, y);
-    }
-
-    private E2eeCalculator() {
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java
deleted file mode 100644
index 794f100..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.utils;
-
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.google.errorprone.annotations.FormatMethod;
-
-/**
- * The base context for a logging statement.
- */
-public class Logger {
-    private final String mString;
-
-    public Logger(String tag) {
-        this.mString = tag;
-    }
-
-    @FormatMethod
-    public void log(String message, Object... objects) {
-        log(null, message, objects);
-    }
-
-    /** Logs to the console. */
-    @FormatMethod
-    public void log(@Nullable Throwable exception, String message, Object... objects) {
-        if (exception == null) {
-            Log.i(mString, String.format(message, objects));
-        } else {
-            Log.w(mString, String.format(message, objects));
-            Log.w(mString, String.format("Cause: %s", exception));
-        }
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp b/nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp
deleted file mode 100644
index 697c88d..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "MoblySnippetHelperLib",
-    srcs: ["src/**/*.kt"],
-    sdk_version: "test_current",
-    static_libs: ["mobly-snippet-lib",],
-}
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml
deleted file mode 100644
index 4858f46..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.mobly.snippet.util">
-
-</manifest>
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt b/nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt
deleted file mode 100644
index 0dbcb57..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.google.android.mobly.snippet.util
-
-import android.os.Bundle
-import com.google.android.mobly.snippet.event.EventCache
-import com.google.android.mobly.snippet.event.SnippetEvent
-
-/**
- * Posts an {@link SnippetEvent} to the event cache with data bundle [fill] by the given function.
- *
- * This is a helper function to make your client side codes more concise. Sample usage:
- * ```
- *   postSnippetEvent(callbackId, "onReceiverFound") {
- *     putLong("discoveryTimeMs", discoveryTimeMs)
- *     putBoolean("isKnown", isKnown)
- *   }
- * ```
- *
- * @param callbackId the callbackId passed to the {@link
- * com.google.android.mobly.snippet.rpc.AsyncRpc} method.
- * @param eventName the name of the event.
- * @param fill the function to fill the data bundle.
- */
-fun postSnippetEvent(callbackId: String, eventName: String, fill: Bundle.() -> Unit) {
-  val eventData = Bundle().apply(fill)
-  val snippetEvent = SnippetEvent(callbackId, eventName).apply { data.putAll(eventData) }
-  EventCache.getInstance().postEvent(snippetEvent)
-}
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
deleted file mode 100644
index ec0392c..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Run the tests: atest --host MoblySnippetHelperRoboTest
-android_robolectric_test {
-    name: "MoblySnippetHelperRoboTest",
-    srcs: ["src/**/*.kt"],
-    instrumentation_for: "NearbyMultiDevicesClientsSnippets",
-    java_resources: ["robolectric.properties"],
-
-    static_libs: [
-        "MoblySnippetHelperLib",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "junit",
-        "mobly-snippet-lib",
-        "truth-prebuilt",
-    ],
-    test_options: {
-        // timeout in seconds.
-        timeout: 36000,
-    },
-    upstream: true,
-}
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml
deleted file mode 100644
index f1fef23..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.google.android.mobly.snippet.util"/>
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties
deleted file mode 100644
index 2ea03bb..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2022 Google Inc.
-#
-# 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.
-#
-sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt
deleted file mode 100644
index 641ab82..0000000
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.google.android.mobly.snippet.util
-
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.android.mobly.snippet.event.EventSnippet
-import com.google.android.mobly.snippet.util.Log
-import com.google.common.truth.Truth.assertThat
-import org.json.JSONObject
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-
-/** Robolectric tests for SnippetEventHelper.kt. */
-@RunWith(RobolectricTestRunner::class)
-class SnippetEventHelperTest {
-
-  @Test
-  fun testPostSnippetEvent_withDataBundle_writesEventCache() {
-    val testCallbackId = "test_1234"
-    val testEventName = "onTestEvent"
-    val testBundleDataStrKey = "testStrKey"
-    val testBundleDataStrValue = "testStrValue"
-    val testBundleDataIntKey = "testIntKey"
-    val testBundleDataIntValue = 777
-    val eventSnippet = EventSnippet()
-    Log.initLogTag(InstrumentationRegistry.getInstrumentation().context)
-
-    postSnippetEvent(testCallbackId, testEventName) {
-      putString(testBundleDataStrKey, testBundleDataStrValue)
-      putInt(testBundleDataIntKey, testBundleDataIntValue)
-    }
-
-    val event = eventSnippet.eventWaitAndGet(testCallbackId, testEventName, null)
-    assertThat(event.getJSONObject("data").toString())
-      .isEqualTo(
-        JSONObject()
-          .put(testBundleDataIntKey, testBundleDataIntValue)
-          .put(testBundleDataStrKey, testBundleDataStrValue)
-          .toString()
-      )
-  }
-}
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
deleted file mode 100644
index b6c1c9d..0000000
--- a/nearby/tests/multidevices/host/Android.bp
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Run the tests: atest -v NearbyMultiDevicesTestSuite
-// Check go/run-nearby-mainline-e2e for more details.
-python_test_host {
-    name: "NearbyMultiDevicesTestSuite",
-    main: "suite_main.py",
-    srcs: ["*.py"],
-    libs: [
-        "NearbyMultiDevicesHostHelper",
-        "mobly",
-    ],
-    test_suites: [
-        "general-tests",
-        "mts-tethering",
-    ],
-    test_options: {
-        unit_test: false,
-    },
-    data: [
-        // Package the snippet with the Mobly test.
-        ":NearbyMultiDevicesClientsSnippets",
-        // Package the data provider with the Mobly test.
-        ":NearbyFastPairSeekerDataProvider",
-        // Package the JSON metadata with the Mobly test.
-        "test_data/**/*",
-    ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
-}
-
-python_library_host {
-    name: "NearbyMultiDevicesHostHelper",
-    srcs: ["test_helper/*.py"],
-}
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
deleted file mode 100644
index fff0ed1..0000000
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ /dev/null
@@ -1,137 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-<configuration description="Config for CTS Nearby Mainline multi devices end-to-end test suite">
-    <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
-    <!-- Only run NearbyMultiDevicesTestSuite in MTS if the Nearby Mainline module is installed. -->
-    <object type="module_controller"
-            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
-    </object>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="NearbyMultiDevicesTestSuite" />
-    <option name="config-descriptor:metadata" key="component" value="wifi" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
-
-    <device name="device1">
-        <!-- For coverage to work, the APK should not be uninstalled until after coverage is pulled.
-             So it's a lot easier to install APKs outside the python code.
-        -->
-        <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
-        <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-            <option name="remount-system" value="true" />
-            <option name="push" value="NearbyMultiDevicesClientsSnippets.apk->/system/app/NearbyMultiDevicesClientsSnippets/NearbyMultiDevicesClientsSnippets.apk" />
-            <option name="push" value="NearbyFastPairSeekerDataProvider.apk->/system/app/NearbyFastPairSeekerDataProvider/NearbyFastPairSeekerDataProvider.apk" />
-        </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
-        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-            <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
-            <option name="run-command" value="wm dismiss-keyguard" />
-        </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-            <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
-            <option name="screen-always-on" value="on" />
-            <!-- List permissions requested by the APK: aapt d permissions <PATH_TO_YOUR_APK> -->
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_ADMIN" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_ADVERTISE" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_CONNECT" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_PRIVILEGED" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_SCAN" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.INTERNET" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.GET_ACCOUNTS" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.WRITE_SECURE_SETTINGS" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.REORDER_TASKS" />
-        </target_preparer>
-    </device>
-    <device name="device2">
-        <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
-        <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-            <option name="remount-system" value="true" />
-            <option name="push" value="NearbyMultiDevicesClientsSnippets.apk->/system/app/NearbyMultiDevicesClientsSnippets/NearbyMultiDevicesClientsSnippets.apk" />
-            <option name="push" value="NearbyFastPairSeekerDataProvider.apk->/system/app/NearbyFastPairSeekerDataProvider/NearbyFastPairSeekerDataProvider.apk" />
-        </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
-        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-            <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
-            <option name="run-command" value="wm dismiss-keyguard" />
-        </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-            <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
-            <option name="screen-always-on" value="on" />
-            <!-- List permissions requested by the APK: aapt d permissions <PATH_TO_YOUR_APK> -->
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_ADMIN" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_ADVERTISE" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_CONNECT" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_PRIVILEGED" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.BLUETOOTH_SCAN" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.INTERNET" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.GET_ACCOUNTS" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.WRITE_SECURE_SETTINGS" />
-            <option
-                name="run-command"
-                value="pm grant android.nearby.multidevices android.permission.REORDER_TASKS" />
-        </target_preparer>
-    </device>
-
-    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
-      <!-- The mobly-par-file-name should match the module name -->
-      <option name="mobly-par-file-name" value="NearbyMultiDevicesTestSuite" />
-      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
-      <option name="mobly-test-timeout" value="60000" />
-    </test>
-</configuration>
-
diff --git a/nearby/tests/multidevices/host/initial_pairing_test.py b/nearby/tests/multidevices/host/initial_pairing_test.py
deleted file mode 100644
index 1a49045..0000000
--- a/nearby/tests/multidevices/host/initial_pairing_test.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#  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.
-
-"""CTS-V Nearby Mainline Fast Pair end-to-end test case: initial pairing test."""
-
-from test_helper import constants
-from test_helper import fast_pair_base_test
-
-# The model ID to simulate on provider side.
-PROVIDER_SIMULATOR_MODEL_ID = constants.DEFAULT_MODEL_ID
-# The public key to simulate as registered headsets.
-PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY = constants.DEFAULT_ANTI_SPOOFING_KEY
-# The anti-spoof key device metadata JSON file for data provider at seeker side.
-PROVIDER_SIMULATOR_KDM_JSON_FILE = constants.DEFAULT_KDM_JSON_FILE
-
-# Time in seconds for events waiting.
-SETUP_TIMEOUT_SEC = constants.SETUP_TIMEOUT_SEC
-BECOME_DISCOVERABLE_TIMEOUT_SEC = constants.BECOME_DISCOVERABLE_TIMEOUT_SEC
-START_ADVERTISING_TIMEOUT_SEC = constants.START_ADVERTISING_TIMEOUT_SEC
-HALF_SHEET_POPUP_TIMEOUT_SEC = constants.HALF_SHEET_POPUP_TIMEOUT_SEC
-MANAGE_ACCOUNT_DEVICE_TIMEOUT_SEC = constants.AVERAGE_PAIRING_TIMEOUT_SEC * 2
-
-
-class InitialPairingTest(fast_pair_base_test.FastPairBaseTest):
-    """Fast Pair initial pairing test."""
-
-    def setup_test(self) -> None:
-        super().setup_test()
-        self._provider.start_model_id_advertising(PROVIDER_SIMULATOR_MODEL_ID,
-                                                  PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY)
-        self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
-        self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
-        self._seeker.put_anti_spoof_key_device_metadata(PROVIDER_SIMULATOR_MODEL_ID,
-                                                        PROVIDER_SIMULATOR_KDM_JSON_FILE)
-        self._seeker.set_fast_pair_scan_enabled(True)
-
-    # TODO(b/214015364): Remove Bluetooth bound on both sides ("Forget device").
-    def teardown_test(self) -> None:
-        self._seeker.set_fast_pair_scan_enabled(False)
-        self._provider.teardown_provider_simulator()
-        self._seeker.dismiss_halfsheet()
-        super().teardown_test()
-
-    def test_seeker_initial_pair_provider(self) -> None:
-        self._seeker.wait_and_assert_halfsheet_showed(
-            timeout_seconds=HALF_SHEET_POPUP_TIMEOUT_SEC,
-            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID)
-        self._seeker.start_pairing()
-        self._seeker.wait_and_assert_account_device(
-            get_account_key_from_provider=self._provider.get_latest_received_account_key,
-            timeout_seconds=MANAGE_ACCOUNT_DEVICE_TIMEOUT_SEC)
diff --git a/nearby/tests/multidevices/host/seeker_discover_provider_test.py b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
deleted file mode 100644
index 6356595..0000000
--- a/nearby/tests/multidevices/host/seeker_discover_provider_test.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#  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.
-
-"""CTS-V Nearby Mainline Fast Pair end-to-end test case: seeker can discover the provider."""
-
-from test_helper import constants
-from test_helper import fast_pair_base_test
-
-# The model ID to simulate on provider side.
-PROVIDER_SIMULATOR_MODEL_ID = constants.DEFAULT_MODEL_ID
-# The public key to simulate as registered headsets.
-PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY = constants.DEFAULT_ANTI_SPOOFING_KEY
-
-# Time in seconds for events waiting.
-BECOME_DISCOVERABLE_TIMEOUT_SEC = constants.BECOME_DISCOVERABLE_TIMEOUT_SEC
-START_ADVERTISING_TIMEOUT_SEC = constants.START_ADVERTISING_TIMEOUT_SEC
-SCAN_TIMEOUT_SEC = constants.SCAN_TIMEOUT_SEC
-
-
-class SeekerDiscoverProviderTest(fast_pair_base_test.FastPairBaseTest):
-    """Fast Pair seeker discover provider test."""
-
-    def setup_test(self) -> None:
-        super().setup_test()
-        self._provider.start_model_id_advertising(
-            PROVIDER_SIMULATOR_MODEL_ID, PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY)
-        self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
-        self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
-        self._seeker.start_scan()
-
-    def teardown_test(self) -> None:
-        self._seeker.stop_scan()
-        self._provider.teardown_provider_simulator()
-        super().teardown_test()
-
-    def test_seeker_start_scanning_find_provider(self) -> None:
-        provider_ble_mac_address = self._provider.get_ble_mac_address()
-        self._seeker.wait_and_assert_provider_found(
-            timeout_seconds=SCAN_TIMEOUT_SEC,
-            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID,
-            expected_ble_mac_address=provider_ble_mac_address)
diff --git a/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py b/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py
deleted file mode 100644
index f6561e5..0000000
--- a/nearby/tests/multidevices/host/seeker_show_halfsheet_test.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#  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.
-
-"""CTS-V Nearby Mainline Fast Pair end-to-end test case: seeker show half sheet UI."""
-
-from test_helper import constants
-from test_helper import fast_pair_base_test
-
-# The model ID to simulate on provider side.
-PROVIDER_SIMULATOR_MODEL_ID = constants.DEFAULT_MODEL_ID
-# The public key to simulate as registered headsets.
-PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY = constants.DEFAULT_ANTI_SPOOFING_KEY
-# The anti-spoof key device metadata JSON file for data provider at seeker side.
-PROVIDER_SIMULATOR_KDM_JSON_FILE = constants.DEFAULT_KDM_JSON_FILE
-
-# Time in seconds for events waiting.
-SETUP_TIMEOUT_SEC = constants.SETUP_TIMEOUT_SEC
-BECOME_DISCOVERABLE_TIMEOUT_SEC = constants.BECOME_DISCOVERABLE_TIMEOUT_SEC
-START_ADVERTISING_TIMEOUT_SEC = constants.START_ADVERTISING_TIMEOUT_SEC
-HALF_SHEET_POPUP_TIMEOUT_SEC = constants.HALF_SHEET_POPUP_TIMEOUT_SEC
-
-
-class SeekerShowHalfSheetTest(fast_pair_base_test.FastPairBaseTest):
-    """Fast Pair seeker show half sheet UI test."""
-
-    def setup_test(self) -> None:
-        super().setup_test()
-        self._provider.start_model_id_advertising(PROVIDER_SIMULATOR_MODEL_ID,
-                                                  PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY)
-        self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
-        self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
-        self._seeker.put_anti_spoof_key_device_metadata(PROVIDER_SIMULATOR_MODEL_ID,
-                                                        PROVIDER_SIMULATOR_KDM_JSON_FILE)
-        self._seeker.set_fast_pair_scan_enabled(True)
-
-    def teardown_test(self) -> None:
-        self._seeker.set_fast_pair_scan_enabled(False)
-        self._provider.teardown_provider_simulator()
-        self._seeker.dismiss_halfsheet()
-        super().teardown_test()
-
-    def test_seeker_show_half_sheet(self) -> None:
-        self._seeker.wait_and_assert_halfsheet_showed(
-            timeout_seconds=HALF_SHEET_POPUP_TIMEOUT_SEC,
-            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID)
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
deleted file mode 100644
index 9a580fb..0000000
--- a/nearby/tests/multidevices/host/suite_main.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#  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.
-
-"""The entry point for Nearby Mainline multi devices end-to-end test suite."""
-
-import logging
-import sys
-
-from mobly import suite_runner
-
-import initial_pairing_test
-import seeker_discover_provider_test
-import seeker_show_halfsheet_test
-
-_BOOTSTRAP_LOGGING_FILENAME = '/tmp/nearby_multi_devices_test_suite_log.txt'
-_TEST_CLASSES_LIST = [
-    seeker_discover_provider_test.SeekerDiscoverProviderTest,
-    seeker_show_halfsheet_test.SeekerShowHalfSheetTest,
-    initial_pairing_test.InitialPairingTest,
-]
-
-
-if __name__ == '__main__':
-    logging.basicConfig(filename=_BOOTSTRAP_LOGGING_FILENAME, level=logging.INFO)
-    if '--' in sys.argv:
-        index = sys.argv.index('--')
-        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
-    suite_runner.run_suite(test_classes=_TEST_CLASSES_LIST)
diff --git a/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt b/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt
deleted file mode 100644
index d3deb40..0000000
--- a/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-[
-  {
-    "account_key": "BPy5AaSyMfrFvMNgr6f7GA==",
-    "sha256_account_key_public_address": "jNGRz+Ni6ZuLd8hVF3lmGoJnF5byXBUyVi9CmnrF1so=",
-    "fast_pair_device_metadata": {
-      "image_url": "https:\/\/lh3.googleusercontent.com\/2PffmZiopo2AjT8sshX0Se3jV-91cp4yOCIay2bBvZqKoKGVy5B4uyzdHsde6UrUSGaoCqV-h4edd5ZljA4oSGc",
-      "intent_uri": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms\/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.apps.wearables.maestro.companion;end",
-      "ble_tx_power": 0,
-      "trigger_distance": 0,
-      "device_type": 0,
-      "left_bud_url": "",
-      "right_bud_url": "",
-      "case_url": "",
-      "initial_notification_description": "Tap to pair. Earbuds will be tied to %s",
-      "initial_notification_description_no_account": "Tap to pair with this device",
-      "initial_pairing_description": "Pixel Buds A-Series will appear on devices linked with ericth.nearby.dogfood@gmail.com",
-      "connect_success_companion_app_installed": "Your device is ready to be set up",
-      "connect_success_companion_app_not_installed": "Download the device app on Google Play to see all available features",
-      "subsequent_pairing_description": "Connect %s to this phone",
-      "retroactive_pairing_description": "Save device to %s for faster pairing to your other devices",
-      "wait_launch_companion_app_description": "This will take a few moments",
-      "fail_connect_go_to_settings_description": "Try manually pairing to the device by going to Settings"
-    },
-    "fast_pair_discovery_item": {
-      "id": "",
-      "mac_address": "",
-      "action_url": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms\/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.apps.wearables.maestro.companion;end",
-      "device_name": "",
-      "title": "Pixel Buds A-Series",
-      "description": "Tap to pair with this device",
-      "display_url": "",
-      "last_observation_timestamp_millis": 0,
-      "first_observation_timestamp_millis": 0,
-      "state": 1,
-      "action_url_type": 2,
-      "rssi": 0,
-      "pending_app_install_timestamp_millis": 0,
-      "tx_power": 0,
-      "app_name": "",
-      "package_name": "",
-      "trigger_id": "",
-      "icon_png": "",
-      "icon_fife_url": "https:\/\/lh3.googleusercontent.com\/2PffmZiopo2AjT8sshX0Se3jV-91cp4yOCIay2bBvZqKoKGVy5B4uyzdHsde6UrUSGaoCqV-h4edd5ZljA4oSGc",
-      "authentication_public_key_secp256r1": "z+grhW8lWVA34JUQhXOxMrk1WqVy+VpEDd2K+01ZJvS6KdV0OUg7FRMzq+ITuOqKO\/2TIRKEAEfMKdyk2Ob1Vw=="
-    }
-  }
-]
\ No newline at end of file
diff --git a/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt b/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt
deleted file mode 100644
index 3611b03..0000000
--- a/nearby/tests/multidevices/host/test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "anti_spoofing_public_key_str": "z+grhW8lWVA34JUQhXOxMrk1WqVy+VpEDd2K+01ZJvS6KdV0OUg7FRMzq+ITuOqKO\/2TIRKEAEfMKdyk2Ob1Vw==",
-  "fast_pair_device_metadata": {
-    "image_url": "https:\/\/lh3.googleusercontent.com\/2PffmZiopo2AjT8sshX0Se3jV-91cp4yOCIay2bBvZqKoKGVy5B4uyzdHsde6UrUSGaoCqV-h4edd5ZljA4oSGc",
-    "intent_uri": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms\/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.apps.wearables.maestro.companion;end",
-    "ble_tx_power": -11,
-    "trigger_distance": 0.6000000238418579,
-    "device_type": 7,
-    "name": "Pixel Buds A-Series",
-    "left_bud_url": "https:\/\/lh3.googleusercontent.com\/O8SVJ5E7CXUkpkym7ibZbp6wypuO7HaTFcslT_FjmEzJX4KHoIY_kzLTdK2kwJXiDBgg8cC__sG-JJ5aVnQtFjQ",
-    "right_bud_url": "https:\/\/lh3.googleusercontent.com\/X_FsRmEKH_fgKzvopyrlyWJAdczRel42Tih7p9-e-U48gBTaggGVQx70K27TzlqIaqYVuaNpTnGoUsKIgiy4WA",
-    "case_url": "https:\/\/lh3.googleusercontent.com\/mNZ7CGplQSpZhoY79jXDQU4B65eY2f0SndnYZLk1PSm8zKTYeRU7REmrLL_pptD6HpVI2F_oQ6xhhtZKOvB8EQ",
-    "initial_notification_description": "Tap to pair. Earbuds will be tied to %s",
-    "initial_notification_description_no_account": "Tap to pair with this device",
-    "open_companion_app_description": "Tap to finish setup",
-    "update_companion_app_description": "Tap to update device settings and finish setup",
-    "download_companion_app_description": "Tap to download device app on Google Play and see all features",
-    "unable_to_connect_title": "Unable to connect",
-    "unable_to_connect_description": "Try manually pairing to the device",
-    "initial_pairing_description": "%s will appear on devices linked with %s",
-    "connect_success_companion_app_installed": "Your device is ready to be set up",
-    "connect_success_companion_app_not_installed": "Download the device app on Google Play to see all available features",
-    "subsequent_pairing_description": "Connect %s to this phone",
-    "retroactive_pairing_description": "Save device to %s for faster pairing to your other devices",
-    "wait_launch_companion_app_description": "This will take a few moments",
-    "fail_connect_go_to_settings_description": "Try manually pairing to the device by going to Settings"
-  }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/host/test_data/fastpair/simulator_account_devicemeta_json.txt b/nearby/tests/multidevices/host/test_data/fastpair/simulator_account_devicemeta_json.txt
deleted file mode 100644
index ed60860..0000000
--- a/nearby/tests/multidevices/host/test_data/fastpair/simulator_account_devicemeta_json.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-[
-  {
-    "account_key": "BPy5AaSyMfrFvMNgr6f7GA==",
-    "sha256_account_key_public_address": "jNGRz+Ni6ZuLd8hVF3lmGoJnF5byXBUyVi9CmnrF1so=",
-    "fast_pair_device_metadata": {
-      "ble_tx_power": 0,
-      "case_url": "",
-      "connect_success_companion_app_installed": "Your device is ready to be set up",
-      "connect_success_companion_app_not_installed": "Download the device app on Google Play to see all available features",
-      "device_type": 0,
-      "fail_connect_go_to_settings_description": "Try manually pairing to the device by going to Settings",
-      "image_url": "https://lh3.googleusercontent.com/2PffmZiopo2AjT8sshX0Se3jV-91cp4yOCIay2bBvZqKoKGVy5B4uyzdHsde6UrUSGaoCqV-h4edd5ZljA4oSGc",
-      "initial_notification_description": "Tap to pair. Earbuds will be tied to %s",
-      "initial_notification_description_no_account": "Tap to pair with this device",
-      "initial_pairing_description": "Pixel Buds A-Series will appear on devices linked with ericth.nearby.dogfood@gmail.com",
-      "intent_uri": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.apps.wearables.maestro.companion;end",
-      "left_bud_url": "",
-      "retroactive_pairing_description": "Save device to %s for faster pairing to your other devices",
-      "right_bud_url": "",
-      "subsequent_pairing_description": "Connect %s to this phone",
-      "trigger_distance": 0,
-      "wait_launch_companion_app_description": "This will take a few moments"
-    },
-    "fast_pair_discovery_item": {
-      "action_url": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.apps.wearables.maestro.companion;end",
-      "action_url_type": 2,
-      "app_name": "",
-      "authentication_public_key_secp256r1": "z+grhW8lWVA34JUQhXOxMrk1WqVy+VpEDd2K+01ZJvS6KdV0OUg7FRMzq+ITuOqKO/2TIRKEAEfMKdyk2Ob1Vw==",
-      "description": "Tap to pair with this device",
-      "device_name": "",
-      "display_url": "",
-      "first_observation_timestamp_millis": 0,
-      "icon_fife_url": "https://lh3.googleusercontent.com/2PffmZiopo2AjT8sshX0Se3jV-91cp4yOCIay2bBvZqKoKGVy5B4uyzdHsde6UrUSGaoCqV-h4edd5ZljA4oSGc",
-      "icon_png": "",
-      "id": "",
-      "last_observation_timestamp_millis": 0,
-      "mac_address": "",
-      "package_name": "",
-      "pending_app_install_timestamp_millis": 0,
-      "rssi": 0,
-      "state": 1,
-      "title": "Pixel Buds A-Series",
-      "trigger_id": "",
-      "tx_power": 0
-    }
-  }
-]
\ No newline at end of file
diff --git a/nearby/tests/multidevices/host/test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt b/nearby/tests/multidevices/host/test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt
deleted file mode 100644
index fc9706a..0000000
--- a/nearby/tests/multidevices/host/test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-  "anti_spoofing_public_key_str": "sjp\/AOS7+VnTCaueeWorjdeJ8Nc32EOmpe\/QRhzY9+cMNELU1QA3jzgvUXdWW73nl6+EN01eXtLBu2Fw9CGmfA==",
-  "fast_pair_device_metadata": {
-    "ble_tx_power": -10,
-    "case_url": "https://lh3.googleusercontent.com/mNZ7CGplQSpZhoY79jXDQU4B65eY2f0SndnYZLk1PSm8zKTYeRU7REmrLL_pptD6HpVI2F_oQ6xhhtZKOvB8EQ",
-    "connect_success_companion_app_installed": "Your device is ready to be set up",
-    "connect_success_companion_app_not_installed": "Download the device app on Google Play to see all available features",
-    "device_type": 7,
-    "download_companion_app_description": "Tap to download device app on Google Play and see all features",
-    "fail_connect_go_to_settings_description": "Try manually pairing to the device by going to Settings",
-    "image_url": "https://lh3.googleusercontent.com/THpAzISZGa5F86cMsBcTPhRWefBPc5dorBxWdOPCGvbFg6ZMHUjFuE-4kbLuoLoIMHf3Fd8jUvvcxnjp_Q",
-    "initial_notification_description": "Tap to pair. Earbuds will be tied to %s",
-    "initial_notification_description_no_account": "Tap to pair with this device",
-    "initial_pairing_description": "%s will appear on devices linked with %s",
-    "intent_uri": "intent:#Intent;action=com.google.android.gms.nearby.discovery%3AACTION_MAGIC_PAIR;package=com.google.android.gms;component=com.google.android.gms/.nearby.discovery.service.DiscoveryService;S.com.google.android.gms.nearby.discovery%3AEXTRA_COMPANION_APP=com.google.android.testapp;end",
-    "left_bud_url": "https://lh3.googleusercontent.com/O8SVJ5E7CXUkpkym7ibZbp6wypuO7HaTFcslT_FjmEzJX4KHoIY_kzLTdK2kwJXiDBgg8cC__sG-JJ5aVnQtFjQ",
-    "name": "Fast Pair Provider Simulator",
-    "open_companion_app_description": "Tap to finish setup",
-    "retroactive_pairing_description": "Save device to %s for faster pairing to your other devices",
-    "right_bud_url": "https://lh3.googleusercontent.com/X_FsRmEKH_fgKzvopyrlyWJAdczRel42Tih7p9-e-U48gBTaggGVQx70K27TzlqIaqYVuaNpTnGoUsKIgiy4WA",
-    "subsequent_pairing_description": "Connect %s to this phone",
-    "trigger_distance": 0.6000000238418579,
-    "unable_to_connect_description": "Try manually pairing to the device",
-    "unable_to_connect_title": "Unable to connect",
-    "update_companion_app_description": "Tap to update device settings and finish setup",
-    "wait_launch_companion_app_description": "This will take a few moments"
-  }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/host/test_helper/__init__.py b/nearby/tests/multidevices/host/test_helper/__init__.py
deleted file mode 100644
index b0cae91..0000000
--- a/nearby/tests/multidevices/host/test_helper/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-#  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.
diff --git a/nearby/tests/multidevices/host/test_helper/constants.py b/nearby/tests/multidevices/host/test_helper/constants.py
deleted file mode 100644
index 342be8f..0000000
--- a/nearby/tests/multidevices/host/test_helper/constants.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#  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.
-
-# Default model ID to simulate on provider side.
-DEFAULT_MODEL_ID = '00000c'
-
-# Default public key to simulate as registered headsets.
-DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
-
-# Default anti-spoof Key Device Metadata JSON file for data provider at seeker side.
-DEFAULT_KDM_JSON_FILE = 'simulator_antispoofkey_devicemeta_json.txt'
-
-# Time in seconds for events waiting according to Fast Pair certification guidelines:
-# https://developers.google.com/nearby/fast-pair/certification-guideline
-SETUP_TIMEOUT_SEC = 5
-BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
-START_ADVERTISING_TIMEOUT_SEC = 5
-SCAN_TIMEOUT_SEC = 5
-HALF_SHEET_POPUP_TIMEOUT_SEC = 5
-AVERAGE_PAIRING_TIMEOUT_SEC = 12
-
-# The phone to simulate Fast Pair provider (like headphone) needs changes in Android system:
-# 1. System permission check removal
-# 2. Adjusts Bluetooth profile configurations
-# The build fingerprint of the custom ROM for Fast Pair provider simulator.
-FAST_PAIR_PROVIDER_SIMULATOR_BUILD_FINGERPRINT = (
-    'google/bramble/bramble:Tiramisu/MASTER/eng.hylo.20211019.091550:userdebug/dev-keys')
diff --git a/nearby/tests/multidevices/host/test_helper/event_helper.py b/nearby/tests/multidevices/host/test_helper/event_helper.py
deleted file mode 100644
index 8abf05c..0000000
--- a/nearby/tests/multidevices/host/test_helper/event_helper.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#  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.
-
-"""This is a shared library to help handling Mobly event waiting logic."""
-
-import time
-from typing import Callable
-
-from mobly.controllers.android_device_lib import callback_handler
-from mobly.controllers.android_device_lib import snippet_event
-
-# Abbreviations for common use type
-CallbackHandler = callback_handler.CallbackHandler
-SnippetEvent = snippet_event.SnippetEvent
-
-# Type definition for the callback functions to make code formatted nicely
-OnReceivedCallback = Callable[[SnippetEvent, int], bool]
-OnWaitingCallback = Callable[[int], None]
-OnMissedCallback = Callable[[], None]
-
-
-def wait_callback_event(callback_event_handler: CallbackHandler,
-                        event_name: str, timeout_seconds: int,
-                        on_received: OnReceivedCallback,
-                        on_waiting: OnWaitingCallback,
-                        on_missed: OnMissedCallback) -> None:
-    """Waits until the matched event has been received or timeout.
-
-    Here we keep waitAndGet for event callback from EventSnippet.
-    We loop until over timeout_seconds instead of directly
-    waitAndGet(timeout=teardown_timeout_seconds). Because there is
-    MAX_TIMEOUT limitation in callback_handler of Mobly.
-
-    Args:
-      callback_event_handler: Mobly callback events handler.
-      event_name: the specific name of the event to wait.
-      timeout_seconds: the number of seconds to wait before giving up.
-      on_received: calls when event received, return false to keep waiting.
-      on_waiting: calls when waitAndGet timeout.
-      on_missed: calls when giving up.
-    """
-    start_time = time.perf_counter()
-    deadline = start_time + timeout_seconds
-    while time.perf_counter() < deadline:
-        remaining_time_sec = min(callback_handler.DEFAULT_TIMEOUT,
-                                 deadline - time.perf_counter())
-        try:
-            event = callback_event_handler.waitAndGet(
-                event_name, timeout=remaining_time_sec)
-        except callback_handler.TimeoutError:
-            elapsed_time = int(time.perf_counter() - start_time)
-            on_waiting(elapsed_time)
-        else:
-            elapsed_time = int(time.perf_counter() - start_time)
-            if on_received(event, elapsed_time):
-                break
-    else:
-        on_missed()
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_base_test.py b/nearby/tests/multidevices/host/test_helper/fast_pair_base_test.py
deleted file mode 100644
index 8b84839..0000000
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_base_test.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#  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.
-
-"""Base for all Nearby Mainline Fast Pair end-to-end test cases."""
-
-from typing import List, Tuple
-
-from mobly import base_test
-from mobly import signals
-from mobly.controllers import android_device
-
-from test_helper import constants
-from test_helper import fast_pair_provider_simulator
-from test_helper import fast_pair_seeker
-
-# Abbreviations for common use type.
-AndroidDevice = android_device.AndroidDevice
-FastPairProviderSimulator = fast_pair_provider_simulator.FastPairProviderSimulator
-FastPairSeeker = fast_pair_seeker.FastPairSeeker
-REQUIRED_BUILD_FINGERPRINT = constants.FAST_PAIR_PROVIDER_SIMULATOR_BUILD_FINGERPRINT
-
-
-class FastPairBaseTest(base_test.BaseTestClass):
-    """Base class for all Nearby Mainline Fast Pair end-to-end classes to inherit."""
-
-    _duts: List[AndroidDevice]
-    _provider: FastPairProviderSimulator
-    _seeker: FastPairSeeker
-
-    def setup_class(self) -> None:
-        super().setup_class()
-        self._duts = self.register_controller(android_device, min_number=2)
-
-        provider_ad, seeker_ad = self._check_devices_supported()
-        self._provider = FastPairProviderSimulator(provider_ad)
-        self._seeker = FastPairSeeker(seeker_ad)
-        self._provider.load_snippet()
-        self._seeker.load_snippet()
-
-    def setup_test(self) -> None:
-        super().setup_test()
-        self._provider.setup_provider_simulator(constants.SETUP_TIMEOUT_SEC)
-
-    def teardown_test(self) -> None:
-        super().teardown_test()
-        # Create per-test excepts of logcat.
-        for dut in self._duts:
-            dut.services.create_output_excerpts_all(self.current_test_info)
-
-    def _check_devices_supported(self) -> Tuple[AndroidDevice, AndroidDevice]:
-        # Assume the 1st phone is provider, the 2nd one is seeker.
-        provider_ad, seeker_ad = self._duts[:2]
-
-        for ad in self._duts:
-            if ad.build_info['build_fingerprint'] == REQUIRED_BUILD_FINGERPRINT:
-                if ad != provider_ad:
-                    provider_ad, seeker_ad = seeker_ad, provider_ad
-                break
-        else:
-            raise signals.TestAbortClass(
-                f'None of phones has custom ROM ({REQUIRED_BUILD_FINGERPRINT}) for Fast Pair '
-                f'provider simulator. Skip all the test cases!')
-
-        return provider_ad, seeker_ad
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
deleted file mode 100644
index 592c4f1..0000000
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
+++ /dev/null
@@ -1,195 +0,0 @@
-#  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.
-
-"""Fast Pair provider simulator role."""
-
-import time
-
-from mobly import asserts
-from mobly.controllers import android_device
-from mobly.controllers.android_device_lib import jsonrpc_client_base
-from mobly.controllers.android_device_lib import snippet_event
-from typing import Optional
-
-from test_helper import event_helper
-
-# The package name of the provider simulator snippet.
-FP_PROVIDER_SIMULATOR_SNIPPETS_PACKAGE = 'android.nearby.multidevices'
-
-# Events reported from the provider simulator snippet.
-ON_A2DP_SINK_PROFILE_CONNECT_EVENT = 'onA2DPSinkProfileConnected'
-ON_SCAN_MODE_CHANGE_EVENT = 'onScanModeChange'
-ON_ADVERTISING_CHANGE_EVENT = 'onAdvertisingChange'
-
-# Target scan mode.
-DISCOVERABLE_MODE = 'DISCOVERABLE'
-
-# Abbreviations for common use type.
-AndroidDevice = android_device.AndroidDevice
-SnippetEvent = snippet_event.SnippetEvent
-wait_for_event = event_helper.wait_callback_event
-
-
-class FastPairProviderSimulator:
-    """A proxy for provider simulator snippet on the device."""
-
-    def __init__(self, ad: AndroidDevice) -> None:
-        self._ad = ad
-        self._ad.debug_tag = 'FastPairProviderSimulator'
-        self._provider_status_callback = None
-
-    def load_snippet(self) -> None:
-        """Starts the provider simulator snippet and connects.
-
-        Raises:
-          SnippetError: Illegal load operations are attempted.
-        """
-        self._ad.load_snippet(
-            name='fp', package=FP_PROVIDER_SIMULATOR_SNIPPETS_PACKAGE)
-
-    def setup_provider_simulator(self, timeout_seconds: int) -> None:
-        """Sets up the Fast Pair provider simulator.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-        """
-        setup_status_callback = self._ad.fp.setupProviderSimulator()
-
-        def _on_a2dp_sink_profile_connect_event_received(_, elapsed_time: int) -> bool:
-            self._ad.log.info('Provider simulator connected to A2DP sink in %d seconds.',
-                              elapsed_time)
-            return True
-
-        def _on_a2dp_sink_profile_connect_event_waiting(elapsed_time: int) -> None:
-            self._ad.log.info(
-                'Still waiting "%s" event callback from provider side '
-                'after %d seconds...', ON_A2DP_SINK_PROFILE_CONNECT_EVENT, elapsed_time)
-
-        def _on_a2dp_sink_profile_connect_event_missed() -> None:
-            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
-                         f'the specific "{ON_A2DP_SINK_PROFILE_CONNECT_EVENT}" event.')
-
-        wait_for_event(
-            callback_event_handler=setup_status_callback,
-            event_name=ON_A2DP_SINK_PROFILE_CONNECT_EVENT,
-            timeout_seconds=timeout_seconds,
-            on_received=_on_a2dp_sink_profile_connect_event_received,
-            on_waiting=_on_a2dp_sink_profile_connect_event_waiting,
-            on_missed=_on_a2dp_sink_profile_connect_event_missed)
-
-    def start_model_id_advertising(self, model_id: str, anti_spoofing_key: str) -> None:
-        """Starts model id advertising for scanning and initial pairing.
-
-        Args:
-          model_id: A 3-byte hex string for seeker side to recognize the device (ex:
-            0x00000C).
-          anti_spoofing_key: A public key for registered headsets.
-        """
-        self._ad.log.info(
-            'Provider simulator starts advertising as model id "%s" with anti-spoofing key "%s".',
-            model_id, anti_spoofing_key)
-        self._provider_status_callback = (
-            self._ad.fp.startModelIdAdvertising(model_id, anti_spoofing_key))
-
-    def teardown_provider_simulator(self) -> None:
-        """Tears down the Fast Pair provider simulator."""
-        self._ad.fp.teardownProviderSimulator()
-
-    def get_ble_mac_address(self) -> str:
-        """Gets Bluetooth low energy mac address of the provider simulator.
-
-        The BLE mac address will be set by the AdvertisingSet.getOwnAddress()
-        callback. This is the callback flow in the custom Android build. It takes
-        a while after advertising started so we use retry here to wait it.
-
-        Returns:
-          The BLE mac address of the Fast Pair provider simulator.
-        """
-        for _ in range(3):
-            try:
-                return self._ad.fp.getBluetoothLeAddress()
-            except jsonrpc_client_base.ApiError:
-                time.sleep(1)
-
-    def wait_for_discoverable_mode(self, timeout_seconds: int) -> None:
-        """Waits onScanModeChange event to ensure provider is discoverable.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-        """
-
-        def _on_scan_mode_change_event_received(
-                scan_mode_change_event: SnippetEvent, elapsed_time: int) -> bool:
-            scan_mode = scan_mode_change_event.data['mode']
-            self._ad.log.info(
-                'Provider simulator changed the scan mode to %s in %d seconds.',
-                scan_mode, elapsed_time)
-            return scan_mode == DISCOVERABLE_MODE
-
-        def _on_scan_mode_change_event_waiting(elapsed_time: int) -> None:
-            self._ad.log.info(
-                'Still waiting "%s" event callback from provider side '
-                'after %d seconds...', ON_SCAN_MODE_CHANGE_EVENT, elapsed_time)
-
-        def _on_scan_mode_change_event_missed() -> None:
-            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
-                         f'the specific "{ON_SCAN_MODE_CHANGE_EVENT}" event.')
-
-        wait_for_event(
-            callback_event_handler=self._provider_status_callback,
-            event_name=ON_SCAN_MODE_CHANGE_EVENT,
-            timeout_seconds=timeout_seconds,
-            on_received=_on_scan_mode_change_event_received,
-            on_waiting=_on_scan_mode_change_event_waiting,
-            on_missed=_on_scan_mode_change_event_missed)
-
-    def wait_for_advertising_start(self, timeout_seconds: int) -> None:
-        """Waits onAdvertisingChange event to ensure provider is advertising.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-        """
-
-        def _on_advertising_mode_change_event_received(
-                scan_mode_change_event: SnippetEvent, elapsed_time: int) -> bool:
-            advertising_mode = scan_mode_change_event.data['isAdvertising']
-            self._ad.log.info(
-                'Provider simulator changed the advertising mode to %s in %d seconds.',
-                advertising_mode, elapsed_time)
-            return advertising_mode
-
-        def _on_advertising_mode_change_event_waiting(elapsed_time: int) -> None:
-            self._ad.log.info(
-                'Still waiting "%s" event callback from provider side '
-                'after %d seconds...', ON_ADVERTISING_CHANGE_EVENT, elapsed_time)
-
-        def _on_advertising_mode_change_event_missed() -> None:
-            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
-                         f'the specific "{ON_ADVERTISING_CHANGE_EVENT}" event.')
-
-        wait_for_event(
-            callback_event_handler=self._provider_status_callback,
-            event_name=ON_ADVERTISING_CHANGE_EVENT,
-            timeout_seconds=timeout_seconds,
-            on_received=_on_advertising_mode_change_event_received,
-            on_waiting=_on_advertising_mode_change_event_waiting,
-            on_missed=_on_advertising_mode_change_event_missed)
-
-    def get_latest_received_account_key(self) -> Optional[str]:
-        """Gets the latest account key received on the provider side.
-
-        Returns:
-          The account key received at provider side.
-        """
-        return self._ad.fp.getLatestReceivedAccountKey()
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
deleted file mode 100644
index 64fc2f2..0000000
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
+++ /dev/null
@@ -1,186 +0,0 @@
-#  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.
-
-"""Fast Pair seeker role."""
-
-import json
-from typing import Callable, Optional
-
-from mobly import asserts
-from mobly.controllers import android_device
-from mobly.controllers.android_device_lib import snippet_event
-
-from test_helper import event_helper
-from test_helper import utils
-
-# The package name of the Nearby Mainline Fast Pair seeker Mobly snippet.
-FP_SEEKER_SNIPPETS_PACKAGE = 'android.nearby.multidevices'
-
-# Events reported from the seeker snippet.
-ON_PROVIDER_FOUND_EVENT = 'onDiscovered'
-ON_MANAGE_ACCOUNT_DEVICE_EVENT = 'onManageAccountDevice'
-
-# Abbreviations for common use type.
-AndroidDevice = android_device.AndroidDevice
-JsonObject = utils.JsonObject
-ProviderAccountKeyCallable = Callable[[], Optional[str]]
-SnippetEvent = snippet_event.SnippetEvent
-wait_for_event = event_helper.wait_callback_event
-
-
-class FastPairSeeker:
-    """A proxy for seeker snippet on the device."""
-
-    def __init__(self, ad: AndroidDevice) -> None:
-        self._ad = ad
-        self._ad.debug_tag = 'MainlineFastPairSeeker'
-        self._scan_result_callback = None
-        self._pairing_result_callback = None
-
-    def load_snippet(self) -> None:
-        """Starts the seeker snippet and connects.
-
-        Raises:
-          SnippetError: Illegal load operations are attempted.
-        """
-        self._ad.load_snippet(name='fp', package=FP_SEEKER_SNIPPETS_PACKAGE)
-
-    def start_scan(self) -> None:
-        """Starts scanning to find Fast Pair provider devices."""
-        self._scan_result_callback = self._ad.fp.startScan()
-
-    def stop_scan(self) -> None:
-        """Stops the Fast Pair seeker scanning."""
-        self._ad.fp.stopScan()
-
-    def wait_and_assert_provider_found(self, timeout_seconds: int,
-                                       expected_model_id: str,
-                                       expected_ble_mac_address: str) -> None:
-        """Waits and asserts any onDiscovered event from the seeker.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-          expected_model_id: The expected model ID of the remote Fast Pair provider
-            device.
-          expected_ble_mac_address: The expected BLE MAC address of the remote Fast
-            Pair provider device.
-        """
-
-        def _on_provider_found_event_received(provider_found_event: SnippetEvent,
-                                              elapsed_time: int) -> bool:
-            nearby_device_str = provider_found_event.data['device']
-            self._ad.log.info('Seeker discovered first provider(%s) in %d seconds.',
-                              nearby_device_str, elapsed_time)
-            return expected_ble_mac_address in nearby_device_str
-
-        def _on_provider_found_event_waiting(elapsed_time: int) -> None:
-            self._ad.log.info(
-                'Still waiting "%s" event callback from seeker side '
-                'after %d seconds...', ON_PROVIDER_FOUND_EVENT, elapsed_time)
-
-        def _on_provider_found_event_missed() -> None:
-            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
-                         f'the specific "{ON_PROVIDER_FOUND_EVENT}" event.')
-
-        wait_for_event(
-            callback_event_handler=self._scan_result_callback,
-            event_name=ON_PROVIDER_FOUND_EVENT,
-            timeout_seconds=timeout_seconds,
-            on_received=_on_provider_found_event_received,
-            on_waiting=_on_provider_found_event_waiting,
-            on_missed=_on_provider_found_event_missed)
-
-    def put_anti_spoof_key_device_metadata(self, model_id: str, kdm_json_file_name: str) -> None:
-        """Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
-
-        Args:
-          model_id: A string of model id to be associated with.
-          kdm_json_file_name: The FastPairAntispoofKeyDeviceMetadata JSON object.
-        """
-        self._ad.log.info('Puts FastPairAntispoofKeyDeviceMetadata into test data cache for '
-                          'model id "%s".', model_id)
-        kdm_json_object = utils.load_json_fast_pair_test_data(kdm_json_file_name)
-        self._ad.fp.putAntispoofKeyDeviceMetadata(
-            model_id,
-            utils.serialize_as_simplified_json_str(kdm_json_object))
-
-    def set_fast_pair_scan_enabled(self, enable: bool) -> None:
-        """Writes into Settings whether Fast Pair scan is enabled.
-
-        Args:
-          enable: whether the Fast Pair scan should be enabled.
-        """
-        self._ad.log.info('%s Fast Pair scan in Android settings.',
-                          'Enables' if enable else 'Disables')
-        self._ad.fp.setFastPairScanEnabled(enable)
-
-    def wait_and_assert_halfsheet_showed(self, timeout_seconds: int,
-                                         expected_model_id: str) -> None:
-        """Waits and asserts the onHalfSheetShowed event from the seeker.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-          expected_model_id: A 3-byte hex string for seeker side to recognize
-            the remote provider device (ex: 0x00000c).
-        """
-        self._ad.log.info('Waits and asserts the half sheet showed for model id "%s".',
-                          expected_model_id)
-        self._ad.fp.waitAndAssertHalfSheetShowed(expected_model_id, timeout_seconds)
-
-    def dismiss_halfsheet(self) -> None:
-        """Dismisses the half sheet UI if showed."""
-        self._ad.fp.dismissHalfSheet()
-
-    def start_pairing(self) -> None:
-        """Starts pairing the provider via "Connect" button on half sheet UI."""
-        self._pairing_result_callback = self._ad.fp.startPairing()
-
-    def wait_and_assert_account_device(
-            self, timeout_seconds: int,
-            get_account_key_from_provider: ProviderAccountKeyCallable) -> None:
-        """Waits and asserts the onHalfSheetShowed event from the seeker.
-
-        Args:
-          timeout_seconds: The number of seconds to wait before giving up.
-          get_account_key_from_provider: The callable to get expected account key from the provider
-            side.
-        """
-
-        def _on_manage_account_device_event_received(manage_account_device_event: SnippetEvent,
-                                                     elapsed_time: int) -> bool:
-            account_key_json_str = manage_account_device_event.data['accountDeviceJsonString']
-            account_key_from_seeker = json.loads(account_key_json_str)['account_key']
-            account_key_from_provider = get_account_key_from_provider()
-            self._ad.log.info('Seeker add an account device with account key "%s" in %d seconds.',
-                              account_key_from_seeker, elapsed_time)
-            self._ad.log.info('The latest provider side account key is "%s".',
-                              account_key_from_provider)
-            return account_key_from_seeker == account_key_from_provider
-
-        def _on_manage_account_device_event_waiting(elapsed_time: int) -> None:
-            self._ad.log.info(
-                'Still waiting "%s" event callback from seeker side '
-                'after %d seconds...', ON_MANAGE_ACCOUNT_DEVICE_EVENT, elapsed_time)
-
-        def _on_manage_account_device_event_missed() -> None:
-            asserts.fail(f'Timed out after {timeout_seconds} seconds waiting for '
-                         f'the specific "{ON_MANAGE_ACCOUNT_DEVICE_EVENT}" event.')
-
-        wait_for_event(
-            callback_event_handler=self._pairing_result_callback,
-            event_name=ON_MANAGE_ACCOUNT_DEVICE_EVENT,
-            timeout_seconds=timeout_seconds,
-            on_received=_on_manage_account_device_event_received,
-            on_waiting=_on_manage_account_device_event_waiting,
-            on_missed=_on_manage_account_device_event_missed)
diff --git a/nearby/tests/multidevices/host/test_helper/utils.py b/nearby/tests/multidevices/host/test_helper/utils.py
deleted file mode 100644
index a0acb57..0000000
--- a/nearby/tests/multidevices/host/test_helper/utils.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#  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.
-
-import json
-import pathlib
-import sys
-from typing import Any, Dict
-
-# Type definition
-JsonObject = Dict[str, Any]
-
-
-def load_json_fast_pair_test_data(json_file_name: str) -> JsonObject:
-    """Loads a JSON text file from test data directory into a Json object.
-
-    Args:
-      json_file_name: The name of the JSON file.
-    """
-    return json.loads(
-        pathlib.Path(sys.argv[0]).parent.joinpath(
-            'test_data', 'fastpair', json_file_name).read_text()
-    )
-
-
-def serialize_as_simplified_json_str(json_data: JsonObject) -> str:
-    """Serializes a JSON object into a string without empty space.
-
-    Args:
-      json_data: The JSON object to be serialized.
-    """
-    return json.dumps(json_data, separators=(',', ':'))
diff --git a/nearby/tests/multidevices/host/tool/fast_pair_data_provider_shell.sh b/nearby/tests/multidevices/host/tool/fast_pair_data_provider_shell.sh
deleted file mode 100755
index a74c1a9..0000000
--- a/nearby/tests/multidevices/host/tool/fast_pair_data_provider_shell.sh
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-#
-# 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.
-
-# A script to interactively manage FastPairTestDataCache of FastPairTestDataProviderService.
-#
-# FastPairTestDataProviderService (../../clients/test_service/fastpair_seeker_data_provider/) is a
-# run-Time configurable FastPairDataProviderService. It has a FastPairTestDataManager to receive
-# Intent broadcast to add/clear the FastPairTestDataCache. This cache provides the data to return to
-# the Nearby Mainline module for onXXX calls (ex: onLoadFastPairAntispoofKeyDeviceMetadata).
-#
-# To use this tool, make sure you:
-# 1. Flash the ROM your built to the device
-# 2. Build and install NearbyFastPairSeekerDataProvider to the device
-# m NearbyFastPairSeekerDataProvider
-# adb install -r -g ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairSeekerDataProvider/NearbyFastPairSeekerDataProvider.apk
-# 3. Check FastPairService can connect to the FastPairTestDataProviderService.
-# adb logcat ServiceMonitor:* *:S
-# (ex: ServiceMonitor: [FAST_PAIR_DATA_PROVIDER] connected to {
-# android.nearby.fastpair.seeker.dataprovider/android.nearby.fastpair.seeker.dataprovider.FastPairTestDataProviderService})
-#
-# Sample Usages:
-# 1. Send FastPairAntispoofKeyDeviceMetadata for PixelBuds-A to FastPairTestDataCache
-# ./fast_pair_data_provider_shell.sh -m=718c17  -a=../test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt
-# 2. Send FastPairAccountDevicesMetadata for PixelBuds-A to FastPairTestDataCache
-# ./fast_pair_data_provider_shell.sh -d=../test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt
-# 3. Send FastPairAntispoofKeyDeviceMetadata for Provider Simulator to FastPairTestDataCache
-# ./fast_pair_data_provider_shell.sh -m=00000c -a=../test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt
-# 4. Send FastPairAccountDevicesMetadata for Provider Simulator to FastPairTestDataCache
-# ./fast_pair_data_provider_shell.sh -d=../test_data/fastpair/simulator_account_devicemeta_json.txt
-# 5. Clear FastPairTestDataCache
-# ./fast_pair_data_provider_shell.sh -c
-#
-# Check logcat:
-# adb logcat FastPairTestDataManager:* FastPairTestDataProviderService:* *:S
-
-for i in "$@"; do
-  case $i in
-    -a=*|--ask=*)
-      ASK_FILE="${i#*=}"
-      shift # past argument=value
-      ;;
-    -m=*|--model=*)
-      MODEL_ID="${i#*=}"
-      shift # past argument=value
-      ;;
-    -d=*|--adm=*)
-      ADM_FILE="${i#*=}"
-      shift # past argument=value
-      ;;
-    -c)
-      CLEAR="true"
-      shift # past argument
-      ;;
-    -*|--*)
-      echo "Unknown option $i"
-      exit 1
-      ;;
-    *)
-      ;;
-  esac
-done
-
-readonly ACTION_BASE="android.nearby.fastpair.seeker.action"
-readonly ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA="$ACTION_BASE.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA"
-readonly ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA="$ACTION_BASE.ACCOUNT_KEY_DEVICE_METADATA"
-readonly ACTION_RESET_TEST_DATA_CACHE="$ACTION_BASE.RESET"
-readonly DATA_JSON_STRING_KEY="json"
-readonly DATA_MODEL_ID_STRING_KEY="modelId"
-
-if [[ -n "${ASK_FILE}" ]] && [[ -n "${MODEL_ID}" ]]; then
-  echo "Sending AntispoofKeyDeviceMetadata for model ${MODEL_ID} to the FastPairTestDataCache..."
-  ASK_JSON_TEXT=$(tr -d '\n' < "$ASK_FILE")
-  CMD="am broadcast -a $ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA "
-  CMD+="-e $DATA_MODEL_ID_STRING_KEY '$MODEL_ID' "
-  CMD+="-e $DATA_JSON_STRING_KEY '\"'$ASK_JSON_TEXT'\"'"
-  CMD="adb shell \"$CMD\""
-  echo "$CMD" && eval "$CMD"
-fi
-
-if [ -n "${ADM_FILE}" ]; then
-  echo "Sending AccountKeyDeviceMetadata to the FastPairTestDataCache..."
-  ADM_JSON_TEXT=$(tr -d '\n' < "$ADM_FILE")
-  CMD="am broadcast -a $ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA "
-  CMD+="-e $DATA_JSON_STRING_KEY '\"'$ADM_JSON_TEXT'\"'"
-  CMD="adb shell \"$CMD\""
-  echo "$CMD" && eval "$CMD"
-fi
-
-if [ -n "${CLEAR}" ]; then
-  echo "Cleaning FastPairTestDataCache..."
-  CMD="adb shell am broadcast -a $ACTION_RESET_TEST_DATA_CACHE"
-  echo "$CMD" && eval "$CMD"
-fi
diff --git a/nearby/tests/robotests/Android.bp b/nearby/tests/robotests/Android.bp
deleted file mode 100644
index 70fa0c3..0000000
--- a/nearby/tests/robotests/Android.bp
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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.
-
-//############################################
-// Nearby Robolectric test target. #
-//############################################
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_robolectric_test {
-    name: "NearbyRoboTests",
-    srcs: ["src/**/*.java"],
-    instrumentation_for: "NearbyFakeTestApp",
-    java_resource_dirs: ["config"],
-
-    libs: [
-        "android-support-annotations",
-        "services.core",
-    ],
-
-    static_libs: [
-        "androidx.test.core",
-        "androidx.core_core",
-        "androidx.annotation_annotation",
-        "androidx.legacy_legacy-support-v4",
-        "androidx.recyclerview_recyclerview",
-        "androidx.preference_preference",
-        "androidx.appcompat_appcompat",
-        "androidx.lifecycle_lifecycle-runtime",
-        "androidx.mediarouter_mediarouter-nodeps",
-        "error_prone_annotations",
-        "service-nearby-pre-jarjar",
-        "truth-prebuilt",
-        "robolectric_android-all-stub",
-    ],
-
-    test_options: {
-        // timeout in seconds.
-        timeout: 36000,
-    },
-    upstream: true,
-}
diff --git a/nearby/tests/robotests/AndroidManifest.xml b/nearby/tests/robotests/AndroidManifest.xml
deleted file mode 100644
index 25376cf..0000000
--- a/nearby/tests/robotests/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.nearby.common.bluetooth.fastpair.test">
-</manifest>
diff --git a/nearby/tests/robotests/config/robolectric.properties b/nearby/tests/robotests/config/robolectric.properties
deleted file mode 100644
index 932de7d..0000000
--- a/nearby/tests/robotests/config/robolectric.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2021 Google Inc.
-#
-# 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.
-#
-sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/nearby/tests/robotests/fake_app/Android.bp b/nearby/tests/robotests/fake_app/Android.bp
deleted file mode 100644
index 707b38f..0000000
--- a/nearby/tests/robotests/fake_app/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
-    name: "NearbyFakeTestApp",
-    srcs: ["*.java"],
-    platform_apis: true,
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/nearby/tests/robotests/fake_app/AndroidManifest.xml b/nearby/tests/robotests/fake_app/AndroidManifest.xml
deleted file mode 100644
index fdb5390..0000000
--- a/nearby/tests/robotests/fake_app/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.nearby" />
diff --git a/nearby/tests/robotests/fake_app/Empty.java b/nearby/tests/robotests/fake_app/Empty.java
deleted file mode 100644
index 96619d5..0000000
--- a/nearby/tests/robotests/fake_app/Empty.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * 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.
- */
-
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Bluelet.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Bluelet.java
deleted file mode 100644
index 182fde7..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Bluelet.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-import android.os.ParcelUuid;
-
-/**
- * User interface for mocking and simulation of a Bluetooth device.
- */
-public interface Bluelet {
-
-    /**
-     * See {@link #setCreateBondOutcome}.
-     */
-    enum CreateBondOutcome {
-        SUCCESS,
-        FAILURE,
-        TIMEOUT
-    }
-
-    /**
-     * See {@link #setIoCapabilities}. Note that Bluetooth specifies a few more choices, but this is
-     * all DeviceShadower currently supports.
-     */
-    enum IoCapabilities {
-        NO_INPUT_NO_OUTPUT,
-        DISPLAY_YES_NO,
-        KEYBOARD_ONLY
-    }
-
-    /**
-     * See {@link #setFetchUuidsTiming}.
-     */
-    enum FetchUuidsTiming {
-        BEFORE_BONDING,
-        AFTER_BONDING,
-        NEVER
-    }
-
-    /**
-     * Set the initial state of the local Bluetooth adapter at the beginning of the test.
-     * <p>This method is not associated with broadcast event and is intended to be called at the
-     * beginning of the test. Allowed states:
-     *
-     * @see android.bluetooth.BluetoothAdapter#STATE_OFF
-     * @see android.bluetooth.BluetoothAdapter#STATE_ON
-     * </p>
-     */
-    Bluelet setAdapterInitialState(int state) throws IllegalArgumentException;
-
-    /**
-     * Set the bluetooth class of the local Bluetooth device at the beginning of the test.
-     * <p>
-     *
-     * @see android.bluetooth.BluetoothClass.Device
-     * @see android.bluetooth.BluetoothClass.Service
-     */
-    Bluelet setBluetoothClass(int bluetoothClass);
-
-    /**
-     * Set the scan mode of the local Bluetooth device at the beginning of the test.
-     */
-    Bluelet setScanMode(int scanMode);
-
-    /**
-     * Set the Bluetooth profiles supported by this device (e.g. A2DP Sink).
-     */
-    Bluelet setProfileUuids(ParcelUuid... profileUuids);
-
-    /**
-     * Makes bond attempts with this device succeed or fail.
-     *
-     * @param failureReason Ignored unless outcome is {@link CreateBondOutcome#FAILURE}. This is
-     * delivered in the intent that indicates bond state has changed to BOND_NONE. Values:
-     * https://cs.corp.google.com/android/frameworks/base/core/java/android/bluetooth/BluetoothDevice.java?rcl=38d9ee4cd661c10e012f71051d23644c65607eed&l=472
-     */
-    Bluelet setCreateBondOutcome(CreateBondOutcome outcome, int failureReason);
-
-    /**
-     * Sets the IO capabilities of this device. When bonding, a device states its IO capabilities in
-     * the pairing request. The pairing variant used depends on the IO capabilities of both devices
-     * (e.g. Just Works is the only available option for a NoInputNoOutput device, while Numeric
-     * Comparison aka Passkey Confirmation is used if both devices have a display and the ability to
-     * confirm/deny).
-     *
-     * @see <a href="https://blog.bluetooth.com/bluetooth-pairing-part-4">Bluetooth blog</a>
-     */
-    Bluelet setIoCapabilities(IoCapabilities ioCapabilities);
-
-    /**
-     * Make the device refuse connections. By default, connections are accepted.
-     *
-     * @param refuse Connections are refused if True.
-     */
-    Bluelet setRefuseConnections(boolean refuse);
-
-    /**
-     * Make the device refuse GATT connections. By default. connections are accepted.
-     *
-     * @param refuse GATT connections are refused if true.
-     */
-    Bluelet setRefuseGattConnections(boolean refuse);
-
-    /**
-     * When to send the ACTION_UUID broadcast. This can be {@link FetchUuidsTiming#BEFORE_BONDING},
-     * {@link FetchUuidsTiming#AFTER_BONDING}, or {@link FetchUuidsTiming#NEVER}. The default is
-     * {@link FetchUuidsTiming#AFTER_BONDING}.
-     */
-    Bluelet setFetchUuidsTiming(FetchUuidsTiming fetchUuidsTiming);
-
-    /**
-     * Adds a bonded device to the BluetoothAdapter.
-     */
-    Bluelet addBondedDevice(String address);
-
-    /**
-     * Enables the CVE-2019-2225 represents that the pairing variant will switch from Just Works to
-     * Consent when local device's io capability is Display Yes/No and remote is NoInputNoOutput.
-     *
-     * @see <a href="https://source.android.com/security/bulletin/2019-12-01#system">the security
-     * bulletin at 2019-12-01</a>
-     */
-    Bluelet enableCVE20192225(boolean value);
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironment.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironment.java
deleted file mode 100644
index 513d649..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironment.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-import com.android.libraries.testing.deviceshadower.Enums.Distance;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-
-/**
- * Environment to setup and config Bluetooth unit test.
- */
-public class DeviceShadowEnvironment {
-
-    private static final String TAG = "DeviceShadowEnvironment";
-    private static final long RESET_TIMEOUT_MILLIS = 3000;
-
-    private static boolean sIsInitialized = false;
-
-    private DeviceShadowEnvironment() {
-    }
-
-    public static void init() {
-        sIsInitialized = true;
-        DeviceShadowEnvironmentImpl.reset();
-    }
-
-    public static void reset() {
-        sIsInitialized = false;
-
-        // Order matters because each steps check and manipulate internal objects in order.
-        // Wait Scheduler and executors complete, and shut down executors.
-        DeviceShadowEnvironmentImpl.await(RESET_TIMEOUT_MILLIS);
-
-        // Throw RuntimeException if there is any internal exceptions.
-        DeviceShadowEnvironmentImpl.checkInternalExceptions();
-
-        // Clear internal exceptions, and devicelets.
-        DeviceShadowEnvironmentImpl.reset();
-    }
-
-    public static boolean await(long timeoutMillis) {
-        return DeviceShadowEnvironmentImpl.await(timeoutMillis);
-    }
-
-    public static Devicelet addDevice(final String address) {
-        return DeviceShadowEnvironmentImpl.addDevice(address);
-    }
-
-    public static void removeDevice(String address) {
-        DeviceShadowEnvironmentImpl.removeDevice(address);
-    }
-
-    public static void setLocalDevice(final String address) {
-        DeviceShadowEnvironmentImpl.setLocalDevice(address);
-    }
-
-    public static void putNear(String address1, String address2) {
-        DeviceShadowEnvironmentImpl.setDistance(address1, address2, Distance.NEAR);
-    }
-
-    public static void setDistance(String address1, String address2, Distance distance) {
-        DeviceShadowEnvironmentImpl.setDistance(address1, address2, distance);
-    }
-
-    public static Future<Void> run(final String address, final Runnable snippet) {
-        return run(
-                address,
-                () -> {
-                    snippet.run();
-                    return null;
-                });
-    }
-
-    public static <T> Future<T> run(final String address, final Callable<T> snippet) {
-        return DeviceShadowEnvironmentImpl.run(address, snippet);
-    }
-
-    /* package */
-    static boolean isInitialized() {
-        return sIsInitialized;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironmentInternal.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironmentInternal.java
deleted file mode 100644
index a5f8e6d..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/DeviceShadowEnvironmentInternal.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.sms.SmsContentProvider;
-
-/**
- * Internal interface for device shadower.
- */
-public class DeviceShadowEnvironmentInternal {
-
-    /**
-     * Set an interruptible point to tested code.
-     * <p>
-     * This should only make changes when DeviceShadowEnvironment initialized, which means only in
-     * test cases.
-     */
-    public static void setInterruptibleBluetooth(int identifier) {
-        if (DeviceShadowEnvironment.isInitialized()) {
-            assert identifier > 0;
-            DeviceShadowEnvironmentImpl.setInterruptibleBluetooth(identifier);
-        }
-    }
-
-    /**
-     * Mark all bluetooth operation broken after identifier in tested code.
-     */
-    public static void interruptBluetooth(String address, int identifier) {
-        DeviceShadowEnvironmentImpl.interruptBluetooth(address, identifier);
-    }
-
-    /**
-     * Return SMS content provider to be registered by robolectric context.
-     */
-    public static Class<SmsContentProvider> getSmsContentProviderClass() {
-        return SmsContentProvider.class;
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Devicelet.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Devicelet.java
deleted file mode 100644
index bf31ead..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Devicelet.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-/**
- * Devicelet is the handler to operate shadowed device objects in DeviceShadower.
- */
-public interface Devicelet {
-
-    Bluelet bluetooth();
-
-    Nfclet nfc();
-
-    Smslet sms();
-
-    String getAddress();
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Enums.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Enums.java
deleted file mode 100644
index 9eb3514..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Enums.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-/**
- * Contains Enums used by DeviceShadower in interface and internally.
- */
-public interface Enums {
-
-    /**
-     * Represents vague distance between two devicelets.
-     */
-    enum Distance {
-        NEAR,
-        MID,
-        FAR,
-        AWAY,
-    }
-
-    /**
-     * Abstract base interface for operations.
-     */
-    interface Operation {
-
-    }
-
-    /**
-     * NFC operations.
-     */
-    enum NfcOperation implements Operation {
-        GET_ADAPTER,
-        ENABLE,
-        DISABLE,
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Nfclet.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Nfclet.java
deleted file mode 100644
index 4b00f24..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Nfclet.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-/**
- * Interface of Nfclet
- */
-public interface Nfclet {
-
-    Nfclet setInitialState(int state);
-
-    Nfclet setInterruptOperation(Enums.NfcOperation operation);
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Smslet.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Smslet.java
deleted file mode 100644
index 483fab6..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/Smslet.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower;
-
-import android.net.Uri;
-
-/**
- * Interface of Smslet
- */
-public interface Smslet {
-
-    Smslet addSms(Uri uri, String body);
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetooth.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetooth.java
deleted file mode 100644
index be8390e..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetooth.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.content.AttributionSource;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelUuid;
-
-/**
- * Fake interface replacement for hidden IBluetooth class
- */
-public interface IBluetooth {
-
-    // Bluetooth settings.
-    String getAddress();
-
-    String getName();
-
-    boolean setName(String name);
-
-    // Remote device properties.
-    int getRemoteClass(BluetoothDevice device);
-
-    String getRemoteName(BluetoothDevice device);
-
-    int getRemoteType(BluetoothDevice device, AttributionSource attributionSource);
-
-    ParcelUuid[] getRemoteUuids(BluetoothDevice device);
-
-    boolean fetchRemoteUuids(BluetoothDevice device);
-
-    // Bluetooth discovery.
-    int getScanMode();
-
-    boolean setScanMode(int mode, int duration);
-
-    int getDiscoverableTimeout();
-
-    boolean setDiscoverableTimeout(int timeout);
-
-    boolean startDiscovery();
-
-    boolean cancelDiscovery();
-
-    boolean isDiscovering();
-
-    // Adapter state.
-    boolean isEnabled();
-
-    int getState();
-
-    boolean enable();
-
-    boolean disable();
-
-    // Rfcomm sockets.
-    ParcelFileDescriptor connectSocket(BluetoothDevice device, int type, ParcelUuid uuid,
-            int port, int flag);
-
-    ParcelFileDescriptor createSocketChannel(int type, String serviceName, ParcelUuid uuid,
-            int port, int flag);
-
-    // BLE settings.
-    /* SINCE SDK 21 */ boolean isMultiAdvertisementSupported();
-
-    /* SINCE SDK 22 */ boolean isPeripheralModeSupported();
-
-    /* SINCE SDK 21 */  boolean isOffloadedFilteringSupported();
-
-    // Bonding (pairing).
-    int getBondState(BluetoothDevice device, AttributionSource attributionSource);
-
-    boolean createBond(BluetoothDevice device, int transport, OobData remoteP192Data,
-            OobData remoteP256Data, AttributionSource attributionSource);
-
-    boolean setPairingConfirmation(BluetoothDevice device, boolean accept,
-            AttributionSource attributionSource);
-
-    boolean setPasskey(BluetoothDevice device, int passkey);
-
-    boolean cancelBondProcess(BluetoothDevice device);
-
-    boolean removeBond(BluetoothDevice device);
-
-    BluetoothDevice[] getBondedDevices();
-
-    // Connecting to profiles.
-    int getAdapterConnectionState();
-
-    int getProfileConnectionState(int profile);
-
-    // Access permissions
-    int getPhonebookAccessPermission(BluetoothDevice device);
-
-    boolean setPhonebookAccessPermission(BluetoothDevice device, int value);
-
-    int getMessageAccessPermission(BluetoothDevice device);
-
-    boolean setMessageAccessPermission(BluetoothDevice device, int value);
-
-    int getSimAccessPermission(BluetoothDevice device);
-
-    boolean setSimAccessPermission(BluetoothDevice device, int value);
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGatt.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGatt.java
deleted file mode 100644
index 16e4f01..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGatt.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.os.ParcelUuid;
-
-import java.util.List;
-
-/**
- * Fake interface replacement for IBluetoothGatt
- * TODO(b/200231384): include >=N interface.
- */
-public interface IBluetoothGatt {
-
-    /* ONLY SDK 23 */
-    void startScan(int appIf, boolean isServer, ScanSettings settings,
-            List<ScanFilter> filters, List<?> scanStorages, String callPackage);
-
-    /* ONLY SDK 21 */
-    void startScan(int appIf, boolean isServer, ScanSettings settings,
-            List<ScanFilter> filters, List<?> scanStorages);
-
-    /* SINCE SDK 21 */
-    void stopScan(int appIf, boolean isServer);
-
-    /* SINCE SDK 21 */
-    void startMultiAdvertising(
-            int appIf, AdvertiseData advertiseData, AdvertiseData scanResponse,
-            AdvertiseSettings settings);
-
-    /* SINCE SDK 21 */
-    void stopMultiAdvertising(int appIf);
-
-    /* SINCE SDK 21 */
-    void registerClient(ParcelUuid appId, IBluetoothGattCallback callback);
-
-    /* SINCE SDK 21 */
-    void unregisterClient(int clientIf);
-
-    /* SINCE SDK 21 */
-    void clientConnect(int clientIf, String address, boolean isDirect, int transport);
-
-    /* SINCE SDK 21 */
-    void clientDisconnect(int clientIf, String address);
-
-    /* SINCE SDK 21 */
-    void discoverServices(int clientIf, String address);
-
-    /* SINCE SDK 21 */
-    void readCharacteristic(int clientIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            int authReq);
-
-    /* SINCE SDK 21 */
-    void writeCharacteristic(int clientIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            int writeType, int authReq, byte[] value);
-
-    /* SINCE SDK 21 */
-    void readDescriptor(int clientIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            int descrInstanceId, ParcelUuid descrUuid, int authReq);
-
-    /* SINCE SDK 21 */
-    void writeDescriptor(int clientIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            int descrInstanceId, ParcelUuid descrId, int writeType, int authReq, byte[] value);
-
-    /* SINCE SDK 21 */
-    void registerForNotification(int clientIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            boolean enable);
-
-    /* SINCE SDK 21 */
-    void registerServer(ParcelUuid appId, IBluetoothGattServerCallback callback);
-
-    /* SINCE SDK 21 */
-    void unregisterServer(int serverIf);
-
-    /* SINCE SDK 21 */
-    void serverConnect(int servertIf, String address, boolean isDirect, int transport);
-
-    /* SINCE SDK 21 */
-    void serverDisconnect(int serverIf, String address);
-
-    /* SINCE SDK 21 */
-    void beginServiceDeclaration(int serverIf, int srvcType, int srvcInstanceId, int minHandles,
-            ParcelUuid srvcId, boolean advertisePreferred);
-
-    /* SINCE SDK 21 */
-    void addIncludedService(int serverIf, int srvcType, int srvcInstanceId, ParcelUuid srvcId);
-
-    /* SINCE SDK 21 */
-    void addCharacteristic(int serverIf, ParcelUuid charId, int properties, int permissions);
-
-    /* SINCE SDK 21 */
-    void addDescriptor(int serverIf, ParcelUuid descId, int permissions);
-
-    /* SINCE SDK 21 */
-    void endServiceDeclaration(int serverIf);
-
-    /* SINCE SDK 21 */
-    void removeService(int serverIf, int srvcType, int srvcInstanceId, ParcelUuid srvcId);
-
-    /* SINCE SDK 21 */
-    void clearServices(int serverIf);
-
-    /* SINCE SDK 21 */
-    void sendResponse(int serverIf, String address, int requestId,
-            int status, int offset, byte[] value);
-
-    /* SINCE SDK 21 */
-    void sendNotification(int serverIf, String address, int srvcType,
-            int srvcInstanceId, ParcelUuid srvcId, int charInstanceId, ParcelUuid charId,
-            boolean confirm, byte[] value);
-
-    /* SINCE SDK 21 */
-    void configureMTU(int clientIf, String address, int mtu);
-
-    /* SINCE SDK 21 */
-    void connectionParameterUpdate(int clientIf, String address, int connectionPriority);
-
-    void disconnectAll();
-
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattCallback.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattCallback.java
deleted file mode 100644
index b29369b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattCallback.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.ScanResult;
-import android.os.ParcelUuid;
-
-/**
- * Fake interface replacement for IBluetoothGattCallback
- * TODO(b/200231384): include >=N interface.
- */
-public interface IBluetoothGattCallback {
-
-    /* SINCE SDK 21 */
-    void onClientRegistered(int status, int clientIf);
-
-    /* SINCE SDK 21 */
-    void onClientConnectionState(int status, int clientIf, boolean connected, String address);
-
-    /* ONLY SDK 19 */
-    void onScanResult(String address, int rssi, byte[] advData);
-
-    /* SINCE SDK 21 */
-    void onScanResult(ScanResult scanResult);
-
-    /* SINCE SDK 21 */
-    void onGetService(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid);
-
-    /* SINCE SDK 21 */
-    void onGetIncludedService(String address, int srvcType, int srvcInstId,
-            ParcelUuid srvcUuid, int inclSrvcType,
-            int inclSrvcInstId, ParcelUuid inclSrvcUuid);
-
-    /* SINCE SDK 21 */
-    void onGetCharacteristic(String address, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            int charProps);
-
-    /* SINCE SDK 21 */
-    void onGetDescriptor(String address, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            int descrInstId, ParcelUuid descrUuid);
-
-    /* SINCE SDK 21 */
-    void onSearchComplete(String address, int status);
-
-    /* SINCE SDK 21 */
-    void onCharacteristicRead(String address, int status, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            byte[] value);
-
-    /* SINCE SDK 21 */
-    void onCharacteristicWrite(String address, int status, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid);
-
-    /* SINCE SDK 21 */
-    void onExecuteWrite(String address, int status);
-
-    /* SINCE SDK 21 */
-    void onDescriptorRead(String address, int status, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            int descrInstId, ParcelUuid descrUuid,
-            byte[] value);
-
-    /* SINCE SDK 21 */
-    void onDescriptorWrite(String address, int status, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            int descrInstId, ParcelUuid descrUuid);
-
-    /* SINCE SDK 21 */
-    void onNotify(String address, int srvcType,
-            int srvcInstId, ParcelUuid srvcUuid,
-            int charInstId, ParcelUuid charUuid,
-            byte[] value);
-
-    /* SINCE SDK 21 */
-    void onReadRemoteRssi(String address, int rssi, int status);
-
-    /* SDK 21 */
-    void onMultiAdvertiseCallback(int status, boolean isStart,
-            AdvertiseSettings advertiseSettings);
-
-    /* SDK 21 */
-    void onConfigureMTU(String address, int mtu, int status);
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattServerCallback.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattServerCallback.java
deleted file mode 100644
index 10b91bb..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothGattServerCallback.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.os.ParcelUuid;
-
-/**
- * Fake interface of internal IBluetoothGattServerCallback.
- */
-public interface IBluetoothGattServerCallback {
-
-    /* SINCE SDK 21 */
-    void onServerRegistered(int status, int serverIf);
-
-    /* SINCE SDK 21 */
-    void onScanResult(String address, int rssi, byte[] advData);
-
-    /* SINCE SDK 21 */
-    void onServerConnectionState(int status, int serverIf, boolean connected, String address);
-
-    /* SINCE SDK 21 */
-    void onServiceAdded(int status, int srvcType, int srvcInstId, ParcelUuid srvcId);
-
-    /* SINCE SDK 21 */
-    void onCharacteristicReadRequest(String address, int transId, int offset, boolean isLong,
-            int srvcType, int srvcInstId, ParcelUuid srvcId, int charInstId, ParcelUuid charId);
-
-    /* SINCE SDK 21 */
-    void onDescriptorReadRequest(String address, int transId, int offset, boolean isLong,
-            int srvcType, int srvcInstId, ParcelUuid srvcId,
-            int charInstId, ParcelUuid charId, ParcelUuid descrId);
-
-    /* SINCE SDK 21 */
-    void onCharacteristicWriteRequest(String address, int transId, int offset, int length,
-            boolean isPrep, boolean needRsp, int srvcType, int srvcInstId, ParcelUuid srvcId,
-            int charInstId, ParcelUuid charId, byte[] value);
-
-    /* SINCE SDK 21 */
-    void onDescriptorWriteRequest(String address, int transId, int offset, int length,
-            boolean isPrep, boolean needRsp, int srvcType, int srvcInstId, ParcelUuid srvcId,
-            int charInstId, ParcelUuid charId, ParcelUuid descrId, byte[] value);
-
-    /* SINCE SDK 21 */
-    void onExecuteWrite(String address, int transId, boolean execWrite);
-
-    /* SINCE SDK 21 */
-    void onNotificationSent(String address, int status);
-
-    /* SINCE SDK 22 */
-    void onMtuChanged(String address, int mtu);
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java
deleted file mode 100644
index 6bb2209..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-/**
- * Intentionally in package android.bluetooth to fake existing interface in Android.
- */
-package android.bluetooth;
-
-/**
- * Fake interface for IBluetoothManager.
- */
-public interface IBluetoothManager {
-
-    boolean enable();
-
-    boolean disable(boolean persist);
-
-    String getAddress();
-
-    String getName();
-
-    IBluetooth registerAdapter(IBluetoothManagerCallback callback);
-
-    IBluetoothGatt getBluetoothGatt();
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java
deleted file mode 100644
index f39b82f..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-/**
- * Fake interface replacement for hidden IBluetoothManagerCallback class
- */
-public interface IBluetoothManagerCallback {
-
-}
-
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/BeamShareData.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/BeamShareData.java
deleted file mode 100644
index 5357a9b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/BeamShareData.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.net.Uri;
-import android.os.UserHandle;
-
-/**
- * Fake BeamShareData.
- */
-public class BeamShareData {
-
-    public NdefMessage ndefMessage;
-    public Uri[] uris;
-    public UserHandle userHandle;
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/IAppCallback.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/IAppCallback.java
deleted file mode 100644
index 7b62f19..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/IAppCallback.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-/**
- * Fake interface for nfc service.
- */
-public interface IAppCallback {
-
-    /* M */ void onNdefPushComplete(byte peerLlcpVersion);
-
-    /* M */ BeamShareData createBeamShareData(byte peerLlcpVersion);
-
-    /* L */ void onNdefPushComplete();
-
-    /* L */ BeamShareData createBeamShareData();
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/INfcAdapter.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/INfcAdapter.java
deleted file mode 100644
index 08acdbc..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/nfc/INfcAdapter.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-/**
- * Fake interface of INfcAdapter
- */
-public interface INfcAdapter {
-
-    void setAppCallback(IAppCallback callback);
-
-    boolean enable();
-
-    boolean disable(boolean saveState);
-
-    int getState();
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleAdvertiser.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleAdvertiser.java
deleted file mode 100644
index f3328c8..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleAdvertiser.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.AdvertiseCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Future;
-
-/**
- * Helper class to operate a device as BLE advertiser.
- */
-public class BleAdvertiser {
-
-    private static final String TAG = "BleAdvertiser";
-
-    private static final int DEFAULT_MODE = AdvertiseSettings.ADVERTISE_MODE_BALANCED;
-    private static final int DEFAULT_TX_POWER_LEVEL = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH;
-    private static final boolean DEFAULT_CONNECTABLE = true;
-    private static final int DEFAULT_TIMEOUT = 0;
-
-
-    /**
-     * Callback of {@link BleAdvertiser}.
-     */
-    public interface Callback {
-
-        void onStartFailure(String address, int errorCode);
-
-        void onStartSuccess(String address, AdvertiseSettings settingsInEffect);
-    }
-
-    /**
-     * Builder class of {@link BleAdvertiser}.
-     */
-    public static final class Builder {
-
-        private final String mAddress;
-        private final Callback mCallback;
-        private AdvertiseSettings mSettings = defaultSettings();
-        private AdvertiseData mData;
-        private AdvertiseData mResponse;
-
-        public Builder(String address, Callback callback) {
-            this.mAddress = Preconditions.checkNotNull(address);
-            this.mCallback = Preconditions.checkNotNull(callback);
-        }
-
-        public Builder setAdvertiseSettings(AdvertiseSettings settings) {
-            this.mSettings = settings;
-            return this;
-        }
-
-        public Builder setAdvertiseData(AdvertiseData data) {
-            this.mData = data;
-            return this;
-        }
-
-        public Builder setResponseData(AdvertiseData response) {
-            this.mResponse = response;
-            return this;
-        }
-
-        public BleAdvertiser build() {
-            return new BleAdvertiser(mAddress, mCallback, mSettings, mData, mResponse);
-        }
-    }
-
-    private static AdvertiseSettings defaultSettings() {
-        return new AdvertiseSettings.Builder()
-                .setAdvertiseMode(DEFAULT_MODE)
-                .setConnectable(DEFAULT_CONNECTABLE)
-                .setTimeout(DEFAULT_TIMEOUT)
-                .setTxPowerLevel(DEFAULT_TX_POWER_LEVEL).build();
-    }
-
-    private final String mAddress;
-    private final Callback mCallback;
-    private final AdvertiseSettings mSettings;
-    private final AdvertiseData mData;
-    private final AdvertiseData mResponse;
-    private final CountDownLatch mStartAdvertiseLatch;
-    private BluetoothLeAdvertiser mAdvertiser;
-
-    private BleAdvertiser(String address, Callback callback, AdvertiseSettings settings,
-            AdvertiseData data, AdvertiseData response) {
-        this.mAddress = address;
-        this.mCallback = callback;
-        this.mSettings = settings;
-        this.mData = data;
-        this.mResponse = response;
-        mStartAdvertiseLatch = new CountDownLatch(1);
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    /**
-     * Starts advertising.
-     */
-    public Future<Void> start() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
-                mAdvertiser.startAdvertising(mSettings, mData, mResponse, mAdvertiseCallback);
-            }
-        });
-    }
-
-    /**
-     * Stops advertising.
-     */
-    public Future<Void> stop() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mAdvertiser.stopAdvertising(mAdvertiseCallback);
-            }
-        });
-    }
-
-    public void waitTillAdvertiseCompleted() {
-        try {
-            mStartAdvertiseLatch.await();
-        } catch (InterruptedException e) {
-            Log.w(TAG, mAddress + " fails to wait till advertise completed: ", e);
-        }
-    }
-
-    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
-        @Override
-        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
-            Log.v(TAG,
-                    String.format("onStartSuccess(settingsInEffect: %s) on %s ", settingsInEffect,
-                            mAddress));
-            mCallback.onStartSuccess(mAddress, settingsInEffect);
-            mStartAdvertiseLatch.countDown();
-        }
-
-        @Override
-        public void onStartFailure(int errorCode) {
-            Log.v(TAG, String.format("onStartFailure(errorCode: %d) on %s", errorCode, mAddress));
-            mCallback.onStartFailure(mAddress, errorCode);
-            mStartAdvertiseLatch.countDown();
-        }
-    };
-}
-
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleScanner.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleScanner.java
deleted file mode 100644
index 6a44c2b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BleScanner.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import com.google.common.base.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to operate a device as BLE scanner.
- */
-public class BleScanner {
-
-    private static final String TAG = "BleScanner";
-
-    private static final int DEFAULT_MODE = ScanSettings.SCAN_MODE_LOW_LATENCY;
-    private static final int DEFAULT_CALLBACK_TYPE = ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
-    private static final long DEFAULT_DELAY = 0L;
-
-    /**
-     * Callback of {@link BleScanner}.
-     */
-    public interface Callback {
-
-        void onScanResult(String address, int callbackType, ScanResult result);
-
-        void onBatchScanResults(String address, List<ScanResult> results);
-
-        void onScanFailed(String address, int errorCode);
-    }
-
-    /**
-     * Builder class of {@link BleScanner}.
-     */
-    public static final class Builder {
-
-        private final String mAddress;
-        private final Callback mCallback;
-        private ScanSettings mSettings = defaultSettings();
-        private List<ScanFilter> mFilters;
-        private int mNumOfExpectedScanCallbacks = 1;
-
-        public Builder(String address, Callback callback) {
-            this.mAddress = Preconditions.checkNotNull(address);
-            this.mCallback = Preconditions.checkNotNull(callback);
-        }
-
-        public Builder setScanSettings(ScanSettings settings) {
-            this.mSettings = settings;
-            return this;
-        }
-
-        public Builder addScanFilter(ScanFilter... filterArgs) {
-            if (this.mFilters == null) {
-                this.mFilters = new ArrayList<>();
-            }
-            for (ScanFilter filter : filterArgs) {
-                this.mFilters.add(filter);
-            }
-            return this;
-        }
-
-        /**
-         * Sets number of expected scan result callback.
-         *
-         * @param num Number of expected scan result callback, default to 1.
-         */
-        public Builder setNumOfExpectedScanCallbacks(int num) {
-            mNumOfExpectedScanCallbacks = num;
-            return this;
-        }
-
-        public BleScanner build() {
-            return new BleScanner(
-                    mAddress, mCallback, mSettings, mFilters, mNumOfExpectedScanCallbacks);
-        }
-    }
-
-    private static ScanSettings defaultSettings() {
-        return new ScanSettings.Builder()
-                .setScanMode(DEFAULT_MODE)
-                .setCallbackType(DEFAULT_CALLBACK_TYPE)
-                .setReportDelay(DEFAULT_DELAY).build();
-    }
-
-    private final String mAddress;
-    private final Callback mCallback;
-    private final ScanSettings mSettings;
-    private final List<ScanFilter> mFilters;
-    private final BlockingQueue<Integer> mScanResultCounts;
-    private int mNumOfExpectedScanCallbacks;
-    private int mNumOfReceivedScanCallbacks;
-    private BluetoothLeScanner mScanner;
-
-    private BleScanner(String address, Callback callback, ScanSettings settings,
-            List<ScanFilter> filters, int numOfExpectedScanResult) {
-        this.mAddress = address;
-        this.mCallback = callback;
-        this.mSettings = settings;
-        this.mFilters = filters;
-        this.mNumOfExpectedScanCallbacks = numOfExpectedScanResult;
-        this.mNumOfReceivedScanCallbacks = 0;
-        this.mScanResultCounts = new LinkedBlockingQueue<>(numOfExpectedScanResult);
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    public Future<Void> start() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
-                mScanner.startScan(mFilters, mSettings, mScanCallback);
-            }
-        });
-    }
-
-    public void waitTillNextScanResult(long timeoutMillis) {
-        Integer result = null;
-        if (mNumOfReceivedScanCallbacks >= mNumOfExpectedScanCallbacks) {
-            return;
-        }
-        try {
-            if (timeoutMillis < 0) {
-                result = mScanResultCounts.take();
-            } else {
-                result = mScanResultCounts.poll(timeoutMillis, TimeUnit.MILLISECONDS);
-            }
-            if (result != null && result >= 0) {
-                mNumOfReceivedScanCallbacks++;
-            }
-            Log.v(TAG, "Scan results: " + result);
-        } catch (InterruptedException e) {
-            Log.w(TAG, mAddress + " fails to wait till next scan result: ", e);
-        }
-    }
-
-    public void waitTillNextScanResult() {
-        waitTillNextScanResult(-1);
-    }
-
-    public void waitTillAllScanResults() {
-        while (mNumOfReceivedScanCallbacks < mNumOfExpectedScanCallbacks) {
-            try {
-                if (mScanResultCounts.take() >= 0) {
-                    mNumOfReceivedScanCallbacks++;
-                }
-            } catch (InterruptedException e) {
-                Log.w(TAG, String.format("%s fails to wait scan result", mAddress), e);
-                return;
-            }
-        }
-    }
-
-    public Future<Void> stop() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
-                mScanner.stopScan(mScanCallback);
-            }
-        });
-    }
-
-    private final ScanCallback mScanCallback = new ScanCallback() {
-        @Override
-        public void onScanResult(int callbackType, ScanResult result) {
-            Log.v(TAG, String.format("onScanResult(callbackType: %d, result: %s) on %s",
-                    callbackType, result, mAddress));
-            mCallback.onScanResult(mAddress, callbackType, result);
-            try {
-                mScanResultCounts.put(1);
-            } catch (InterruptedException e) {
-                // no-op.
-            }
-        }
-
-        @Override
-        public void onBatchScanResults(List<ScanResult> results) {
-            /**** Not supported yet.
-             Log.v(TAG, String.format("onBatchScanResults(results: %s) on %s",
-             Arrays.toString(results.toArray()), address));
-             callback.onBatchScanResults(address, results);
-             try {
-             scanResultCounts.put(results.size());
-             } catch (InterruptedException e) {
-             // no-op.
-             }
-             */
-        }
-
-        @Override
-        public void onScanFailed(int errorCode) {
-            /**** Not supported yet.
-             Log.v(TAG, String.format("onScanFailed(errorCode: %d) on %s", errorCode, address));
-             callback.onScanFailed(address, errorCode);
-             try {
-             scanResultCounts.put(-1);
-             } catch (InterruptedException e) {
-             // no-op.
-             }
-             */
-        }
-    };
-}
-
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattClient.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattClient.java
deleted file mode 100644
index 69e77af..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattClient.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCallback;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.content.Context;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to operate a device as gatt client.
- */
-public class BluetoothGattClient {
-
-    private static final String TAG = "BluetoothGattClient";
-    private static final int LATCH_TIMEOUT_MILLIS = 1000;
-
-    /**
-     * Callback of BluetoothGattClient.
-     */
-    public interface Callback {
-
-        void onConnectionStateChange(String address, int status, int newState);
-
-        void onCharacteristicChanged(String address, UUID uuid, byte[] value);
-
-        void onCharacteristicRead(String address, UUID uuid, byte[] value, int status);
-
-        void onCharacteristicWrite(String address, UUID uuid, byte[] value, int status);
-
-        void onDescriptorRead(String address, UUID uuid, byte[] value, int status);
-
-        void onDescriptorWrite(String address, UUID uuid, byte[] value, int status);
-
-        void onServicesDiscovered(
-                UUID[] serviceUuid, UUID[] characteristicUuid, UUID[] descriptorUuid, int status);
-
-        void onConfigureMTU(String address, int mtu, int status);
-    }
-
-    private final String mAddress;
-    private final Callback mCallback;
-    private final Context mContext;
-    private final Map<UUID, BluetoothGattCharacteristic> mCharacteristics = new HashMap<>();
-    private final Map<UUID, BluetoothGattDescriptor> mDescriptors = new HashMap<>();
-    private BluetoothGatt mGatt;
-    private CountDownLatch mConnectionLatch;
-    private CountDownLatch mServiceDiscoverLatch;
-
-    public BluetoothGattClient(String address, Callback callback, Context context) {
-        this.mAddress = address;
-        this.mCallback = callback;
-        this.mContext = context;
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    public Future<Void> connect(final String remoteAddress) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mConnectionLatch = new CountDownLatch(1);
-                mGatt = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddress)
-                        .connectGatt(mContext, false /* auto connect */, mGattCallback);
-                try {
-                    mConnectionLatch.await(LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    // no-op.
-                }
-
-                mServiceDiscoverLatch = new CountDownLatch(1);
-                mGatt.discoverServices();
-                try {
-                    mServiceDiscoverLatch.await(LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    // no-op.
-                }
-            }
-        });
-    }
-
-    public Future<Void> close() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGatt.disconnect();
-                mGatt.close();
-            }
-        });
-    }
-
-    public Future<Void> readCharacteristic(final UUID uuid) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGatt.readCharacteristic(mCharacteristics.get(uuid));
-            }
-        });
-    }
-
-    public Future<Void> setNotification(final UUID uuid) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGatt.setCharacteristicNotification(mCharacteristics.get(uuid), true);
-            }
-        });
-    }
-
-    public Future<Void> writeCharacteristic(final UUID uuid, final byte[] value) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                BluetoothGattCharacteristic characteristic = mCharacteristics.get(uuid);
-                characteristic.setValue(value);
-                mGatt.writeCharacteristic(characteristic);
-            }
-        });
-    }
-
-    /**
-     * Reads the value of a descriptor with given UUID.
-     *
-     * <p>If different characteristics on the service have the same descriptor, use {@link
-     * BluetoothGattClient#readDescriptor(UUID, UUID)} instead.
-     */
-    public Future<Void> readDescriptor(final UUID uuid) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGatt.readDescriptor(mDescriptors.get(uuid));
-            }
-        });
-    }
-
-    /**
-     * Reads the descriptor value of the specified characteristic.
-     */
-    public Future<Void> readDescriptor(final UUID descriptorUuid, final UUID characteristicUuid) {
-        return DeviceShadowEnvironment.run(
-                mAddress,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        mGatt.readDescriptor(
-                                mCharacteristics.get(characteristicUuid)
-                                        .getDescriptor(descriptorUuid));
-                    }
-                });
-    }
-
-    /**
-     * Writes to the descriptor with given UUID.
-     *
-     * <p>If different characteristics on the service have the same descriptor, use {@link
-     * BluetoothGattClient#writeDescriptor(UUID, UUID, byte[])} instead.
-     */
-    public Future<Void> writeDescriptor(final UUID uuid, final byte[] value) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                BluetoothGattDescriptor descriptor = mDescriptors.get(uuid);
-                descriptor.setValue(value);
-                mGatt.writeDescriptor(descriptor);
-            }
-        });
-    }
-
-    /**
-     * Writes to the descriptor of the specified characteristic.
-     */
-    public Future<Void> writeDescriptor(
-            final UUID descriptorUuid, final UUID characteristicUuid, final byte[] value) {
-        return DeviceShadowEnvironment.run(
-                mAddress,
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        BluetoothGattDescriptor descriptor =
-                                mCharacteristics.get(characteristicUuid)
-                                        .getDescriptor(descriptorUuid);
-                        descriptor.setValue(value);
-                        mGatt.writeDescriptor(descriptor);
-                    }
-                });
-    }
-
-    public Future<Void> requestMtu(int mtu) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGatt.requestMtu(mtu);
-            }
-        });
-    }
-
-    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
-        @Override
-        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
-            Log.v(TAG, String.format("onConnectionStateChange(status: %s, newState: %s)",
-                    status, newState));
-            if (mConnectionLatch != null) {
-                mConnectionLatch.countDown();
-            }
-            mCallback.onConnectionStateChange(gatt.getDevice().getAddress(), status, newState);
-        }
-
-        @Override
-        public void onCharacteristicChanged(
-                BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
-            Log.v(TAG, String.format("onCharacteristicChanged(characteristic: %s, value: %s)",
-                    characteristic.getUuid(), Arrays.toString(characteristic.getValue())));
-            mCallback.onCharacteristicChanged(
-                    gatt.getDevice().getAddress(), characteristic.getUuid(),
-                    characteristic.getValue());
-        }
-
-        @Override
-        public void onCharacteristicRead(
-                BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
-            Log.v(TAG, String.format("onCharacteristicRead(descriptor: %s, status: %s)",
-                    characteristic.getUuid(), status));
-            mCallback.onCharacteristicRead(
-                    gatt.getDevice().getAddress(), characteristic.getUuid(),
-                    characteristic.getValue(),
-                    status);
-        }
-
-        @Override
-        public void onCharacteristicWrite(
-                BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
-            Log.v(TAG, String.format("onCharacteristicWrite(descriptor: %s, status: %s)",
-                    characteristic.getUuid(), status));
-            mCallback.onCharacteristicWrite(gatt.getDevice().getAddress(),
-                    characteristic.getUuid(), characteristic.getValue(), status);
-        }
-
-        @Override
-        public void onDescriptorRead(
-                BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
-            Log.v(TAG, String.format("onDescriptorRead(descriptor: %s, status: %s)",
-                    descriptor.getUuid(), status));
-            mCallback.onDescriptorRead(
-                    gatt.getDevice().getAddress(), descriptor.getUuid(), descriptor.getValue(),
-                    status);
-        }
-
-        @Override
-        public void onDescriptorWrite(
-                BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
-            Log.v(TAG, String.format("onDescriptorWrite(descriptor: %s, status: %s)",
-                    descriptor.getUuid(), status));
-            mCallback.onDescriptorWrite(
-                    gatt.getDevice().getAddress(), descriptor.getUuid(), descriptor.getValue(),
-                    status);
-        }
-
-        @Override
-        public synchronized void onServicesDiscovered(BluetoothGatt gatt, int status) {
-            Log.v(TAG, "Discovered service: " + gatt.getServices());
-            List<UUID> serviceUuid = new ArrayList<>();
-            List<UUID> characteristicUuid = new ArrayList<>();
-            List<UUID> descriptorUuid = new ArrayList<>();
-            for (BluetoothGattService service : gatt.getServices()) {
-                serviceUuid.add(service.getUuid());
-                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
-                    mCharacteristics.put(characteristic.getUuid(), characteristic);
-                    characteristicUuid.add(characteristic.getUuid());
-                    for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
-                        mDescriptors.put(descriptor.getUuid(), descriptor);
-                        descriptorUuid.add(descriptor.getUuid());
-                    }
-                }
-            }
-
-            Collections.sort(serviceUuid);
-            Collections.sort(characteristicUuid);
-            Collections.sort(descriptorUuid);
-
-            mCallback.onServicesDiscovered(serviceUuid.toArray(new UUID[serviceUuid.size()]),
-                    characteristicUuid.toArray(new UUID[characteristicUuid.size()]),
-                    descriptorUuid.toArray(new UUID[descriptorUuid.size()]),
-                    status);
-            mServiceDiscoverLatch.countDown();
-        }
-
-        @Override
-        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
-            Log.v(TAG, String.format("onMtuChanged(mtu: %s, status: %s)", mtu, status));
-            mCallback.onConfigureMTU(gatt.getDevice().getAddress(), mtu, status);
-        }
-    };
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattMaster.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattMaster.java
deleted file mode 100644
index e9f364a..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothGattMaster.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattServer;
-import android.bluetooth.BluetoothGattServerCallback;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Future;
-
-/**
- * Helper class to operate a device as gatt server.
- */
-public class BluetoothGattMaster {
-
-    private static final String TAG = "BluetoothGattMaster";
-
-    /**
-     * Callback of BluetoothGattMaster.
-     */
-    public interface Callback {
-
-        void onConnectionStateChange(String address, int status, int newState);
-
-        void onCharacteristicReadRequest(String address, UUID uuid);
-
-        void onCharacteristicWriteRequest(String address, UUID uuid, byte[] value,
-                boolean preparedWrite, boolean responseNeeded);
-
-        void onDescriptorReadRequest(String address, UUID uuid);
-
-        void onDescriptorWriteRequest(String address, UUID uuid, byte[] value,
-                boolean preparedWrite, boolean responseNeeded);
-
-        void onNotificationSent(String address, int status);
-
-        void onExecuteWrite(String address, boolean execute);
-
-        void onServiceAdded(UUID uuid, int status);
-
-        void onMtuChanged(String address, int mtu);
-    }
-
-    private final String mAddress;
-    private final Callback mCallback;
-    private final Context mContext;
-    private BluetoothGattServer mGattServer;
-    private final Map<UUID, BluetoothGattCharacteristic> mCharacteristics = new HashMap<>();
-
-    public BluetoothGattMaster(String address, Callback callback, Context context) {
-        this.mAddress = address;
-        this.mCallback = callback;
-        this.mContext = context;
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    public Future<Void> start(final BluetoothGattService service) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
-                mGattServer = manager.openGattServer(mContext, mGattServerCallback);
-                mGattServer.addService(service);
-            }
-        });
-    }
-
-    public Future<Void> stop() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mGattServer.close();
-            }
-        });
-    }
-
-    public Future<Void> notifyCharacteristic(
-            final String remoteAddress, final UUID uuid, final byte[] value,
-            final boolean confirm) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                BluetoothGattCharacteristic characteristic = mCharacteristics.get(uuid);
-                characteristic.setValue(value);
-                mGattServer.notifyCharacteristicChanged(
-                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddress),
-                        characteristic, confirm);
-            }
-        });
-    }
-
-    private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
-        @Override
-        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
-            String address = device.getAddress();
-            Log.v(TAG, String.format(
-                    "BluetoothGattServerManager.onConnectionStateChange on %s: status %d,"
-                            + " newState %d", address, status, newState));
-            mCallback.onConnectionStateChange(address, status, newState);
-        }
-
-        @Override
-        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
-                BluetoothGattCharacteristic characteristic) {
-            String address = device.getAddress();
-            UUID uuid = characteristic.getUuid();
-            Log.v(TAG,
-                    String.format("BluetoothGattServerManager.onCharacteristicReadRequest on %s: "
-                                    + "characteristic %s, request %d, offset %d",
-                            address, uuid, requestId, offset));
-            mCallback.onCharacteristicReadRequest(address, uuid);
-            mGattServer.sendResponse(
-                    device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
-                    characteristic.getValue());
-        }
-
-        @Override
-        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
-                BluetoothGattCharacteristic characteristic, boolean preparedWrite,
-                boolean responseNeeded,
-                int offset, byte[] value) {
-            String address = device.getAddress();
-            UUID uuid = characteristic.getUuid();
-            Log.v(TAG,
-                    String.format("BluetoothGattServerManager.onCharacteristicWriteRequest on %s: "
-                                    + "characteristic %s, request %d, offset %d, preparedWrite %b, "
-                                    + "responseNeeded %b",
-                            address, uuid, requestId, offset, preparedWrite, responseNeeded));
-            mCallback.onCharacteristicWriteRequest(address, uuid, value, preparedWrite,
-                    responseNeeded);
-
-            if (responseNeeded) {
-                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
-                        null);
-            }
-        }
-
-        @Override
-        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
-                BluetoothGattDescriptor descriptor) {
-            String address = device.getAddress();
-            UUID uuid = descriptor.getUuid();
-            Log.v(TAG, String.format("BluetoothGattServerManager.onDescriptorReadRequest on %s: "
-                            + " descriptor %s, requestId %d, offset %d",
-                    address, uuid, requestId, offset));
-            mCallback.onDescriptorReadRequest(address, uuid);
-            mGattServer.sendResponse(
-                    device, requestId, BluetoothGatt.GATT_SUCCESS, offset, descriptor.getValue());
-        }
-
-        @Override
-        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
-                BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded,
-                int offset, byte[] value) {
-            String address = device.getAddress();
-            UUID uuid = descriptor.getUuid();
-            Log.v(TAG, String.format("BluetoothGattServerManager.onDescriptorWriteRequest on %s: "
-                            + "descriptor %s, requestId %d, offset %d, preparedWrite %b, "
-                            + "responseNeeded %b",
-                    address, uuid, requestId, offset, preparedWrite, responseNeeded));
-            mCallback.onDescriptorWriteRequest(address, uuid, value, preparedWrite, responseNeeded);
-
-            if (responseNeeded) {
-                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
-                        null);
-            }
-        }
-
-        @Override
-        public void onNotificationSent(BluetoothDevice device, int status) {
-            String address = device.getAddress();
-            Log.v(TAG,
-                    String.format("BluetoothGattServerManager.onNotificationSent on %s: status %d",
-                            address, status));
-            mCallback.onNotificationSent(address, status);
-        }
-
-        @Override
-        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
-            /*** Not implemented yet
-             String address = device.getAddress();
-             Log.v(TAG, String.format(
-             "BluetoothGattServerManager.onExecuteWrite on %s: requestId %d, execute %b",
-             address, requestId, execute));
-             callback.onExecuteWrite(address, execute);
-             */
-        }
-
-        @Override
-        public void onServiceAdded(int status, BluetoothGattService service) {
-            UUID uuid = service.getUuid();
-            Log.v(TAG, String.format(
-                    "BluetoothGattServerManager.onServiceAdded: service %s, status %d",
-                    uuid, status));
-            mCallback.onServiceAdded(uuid, status);
-
-            for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
-                mCharacteristics.put(characteristic.getUuid(), characteristic);
-            }
-        }
-
-        @Override
-        public void onMtuChanged(BluetoothDevice device, int mtu) {
-            Log.v(TAG, String.format("onMtuChanged(mtu: %s)", mtu));
-            mCallback.onMtuChanged(device.getAddress(), mtu);
-        }
-    };
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommAcceptor.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommAcceptor.java
deleted file mode 100644
index 5204c2a..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommAcceptor.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothServerSocket;
-import android.bluetooth.BluetoothSocket;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironmentInternal;
-import com.android.libraries.testing.deviceshadower.helpers.utils.IOUtils;
-
-import java.io.IOException;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Helper class to operate a device with basic functionality to accept BluetoothRfcommConnection.
- *
- * <p>
- * Usage: // Create a virtual device to accept incoming connection. BluetoothRfcommAcceptor acceptor
- * = new BluetoothRfcommAcceptor(address, uuid, callback); // Start accepting incoming connection,
- * with given uuid. acceptor.start(); // Connector needs to wait till acceptor started to make sure
- * there is a server socket created. acceptor.waitTillServerSocketStarted();
- *
- * // Connector can initiate connection.
- *
- * // A blocking call to wait for connection. acceptor.waitTillConnected();
- *
- * // Acceptor sends a message acceptor.send("Hello".getBytes());
- *
- * // Cancel acceptor to release all blocking calls. acceptor.cancel();
- */
-public class BluetoothRfcommAcceptor {
-
-    private static final String TAG = "BluetoothRfcommAcceptor";
-
-    /**
-     * Identifiers to control Bluetooth operation.
-     */
-    public static final int PRE_START = 4;
-    public static final int PRE_ACCEPT = 1;
-    public static final int PRE_WRITE = 3;
-    public static final int PRE_READ = 2;
-
-    private final String mAddress;
-    private final UUID mUuid;
-    private BluetoothSocket mSocket;
-    private BluetoothServerSocket mServerSocket;
-
-    private final AtomicBoolean mCancelled;
-    private final Callback mCallback;
-    private final CountDownLatch mStartLatch = new CountDownLatch(1);
-    private final CountDownLatch mConnectLatch = new CountDownLatch(1);
-    private final Queue<CountDownLatch> mReadLatches = new ConcurrentLinkedQueue<>();
-
-    /**
-     * Callback of BluetoothRfcommAcceptor.
-     */
-    public interface Callback {
-
-        void onSocketAccepted(BluetoothSocket socket);
-
-        void onDataReceived(byte[] data);
-
-        void onDataWritten(byte[] data);
-
-        void onError(Exception exception);
-    }
-
-    public BluetoothRfcommAcceptor(String address, UUID uuid, Callback callback) {
-        this.mAddress = address;
-        this.mUuid = uuid;
-        this.mCallback = callback;
-        this.mCancelled = new AtomicBoolean(false);
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    /**
-     * Start bluetooth server socket, accept incoming connection, and receive incoming data once
-     * connected.
-     */
-    public Future<Void> start() {
-        return DeviceShadowEnvironment.run(mAddress, mCode);
-    }
-
-    /**
-     * Blocking call to wait bluetooth server socket started.
-     */
-    public void waitTillServerSocketStarted() {
-        try {
-            mStartLatch.await();
-        } catch (InterruptedException e) {
-            Log.w(TAG, mAddress + " fail to wait till started: ", e);
-        }
-    }
-
-    public void waitTillConnected() {
-        try {
-            mConnectLatch.await();
-        } catch (InterruptedException e) {
-            Log.w(TAG, mAddress + " fail to wait till started: ", e);
-        }
-    }
-
-    public void waitTillDataReceived() {
-        try {
-            if (mReadLatches.size() > 0) {
-                mReadLatches.poll().await();
-            }
-        } catch (InterruptedException e) {
-            // no-op
-        }
-    }
-
-    /**
-     * Stop receiving data by closing socket.
-     */
-    public Future<Void> cancel() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mCancelled.set(true);
-                try {
-                    mSocket.close();
-                } catch (IOException e) {
-                    Log.w(TAG, mAddress + " fail to close server socket", e);
-                }
-            }
-        });
-    }
-
-    /**
-     * Send data to connected device.
-     */
-    public Future<Void> send(final byte[] data) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                if (mSocket != null) {
-                    try {
-                        DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_WRITE);
-                        IOUtils.write(mSocket.getOutputStream(), data);
-                        Log.d(TAG, mAddress + " write: " + new String(data));
-                        mCallback.onDataWritten(data);
-                    } catch (IOException e) {
-                        Log.w(TAG, mAddress + " fail to write: ", e);
-                        mCallback.onError(new IOException("Fail to write", e));
-                    }
-                }
-            }
-        });
-    }
-
-    private Runnable mCode = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_START);
-                mServerSocket = BluetoothAdapter.getDefaultAdapter()
-                        .listenUsingInsecureRfcommWithServiceRecord("AA", mUuid);
-            } catch (IOException e) {
-                Log.w(TAG, mAddress + " fail to start server socket: ", e);
-                mCallback.onError(new IOException("Fail to start server socket", e));
-                return;
-            } finally {
-                mStartLatch.countDown();
-            }
-
-            try {
-                DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_ACCEPT);
-                mSocket = mServerSocket.accept();
-                Log.d(TAG, mAddress + " accept: " + mSocket.getRemoteDevice().getAddress());
-                mCallback.onSocketAccepted(mSocket);
-                mServerSocket.close();
-            } catch (IOException e) {
-                Log.w(TAG, mAddress + " fail to connect: ", e);
-                mCallback.onError(new IOException("Fail to connect", e));
-                return;
-            } finally {
-                mConnectLatch.countDown();
-            }
-
-            do {
-                try {
-                    CountDownLatch latch = new CountDownLatch(1);
-                    mReadLatches.add(latch);
-                    DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_READ);
-                    byte[] data = IOUtils.read(mSocket.getInputStream());
-                    Log.d(TAG, mAddress + " read: " + new String(data));
-                    mCallback.onDataReceived(data);
-                    latch.countDown();
-                } catch (IOException e) {
-                    Log.w(TAG, mAddress + " fail to read: ", e);
-                    mCallback.onError(new IOException("Fail to read", e));
-                    return;
-                }
-            } while (!mCancelled.get());
-
-            Log.d(TAG, mAddress + " stop receiving");
-        }
-    };
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommConnector.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommConnector.java
deleted file mode 100644
index e386d59..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/bluetooth/BluetoothRfcommConnector.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothSocket;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironmentInternal;
-import com.android.libraries.testing.deviceshadower.helpers.utils.IOUtils;
-
-import java.io.IOException;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Helper class to operate a device with basic functionality to accept BluetoothRfcommConnection.
- *
- * <p>
- * Usage: // Create a virtual device to initiate connection. BluetoothRfcommConnector connector =
- * new BluetoothRfcommConnector(address, callback); // Start connection to a remote address with
- * given uuid. connector.start(remoteAddress, remoteUuid);
- *
- * // A blocking call to wait for connection. connector.waitTillConnected();
- *
- * // Connector sends a message connector.send("Hello".getBytes());
- *
- * // Cancel connector to release all blocking calls. connector.cancel();
- */
-public class BluetoothRfcommConnector {
-
-    private static final String TAG = "BluetoothRfcommConnector";
-
-    /**
-     * Identifiers to control Bluetooth operation.
-     */
-    public static final int PRE_CONNECT = 1;
-    public static final int PRE_READ = 2;
-    public static final int PRE_WRITE = 3;
-
-    private final String mAddress;
-    private String mRemoteAddress = null;
-    private final UUID mRemoteUuid;
-    private BluetoothSocket mSocket;
-
-    private final Callback mCallback;
-    private final AtomicBoolean mCancelled;
-    private final CountDownLatch mConnectLatch = new CountDownLatch(1);
-    private final Queue<CountDownLatch> mReadLatches = new ConcurrentLinkedQueue<>();
-
-    /**
-     * Callback of BluetoothRfcommConnector.
-     */
-    public interface Callback {
-
-        void onConnected(BluetoothSocket socket);
-
-        void onDataReceived(byte[] data);
-
-        void onDataWritten(byte[] data);
-
-        void onError(Exception exception);
-    }
-
-    public BluetoothRfcommConnector(String address, UUID uuid, Callback callback) {
-        this.mAddress = address;
-        this.mRemoteUuid = uuid;
-        this.mCallback = callback;
-        this.mCancelled = new AtomicBoolean(false);
-        DeviceShadowEnvironment.addDevice(address).bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON);
-    }
-
-    /**
-     * Start connection to a remote address, and receive data once connected.
-     */
-    public Future<Void> start(String remoteAddress) {
-        this.mRemoteAddress = remoteAddress;
-        return DeviceShadowEnvironment.run(mAddress, mCode);
-    }
-
-    /**
-     * Stop receiving data.
-     */
-    public Future<Void> cancel() {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mCancelled.set(true);
-                try {
-                    mSocket.close();
-                } catch (IOException e) {
-                    Log.w(TAG, mAddress + " fail to close socket", e);
-                }
-            }
-        });
-    }
-
-    public void waitTillConnected() {
-        try {
-            mConnectLatch.await();
-        } catch (InterruptedException e) {
-            Log.w(TAG, mAddress + " fail to wait till started: ", e);
-        }
-    }
-
-    public void waitTillDataReceived() {
-        try {
-            if (mReadLatches.size() > 0) {
-                mReadLatches.poll().await();
-            }
-        } catch (InterruptedException e) {
-            // no-op.
-        }
-    }
-
-    /**
-     * Send data to conneceted device.
-     */
-    public Future<Void> send(final byte[] data) {
-        return DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                if (mSocket != null) {
-                    try {
-                        DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_WRITE);
-                        IOUtils.write(mSocket.getOutputStream(), data);
-                        Log.d(TAG, mAddress + " write: " + new String(data));
-                        mCallback.onDataWritten(data);
-                    } catch (IOException e) {
-                        Log.w(TAG, mAddress + " fail to write: ", e);
-                        mCallback.onError(new IOException("Fail to write", e));
-                    }
-                }
-            }
-        });
-    }
-
-    private Runnable mCode = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_CONNECT);
-                mSocket = BluetoothAdapter.getDefaultAdapter()
-                        .getRemoteDevice(mRemoteAddress)
-                        .createInsecureRfcommSocketToServiceRecord(mRemoteUuid);
-                mSocket.connect();
-                Log.d(TAG, mAddress + " accept: " + mSocket.getRemoteDevice().getAddress());
-                mCallback.onConnected(mSocket);
-            } catch (IOException e) {
-                Log.w(TAG, mAddress + " fail to connect: ", e);
-                mCallback.onError(new IOException("Fail to connect", e));
-            } finally {
-                mConnectLatch.countDown();
-            }
-
-            try {
-                do {
-                    CountDownLatch latch = new CountDownLatch(1);
-                    mReadLatches.add(latch);
-                    DeviceShadowEnvironmentInternal.setInterruptibleBluetooth(PRE_READ);
-                    byte[] data = IOUtils.read(mSocket.getInputStream());
-                    Log.d(TAG, mAddress + " read: " + new String(data));
-                    mCallback.onDataReceived(data);
-                    latch.countDown();
-                } while (!mCancelled.get());
-            } catch (IOException e) {
-                Log.w(TAG, mAddress + " fail to read: ", e);
-                mCallback.onError(new IOException("Fail to read", e));
-            }
-            Log.d(TAG, mAddress + " stop receiving");
-        }
-    };
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcActivity.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcActivity.java
deleted file mode 100644
index 8ae4435..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcActivity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.nfc;
-
-import android.app.Activity;
-
-/**
- * Activity that triggers or receives NFC events.
- */
-public class NfcActivity extends Activity {
-
-    private NfcReceiver.Callback mCallback;
-
-    public void setCallback(NfcReceiver.Callback callback) {
-        this.mCallback = callback;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        NfcReceiver.processIntent(mCallback, getIntent());
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcReceiver.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcReceiver.java
deleted file mode 100644
index b85a124..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcReceiver.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.nfc;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.nfc.NdefMessage;
-import android.nfc.NfcAdapter;
-import android.os.Parcelable;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to receive NFC events.
- */
-public class NfcReceiver {
-
-    private static final String TAG = "NfcReceiver";
-
-    /**
-     * Callback to receive message.
-     */
-    public interface Callback {
-
-        void onReceive(String message);
-    }
-
-    private final String mAddress;
-    private final Activity mActivity;
-    private CountDownLatch mReceiveLatch;
-
-    private final BroadcastReceiver mReceiver;
-    private final IntentFilter mFilter;
-
-    public NfcReceiver(String address, Activity activity, final Callback callback) {
-        this(address, activity, new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
-                    processIntent(callback, intent);
-                }
-            }
-        });
-        DeviceShadowEnvironment.addDevice(address);
-    }
-
-    public NfcReceiver(
-            final String address, Activity activity, final BroadcastReceiver clientReceiver) {
-        this.mAddress = address;
-        this.mActivity = activity;
-
-        this.mFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
-        this.mReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                Log.v(TAG, "Receive broadcast on device " + address);
-                clientReceiver.onReceive(context, intent);
-                mReceiveLatch.countDown();
-            }
-        };
-        DeviceShadowEnvironment.addDevice(address);
-    }
-
-    public void startReceive() throws InterruptedException, ExecutionException {
-        mReceiveLatch = new CountDownLatch(1);
-
-        DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mActivity.getApplication().registerReceiver(mReceiver, mFilter);
-            }
-        }).get();
-    }
-
-    public void waitUntilReceive(long timeoutMillis) throws InterruptedException {
-        mReceiveLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
-    }
-
-    public void stopReceive() throws InterruptedException, ExecutionException {
-        DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                mActivity.getApplication().unregisterReceiver(mReceiver);
-            }
-        }).get();
-    }
-
-    static void processIntent(Callback callback, Intent intent) {
-        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
-        if (rawMsgs != null && rawMsgs.length > 0) {
-            // only one message sent during the beam
-            NdefMessage msg = (NdefMessage) rawMsgs[0];
-            if (callback != null) {
-                callback.onReceive(new String(msg.getRecords()[0].getPayload()));
-            }
-        }
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcSender.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcSender.java
deleted file mode 100644
index dbbb5fa..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/nfc/NfcSender.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.nfc;
-
-import android.app.Activity;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NfcAdapter;
-import android.nfc.NfcAdapter.CreateNdefMessageCallback;
-import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
-import android.nfc.NfcEvent;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * Helper class to send NFC events.
- */
-public class NfcSender {
-
-    private static final String NFC_PACKAGE = "DS_PKG";
-    private static final String NFC_TAG = "DS_TAG";
-
-    /**
-     * Callback to update sender status.
-     */
-    public interface Callback {
-
-        void onSend(String message);
-    }
-
-    private final String mAddress;
-    private final Activity mActivity;
-    private final Callback mCallback;
-    private final SenderCallback mSenderCallback;
-    private String mSessage;
-
-    public NfcSender(String address, Activity activity, Callback callback) {
-        this.mCallback = callback;
-        this.mAddress = address;
-        this.mActivity = activity;
-        DeviceShadowEnvironment.addDevice(address);
-        this.mSenderCallback = new SenderCallback();
-    }
-
-    public void startSend(String message) throws InterruptedException, ExecutionException {
-        this.mSessage = message;
-        DeviceShadowEnvironment.run(mAddress, new Runnable() {
-            @Override
-            public void run() {
-                NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(mActivity);
-                nfcAdapter.setNdefPushMessageCallback(mSenderCallback, mActivity);
-                nfcAdapter.setOnNdefPushCompleteCallback(mSenderCallback, mActivity);
-            }
-        }).get();
-    }
-
-    class SenderCallback implements CreateNdefMessageCallback, OnNdefPushCompleteCallback {
-
-        @Override
-        public NdefMessage createNdefMessage(NfcEvent event) {
-            NdefMessage msg = new NdefMessage(new NdefRecord[]{
-                    NdefRecord.createExternal(NFC_PACKAGE, NFC_TAG, mSessage.getBytes())
-            });
-            return msg;
-        }
-
-        @Override
-        public void onNdefPushComplete(NfcEvent event) {
-            mCallback.onSend(mSessage);
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/utils/IOUtils.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/utils/IOUtils.java
deleted file mode 100644
index d89754b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/helpers/utils/IOUtils.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.helpers.utils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Utils for IO methods.
- */
-public class IOUtils {
-
-    /**
-     * Write num of bytes to be sent and payload through OutputStream.
-     */
-    public static void write(OutputStream os, byte[] data) throws IOException {
-        ByteBuffer buffer = ByteBuffer.allocate(4 + data.length).putInt(data.length).put(data);
-        os.write(buffer.array());
-    }
-
-    /**
-     * Read num of bytes to be read, and payload through InputStream.
-     *
-     * @return payload received.
-     */
-    public static byte[] read(InputStream is) throws IOException {
-        byte[] size = new byte[4];
-        is.read(size, 0, 4 /* bytes of int type */);
-
-        byte[] data = new byte[ByteBuffer.wrap(size).getInt()];
-        is.read(data);
-        return data;
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowEnvironmentImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowEnvironmentImpl.java
deleted file mode 100644
index 6a06ce4..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowEnvironmentImpl.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal;
-
-import android.content.ContentProvider;
-import android.os.Looper;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.Enums.Distance;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl;
-import com.android.libraries.testing.deviceshadower.internal.common.NamedRunnable;
-import com.android.libraries.testing.deviceshadower.internal.common.Scheduler;
-import com.android.libraries.testing.deviceshadower.internal.nfc.NfcletImpl;
-import com.android.libraries.testing.deviceshadower.internal.sms.SmsContentProvider;
-import com.android.libraries.testing.deviceshadower.internal.sms.SmsletImpl;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.collect.ImmutableList;
-
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowLooper;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Proxy to manage internal data models, and help shadows to exchange data.
- */
-public class DeviceShadowEnvironmentImpl {
-
-    private static final Logger LOGGER = Logger.create("DeviceShadowEnvironmentImpl");
-    private static final long SCHEDULER_WAIT_TIMEOUT_MILLIS = 5000L;
-
-    // ThreadLocal to store local address for each device.
-    private static InheritableThreadLocal<DeviceletImpl> sLocalDeviceletImpl =
-            new InheritableThreadLocal<>();
-
-    // Devicelets contains all registered devicelet to simulate a device.
-    private static final Map<String, DeviceletImpl> DEVICELETS = new ConcurrentHashMap<>();
-
-    @VisibleForTesting
-    static final Map<String, ExecutorService> EXECUTORS = new ConcurrentHashMap<>();
-
-    private static final List<DeviceShadowException> INTERNAL_EXCEPTIONS =
-            Collections.synchronizedList(new ArrayList<DeviceShadowException>());
-
-    private static final ContentProvider smsContentProvider = new SmsContentProvider();
-
-    public static DeviceletImpl getDeviceletImpl(String address) {
-        return DEVICELETS.get(address);
-    }
-
-    public static void checkInternalExceptions() {
-        if (INTERNAL_EXCEPTIONS.size() > 0) {
-            for (DeviceShadowException exception : INTERNAL_EXCEPTIONS) {
-                LOGGER.e("Internal exception", exception);
-            }
-            INTERNAL_EXCEPTIONS.clear();
-            throw new RuntimeException("DeviceShadower has internal exceptions");
-        }
-    }
-
-    public static void reset() {
-        // reset local devicelet for single device testing
-        sLocalDeviceletImpl.remove();
-        DEVICELETS.clear();
-        BlueletImpl.reset();
-        INTERNAL_EXCEPTIONS.clear();
-    }
-
-    public static boolean await(long timeoutMillis) {
-        boolean schedulerDone = false;
-        try {
-            schedulerDone = Scheduler.await(timeoutMillis);
-        } catch (InterruptedException e) {
-            // no-op.
-        } finally {
-            if (!schedulerDone) {
-                catchInternalException(new DeviceShadowException("Scheduler not complete"));
-                for (DeviceletImpl devicelet : DEVICELETS.values()) {
-                    LOGGER.e(
-                            String.format(
-                                    "Device %s\n\tUI: %s\n\tService: %s",
-                                    devicelet.getAddress(),
-                                    devicelet.getUiScheduler(),
-                                    devicelet.getServiceScheduler()));
-                }
-                Scheduler.clear();
-            }
-        }
-        for (ExecutorService executor : EXECUTORS.values()) {
-            executor.shutdownNow();
-        }
-        boolean terminateSuccess = true;
-        for (ExecutorService executor : EXECUTORS.values()) {
-            try {
-                executor.awaitTermination(timeoutMillis, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                terminateSuccess = false;
-            }
-            if (!executor.isTerminated()) {
-                LOGGER.e("Failed to terminate executor.");
-                terminateSuccess = false;
-            }
-        }
-        EXECUTORS.clear();
-        return schedulerDone && terminateSuccess;
-    }
-
-    public static boolean hasLocalDeviceletImpl() {
-        return sLocalDeviceletImpl.get() != null;
-    }
-
-    public static DeviceletImpl getLocalDeviceletImpl() {
-        return sLocalDeviceletImpl.get();
-    }
-
-    public static List<DeviceletImpl> getDeviceletImpls() {
-        return ImmutableList.copyOf(DEVICELETS.values());
-    }
-
-    public static BlueletImpl getLocalBlueletImpl() {
-        return sLocalDeviceletImpl.get().blueletImpl();
-    }
-
-    public static BlueletImpl getBlueletImpl(String address) {
-        DeviceletImpl devicelet = getDeviceletImpl(address);
-        return devicelet == null ? null : devicelet.blueletImpl();
-    }
-
-    public static NfcletImpl getLocalNfcletImpl() {
-        return sLocalDeviceletImpl.get().nfcletImpl();
-    }
-
-    public static NfcletImpl getNfcletImpl(String address) {
-        DeviceletImpl devicelet = getDeviceletImpl(address);
-        return devicelet == null ? null : devicelet.nfcletImpl();
-    }
-
-    public static SmsletImpl getLocalSmsletImpl() {
-        return sLocalDeviceletImpl.get().smsletImpl();
-    }
-
-    public static ContentProvider getSmsContentProvider() {
-        return smsContentProvider;
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public static DeviceletImpl addDevice(String address) {
-        EXECUTORS.put(address, Executors.newCachedThreadPool());
-
-        // DeviceShadower keeps track of the "local" device based on the current thread. It uses an
-        // InheritableThreadLocal, so threads created by the current thread also get the same
-        // thread-local value. Add the device on its own thread, to set the thread local for that
-        // thread and its children.
-        try {
-            EXECUTORS
-                    .get(address)
-                    .submit(
-                            () -> {
-                                DeviceletImpl devicelet = new DeviceletImpl(address);
-                                DEVICELETS.put(address, devicelet);
-                                setLocalDevice(address);
-                                // Ensure these threads are actually created, by posting one empty
-                                // runnable.
-                                devicelet.getServiceScheduler()
-                                        .post(NamedRunnable.create("Init", () -> {
-                                        }));
-                                devicelet.getUiScheduler().post(NamedRunnable.create("Init", () -> {
-                                }));
-                            })
-                    .get();
-        } catch (InterruptedException | ExecutionException e) {
-            throw new IllegalStateException(e);
-        }
-
-        return DEVICELETS.get(address);
-    }
-
-    public static void removeDevice(String address) {
-        DEVICELETS.remove(address);
-        EXECUTORS.remove(address);
-    }
-
-    public static void setInterruptibleBluetooth(int identifier) {
-        getLocalBlueletImpl().setInterruptible(identifier);
-    }
-
-    public static void interruptBluetooth(String address, int identifier) {
-        getBlueletImpl(address).interrupt(identifier);
-    }
-
-    public static void setDistance(String address1, String address2, final Distance distance) {
-        final DeviceletImpl device1 = getDeviceletImpl(address1);
-        final DeviceletImpl device2 = getDeviceletImpl(address2);
-
-        Future<Void> result1 = null;
-        Future<Void> result2 = null;
-        if (device1.updateDistance(address2, distance)) {
-            result1 =
-                    run(
-                            address1,
-                            () -> {
-                                device1.onDistanceChange(device2, distance);
-                                return null;
-                            });
-        }
-
-        if (device2.updateDistance(address1, distance)) {
-            result2 =
-                    run(
-                            address2,
-                            () -> {
-                                device2.onDistanceChange(device1, distance);
-                                return null;
-                            });
-        }
-
-        try {
-            if (result1 != null) {
-                result1.get();
-            }
-            if (result2 != null) {
-                result2.get();
-            }
-        } catch (InterruptedException | ExecutionException e) {
-            catchInternalException(new DeviceShadowException(e));
-        }
-    }
-
-    /**
-     * Set local Bluelet for current thread.
-     *
-     * <p>This can be used to convert current running thread to hold a bluelet object, so that unit
-     * test does not have to call BluetoothEnvironment.run() to run code.
-     */
-    @VisibleForTesting
-    public static void setLocalDevice(String address) {
-        DeviceletImpl local = DEVICELETS.get(address);
-        if (local == null) {
-            throw new RuntimeException(address + " is not initialized by BluetoothEnvironment");
-        }
-        sLocalDeviceletImpl.set(local);
-    }
-
-    public static <T> Future<T> run(final String address, final Callable<T> snippet) {
-        return EXECUTORS
-                .get(address)
-                .submit(
-                        () -> {
-                            DeviceShadowEnvironmentImpl.setLocalDevice(address);
-                            ShadowLooper mainLooper = Shadows.shadowOf(Looper.getMainLooper());
-                            try {
-                                T result = snippet.call();
-
-                                // Avoid idling the main looper in paused mode since doing so is
-                                // only allowed from the main thread.
-                                if (!mainLooper.isPaused()) {
-                                    // In Robolectric, runnable doesn't run when posting thread
-                                    // differs from looper thread, idle main looper explicitly to
-                                    // execute posted Runnables.
-                                    ShadowLooper.idleMainLooper();
-                                }
-
-                                // Wait all scheduled runnables complete.
-                                Scheduler.await(SCHEDULER_WAIT_TIMEOUT_MILLIS);
-                                return result;
-                            } catch (Exception e) {
-                                LOGGER.e("Fail to call code on device: " + address, e);
-                                if (!mainLooper.isPaused()) {
-                                    // reset() is not supported in paused mode.
-                                    mainLooper.reset();
-                                }
-                                throw new RuntimeException(e);
-                            }
-                        });
-    }
-
-    // @CanIgnoreReturnValue
-    // Return value can be ignored because {@link Scheduler} will call
-    // {@link catchInternalException} to catch exceptions, and throw when test completes.
-    public static Future<?> runOnUi(String address, NamedRunnable snippet) {
-        Scheduler scheduler = DeviceShadowEnvironmentImpl.getDeviceletImpl(address)
-                .getUiScheduler();
-        return run(scheduler, address, snippet);
-    }
-
-    // @CanIgnoreReturnValue
-    // Return value can be ignored because {@link Scheduler} will call
-    // {@link catchInternalException} to catch exceptions, and throw when test completes.
-    public static Future<?> runOnService(String address, NamedRunnable snippet) {
-        Scheduler scheduler =
-                DeviceShadowEnvironmentImpl.getDeviceletImpl(address).getServiceScheduler();
-        return run(scheduler, address, snippet);
-    }
-
-    // @CanIgnoreReturnValue
-    // Return value can be ignored because {@link Scheduler} will call
-    // {@link catchInternalException} to catch exceptions, and throw when test completes.
-    private static Future<?> run(
-            Scheduler scheduler, final String address, final NamedRunnable snippet) {
-        return scheduler.post(
-                NamedRunnable.create(
-                        snippet.toString(),
-                        () -> {
-                            DeviceShadowEnvironmentImpl.setLocalDevice(address);
-                            snippet.run();
-                        }));
-    }
-
-    public static void catchInternalException(Exception exception) {
-        INTERNAL_EXCEPTIONS.add(new DeviceShadowException(exception));
-    }
-
-    // This is used to test Device Shadower internal.
-    @VisibleForTesting
-    public static void setDeviceletForTest(String address, DeviceletImpl devicelet) {
-        DEVICELETS.put(address, devicelet);
-    }
-
-    @VisibleForTesting
-    public static void setExecutorForTest(String address) {
-        setExecutorForTest(address, Executors.newCachedThreadPool());
-    }
-
-    @VisibleForTesting
-    public static void setExecutorForTest(String address, ExecutorService executor) {
-        EXECUTORS.put(address, executor);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowException.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowException.java
deleted file mode 100644
index 77d358f..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceShadowException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal;
-
-/**
- * Internal exception to indicate error from DeviceShadower framework.
- */
-public class DeviceShadowException extends Exception {
-
-    public DeviceShadowException(Throwable e) {
-        super(e);
-    }
-
-    public DeviceShadowException(String msg) {
-        super(msg);
-    }
-
-    public DeviceShadowException(String msg, Throwable e) {
-        super(msg, e);
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceletImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceletImpl.java
deleted file mode 100644
index 9aea065..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/DeviceletImpl.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal;
-
-import com.android.libraries.testing.deviceshadower.Bluelet;
-import com.android.libraries.testing.deviceshadower.Devicelet;
-import com.android.libraries.testing.deviceshadower.Enums.Distance;
-import com.android.libraries.testing.deviceshadower.Nfclet;
-import com.android.libraries.testing.deviceshadower.Smslet;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl;
-import com.android.libraries.testing.deviceshadower.internal.common.BroadcastManager;
-import com.android.libraries.testing.deviceshadower.internal.common.Scheduler;
-import com.android.libraries.testing.deviceshadower.internal.nfc.NfcletImpl;
-import com.android.libraries.testing.deviceshadower.internal.sms.SmsletImpl;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * DeviceletImpl is the implementation to hold different medium-let in DeviceShadowEnvironment.
- */
-public class DeviceletImpl implements Devicelet {
-
-    private final BlueletImpl mBluelet;
-    private final NfcletImpl mNfclet;
-    private final SmsletImpl mSmslet;
-    private final BroadcastManager mBroadcastManager;
-    private final String mAddress;
-    private final Map<String, Distance> mDistanceMap = new HashMap<>();
-    private final Scheduler mServiceScheduler;
-    private final Scheduler mUiScheduler;
-
-    public DeviceletImpl(String address) {
-        this.mAddress = address;
-        this.mServiceScheduler = new Scheduler(address + "-service");
-        this.mUiScheduler = new Scheduler(address + "-main");
-        this.mBroadcastManager = new BroadcastManager(mUiScheduler);
-        this.mBluelet = new BlueletImpl(address, mBroadcastManager);
-        this.mNfclet = new NfcletImpl();
-        this.mSmslet = new SmsletImpl();
-    }
-
-    @Override
-    public Bluelet bluetooth() {
-        return mBluelet;
-    }
-
-    public BlueletImpl blueletImpl() {
-        return mBluelet;
-    }
-
-    @Override
-    public Nfclet nfc() {
-        return mNfclet;
-    }
-
-    public NfcletImpl nfcletImpl() {
-        return mNfclet;
-    }
-
-    @Override
-    public Smslet sms() {
-        return mSmslet;
-    }
-
-    public SmsletImpl smsletImpl() {
-        return mSmslet;
-    }
-
-    public BroadcastManager getBroadcastManager() {
-        return mBroadcastManager;
-    }
-
-    @Override
-    public String getAddress() {
-        return mAddress;
-    }
-
-    Scheduler getServiceScheduler() {
-        return mServiceScheduler;
-    }
-
-    Scheduler getUiScheduler() {
-        return mUiScheduler;
-    }
-
-    /**
-     * Update distance to remote device.
-     *
-     * @return true if distance updated.
-     */
-    /*package*/ boolean updateDistance(String remoteAddress, Distance distance) {
-        Distance currentDistance = mDistanceMap.get(remoteAddress);
-        if (currentDistance == null || !distance.equals(currentDistance)) {
-            mDistanceMap.put(remoteAddress, distance);
-            return true;
-        }
-        return false;
-    }
-
-    /*package*/ void onDistanceChange(DeviceletImpl remote, Distance distance) {
-        if (distance == Distance.NEAR) {
-            mNfclet.onNear(remote.mNfclet);
-        }
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/AdapterDelegate.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/AdapterDelegate.java
deleted file mode 100644
index b5227b7..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/AdapterDelegate.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass.Device;
-import android.os.Build.VERSION;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.DeviceletImpl;
-import com.android.libraries.testing.deviceshadower.internal.common.NamedRunnable;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Class handling Bluetooth Adapter State change. Currently async event processing is not supported,
- * and there is no deferred operation when adapter is in a pending state.
- */
-class AdapterDelegate {
-
-    /**
-     * Callback for adapter
-     */
-    public interface Callback {
-
-        void onAdapterStateChange(State prevState, State newState);
-
-        void onBleStateChange(State prevState, State newState);
-
-        void onDiscoveryStarted();
-
-        void onDiscoveryFinished();
-
-        void onDeviceFound(String address, int bluetoothClass, String name);
-    }
-
-    @GuardedBy("this")
-    private State mCurrentState;
-
-    private final String mAddress;
-    private final Callback mCallback;
-    private AtomicBoolean mIsDiscovering = new AtomicBoolean(false);
-    private final AtomicInteger mScanMode = new AtomicInteger(BluetoothAdapter.SCAN_MODE_NONE);
-    private int mBluetoothClass = Device.PHONE_SMART;
-
-    AdapterDelegate(String address, Callback callback) {
-        this.mAddress = address;
-        this.mCurrentState = State.OFF;
-        this.mCallback = callback;
-    }
-
-    synchronized void processEvent(Event event) {
-        State newState = TRANSITION[mCurrentState.ordinal()][event.ordinal()];
-        if (newState == null) {
-            return;
-        }
-        State prevState = mCurrentState;
-        mCurrentState = newState;
-        handleStateChange(prevState, newState);
-    }
-
-    private void handleStateChange(State prevState, State newState) {
-        // TODO(b/200231384): fake service bind/unbind on state change
-        if (prevState.equals(newState)) {
-            return;
-        }
-        if (VERSION.SDK_INT < 23) {
-            mCallback.onAdapterStateChange(prevState, newState);
-        } else {
-            mCallback.onBleStateChange(prevState, newState);
-            if (newState.equals(State.BLE_TURNING_ON)
-                    || newState.equals(State.BLE_TURNING_OFF)
-                    || newState.equals(State.OFF)
-                    || (newState.equals(State.BLE_ON) && prevState.equals(State.BLE_TURNING_ON))) {
-                return;
-            }
-            if (newState.equals(State.BLE_ON)) {
-                newState = State.OFF;
-            } else if (prevState.equals(State.BLE_ON)) {
-                prevState = State.OFF;
-            }
-            mCallback.onAdapterStateChange(prevState, newState);
-        }
-    }
-
-    synchronized State getState() {
-        return mCurrentState;
-    }
-
-    synchronized void setState(State state) {
-        mCurrentState = state;
-    }
-
-    void setBluetoothClass(int bluetoothClass) {
-        this.mBluetoothClass = bluetoothClass;
-    }
-
-    int getBluetoothClass() {
-        return mBluetoothClass;
-    }
-
-    @SuppressWarnings("FutureReturnValueIgnored")
-    void startDiscovery() {
-        synchronized (this) {
-            if (mIsDiscovering.get()) {
-                return;
-            }
-            mIsDiscovering.set(true);
-        }
-
-        mCallback.onDiscoveryStarted();
-
-        NamedRunnable onDeviceFound =
-                NamedRunnable.create(
-                        "BluetoothAdapter.onDeviceFound",
-                        new Runnable() {
-                            @Override
-                            public void run() {
-                                List<DeviceletImpl> devices =
-                                        DeviceShadowEnvironmentImpl.getDeviceletImpls();
-                                for (DeviceletImpl devicelet : devices) {
-                                    BlueletImpl bluelet = devicelet.blueletImpl();
-                                    if (mAddress.equals(devicelet.getAddress())
-                                            || bluelet.getAdapterDelegate().mScanMode.get()
-                                                    != BluetoothAdapter
-                                            .SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-                                        continue;
-                                    }
-                                    mCallback.onDeviceFound(
-                                            bluelet.address,
-                                            bluelet.getAdapterDelegate().mBluetoothClass,
-                                            bluelet.mName);
-                                }
-                                finishDiscovery();
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnUi(mAddress, onDeviceFound);
-    }
-
-    void cancelDiscovery() {
-        finishDiscovery();
-    }
-
-    boolean isDiscovering() {
-        return mIsDiscovering.get();
-    }
-
-    void setScanMode(int scanMode) {
-        // TODO(b/200231384): broadcast scan mode change.
-        this.mScanMode.set(scanMode);
-    }
-
-    int getScanMode() {
-        return mScanMode.get();
-    }
-
-    private void finishDiscovery() {
-        synchronized (this) {
-            if (!mIsDiscovering.get()) {
-                return;
-            }
-            mIsDiscovering.set(false);
-        }
-        mCallback.onDiscoveryFinished();
-    }
-
-    enum State {
-        OFF(BluetoothAdapter.STATE_OFF),
-        TURNING_ON(BluetoothAdapter.STATE_TURNING_ON),
-        ON(BluetoothAdapter.STATE_ON),
-        TURNING_OFF(BluetoothAdapter.STATE_TURNING_OFF),
-        // States for API23+
-        BLE_TURNING_ON(BluetoothConstants.STATE_BLE_TURNING_ON),
-        BLE_ON(BluetoothConstants.STATE_BLE_ON),
-        BLE_TURNING_OFF(BluetoothConstants.STATE_BLE_TURNING_OFF);
-
-        private static final Map<Integer, State> LOOKUP = new HashMap<>();
-
-        static {
-            for (State state : State.values()) {
-                LOOKUP.put(state.getValue(), state);
-            }
-        }
-
-        static State lookup(int value) {
-            return LOOKUP.get(value);
-        }
-
-        private final int mValue;
-
-        State(int value) {
-            this.mValue = value;
-        }
-
-        int getValue() {
-            return mValue;
-        }
-    }
-
-    /*
-     * Represents Bluetooth events which can trigger adapter state change.
-     */
-    enum Event {
-        USER_TURN_ON,
-        USER_TURN_OFF,
-        BREDR_STARTED,
-        BREDR_STOPPED,
-        // Events for API23+
-        BLE_TURN_ON,
-        BLE_TURN_OFF,
-        BLE_STARTED,
-        BLE_STOPPED
-    }
-
-    private static final State[][] TRANSITION =
-            new State[State.values().length][Event.values().length];
-
-    static {
-        if (VERSION.SDK_INT < 23) {
-            // transition table before API23
-            TRANSITION[State.OFF.ordinal()][Event.USER_TURN_ON.ordinal()] = State.TURNING_ON;
-            TRANSITION[State.TURNING_ON.ordinal()][Event.BREDR_STARTED.ordinal()] = State.ON;
-            TRANSITION[State.ON.ordinal()][Event.USER_TURN_OFF.ordinal()] = State.TURNING_OFF;
-            TRANSITION[State.TURNING_OFF.ordinal()][Event.BREDR_STOPPED.ordinal()] = State.OFF;
-        } else {
-            // transition table starting from API23
-            TRANSITION[State.OFF.ordinal()][Event.BLE_TURN_ON.ordinal()] = State.BLE_TURNING_ON;
-            TRANSITION[State.BLE_TURNING_ON.ordinal()][Event.BLE_STARTED.ordinal()] = State.BLE_ON;
-            TRANSITION[State.BLE_ON.ordinal()][Event.USER_TURN_ON.ordinal()] = State.TURNING_ON;
-            TRANSITION[State.TURNING_ON.ordinal()][Event.BREDR_STARTED.ordinal()] = State.ON;
-            TRANSITION[State.ON.ordinal()][Event.BLE_TURN_OFF.ordinal()] = State.TURNING_OFF;
-            TRANSITION[State.TURNING_OFF.ordinal()][Event.BREDR_STOPPED.ordinal()] = State.BLE_ON;
-            TRANSITION[State.BLE_ON.ordinal()][Event.USER_TURN_OFF.ordinal()] =
-                    State.BLE_TURNING_OFF;
-            TRANSITION[State.BLE_TURNING_OFF.ordinal()][Event.BLE_STOPPED.ordinal()] = State.OFF;
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BlueletImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BlueletImpl.java
deleted file mode 100644
index 4e534e3..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BlueletImpl.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.Manifest.permission;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothManager;
-import android.content.AttributionSource;
-import android.content.Intent;
-import android.os.Build.VERSION;
-import android.os.ParcelUuid;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.Bluelet;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.AdapterDelegate.Event;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.AdapterDelegate.State;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.RfcommDelegate;
-import com.android.libraries.testing.deviceshadower.internal.common.BroadcastManager;
-import com.android.libraries.testing.deviceshadower.internal.common.Interrupter;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A container class of a real-world Bluetooth device.
- */
-public class BlueletImpl implements Bluelet {
-
-    enum PairingConfirmation {
-        UNKNOWN,
-        CONFIRMED,
-        DENIED
-    }
-
-    /**
-     * See hidden {@link #EXTRA_REASON} and reason values in {@link BluetoothDevice}.
-     */
-    static final int REASON_SUCCESS = 0;
-    /**
-     * See hidden {@link #EXTRA_REASON} and reason values in {@link BluetoothDevice}.
-     */
-    static final int UNBOND_REASON_AUTH_FAILED = 1;
-    /**
-     * See hidden {@link #EXTRA_REASON} and reason values in {@link BluetoothDevice}.
-     */
-    static final int UNBOND_REASON_AUTH_CANCELED = 3;
-
-    /**
-     * Hidden in {@link BluetoothDevice}.
-     */
-    private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
-
-    private static final Logger LOGGER = Logger.create("BlueletImpl");
-
-    private static final ImmutableMap<Integer, Integer> PROFILE_STATE_TO_ADAPTER_STATE =
-            ImmutableMap.<Integer, Integer>builder()
-                    .put(BluetoothProfile.STATE_CONNECTED, BluetoothAdapter.STATE_CONNECTED)
-                    .put(BluetoothProfile.STATE_CONNECTING, BluetoothAdapter.STATE_CONNECTING)
-                    .put(BluetoothProfile.STATE_DISCONNECTING, BluetoothAdapter.STATE_DISCONNECTING)
-                    .put(BluetoothProfile.STATE_DISCONNECTED, BluetoothAdapter.STATE_DISCONNECTED)
-                    .build();
-
-    public static void reset() {
-        RfcommDelegate.reset();
-    }
-
-    public final String address;
-    String mName;
-    ParcelUuid[] mProfileUuids = new ParcelUuid[0];
-    int mPhonebookAccessPermission;
-    int mMessageAccessPermission;
-    int mSimAccessPermission;
-    final BluetoothAdapter mAdapter;
-    int mPassKey;
-
-    private CreateBondOutcome mCreateBondOutcome = CreateBondOutcome.SUCCESS;
-    private int mCreateBondFailureReason;
-    private IoCapabilities mIoCapabilities = IoCapabilities.NO_INPUT_NO_OUTPUT;
-    private boolean mRefuseConnections;
-    private FetchUuidsTiming mFetchUuidsTiming = FetchUuidsTiming.AFTER_BONDING;
-    private boolean mEnableCVE20192225;
-
-    private final Interrupter mInterrupter;
-    private final AdapterDelegate mAdapterDelegate;
-    private final RfcommDelegate mRfcommDelegate;
-    private final GattDelegate mGattDelegate;
-    private final BluetoothBroadcastHandler mBluetoothBroadcastHandler;
-    private final Map<String, Integer> mRemoteAddressToBondState = new HashMap<>();
-    private final Map<String, PairingConfirmation> mRemoteAddressToPairingConfirmation =
-            new HashMap<>();
-    private final Map<Integer, Integer> mProfileTypeToConnectionState = new HashMap<>();
-    private final Set<BluetoothDevice> mBondedDevices = new HashSet<>();
-
-    public BlueletImpl(String address, BroadcastManager broadcastManager) {
-        this.address = address;
-        this.mName = address;
-        this.mAdapter = callConstructor(BluetoothAdapter.class,
-                ClassParameter.from(IBluetoothManager.class, new IBluetoothManagerImpl()),
-                ClassParameter.from(AttributionSource.class,
-                        AttributionSource.myAttributionSource()));
-        mBluetoothBroadcastHandler = new BluetoothBroadcastHandler(broadcastManager);
-        mInterrupter = new Interrupter();
-        mAdapterDelegate = new AdapterDelegate(address, mBluetoothBroadcastHandler);
-        mRfcommDelegate = new RfcommDelegate(address, mBluetoothBroadcastHandler, mInterrupter);
-        mGattDelegate = new GattDelegate(address);
-    }
-
-    @Override
-    public Bluelet setAdapterInitialState(int state) throws IllegalArgumentException {
-        LOGGER.d(String.format("Address: %s, setAdapterInitialState(%d)", address, state));
-        Preconditions.checkArgument(
-                state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_ON,
-                "State must be BluetoothAdapter.STATE_ON or BluetoothAdapter.STATE_OFF.");
-        mAdapterDelegate.setState(State.lookup(state));
-        return this;
-    }
-
-    @Override
-    public Bluelet setBluetoothClass(int bluetoothClass) {
-        mAdapterDelegate.setBluetoothClass(bluetoothClass);
-        return this;
-    }
-
-    @Override
-    public Bluelet setScanMode(int scanMode) {
-        mAdapterDelegate.setScanMode(scanMode);
-        return this;
-    }
-
-    @Override
-    public Bluelet setProfileUuids(ParcelUuid... profileUuids) {
-        this.mProfileUuids = profileUuids;
-        return this;
-    }
-
-    @Override
-    public Bluelet setIoCapabilities(IoCapabilities ioCapabilities) {
-        this.mIoCapabilities = ioCapabilities;
-        return this;
-    }
-
-    @Override
-    public Bluelet setCreateBondOutcome(CreateBondOutcome outcome, int failureReason) {
-        mCreateBondOutcome = outcome;
-        mCreateBondFailureReason = failureReason;
-        return this;
-    }
-
-    @Override
-    public Bluelet setRefuseConnections(boolean refuse) {
-        mRefuseConnections = refuse;
-        return this;
-    }
-
-    @Override
-    public Bluelet setRefuseGattConnections(boolean refuse) {
-        getGattDelegate().setRefuseConnections(refuse);
-        return this;
-    }
-
-    @Override
-    public Bluelet setFetchUuidsTiming(FetchUuidsTiming fetchUuidsTiming) {
-        this.mFetchUuidsTiming = fetchUuidsTiming;
-        return this;
-    }
-
-    @Override
-    public Bluelet addBondedDevice(String address) {
-        this.mBondedDevices.add(mAdapter.getRemoteDevice(address));
-        return this;
-    }
-
-    @Override
-    public Bluelet enableCVE20192225(boolean value) {
-        this.mEnableCVE20192225 = value;
-        return this;
-    }
-
-    IoCapabilities getIoCapabilities() {
-        return mIoCapabilities;
-    }
-
-    CreateBondOutcome getCreateBondOutcome() {
-        return mCreateBondOutcome;
-    }
-
-    int getCreateBondFailureReason() {
-        return mCreateBondFailureReason;
-    }
-
-    public boolean getRefuseConnections() {
-        return mRefuseConnections;
-    }
-
-    public FetchUuidsTiming getFetchUuidsTiming() {
-        return mFetchUuidsTiming;
-    }
-
-    BluetoothDevice[] getBondedDevices() {
-        return mBondedDevices.toArray(new BluetoothDevice[0]);
-    }
-
-    public boolean getEnableCVE20192225() {
-        return mEnableCVE20192225;
-    }
-
-    public void enableAdapter() {
-        LOGGER.d(String.format("Address: %s, enableAdapter()", address));
-        // TODO(b/200231384): async enabling, configurable delay, failure path
-        if (VERSION.SDK_INT < 23) {
-            mAdapterDelegate.processEvent(Event.USER_TURN_ON);
-            mAdapterDelegate.processEvent(Event.BREDR_STARTED);
-        } else {
-            mAdapterDelegate.processEvent(Event.BLE_TURN_ON);
-            mAdapterDelegate.processEvent(Event.BLE_STARTED);
-            mAdapterDelegate.processEvent(Event.USER_TURN_ON);
-            mAdapterDelegate.processEvent(Event.BREDR_STARTED);
-        }
-    }
-
-    public void disableAdapter() {
-        LOGGER.d(String.format("Address: %s, disableAdapter()", address));
-        // TODO(b/200231384): async disabling, configurable delay, failure path
-        if (VERSION.SDK_INT < 23) {
-            mAdapterDelegate.processEvent(Event.USER_TURN_OFF);
-            mAdapterDelegate.processEvent(Event.BREDR_STOPPED);
-        } else {
-            mAdapterDelegate.processEvent(Event.BLE_TURN_OFF);
-            mAdapterDelegate.processEvent(Event.BREDR_STOPPED);
-            mAdapterDelegate.processEvent(Event.USER_TURN_OFF);
-            mAdapterDelegate.processEvent(Event.BLE_STOPPED);
-        }
-    }
-
-    public AdapterDelegate getAdapterDelegate() {
-        return mAdapterDelegate;
-    }
-
-    public RfcommDelegate getRfcommDelegate() {
-        return mRfcommDelegate;
-    }
-
-    public GattDelegate getGattDelegate() {
-        return mGattDelegate;
-    }
-
-    public BluetoothAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    public void setInterruptible(int identifier) {
-        LOGGER.d(String.format("Address: %s, setInterruptible(%d)", address, identifier));
-        mInterrupter.setInterruptible(identifier);
-    }
-
-    public void interrupt(int identifier) {
-        LOGGER.d(String.format("Address: %s, interrupt(%d)", address, identifier));
-        mInterrupter.interrupt(identifier);
-    }
-
-    @VisibleForTesting
-    public void setAdapterState(int state) throws IllegalArgumentException {
-        State s = State.lookup(state);
-        if (s == null) {
-            throw new IllegalArgumentException();
-        }
-        mAdapterDelegate.setState(s);
-    }
-
-    public int getBondState(String remoteAddress) {
-        return mRemoteAddressToBondState.containsKey(remoteAddress)
-                ? mRemoteAddressToBondState.get(remoteAddress)
-                : BluetoothDevice.BOND_NONE;
-    }
-
-    public void setBondState(String remoteAddress, int bondState, int failureReason) {
-        Intent intent =
-                newDeviceIntent(BluetoothDevice.ACTION_BOND_STATE_CHANGED, remoteAddress)
-                        .putExtra(BluetoothDevice.EXTRA_BOND_STATE, bondState)
-                        .putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE,
-                                getBondState(remoteAddress));
-
-        if (failureReason != REASON_SUCCESS) {
-            intent.putExtra(EXTRA_REASON, failureReason);
-        }
-
-        LOGGER.d(
-                String.format(
-                        "Address: %s, Bluetooth Bond State Change Intent: remote=%s, %s -> %s "
-                                + "(reason=%s)",
-                        address, remoteAddress, getBondState(remoteAddress), bondState,
-                        failureReason));
-        mRemoteAddressToBondState.put(remoteAddress, bondState);
-        mBluetoothBroadcastHandler.mBroadcastManager.sendBroadcast(
-                intent, android.Manifest.permission.BLUETOOTH);
-    }
-
-    public void onPairingRequest(String remoteAddress, int variant, int key) {
-        Intent intent =
-                newDeviceIntent(BluetoothDevice.ACTION_PAIRING_REQUEST, remoteAddress)
-                        .putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant)
-                        .putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, key);
-
-        LOGGER.d(
-                String.format(
-                        "Address: %s, Bluetooth Pairing Request Intent: remote=%s, variant=%s, "
-                                + "key=%s", address, remoteAddress, variant, key));
-        mBluetoothBroadcastHandler.mBroadcastManager.sendBroadcast(intent, permission.BLUETOOTH);
-    }
-
-    public PairingConfirmation getPairingConfirmation(String remoteAddress) {
-        PairingConfirmation confirmation = mRemoteAddressToPairingConfirmation.get(remoteAddress);
-        return confirmation == null ? PairingConfirmation.UNKNOWN : confirmation;
-    }
-
-    public void setPairingConfirmation(String remoteAddress, PairingConfirmation confirmation) {
-        mRemoteAddressToPairingConfirmation.put(remoteAddress, confirmation);
-    }
-
-    public void onFetchedUuids(String remoteAddress, ParcelUuid[] profileUuids) {
-        Intent intent =
-                newDeviceIntent(BluetoothDevice.ACTION_UUID, remoteAddress)
-                        .putExtra(BluetoothDevice.EXTRA_UUID, profileUuids);
-
-        LOGGER.d(
-                String.format(
-                        "Address: %s, Bluetooth Found UUIDs Intent: remoteAddress=%s, uuids=%s",
-                        address, remoteAddress, Arrays.toString(profileUuids)));
-        mBluetoothBroadcastHandler.mBroadcastManager.sendBroadcast(
-                intent, android.Manifest.permission.BLUETOOTH);
-    }
-
-    private static int maxProfileState(int a, int b) {
-        // Prefer connected > connecting > disconnecting > disconnected.
-        switch (a) {
-            case BluetoothProfile.STATE_CONNECTED:
-                return a;
-            case BluetoothProfile.STATE_CONNECTING:
-                return b == BluetoothProfile.STATE_CONNECTED ? b : a;
-            case BluetoothProfile.STATE_DISCONNECTING:
-                return b == BluetoothProfile.STATE_CONNECTED
-                        || b == BluetoothProfile.STATE_CONNECTING
-                        ? b
-                        : a;
-            case BluetoothProfile.STATE_DISCONNECTED:
-            default:
-                return b;
-        }
-    }
-
-    public int getAdapterConnectionState() {
-        int maxState = BluetoothProfile.STATE_DISCONNECTED;
-        for (int state : mProfileTypeToConnectionState.values()) {
-            maxState = maxProfileState(maxState, state);
-        }
-        return PROFILE_STATE_TO_ADAPTER_STATE.get(maxState);
-    }
-
-    public int getProfileConnectionState(int profileType) {
-        return mProfileTypeToConnectionState.containsKey(profileType)
-                ? mProfileTypeToConnectionState.get(profileType)
-                : BluetoothProfile.STATE_DISCONNECTED;
-    }
-
-    public void setProfileConnectionState(int profileType, int state, String remoteAddress) {
-        int previousAdapterState = getAdapterConnectionState();
-        mProfileTypeToConnectionState.put(profileType, state);
-        int adapterState = getAdapterConnectionState();
-        if (previousAdapterState != adapterState) {
-            Intent intent =
-                    newDeviceIntent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED, remoteAddress)
-                            .putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE,
-                                    previousAdapterState)
-                            .putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, adapterState);
-
-            LOGGER.d(
-                    "Adapter Connection State Changed Intent: "
-                            + previousAdapterState
-                            + " -> "
-                            + adapterState);
-            mBluetoothBroadcastHandler.mBroadcastManager.sendBroadcast(
-                    intent, android.Manifest.permission.BLUETOOTH);
-        }
-    }
-
-    static class BluetoothBroadcastHandler implements AdapterDelegate.Callback,
-            RfcommDelegate.Callback {
-
-        private final BroadcastManager mBroadcastManager;
-
-        BluetoothBroadcastHandler(BroadcastManager broadcastManager) {
-            this.mBroadcastManager = broadcastManager;
-        }
-
-        @Override
-        public void onAdapterStateChange(State prevState, State newState) {
-            int prev = prevState.getValue();
-            int cur = newState.getValue();
-            LOGGER.d("Bluetooth State Change Intent: " + State.lookup(prev) + " -> " + State.lookup(
-                    cur));
-            Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
-            intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prev);
-            intent.putExtra(BluetoothAdapter.EXTRA_STATE, cur);
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-
-        @Override
-        public void onBleStateChange(State prevState, State newState) {
-            int prev = prevState.getValue();
-            int cur = newState.getValue();
-            LOGGER.d("BLE State Change Intent: " + State.lookup(prev) + " -> " + State.lookup(cur));
-            Intent intent = new Intent(BluetoothConstants.ACTION_BLE_STATE_CHANGED);
-            intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prev);
-            intent.putExtra(BluetoothAdapter.EXTRA_STATE, cur);
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-
-        @Override
-        public void onConnectionStateChange(String remoteAddress, boolean isConnected) {
-            LOGGER.d("Bluetooth Connection State Change Intent, isConnected: " + isConnected);
-            Intent intent =
-                    isConnected
-                            ? newDeviceIntent(BluetoothDevice.ACTION_ACL_CONNECTED, remoteAddress)
-                            : newDeviceIntent(BluetoothDevice.ACTION_ACL_DISCONNECTED,
-                                    remoteAddress);
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-
-        @Override
-        public void onDiscoveryStarted() {
-            LOGGER.d("Bluetooth discovery started.");
-            Intent intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-
-        @Override
-        public void onDiscoveryFinished() {
-            LOGGER.d("Bluetooth discovery finished.");
-            Intent intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-
-        @Override
-        public void onDeviceFound(String address, int bluetoothClass, String name) {
-            LOGGER.d("Bluetooth device found, address: " + address);
-            Intent intent =
-                    newDeviceIntent(BluetoothDevice.ACTION_FOUND, address)
-                            .putExtra(
-                                    BluetoothDevice.EXTRA_CLASS,
-                                    callConstructor(
-                                            BluetoothClass.class,
-                                            ClassParameter.from(int.class, bluetoothClass)))
-                            .putExtra(BluetoothDevice.EXTRA_NAME, name);
-            // TODO(b/200231384): support rssi
-            // TODO(b/200231384): send broadcast with additional ACCESS_COARSE_LOCATION permission
-            // once broadcast permission is implemented.
-            mBroadcastManager.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
-        }
-    }
-
-    private static Intent newDeviceIntent(String action, String address) {
-        return new Intent(action)
-                .putExtra(
-                        BluetoothDevice.EXTRA_DEVICE,
-                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BluetoothConstants.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BluetoothConstants.java
deleted file mode 100644
index fa519da..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/BluetoothConstants.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-/**
- * A class to hold Bluetooth constants.
- */
-public class BluetoothConstants {
-
-    /*** Bluetooth Adapter State ***/
-    // Must be identical to BluetoothAdapter hidden field STATE_BLE_TURNING_ON
-    public static final int STATE_BLE_TURNING_ON = 14;
-
-    // Must be identical to BluetoothAdapter hidden field STATE_BLE_ON
-    public static final int STATE_BLE_ON = 15;
-
-    // Must be identical to BluetoothAdapter hidden field STATE_BLE_TURNING_OFF
-    public static final int STATE_BLE_TURNING_OFF = 16;
-
-    // Must be identical to BluetoothAdapter hidden field ACTION_BLE_STATE_CHANGED
-    public static final String ACTION_BLE_STATE_CHANGED =
-            "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
-
-    /*** Rfcomm Socket ***/
-    // Must be identical to BluetoothSocket field TYPE_RFCOMM.
-    // The field was package-private before M.
-    public static final int TYPE_RFCOMM = 1;
-
-    public static final int SOCKET_CLOSE = -10000;
-
-    // Android Bluetooth use -1 as port when creating server socket with uuid
-    public static final int SERVER_SOCKET_CHANNEL_AUTO_ASSIGN = -1;
-
-    // Android Bluetooth use -1 as port when creating socket with a uuid
-    public static final int SOCKET_CHANNEL_CONNECT_WITH_UUID = -1;
-
-    /*** BLE Advertise/Scan ***/
-    // Must be identical to AdvertiseCallback hidden field ADVERTISE_SUCCESS.
-    public static final int ADVERTISE_SUCCESS = 0;
-
-    // Must be identical to ScanRecord field DATA_TYPE_FLAGS.
-    public static final int DATA_TYPE_FLAGS = 0x01;
-
-    // Must be identical to ScanRecord field DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE.
-    public static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
-
-    // Must be identical to ScanRecord field DATA_TYPE_LOCAL_NAME_COMPLETE.
-    public static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
-
-    // Must be identical to ScanRecord field DATA_TYPE_TX_POWER_LEVEL.
-    public static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
-
-    // Must be identical to ScanRecord field DATA_TYPE_SERVICE_DATA.
-    public static final int DATA_TYPE_SERVICE_DATA = 0x16;
-
-    // Must be identical to ScanRecord field DATA_TYPE_MANUFACTURER_SPECIFIC_DATA.
-    public static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
-
-    /**
-     * @see #DATA_TYPE_FLAGS
-     */
-    public interface Flags {
-
-        byte LE_LIMITED_DISCOVERABLE_MODE = 1;
-        byte LE_GENERAL_DISCOVERABLE_MODE = 1 << 1;
-        byte BR_EDR_NOT_SUPPORTED = 1 << 2;
-        byte SIMULTANEOUS_LE_AND_BR_EDR_CONTROLLER = 1 << 3;
-        byte SIMULTANEOUS_LE_AND_BR_EDR_HOST = 1 << 4;
-    }
-
-    /**
-     * Observed that Android sets this for {@link #DATA_TYPE_FLAGS} when a packet is connectable (on
-     * a Nexus 6P running 7.1.2).
-     */
-    public static final byte FLAGS_IN_CONNECTABLE_PACKETS =
-            Flags.BR_EDR_NOT_SUPPORTED
-                    | Flags.LE_GENERAL_DISCOVERABLE_MODE
-                    | Flags.SIMULTANEOUS_LE_AND_BR_EDR_CONTROLLER
-                    | Flags.SIMULTANEOUS_LE_AND_BR_EDR_HOST;
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/GattDelegate.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/GattDelegate.java
deleted file mode 100644
index 4618561..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/GattDelegate.java
+++ /dev/null
@@ -1,609 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.IBluetoothGattCallback;
-import android.bluetooth.IBluetoothGattServerCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanRecord;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.os.Build;
-import android.os.Build.VERSION;
-import android.os.ParcelUuid;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.DeviceletImpl;
-import com.android.libraries.testing.deviceshadower.internal.utils.GattHelper;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.base.Preconditions;
-import com.google.common.primitives.Bytes;
-
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Delegate to operate gatt operations.
- */
-public class GattDelegate {
-
-    private static final int DEFAULT_RSSI = -50;
-    private static final Logger LOGGER = Logger.create("GattDelegate");
-
-    // chipset properties
-    // use 2 as API 21 requires multi-advertisement support to use Le Advertising.
-    private final int mMaxAdvertiseInstances = 2;
-    private final AtomicBoolean mIsOffloadedFilteringSupported = new AtomicBoolean(false);
-    private final String mAddress;
-    private final AtomicInteger mCurrentClientIf = new AtomicInteger(0);
-    private final AtomicInteger mCurrentServerIf = new AtomicInteger(0);
-    private final AtomicBoolean mCurrentConnectionState = new AtomicBoolean(false);
-    private final Map<ParcelUuid, Service> mServices = new HashMap<>();
-    private final Map<Integer, IBluetoothGattCallback> mClientCallbacks;
-    private final Map<Integer, IBluetoothGattServerCallback> mServerCallbacks;
-    private final Map<Integer, Advertiser> mAdvertisers;
-    private final Map<Integer, Scanner> mScanners;
-    @Nullable
-    private Request mLastRequest;
-    private boolean mConnectable = true;
-
-    /**
-     * The parameters of a request, e.g. readCharacteristic(). Subclass for each request.
-     *
-     * @see #getLastRequest()
-     */
-    abstract static class Request {
-
-        final int mSrvcType;
-        final int mSrvcInstId;
-        final ParcelUuid mSrvcId;
-        final int mCharInstId;
-        final ParcelUuid mCharId;
-
-        Request(int srvcType, int srvcInstId, ParcelUuid srvcId, int charInstId,
-                ParcelUuid charId) {
-            this.mSrvcType = srvcType;
-            this.mSrvcInstId = srvcInstId;
-            this.mSrvcId = srvcId;
-            this.mCharInstId = charInstId;
-            this.mCharId = charId;
-        }
-    }
-
-    /**
-     * Corresponds to {@link android.bluetooth.IBluetoothGatt#readCharacteristic}.
-     */
-    static class ReadCharacteristicRequest extends Request {
-
-        ReadCharacteristicRequest(
-                int srvcType, int srvcInstId, ParcelUuid srvcId, int charInstId,
-                ParcelUuid charId) {
-            super(srvcType, srvcInstId, srvcId, charInstId, charId);
-        }
-    }
-
-    /**
-     * Corresponds to {@link android.bluetooth.IBluetoothGatt#readDescriptor}.
-     */
-    static class ReadDescriptorRequest extends Request {
-
-        final int mDescrInstId;
-        final ParcelUuid mDescrId;
-
-        ReadDescriptorRequest(
-                int srvcType,
-                int srvcInstId,
-                ParcelUuid srvcId,
-                int charInstId,
-                ParcelUuid charId,
-                int descrInstId,
-                ParcelUuid descrId) {
-            super(srvcType, srvcInstId, srvcId, charInstId, charId);
-            this.mDescrInstId = descrInstId;
-            this.mDescrId = descrId;
-        }
-    }
-
-    GattDelegate(String address) {
-        this(
-                address,
-                new HashMap<>(),
-                new HashMap<>(),
-                new ConcurrentHashMap<>(),
-                new ConcurrentHashMap<>());
-    }
-
-    @VisibleForTesting
-    GattDelegate(
-            String address,
-            Map<Integer, IBluetoothGattCallback> clientCallbacks,
-            Map<Integer, IBluetoothGattServerCallback> serverCallbacks,
-            Map<Integer, Advertiser> advertisers,
-            Map<Integer, Scanner> scanners) {
-        this.mAddress = address;
-        this.mClientCallbacks = clientCallbacks;
-        this.mServerCallbacks = serverCallbacks;
-        this.mAdvertisers = advertisers;
-        this.mScanners = scanners;
-    }
-
-    public void setRefuseConnections(boolean refuse) {
-        this.mConnectable = !refuse;
-    }
-
-    /**
-     * Used to maintain state between the request (e.g. readCharacteristic()) and sendResponse().
-     */
-    @Nullable
-    Request getLastRequest() {
-        return mLastRequest;
-    }
-
-    /**
-     * @see #getLastRequest()
-     */
-    void setLastRequest(@Nullable Request params) {
-        mLastRequest = params;
-    }
-
-    public int getClientIf() {
-        // TODO(b/200231384): support multiple client if.
-        return mCurrentClientIf.get();
-    }
-
-    public int getServerIf() {
-        // TODO(b/200231384): support multiple server if.
-        return mCurrentServerIf.get();
-    }
-
-    public IBluetoothGattServerCallback getServerCallback(int serverIf) {
-        return mServerCallbacks.get(serverIf);
-    }
-
-    public IBluetoothGattCallback getClientCallback(int clientIf) {
-        return mClientCallbacks.get(clientIf);
-    }
-
-    public int registerServer(IBluetoothGattServerCallback callback) {
-        mServerCallbacks.put(mCurrentServerIf.incrementAndGet(), callback);
-        return getServerIf();
-    }
-
-    public int registerClient(IBluetoothGattCallback callback) {
-        mClientCallbacks.put(mCurrentClientIf.incrementAndGet(), callback);
-        LOGGER.d(String.format("Client registered on %s, clientIf: %d", mAddress, getClientIf()));
-        return getClientIf();
-    }
-
-    public void unregisterClient(int clientIf) {
-        mClientCallbacks.remove(clientIf);
-        LOGGER.d(String.format("Client unregistered on %s, clientIf: %d", mAddress, clientIf));
-    }
-
-    public void unregisterServer(int serverIf) {
-        mServerCallbacks.remove(serverIf);
-    }
-
-    public int getMaxAdvertiseInstances() {
-        return mMaxAdvertiseInstances;
-    }
-
-    public boolean isOffloadedFilteringSupported() {
-        return mIsOffloadedFilteringSupported.get();
-    }
-
-    public boolean connect(String address) {
-        return mConnectable;
-    }
-
-    public boolean disconnect(String address) {
-        return true;
-    }
-
-    public void clientConnectionStateChange(
-            int state, int clientIf, boolean connected, String address) {
-        if (connected != mCurrentConnectionState.get()) {
-            mCurrentConnectionState.set(connected);
-            IBluetoothGattCallback callback = getClientCallback(clientIf);
-            if (callback != null) {
-                callback.onClientConnectionState(state, clientIf, connected, address);
-            }
-        }
-    }
-
-    public void serverConnectionStateChange(
-            int state, int serverIf, boolean connected, String address) {
-        if (connected != mCurrentConnectionState.get()) {
-            mCurrentConnectionState.set(connected);
-            IBluetoothGattServerCallback callback = getServerCallback(serverIf);
-            if (callback != null) {
-                callback.onServerConnectionState(state, serverIf, connected, address);
-            }
-        }
-    }
-
-    public Service addService(ParcelUuid uuid) {
-        Service srvc = new Service(uuid);
-        mServices.put(uuid, srvc);
-        return srvc;
-    }
-
-    public Collection<Service> getServices() {
-        return mServices.values();
-    }
-
-    public Service getService(ParcelUuid uuid) {
-        return mServices.get(uuid);
-    }
-
-    public void clientSetMtu(int clientIf, int mtu, String serverAddress) {
-        IBluetoothGattCallback callback = getClientCallback(clientIf);
-        if (callback != null && Build.VERSION.SDK_INT >= 21) {
-            callback.onConfigureMTU(serverAddress, mtu, BluetoothGatt.GATT_SUCCESS);
-        }
-    }
-
-    public void serverSetMtu(int serverIf, int mtu, String clientAddress) {
-        IBluetoothGattServerCallback callback = getServerCallback(serverIf);
-        if (callback != null && Build.VERSION.SDK_INT >= 22) {
-            callback.onMtuChanged(clientAddress, mtu);
-        }
-    }
-
-    public void startMultiAdvertising(
-            int appIf,
-            AdvertiseData advertiseData,
-            AdvertiseData scanResponse,
-            final AdvertiseSettings settings) {
-        LOGGER.d(String.format("startMultiAdvertising(%d) on %s", appIf, mAddress));
-        final Advertiser advertiser =
-                new Advertiser(
-                        appIf,
-                        mAddress,
-                        DeviceShadowEnvironmentImpl.getLocalBlueletImpl().mName,
-                        txPowerFromFlag(settings.getTxPowerLevel()),
-                        advertiseData,
-                        scanResponse,
-                        settings);
-        mAdvertisers.put(appIf, advertiser);
-        final IBluetoothGattCallback callback = mClientCallbacks.get(appIf);
-        @SuppressWarnings("unused") // go/futurereturn-lsc
-        Future<?> possiblyIgnoredError =
-                DeviceShadowEnvironmentImpl.run(
-                        mAddress,
-                        () -> {
-                            callback.onMultiAdvertiseCallback(
-                                    BluetoothConstants.ADVERTISE_SUCCESS, true /* isStart */,
-                                    settings);
-                            return null;
-                        });
-    }
-
-    /**
-     * Returns TxPower in dBm as measured at the source.
-     *
-     * <p>Note that this will vary by device and the values are only roughly accurate. The
-     * measurements were taken with a Nexus 6. Copied from the TxEddystone-UID app:
-     * {https://github.com/google/eddystone/blob/master/eddystone-uid/tools/txeddystone-uid/TxEddystone-UID/app/src/main/java/com/google/sample/txeddystone_uid/MainActivity.java}
-     */
-    private static byte txPowerFromFlag(int txPowerFlag) {
-        switch (txPowerFlag) {
-            case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH:
-                return (byte) -16;
-            case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
-                return (byte) -26;
-            case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
-                return (byte) -35;
-            case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
-                return (byte) -59;
-            default:
-                throw new IllegalStateException("Unknown TxPower level=" + txPowerFlag);
-        }
-    }
-
-    public void stopMultiAdvertising(int appIf) {
-        LOGGER.d(String.format("stopAdvertising(%d) on %s", appIf, mAddress));
-        Advertiser advertiser = mAdvertisers.get(appIf);
-        if (advertiser == null) {
-            LOGGER.d(String.format("Advertising already stopped on %s, clientIf: %d", mAddress,
-                    appIf));
-            return;
-        }
-        mAdvertisers.remove(appIf);
-        final IBluetoothGattCallback callback = mClientCallbacks.get(appIf);
-        @SuppressWarnings("unused") // go/futurereturn-lsc
-        Future<?> possiblyIgnoredError =
-                DeviceShadowEnvironmentImpl.run(
-                        mAddress,
-                        () -> {
-                            callback.onMultiAdvertiseCallback(
-                                    BluetoothConstants.ADVERTISE_SUCCESS, false /* isStart */,
-                                    null /* setting */);
-                            return null;
-                        });
-    }
-
-    public void startScan(final int appIf, ScanSettings settings, List<ScanFilter> filters) {
-        LOGGER.d(String.format("startScan(%d) on %s", appIf, mAddress));
-        if (filters == null) {
-            filters = new ArrayList<>();
-        }
-        final Scanner scanner = new Scanner(appIf, settings, filters);
-        mScanners.put(appIf, scanner);
-        @SuppressWarnings("unused") // go/futurereturn-lsc
-        Future<?> possiblyIgnoredError =
-                DeviceShadowEnvironmentImpl.run(
-                        mAddress,
-                        () -> {
-                            try {
-                                scan(scanner);
-                            } catch (InterruptedException e) {
-                                LOGGER.e(
-                                        String.format("Failed to scan on %s, clientIf: %d.",
-                                                mAddress, scanner.mClientIf),
-                                        e);
-                            }
-                            return null;
-                        });
-    }
-
-    // TODO(b/200231384): support periodic scan with interval and scan window.
-    private void scan(Scanner scanner) throws InterruptedException {
-        // fetch existing advertisements
-        List<DeviceletImpl> devicelets = DeviceShadowEnvironmentImpl.getDeviceletImpls();
-        for (DeviceletImpl devicelet : devicelets) {
-            BlueletImpl bluelet = devicelet.blueletImpl();
-            if (bluelet.address.equals(mAddress)) {
-                continue;
-            }
-            for (Advertiser advertiser : bluelet.getGattDelegate().mAdvertisers.values()) {
-                if (VERSION.SDK_INT < 21) {
-                    throw new UnsupportedOperationException(
-                            String.format("API %d is not supported.", VERSION.SDK_INT));
-                }
-
-                byte[] advertiseData =
-                        GattHelper.convertAdvertiseData(
-                                advertiser.mAdvertiseData,
-                                advertiser.mTxPowerLevel,
-                                advertiser.mName,
-                                advertiser.mSettings.isConnectable());
-                byte[] scanResponse =
-                        GattHelper.convertAdvertiseData(
-                                advertiser.mScanResponse,
-                                advertiser.mTxPowerLevel,
-                                advertiser.mName,
-                                advertiser.mSettings.isConnectable());
-
-                ScanRecord scanRecord =
-                        ReflectionHelpers.callStaticMethod(
-                                ScanRecord.class,
-                                "parseFromBytes",
-                                ClassParameter.from(byte[].class,
-                                        Bytes.concat(advertiseData, scanResponse)));
-                ScanResult scanResult =
-                        new ScanResult(
-                                BluetoothAdapter.getDefaultAdapter()
-                                        .getRemoteDevice(advertiser.mAddress),
-                                scanRecord,
-                                DEFAULT_RSSI,
-                                SystemClock.elapsedRealtimeNanos());
-
-                if (!matchFilters(scanResult, scanner.mFilters)) {
-                    continue;
-                }
-
-                IBluetoothGattCallback callback = mClientCallbacks.get(scanner.mClientIf);
-                if (callback == null) {
-                    LOGGER.e(
-                            String.format("Callback is null on %s, clientIf: %d", mAddress,
-                                    scanner.mClientIf));
-                    return;
-                }
-                callback.onScanResult(scanResult);
-            }
-        }
-    }
-
-    private boolean matchFilters(ScanResult scanResult, List<ScanFilter> filters) {
-        for (ScanFilter filter : filters) {
-            if (!filter.matches(scanResult)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public void stopScan(int appIf) {
-        LOGGER.d(String.format("stopScan(%d) on %s", appIf, mAddress));
-        Scanner scanner = mScanners.get(appIf);
-        if (scanner == null) {
-            LOGGER.d(
-                    String.format("Scanning already stopped on %s, clientIf: %d", mAddress, appIf));
-            return;
-        }
-        mScanners.remove(appIf);
-    }
-
-    static class Service {
-
-        private Map<ParcelUuid, Characteristic> mCharacteristics = new HashMap<>();
-        private ParcelUuid mUuid;
-
-        Service(ParcelUuid uuid) {
-            this.mUuid = uuid;
-        }
-
-        Characteristic getCharacteristic(ParcelUuid uuid) {
-            return mCharacteristics.get(uuid);
-        }
-
-        Characteristic addCharacteristic(ParcelUuid uuid, int properties, int permissions) {
-            Characteristic ch = new Characteristic(uuid, properties, permissions);
-            mCharacteristics.put(uuid, ch);
-            return ch;
-        }
-
-        Collection<Characteristic> getCharacteristics() {
-            return mCharacteristics.values();
-        }
-
-        ParcelUuid getUuid() {
-            return this.mUuid;
-        }
-    }
-
-    static class Characteristic {
-
-        private int mProperties;
-        private ParcelUuid mUuid;
-        private Map<ParcelUuid, Descriptor> mDescriptors = new HashMap<>();
-        private Set<String> mNotifyClients = new HashSet<>();
-        private byte[] mValue;
-
-        Characteristic(ParcelUuid uuid, int properties, int permissions) {
-            this.mProperties = properties;
-            this.mUuid = uuid;
-        }
-
-        Descriptor getDescriptor(ParcelUuid uuid) {
-            return mDescriptors.get(uuid);
-        }
-
-        Descriptor addDescriptor(ParcelUuid uuid, int permissions) {
-            Descriptor desc = new Descriptor(uuid, permissions);
-            mDescriptors.put(uuid, desc);
-            return desc;
-        }
-
-        Collection<Descriptor> getDescriptors() {
-            return mDescriptors.values();
-        }
-
-        void setValue(byte[] value) {
-            this.mValue = value;
-        }
-
-        byte[] getValue() {
-            return mValue;
-        }
-
-        ParcelUuid getUuid() {
-            return mUuid;
-        }
-
-        int getProperties() {
-            return mProperties;
-        }
-
-        void registerNotification(String client, int clientIf) {
-            mNotifyClients.add(client);
-        }
-
-        Set<String> getNotifyClients() {
-            return mNotifyClients;
-        }
-    }
-
-    static class Descriptor {
-
-        int mPermissions;
-        ParcelUuid mUuid;
-        byte[] mValue;
-
-        Descriptor(ParcelUuid uuid, int permissions) {
-            this.mUuid = uuid;
-            this.mPermissions = permissions;
-        }
-
-        void setValue(byte[] value) {
-            this.mValue = value;
-        }
-
-        byte[] getValue() {
-            return mValue;
-        }
-
-        ParcelUuid getUuid() {
-            return mUuid;
-        }
-    }
-
-    @VisibleForTesting
-    static class Advertiser {
-
-        final int mClientIf;
-        final String mAddress;
-        final String mName;
-        final int mTxPowerLevel;
-        final AdvertiseData mAdvertiseData;
-        @Nullable
-        final AdvertiseData mScanResponse;
-        final AdvertiseSettings mSettings;
-
-        Advertiser(
-                int clientIf,
-                String address,
-                String name,
-                int txPowerLevel,
-                AdvertiseData advertiseData,
-                AdvertiseData scanResponse,
-                AdvertiseSettings settings) {
-            this.mClientIf = clientIf;
-            this.mAddress = Preconditions.checkNotNull(address);
-            this.mName = name;
-            this.mTxPowerLevel = txPowerLevel;
-            this.mAdvertiseData = Preconditions.checkNotNull(advertiseData);
-            this.mScanResponse = scanResponse;
-            this.mSettings = Preconditions.checkNotNull(settings);
-        }
-    }
-
-    @VisibleForTesting
-    static class Scanner {
-
-        final int mClientIf;
-        final ScanSettings mSettings;
-        final List<ScanFilter> mFilters;
-
-        Scanner(int clientIf, ScanSettings settings, List<ScanFilter> filters) {
-            this.mClientIf = clientIf;
-            this.mSettings = Preconditions.checkNotNull(settings);
-            this.mFilters = Preconditions.checkNotNull(filters);
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothGattImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothGattImpl.java
deleted file mode 100644
index 0ac287d..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothGattImpl.java
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.IBluetoothGatt;
-import android.bluetooth.IBluetoothGattCallback;
-import android.bluetooth.IBluetoothGattServerCallback;
-import android.bluetooth.le.AdvertiseData;
-import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.os.ParcelUuid;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.GattDelegate.ReadCharacteristicRequest;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.GattDelegate.ReadDescriptorRequest;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.GattDelegate.Request;
-import com.android.libraries.testing.deviceshadower.internal.common.NamedRunnable;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of IBluetoothGatt.
- */
-public class IBluetoothGattImpl implements IBluetoothGatt {
-
-    private static final Logger LOGGER = Logger.create("IBluetoothGattImpl");
-    private GattDelegate.Service mCurrentService;
-    private GattDelegate.Characteristic mCurrentCharacteristic;
-
-    @Override
-    public void startScan(
-            int appIf,
-            boolean isServer,
-            ScanSettings settings,
-            List<ScanFilter> filters,
-            List<?> scanStorages,
-            String callingPackage) {
-        localGattDelegate().startScan(appIf, settings, filters);
-    }
-
-    @Override
-    public void startScan(
-            int appIf,
-            boolean isServer,
-            ScanSettings settings,
-            List<ScanFilter> filters,
-            List<?> scanStorages) {
-        startScan(appIf, isServer, settings, filters, scanStorages, "" /* callingPackage */);
-    }
-
-    @Override
-    public void stopScan(int appIf, boolean isServer) {
-        localGattDelegate().stopScan(appIf);
-    }
-
-    @Override
-    public void startMultiAdvertising(
-            int appIf,
-            AdvertiseData advertiseData,
-            AdvertiseData scanResponse,
-            AdvertiseSettings settings) {
-        localGattDelegate().startMultiAdvertising(appIf, advertiseData, scanResponse, settings);
-    }
-
-    @Override
-    public void stopMultiAdvertising(int appIf) {
-        localGattDelegate().stopMultiAdvertising(appIf);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void registerClient(ParcelUuid appId, final IBluetoothGattCallback callback) {
-        final int clientIf = localGattDelegate().registerClient(callback);
-        NamedRunnable onClientRegistered =
-                NamedRunnable.create(
-                        "ClientGatt.onClientRegistered=" + clientIf,
-                        () -> {
-                            callback.onClientRegistered(BluetoothGatt.GATT_SUCCESS, clientIf);
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(localAddress(), onClientRegistered);
-    }
-
-    @Override
-    public void unregisterClient(int clientIf) {
-        localGattDelegate().unregisterClient(clientIf);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void clientConnect(
-            final int clientIf, final String serverAddress, boolean isDirect, int transport) {
-        // TODO(b/200231384): implement auto connect.
-        String clientAddress = localAddress();
-        int serverIf = remoteGattDelegate(serverAddress).getServerIf();
-        boolean success = remoteGattDelegate(serverAddress).connect(clientAddress);
-        if (!success) {
-            LOGGER.i(String.format("clientConnect failed: %s connect %s", serverAddress,
-                    clientAddress));
-            return;
-        }
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                clientAddress,
-                newClientConnectionStateChangeRunnable(clientIf, true, serverAddress));
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                serverAddress,
-                newServerConnectionStateChangeRunnable(serverIf, true, clientAddress));
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void clientDisconnect(final int clientIf, final String serverAddress) {
-        final String clientAddress = localAddress();
-        remoteGattDelegate(serverAddress).disconnect(clientAddress);
-        int serverIf = remoteGattDelegate(serverAddress).getServerIf();
-
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                clientAddress,
-                newClientConnectionStateChangeRunnable(clientIf, false, serverAddress));
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                serverAddress,
-                newServerConnectionStateChangeRunnable(serverIf, false, clientAddress));
-    }
-
-    @Override
-    public void discoverServices(int clientIf, String serverAddress) {
-        final IBluetoothGattCallback callback = localGattDelegate().getClientCallback(clientIf);
-        if (callback == null) {
-            return;
-        }
-        for (GattDelegate.Service service : remoteGattDelegate(serverAddress).getServices()) {
-            callback.onGetService(serverAddress, 0 /*srvcType*/, 0 /*srvcInstId*/,
-                    service.getUuid());
-
-            for (GattDelegate.Characteristic characteristic : service.getCharacteristics()) {
-                callback.onGetCharacteristic(
-                        serverAddress,
-                        0 /*srvcType*/,
-                        0 /*srvcInstId*/,
-                        service.getUuid(),
-                        0 /*charInstId*/,
-                        characteristic.getUuid(),
-                        characteristic.getProperties());
-                for (GattDelegate.Descriptor descriptor : characteristic.getDescriptors()) {
-                    callback.onGetDescriptor(
-                            serverAddress,
-                            0 /*srvcType*/,
-                            0 /*srvcInstId*/,
-                            service.getUuid(),
-                            0 /*charInstId*/,
-                            characteristic.getUuid(),
-                            0 /*descrInstId*/,
-                            descriptor.getUuid());
-                }
-            }
-        }
-
-        callback.onSearchComplete(serverAddress, BluetoothGatt.GATT_SUCCESS);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void readCharacteristic(
-            final int clientIf,
-            final String serverAddress,
-            final int srvcType,
-            final int srvcInstId,
-            final ParcelUuid srvcId,
-            final int charInstId,
-            final ParcelUuid charId,
-            final int authReq) {
-        // TODO(b/200231384): implement authReq.
-        final String clientAddress = localAddress();
-        localGattDelegate()
-                .setLastRequest(
-                        new ReadCharacteristicRequest(srvcType, srvcInstId, srvcId, charInstId,
-                                charId));
-
-        NamedRunnable serverOnCharacteristicReadRequest =
-                NamedRunnable.create(
-                        "ServerGatt.onCharacteristicReadRequest",
-                        () -> {
-                            int serverIf = localGattDelegate().getServerIf();
-                            IBluetoothGattServerCallback callback =
-                                    localGattDelegate().getServerCallback(serverIf);
-                            if (callback != null) {
-                                callback.onCharacteristicReadRequest(
-                                        clientAddress,
-                                        0 /*transId*/,
-                                        0 /*offset*/,
-                                        false /*isLong*/,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId);
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(serverAddress, serverOnCharacteristicReadRequest);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void writeCharacteristic(
-            final int clientIf,
-            final String serverAddress,
-            final int srvcType,
-            final int srvcInstId,
-            final ParcelUuid srvcId,
-            final int charInstId,
-            final ParcelUuid charId,
-            final int writeType,
-            final int authReq,
-            final byte[] value) {
-        // TODO(b/200231384): implement write with response needed.
-        remoteGattDelegate(serverAddress).getService(srvcId).getCharacteristic(charId)
-                .setValue(value);
-        final String clientAddress = localAddress();
-
-        NamedRunnable clientOnCharacteristicWrite =
-                NamedRunnable.create(
-                        "ClientGatt.onCharacteristicWrite",
-                        () -> {
-                            IBluetoothGattCallback callback = localGattDelegate().getClientCallback(
-                                    clientIf);
-                            if (callback != null) {
-                                callback.onCharacteristicWrite(
-                                        serverAddress,
-                                        BluetoothGatt.GATT_SUCCESS,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId);
-                            }
-                        });
-
-        NamedRunnable onCharacteristicWriteRequest =
-                NamedRunnable.create(
-                        "ServerGatt.onCharacteristicWriteRequest",
-                        () -> {
-                            int serverIf = localGattDelegate().getServerIf();
-                            IBluetoothGattServerCallback callback =
-                                    localGattDelegate().getServerCallback(serverIf);
-                            if (callback != null) {
-                                callback.onCharacteristicWriteRequest(
-                                        clientAddress,
-                                        0 /*transId*/,
-                                        0 /*offset*/,
-                                        value.length,
-                                        false /*isPrep*/,
-                                        false /*needRsp*/,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId,
-                                        value);
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(clientAddress, clientOnCharacteristicWrite);
-
-        DeviceShadowEnvironmentImpl.runOnService(serverAddress, onCharacteristicWriteRequest);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void readDescriptor(
-            final int clientIf,
-            final String serverAddress,
-            final int srvcType,
-            final int srvcInstId,
-            final ParcelUuid srvcId,
-            final int charInstId,
-            final ParcelUuid charId,
-            final int descrInstId,
-            final ParcelUuid descrId,
-            final int authReq) {
-        final String clientAddress = localAddress();
-        localGattDelegate()
-                .setLastRequest(
-                        new ReadDescriptorRequest(
-                                srvcType, srvcInstId, srvcId, charInstId, charId, descrInstId,
-                                descrId));
-
-        NamedRunnable serverOnDescriptorReadRequest =
-                NamedRunnable.create(
-                        "ServerGatt.onDescriptorReadRequest",
-                        () -> {
-                            int serverIf = localGattDelegate().getServerIf();
-                            IBluetoothGattServerCallback callback =
-                                    localGattDelegate().getServerCallback(serverIf);
-                            if (callback != null) {
-                                callback.onDescriptorReadRequest(
-                                        clientAddress,
-                                        0 /*transId*/,
-                                        0 /*offset*/,
-                                        false /*isLong*/,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId,
-                                        descrId);
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(serverAddress, serverOnDescriptorReadRequest);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void writeDescriptor(
-            final int clientIf,
-            final String serverAddress,
-            final int srvcType,
-            final int srvcInstId,
-            final ParcelUuid srvcId,
-            final int charInstId,
-            final ParcelUuid charId,
-            final int descrInstId,
-            final ParcelUuid descrId,
-            final int writeType,
-            final int authReq,
-            final byte[] value) {
-        // TODO(b/200231384): implement write with response needed.
-        remoteGattDelegate(serverAddress)
-                .getService(srvcId)
-                .getCharacteristic(charId)
-                .getDescriptor(descrId)
-                .setValue(value);
-        final String clientAddress = localAddress();
-
-        NamedRunnable serverOnDescriptorWriteRequest =
-                NamedRunnable.create(
-                        "ServerGatt.onDescriptorWriteRequest",
-                        () -> {
-                            int serverIf = localGattDelegate().getServerIf();
-                            IBluetoothGattServerCallback callback =
-                                    localGattDelegate().getServerCallback(serverIf);
-                            if (callback != null) {
-                                callback.onDescriptorWriteRequest(
-                                        clientAddress,
-                                        0 /*transId*/,
-                                        0 /*offset*/,
-                                        value.length,
-                                        false /*isPrep*/,
-                                        false /*needRsp*/,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId,
-                                        descrId,
-                                        value);
-                            }
-                        });
-
-        NamedRunnable clientOnDescriptorWrite =
-                NamedRunnable.create(
-                        "ClientGatt.onDescriptorWrite",
-                        () -> {
-                            IBluetoothGattCallback callback = localGattDelegate().getClientCallback(
-                                    clientIf);
-                            if (callback != null) {
-                                callback.onDescriptorWrite(
-                                        serverAddress,
-                                        BluetoothGatt.GATT_SUCCESS,
-                                        0 /*srvcType*/,
-                                        srvcInstId,
-                                        srvcId,
-                                        charInstId,
-                                        charId,
-                                        descrInstId,
-                                        descrId);
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(serverAddress, serverOnDescriptorWriteRequest);
-
-        DeviceShadowEnvironmentImpl.runOnService(clientAddress, clientOnDescriptorWrite);
-    }
-
-    @Override
-    public void registerForNotification(
-            int clientIf,
-            String remoteAddress,
-            int srvcType,
-            int srvcInstId,
-            ParcelUuid srvcId,
-            int charInstId,
-            ParcelUuid charId,
-            boolean enable) {
-        remoteGattDelegate(remoteAddress)
-                .getService(srvcId)
-                .getCharacteristic(charId)
-                .registerNotification(localAddress(), clientIf);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void registerServer(ParcelUuid appId, final IBluetoothGattServerCallback callback) {
-        // TODO(b/200231384): support multiple serverIf.
-        final int serverIf = localGattDelegate().registerServer(callback);
-        NamedRunnable serverOnRegistered =
-                NamedRunnable.create(
-                        "ServerGatt.onServerRegistered",
-                        () -> {
-                            callback.onServerRegistered(BluetoothGatt.GATT_SUCCESS, serverIf);
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(localAddress(), serverOnRegistered);
-    }
-
-    @Override
-    public void unregisterServer(int serverIf) {
-        localGattDelegate().unregisterServer(serverIf);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void serverConnect(
-            final int serverIf, final String clientAddress, boolean isDirect, int transport) {
-        // TODO(b/200231384): implement isDirect and transport.
-        boolean success = localGattDelegate().connect(clientAddress);
-        final String serverAddress = localAddress();
-        if (!success) {
-            return;
-        }
-        int clientIf = remoteGattDelegate(clientAddress).getClientIf();
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                serverAddress,
-                newServerConnectionStateChangeRunnable(serverIf, true, clientAddress));
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                clientAddress,
-                newClientConnectionStateChangeRunnable(clientIf, true, serverAddress));
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void serverDisconnect(final int serverIf, final String clientAddress) {
-        localGattDelegate().disconnect(clientAddress);
-        String serverAddress = localAddress();
-        int clientIf = remoteGattDelegate(clientAddress).getClientIf();
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                serverAddress,
-                newServerConnectionStateChangeRunnable(serverIf, false, clientAddress));
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                clientAddress,
-                newClientConnectionStateChangeRunnable(clientIf, false, serverAddress));
-    }
-
-    @Override
-    public void beginServiceDeclaration(
-            int serverIf,
-            int srvcType,
-            int srvcInstId,
-            int minHandles,
-            ParcelUuid srvcId,
-            boolean advertisePreferred) {
-        // TODO(b/200231384): support different service type, instanceId, advertisePreferred.
-        mCurrentService = localGattDelegate().addService(srvcId);
-    }
-
-    @Override
-    public void addIncludedService(int serverIf, int srvcType, int srvcInstId, ParcelUuid srvcId) {
-        // TODO(b/200231384): implement this.
-    }
-
-    @Override
-    public void addCharacteristic(int serverIf, ParcelUuid charId, int properties,
-            int permissions) {
-        mCurrentCharacteristic = mCurrentService.addCharacteristic(charId, properties, permissions);
-    }
-
-    @Override
-    public void addDescriptor(int serverIf, ParcelUuid descId, int permissions) {
-        mCurrentCharacteristic.addDescriptor(descId, permissions);
-    }
-
-    @Override
-    public void endServiceDeclaration(int serverIf) {
-        // TODO(b/200231384): choose correct srvc type and inst id.
-        IBluetoothGattServerCallback callback = localGattDelegate().getServerCallback(serverIf);
-        if (callback != null) {
-            callback.onServiceAdded(
-                    BluetoothGatt.GATT_SUCCESS, 0 /*srvcType*/, 0 /*srvcInstId*/,
-                    mCurrentService.getUuid());
-        }
-        mCurrentService = null;
-    }
-
-    @Override
-    public void removeService(int serverIf, int srvcType, int srvcInstId, ParcelUuid srvcId) {
-        // TODO(b/200231384): implement remove service.
-        // localGattDelegate().removeService(srvcId);
-    }
-
-    @Override
-    public void clearServices(int serverIf) {
-        // TODO(b/200231384): support multiple serverIf.
-        // localGattDelegate().clearService();
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void sendResponse(
-            int serverIf, String clientAddress, int requestId, int status, int offset,
-            byte[] value) {
-        // TODO(b/200231384): implement more operations.
-        String serverAddress = localAddress();
-
-        DeviceShadowEnvironmentImpl.runOnService(
-                clientAddress,
-                NamedRunnable.create(
-                        "ClientGatt.receiveResponse",
-                        () -> {
-                            IBluetoothGattCallback callback =
-                                    localGattDelegate().getClientCallback(
-                                            localGattDelegate().getClientIf());
-                            if (callback != null) {
-                                Request request = localGattDelegate().getLastRequest();
-                                localGattDelegate().setLastRequest(null);
-                                if (request != null) {
-                                    if (request instanceof ReadCharacteristicRequest) {
-                                        callback.onCharacteristicRead(
-                                                serverAddress,
-                                                status,
-                                                request.mSrvcType,
-                                                request.mSrvcInstId,
-                                                request.mSrvcId,
-                                                request.mCharInstId,
-                                                request.mCharId,
-                                                value);
-                                    } else if (request instanceof ReadDescriptorRequest) {
-                                        ReadDescriptorRequest readDescriptorRequest =
-                                                (ReadDescriptorRequest) request;
-                                        callback.onDescriptorRead(
-                                                serverAddress,
-                                                status,
-                                                readDescriptorRequest.mSrvcType,
-                                                readDescriptorRequest.mSrvcInstId,
-                                                readDescriptorRequest.mSrvcId,
-                                                readDescriptorRequest.mCharInstId,
-                                                readDescriptorRequest.mCharId,
-                                                readDescriptorRequest.mDescrInstId,
-                                                readDescriptorRequest.mDescrId,
-                                                value);
-                                    }
-                                }
-                            }
-                        }));
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void sendNotification(
-            final int serverIf,
-            final String address,
-            final int srvcType,
-            final int srvcInstId,
-            final ParcelUuid srvcId,
-            final int charInstId,
-            final ParcelUuid charId,
-            boolean confirm,
-            final byte[] value) {
-        GattDelegate.Characteristic characteristic =
-                localGattDelegate().getService(srvcId).getCharacteristic(charId);
-        characteristic.setValue(value);
-        final String serverAddress = localAddress();
-        for (final String clientAddress : characteristic.getNotifyClients()) {
-            NamedRunnable clientOnNotify =
-                    NamedRunnable.create(
-                            "ClientGatt.onNotify",
-                            () -> {
-                                int clientIf = localGattDelegate().getClientIf();
-                                IBluetoothGattCallback callback =
-                                        localGattDelegate().getClientCallback(clientIf);
-                                if (callback != null) {
-                                    callback.onNotify(
-                                            serverAddress, srvcType, srvcInstId, srvcId, charInstId,
-                                            charId, value);
-                                }
-                            });
-
-            DeviceShadowEnvironmentImpl.runOnService(clientAddress, clientOnNotify);
-        }
-
-        NamedRunnable serverOnNotificationSent =
-                NamedRunnable.create(
-                        "ServerGatt.onNotificationSent",
-                        () -> {
-                            IBluetoothGattServerCallback callback =
-                                    localGattDelegate().getServerCallback(serverIf);
-                            if (callback != null) {
-                                callback.onNotificationSent(address, BluetoothGatt.GATT_SUCCESS);
-                            }
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(serverAddress, serverOnNotificationSent);
-    }
-
-    @Override
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void configureMTU(int clientIf, String address, int mtu) {
-        final String clientAddress = localAddress();
-
-        NamedRunnable clientSetMtu =
-                NamedRunnable.create(
-                        "ClientGatt.setMtu",
-                        () -> {
-                            localGattDelegate().clientSetMtu(clientIf, mtu, address);
-                        });
-        NamedRunnable serverSetMtu =
-                NamedRunnable.create(
-                        "ServerGatt.setMtu",
-                        () -> {
-                            int serverIf = localGattDelegate().getServerIf();
-                            localGattDelegate().serverSetMtu(serverIf, mtu, clientAddress);
-                        });
-
-        DeviceShadowEnvironmentImpl.runOnService(clientAddress, clientSetMtu);
-
-        DeviceShadowEnvironmentImpl.runOnService(address, serverSetMtu);
-    }
-
-    @Override
-    public void connectionParameterUpdate(int clientIf, String address, int connectionPriority) {
-        // TODO(b/200231384): Implement.
-    }
-
-    @Override
-    public void disconnectAll() {
-    }
-
-    @Override
-    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        return new ArrayList<>();
-    }
-
-    @VisibleForTesting
-    static GattDelegate remoteGattDelegate(String address) {
-        return DeviceShadowEnvironmentImpl.getBlueletImpl(address).getGattDelegate();
-    }
-
-    private static GattDelegate localGattDelegate() {
-        return DeviceShadowEnvironmentImpl.getLocalBlueletImpl().getGattDelegate();
-    }
-
-    private static String localAddress() {
-        return DeviceShadowEnvironmentImpl.getLocalBlueletImpl().address;
-    }
-
-    private static NamedRunnable newClientConnectionStateChangeRunnable(
-            final int clientIf, final boolean isConnected, final String serverAddress) {
-        return NamedRunnable.create(
-                "ClientGatt.clientConnectionStateChange",
-                () -> {
-                    localGattDelegate()
-                            .clientConnectionStateChange(
-                                    BluetoothGatt.GATT_SUCCESS, clientIf, isConnected,
-                                    serverAddress);
-                });
-    }
-
-    private static NamedRunnable newServerConnectionStateChangeRunnable(
-            final int serverIf, final boolean isConnected, final String clientAddress) {
-        return NamedRunnable.create(
-                "ServerGatt.serverConnectionStateChange",
-                () -> {
-                    localGattDelegate()
-                            .serverConnectionStateChange(
-                                    BluetoothGatt.GATT_SUCCESS, serverIf, isConnected,
-                                    clientAddress);
-                });
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothImpl.java
deleted file mode 100644
index ccf0ac3..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothImpl.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.IBluetooth;
-import android.bluetooth.OobData;
-import android.content.AttributionSource;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelUuid;
-
-import com.android.libraries.testing.deviceshadower.Bluelet.CreateBondOutcome;
-import com.android.libraries.testing.deviceshadower.Bluelet.FetchUuidsTiming;
-import com.android.libraries.testing.deviceshadower.Bluelet.IoCapabilities;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.AdapterDelegate.State;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl.PairingConfirmation;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.base.Preconditions;
-
-import java.util.Random;
-
-/**
- * Implementation of IBluetooth interface.
- */
-public class IBluetoothImpl implements IBluetooth {
-
-    private static final Logger LOGGER = Logger.create("BlueletImpl");
-
-    private enum PairingVariant {
-        JUST_WORKS,
-        /**
-         * AKA Passkey Confirmation.
-         */
-        NUMERIC_COMPARISON,
-        PASSKEY_INPUT,
-        CONSENT
-    }
-
-    /**
-     * User will be prompted to accept or deny the incoming pairing request.
-     */
-    private static final int PAIRING_VARIANT_CONSENT = 3;
-
-    /**
-     * User will be prompted to enter the passkey displayed on remote device. This is used for
-     * Bluetooth 2.1 pairing.
-     */
-    private static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
-
-    public IBluetoothImpl() {
-    }
-
-    @Override
-    public String getAddress() {
-        return localBlueletImpl().address;
-    }
-
-    @Override
-    public String getName() {
-        return localBlueletImpl().mName;
-    }
-
-    @Override
-    public boolean setName(String name) {
-        localBlueletImpl().mName = name;
-        return true;
-    }
-
-    @Override
-    public int getRemoteClass(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).getAdapterDelegate().getBluetoothClass();
-    }
-
-    @Override
-    public String getRemoteName(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).mName;
-    }
-
-    @Override
-    public int getRemoteType(BluetoothDevice device, AttributionSource attributionSource) {
-        return BluetoothDevice.DEVICE_TYPE_LE;
-    }
-
-    @Override
-    public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).mProfileUuids;
-    }
-
-    @Override
-    public boolean fetchRemoteUuids(BluetoothDevice device) {
-        localBlueletImpl().onFetchedUuids(device.getAddress(), getRemoteUuids(device));
-        return true;
-    }
-
-    @Override
-    public int getBondState(BluetoothDevice device, AttributionSource attributionSource) {
-        return localBlueletImpl().getBondState(device.getAddress());
-    }
-
-    @Override
-    public boolean createBond(BluetoothDevice device, int transport, OobData remoteP192Data,
-            OobData remoteP256Data, AttributionSource attributionSource) {
-        setBondState(device.getAddress(), BluetoothDevice.BOND_BONDING, BlueletImpl.REASON_SUCCESS);
-
-        BlueletImpl remoteBluelet = remoteBlueletImpl(device.getAddress());
-        BlueletImpl localBluelet = localBlueletImpl();
-
-        // Like the real Bluetooth stack, choose a pairing variant based on IO Capabilities.
-        // https://blog.bluetooth.com/bluetooth-pairing-part-2-key-generation-methods
-        PairingVariant variant = PairingVariant.JUST_WORKS;
-        if (localBluelet.getIoCapabilities() == IoCapabilities.DISPLAY_YES_NO) {
-            if (remoteBluelet.getIoCapabilities() == IoCapabilities.DISPLAY_YES_NO) {
-                variant = PairingVariant.NUMERIC_COMPARISON;
-            } else if (remoteBluelet.getIoCapabilities() == IoCapabilities.KEYBOARD_ONLY) {
-                variant = PairingVariant.PASSKEY_INPUT;
-            } else if (remoteBluelet.getIoCapabilities() == IoCapabilities.NO_INPUT_NO_OUTPUT
-                    && localBluelet.getEnableCVE20192225()) {
-                // After CVE-2019-2225, Bluetooth decides to ask consent instead of JustWorks.
-                variant = PairingVariant.CONSENT;
-            }
-        }
-
-        // Bonding doesn't complete until the passkey is confirmed on both devices. The passkey is a
-        // positive 6-digit integer, generated by the Bluetooth stack.
-        int passkey = new Random().nextInt(999999) + 1;
-        switch (variant) {
-            case NUMERIC_COMPARISON:
-                localBluelet.onPairingRequest(
-                        remoteBluelet.address, BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION,
-                        passkey);
-                remoteBluelet.onPairingRequest(
-                        localBluelet.address, BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION,
-                        passkey);
-                break;
-            case JUST_WORKS:
-                // Bonding completes immediately, with no PAIRING_REQUEST broadcast.
-                finishBonding(device);
-                break;
-            case PASSKEY_INPUT:
-                localBluelet.onPairingRequest(
-                        remoteBluelet.address, PAIRING_VARIANT_DISPLAY_PASSKEY, passkey);
-                localBluelet.mPassKey = passkey;
-                remoteBluelet.onPairingRequest(
-                        localBluelet.address, PAIRING_VARIANT_DISPLAY_PASSKEY, passkey);
-                break;
-            case CONSENT:
-                localBluelet.onPairingRequest(remoteBluelet.address,
-                        PAIRING_VARIANT_CONSENT, /* key= */ 0);
-                if (remoteBluelet.getIoCapabilities() == IoCapabilities.NO_INPUT_NO_OUTPUT) {
-                    remoteBluelet.setPairingConfirmation(localBluelet.address,
-                            PairingConfirmation.CONFIRMED);
-                } else {
-                    remoteBluelet.onPairingRequest(
-                            localBluelet.address, PAIRING_VARIANT_CONSENT, /* key= */ 0);
-                }
-                break;
-        }
-        return true;
-    }
-
-    private void finishBonding(BluetoothDevice device) {
-        BlueletImpl remoteBluelet = remoteBlueletImpl(device.getAddress());
-        finishBonding(
-                device, remoteBluelet.getCreateBondOutcome(),
-                remoteBluelet.getCreateBondFailureReason());
-    }
-
-    private void finishBonding(BluetoothDevice device, CreateBondOutcome outcome,
-            int failureReason) {
-        switch (outcome) {
-            case SUCCESS:
-                setBondState(device.getAddress(), BluetoothDevice.BOND_BONDED,
-                        BlueletImpl.REASON_SUCCESS);
-                break;
-            case FAILURE:
-                setBondState(device.getAddress(), BluetoothDevice.BOND_NONE, failureReason);
-                break;
-            case TIMEOUT:
-                // Send nothing.
-                break;
-        }
-    }
-
-    @Override
-    public boolean setPairingConfirmation(BluetoothDevice device, boolean confirmed,
-            AttributionSource attributionSource) {
-        localBlueletImpl()
-                .setPairingConfirmation(
-                        device.getAddress(),
-                        confirmed ? PairingConfirmation.CONFIRMED : PairingConfirmation.DENIED);
-
-        PairingConfirmation remoteConfirmation =
-                remoteBlueletImpl(device.getAddress()).getPairingConfirmation(
-                        localBlueletImpl().address);
-        if (confirmed && remoteConfirmation == PairingConfirmation.CONFIRMED) {
-            LOGGER.d(String.format("CONFIRMED"));
-            finishBonding(device);
-        } else if (!confirmed || remoteConfirmation == PairingConfirmation.DENIED) {
-            LOGGER.d(String.format("NOT CONFIRMED"));
-            finishBonding(device, CreateBondOutcome.FAILURE, BlueletImpl.UNBOND_REASON_AUTH_FAILED);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean setPasskey(BluetoothDevice device, int passkey) {
-        BlueletImpl remoteBluelet = remoteBlueletImpl(device.getAddress());
-        if (passkey == remoteBluelet.mPassKey) {
-            finishBonding(device);
-        } else {
-            finishBonding(device, CreateBondOutcome.FAILURE, BlueletImpl.UNBOND_REASON_AUTH_FAILED);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean cancelBondProcess(BluetoothDevice device) {
-        finishBonding(device, CreateBondOutcome.FAILURE, BlueletImpl.UNBOND_REASON_AUTH_CANCELED);
-        return true;
-    }
-
-    @Override
-    public boolean removeBond(BluetoothDevice device) {
-        setBondState(device.getAddress(), BluetoothDevice.BOND_NONE, BlueletImpl.REASON_SUCCESS);
-        return true;
-    }
-
-    @Override
-    public BluetoothDevice[] getBondedDevices() {
-        return localBlueletImpl().getBondedDevices();
-    }
-
-    @Override
-    public int getAdapterConnectionState() {
-        return localBlueletImpl().getAdapterConnectionState();
-    }
-
-    @Override
-    public int getProfileConnectionState(int profile) {
-        return localBlueletImpl().getProfileConnectionState(profile);
-    }
-
-    @Override
-    public int getPhonebookAccessPermission(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).mPhonebookAccessPermission;
-    }
-
-    @Override
-    public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
-        remoteBlueletImpl(device.getAddress()).mPhonebookAccessPermission = value;
-        return true;
-    }
-
-    @Override
-    public int getMessageAccessPermission(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).mMessageAccessPermission;
-    }
-
-    @Override
-    public boolean setMessageAccessPermission(BluetoothDevice device, int value) {
-        remoteBlueletImpl(device.getAddress()).mMessageAccessPermission = value;
-        return true;
-    }
-
-    @Override
-    public int getSimAccessPermission(BluetoothDevice device) {
-        return remoteBlueletImpl(device.getAddress()).mSimAccessPermission;
-    }
-
-    @Override
-    public boolean setSimAccessPermission(BluetoothDevice device, int value) {
-        remoteBlueletImpl(device.getAddress()).mSimAccessPermission = value;
-        return true;
-    }
-
-    private static void setBondState(String remoteAddress, int state, int failureReason) {
-        BlueletImpl remoteBluelet = remoteBlueletImpl(remoteAddress);
-
-        if (remoteBluelet.getFetchUuidsTiming() == FetchUuidsTiming.BEFORE_BONDING) {
-            fetchUuidsOnBondedState(remoteAddress, state);
-        }
-
-        remoteBluelet.setBondState(localBlueletImpl().address, state, failureReason);
-        localBlueletImpl().setBondState(remoteAddress, state, failureReason);
-
-        if (remoteBluelet.getFetchUuidsTiming() == FetchUuidsTiming.AFTER_BONDING) {
-            fetchUuidsOnBondedState(remoteAddress, state);
-        }
-    }
-
-    private static void fetchUuidsOnBondedState(String remoteAddress, int state) {
-        if (state == BluetoothDevice.BOND_BONDED) {
-            remoteBlueletImpl(remoteAddress)
-                    .onFetchedUuids(localBlueletImpl().address, localBlueletImpl().mProfileUuids);
-            localBlueletImpl()
-                    .onFetchedUuids(remoteAddress, remoteBlueletImpl(remoteAddress).mProfileUuids);
-        }
-    }
-
-    @Override
-    public int getScanMode() {
-        return localBlueletImpl().getAdapterDelegate().getScanMode();
-    }
-
-    @Override
-    public boolean setScanMode(int mode, int duration) {
-        localBlueletImpl().getAdapterDelegate().setScanMode(mode);
-        return true;
-    }
-
-    @Override
-    public int getDiscoverableTimeout() {
-        return -1;
-    }
-
-    @Override
-    public boolean setDiscoverableTimeout(int timeout) {
-        return true;
-    }
-
-    @Override
-    public boolean startDiscovery() {
-        localBlueletImpl().getAdapterDelegate().startDiscovery();
-        return true;
-    }
-
-    @Override
-    public boolean cancelDiscovery() {
-        localBlueletImpl().getAdapterDelegate().cancelDiscovery();
-        return true;
-    }
-
-    @Override
-    public boolean isDiscovering() {
-        return localBlueletImpl().getAdapterDelegate().isDiscovering();
-
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return localBlueletImpl().getAdapterDelegate().getState().equals(State.ON);
-    }
-
-    @Override
-    public int getState() {
-        return localBlueletImpl().getAdapterDelegate().getState().getValue();
-    }
-
-    @Override
-    public boolean enable() {
-        localBlueletImpl().enableAdapter();
-        return true;
-    }
-
-    @Override
-    public boolean disable() {
-        localBlueletImpl().disableAdapter();
-        return true;
-    }
-
-    @Override
-    public ParcelFileDescriptor connectSocket(BluetoothDevice device, int type, ParcelUuid uuid,
-            int port, int flag) {
-        Preconditions.checkArgument(
-                port == BluetoothConstants.SOCKET_CHANNEL_CONNECT_WITH_UUID,
-                "Connect to port is not supported.");
-        Preconditions.checkArgument(
-                type == BluetoothConstants.TYPE_RFCOMM,
-                "Only Rfcomm socket is supported.");
-        return localBlueletImpl().getRfcommDelegate()
-                .connectSocket(device.getAddress(), uuid.getUuid());
-    }
-
-    @Override
-    public ParcelFileDescriptor createSocketChannel(int type, String serviceName, ParcelUuid uuid,
-            int port, int flag) {
-        Preconditions.checkArgument(
-                port == BluetoothConstants.SERVER_SOCKET_CHANNEL_AUTO_ASSIGN,
-                "Listen on port is not supported.");
-        Preconditions.checkArgument(
-                type == BluetoothConstants.TYPE_RFCOMM,
-                "Only Rfcomm socket is supported.");
-        return localBlueletImpl().getRfcommDelegate().createSocketChannel(serviceName, uuid);
-    }
-
-    @Override
-    public boolean isMultiAdvertisementSupported() {
-        return maxAdvertiseInstances() > 1;
-    }
-
-    @Override
-    public boolean isPeripheralModeSupported() {
-        return maxAdvertiseInstances() > 0;
-    }
-
-    private int maxAdvertiseInstances() {
-        return localBlueletImpl().getGattDelegate().getMaxAdvertiseInstances();
-    }
-
-    @Override
-    public boolean isOffloadedFilteringSupported() {
-        return localBlueletImpl().getGattDelegate().isOffloadedFilteringSupported();
-    }
-
-    private static BlueletImpl localBlueletImpl() {
-        return DeviceShadowEnvironmentImpl.getLocalBlueletImpl();
-    }
-
-    private static BlueletImpl remoteBlueletImpl(String address) {
-        return DeviceShadowEnvironmentImpl.getBlueletImpl(address);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothManagerImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothManagerImpl.java
deleted file mode 100644
index cb38a41..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/IBluetoothManagerImpl.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth;
-
-import android.bluetooth.IBluetooth;
-import android.bluetooth.IBluetoothGatt;
-import android.bluetooth.IBluetoothManager;
-import android.bluetooth.IBluetoothManagerCallback;
-
-/**
- * Implementation of IBluetoothManager interface
- */
-public class IBluetoothManagerImpl implements IBluetoothManager {
-
-    private final IBluetooth mFakeBluetoothService = new IBluetoothImpl();
-    private final IBluetoothGatt mFakeGattService = new IBluetoothGattImpl();
-
-    @Override
-    public String getAddress() {
-        return mFakeBluetoothService.getAddress();
-    }
-
-    @Override
-    public String getName() {
-        return mFakeBluetoothService.getName();
-    }
-
-    @Override
-    public IBluetooth registerAdapter(IBluetoothManagerCallback callback) {
-        return mFakeBluetoothService;
-    }
-
-    @Override
-    public IBluetoothGatt getBluetoothGatt() {
-        return mFakeGattService;
-    }
-
-    @Override
-    public boolean enable() {
-        mFakeBluetoothService.enable();
-        return true;
-    }
-
-    @Override
-    public boolean disable(boolean persist) {
-        mFakeBluetoothService.disable();
-        return true;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/FileDescriptorFactory.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/FileDescriptorFactory.java
deleted file mode 100644
index 12fa587..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/FileDescriptorFactory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
-
-import java.io.FileDescriptor;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Factory which creates {@link FileDescriptor} given an MAC address. Each MAC address can have many
- * FileDescriptor but each FileDescriptor only maps to one MAC address.
- */
-public class FileDescriptorFactory {
-
-    private static FileDescriptorFactory sInstance = null;
-
-    public static synchronized FileDescriptorFactory getInstance() {
-        if (sInstance == null) {
-            sInstance = new FileDescriptorFactory();
-        }
-        return sInstance;
-    }
-
-    public static synchronized void reset() {
-        sInstance = null;
-    }
-
-    private final Map<FileDescriptor, String> mAddressMap;
-
-    private FileDescriptorFactory() {
-        mAddressMap = new ConcurrentHashMap<>();
-    }
-
-    public FileDescriptor createFileDescriptor(String address) {
-        FileDescriptor fd = new FileDescriptor();
-        mAddressMap.put(fd, address);
-        return fd;
-    }
-
-    public String getAddress(FileDescriptor fd) {
-        return mAddressMap.get(fd);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PageScanHandler.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PageScanHandler.java
deleted file mode 100644
index 82b97ff..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PageScanHandler.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
-
-import android.os.Build.VERSION;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.utils.MacAddressGenerator;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Map;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-
-/**
- * Encapsulate page scan operations -- handle connection establishment between Bluetooth devices.
- */
-public class PageScanHandler {
-
-    private static final ConnectionRequest REQUEST_SERVER_SOCKET_CLOSE = new ConnectionRequest();
-
-    private static PageScanHandler sInstance = null;
-
-    public static synchronized PageScanHandler getInstance() {
-        if (sInstance == null) {
-            sInstance = new PageScanHandler();
-        }
-        return sInstance;
-    }
-
-    public static synchronized void reset() {
-        sInstance = null;
-    }
-
-    // use FileDescriptor to identify incoming data before socket is connected.
-    private final Map<FileDescriptor, BlockingQueue<Integer>> mIncomingDataMap;
-    // map a server socket fd to a connection request queue
-    private final Map<FileDescriptor, BlockingQueue<ConnectionRequest>> mConnectionRequests;
-    // map a fd on client side to a fd of BluetoothSocket(not BluetoothServerSocket) on server side
-    private final Map<FileDescriptor, FileDescriptor> mClientServerFdMap;
-    // map a client fd to a connection request so the client socket can finish the pending
-    // connection
-    private final Map<FileDescriptor, ConnectionRequest> mPendingConnections;
-
-    private PageScanHandler() {
-        mIncomingDataMap = new ConcurrentHashMap<>();
-        mConnectionRequests = new ConcurrentHashMap<>();
-        mClientServerFdMap = new ConcurrentHashMap<>();
-        mPendingConnections = new ConcurrentHashMap<>();
-    }
-
-    public void postConnectionRequest(FileDescriptor serverSocketFd, ConnectionRequest request)
-            throws InterruptedException {
-        // used by the returning socket on server-side
-        FileDescriptor fd = FileDescriptorFactory.getInstance()
-                .createFileDescriptor(request.mServerAddress);
-        mClientServerFdMap.put(request.mClientFd, fd);
-        BlockingQueue<ConnectionRequest> requests = mConnectionRequests.get(serverSocketFd);
-        requests.put(request);
-        mPendingConnections.put(request.mClientFd, request);
-    }
-
-    public void addServerSocket(FileDescriptor serverSocketFd) {
-        mConnectionRequests.put(serverSocketFd, new LinkedBlockingQueue<ConnectionRequest>());
-    }
-
-    public FileDescriptor getServerFd(FileDescriptor clientFd) {
-        return mClientServerFdMap.get(clientFd);
-    }
-
-    // TODO(b/79994182): see go/objecttostring-lsc
-    @SuppressWarnings("ObjectToString")
-    public FileDescriptor processNextConnectionRequest(FileDescriptor serverSocketFd)
-            throws IOException, InterruptedException {
-        ConnectionRequest request = mConnectionRequests.get(serverSocketFd).take();
-        if (request == REQUEST_SERVER_SOCKET_CLOSE) {
-            // TODO(b/79994182): FileDescriptor does not implement toString() in serverSocketFd
-            throw new IOException("Server socket is closed. fd: " + serverSocketFd);
-        }
-        writeInitialConnectionInfo(serverSocketFd, request.mClientAddress, request.mPort);
-        return request.mClientFd;
-    }
-
-    public void waitForConnectionEstablished(FileDescriptor clientFd) throws InterruptedException {
-        ConnectionRequest request = mPendingConnections.get(clientFd);
-        if (request != null) {
-            request.mCountDownLatch.await();
-        }
-    }
-
-    public void finishPendingConnection(FileDescriptor clientFd) {
-        ConnectionRequest request = mPendingConnections.get(clientFd);
-        if (request != null) {
-            request.mCountDownLatch.countDown();
-        }
-    }
-
-    public void cancelServerSocket(FileDescriptor serverSocketFd) throws InterruptedException {
-        mConnectionRequests.get(serverSocketFd).put(REQUEST_SERVER_SOCKET_CLOSE);
-    }
-
-    public void writeInitialConnectionInfo(FileDescriptor fd, String address, int port)
-            throws InterruptedException {
-        for (byte b : initialConnectionInfo(address, port)) {
-            write(fd, Integer.valueOf(b));
-        }
-    }
-
-    public void writePort(FileDescriptor fd, int port) throws InterruptedException {
-        byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(port).array();
-        for (byte b : bytes) {
-            write(fd, Integer.valueOf(b));
-        }
-    }
-
-    public void write(FileDescriptor fd, int data) throws InterruptedException {
-        BlockingQueue<Integer> incomingData = mIncomingDataMap.get(fd);
-        if (incomingData == null) {
-            synchronized (mIncomingDataMap) {
-                incomingData = mIncomingDataMap.get(fd);
-                if (incomingData == null) {
-                    incomingData = new LinkedBlockingQueue<Integer>();
-                    mIncomingDataMap.put(fd, incomingData);
-                }
-            }
-        }
-        incomingData.put(data);
-    }
-
-    public int read(FileDescriptor fd) throws InterruptedException {
-        return mIncomingDataMap.get(fd).take();
-    }
-
-    /**
-     * A connection request from a {@link android.bluetooth.BluetoothSocket}.
-     */
-    @VisibleForTesting
-    public static class ConnectionRequest {
-
-        final FileDescriptor mClientFd;
-        final String mClientAddress;
-        final String mServerAddress;
-        final int mPort;
-        final CountDownLatch mCountDownLatch; // block server socket until connection established
-
-        public ConnectionRequest(FileDescriptor fd, String clientAddress, String serverAddress,
-                int port) {
-            mClientFd = fd;
-            this.mClientAddress = clientAddress;
-            this.mServerAddress = serverAddress;
-            this.mPort = port;
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        private ConnectionRequest() {
-            mClientFd = null;
-            mClientAddress = null;
-            mServerAddress = null;
-            mPort = -1;
-            mCountDownLatch = new CountDownLatch(0);
-        }
-    }
-
-    private static byte[] initialConnectionInfo(String addr, int port) {
-        byte[] mac = MacAddressGenerator.convertStringMacAddress(addr);
-        int channel = port;
-        int status = 0;
-
-        if (VERSION.SDK_INT < 23) {
-            byte[] signal = new byte[16];
-            short signalSize = 16;
-            ByteBuffer buffer = ByteBuffer.wrap(signal);
-            buffer.order(ByteOrder.LITTLE_ENDIAN)
-                    .putShort(signalSize)
-                    .put(mac)
-                    .putInt(channel)
-                    .putInt(status);
-            return buffer.array();
-        } else {
-            byte[] signal = new byte[20];
-            short signalSize = 20;
-            short maxTxPacketSize = 10000;
-            short maxRxPacketSize = 10000;
-            ByteBuffer buffer = ByteBuffer.wrap(signal);
-            buffer.order(ByteOrder.LITTLE_ENDIAN)
-                    .putShort(signalSize)
-                    .put(mac)
-                    .putInt(channel)
-                    .putInt(status)
-                    .putShort(maxTxPacketSize)
-                    .putShort(maxRxPacketSize);
-            return buffer.array();
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PhysicalLink.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PhysicalLink.java
deleted file mode 100644
index e474c69..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/PhysicalLink.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.collect.Sets;
-
-import java.io.FileDescriptor;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A class represents a physical link for communications between two Bluetooth devices.
- */
-public class PhysicalLink {
-
-    // Intended to use RfcommDelegate
-    private static final Logger LOGGER = Logger.create("RfcommDelegate");
-
-    private final Object mLock;
-    // Every socket has unique FileDescriptor, so use it as socket identifier during communication
-    private final Map<FileDescriptor, RfcommSocketConnection> mConnectionLookup;
-    // Map fd of a socket to the fd of the other socket it connects to
-    private final Map<FileDescriptor, FileDescriptor> mFdMap;
-    private final Set<RfcommSocketConnection> mConnections;
-    private final AtomicBoolean mIsEncrypted;
-    private final Map<String, RfcommDelegate.Callback> mCallbacks = new HashMap<>();
-
-    public PhysicalLink(String address1, String address2) {
-        this(address1,
-                DeviceShadowEnvironmentImpl.getBlueletImpl(address1).getRfcommDelegate().mCallback,
-                address2,
-                DeviceShadowEnvironmentImpl.getBlueletImpl(address2).getRfcommDelegate().mCallback,
-                new ConcurrentHashMap<FileDescriptor, RfcommSocketConnection>(),
-                new ConcurrentHashMap<FileDescriptor, FileDescriptor>(),
-                Sets.<RfcommSocketConnection>newConcurrentHashSet());
-    }
-
-    @VisibleForTesting
-    PhysicalLink(String address1, RfcommDelegate.Callback callback1,
-            String address2, RfcommDelegate.Callback callback2,
-            Map<FileDescriptor, RfcommSocketConnection> connectionLookup,
-            Map<FileDescriptor, FileDescriptor> fdMap,
-            Set<RfcommSocketConnection> connections) {
-        mLock = new Object();
-        mCallbacks.put(address1, callback1);
-        mCallbacks.put(address2, callback2);
-        this.mConnectionLookup = connectionLookup;
-        this.mFdMap = fdMap;
-        this.mConnections = connections;
-        mIsEncrypted = new AtomicBoolean(false);
-    }
-
-    public void addConnection(FileDescriptor fd1, FileDescriptor fd2) {
-        synchronized (mLock) {
-            int oldSize = mConnections.size();
-            RfcommSocketConnection connection = new RfcommSocketConnection(
-                    FileDescriptorFactory.getInstance().getAddress(fd1),
-                    FileDescriptorFactory.getInstance().getAddress(fd2)
-            );
-            mConnections.add(connection);
-            mConnectionLookup.put(fd1, connection);
-            mConnectionLookup.put(fd2, connection);
-            mFdMap.put(fd1, fd2);
-            mFdMap.put(fd2, fd1);
-            if (oldSize == 0) {
-                onConnectionStateChange(true);
-            }
-        }
-    }
-
-    // TODO(b/79994182): see go/objecttostring-lsc
-    @SuppressWarnings("ObjectToString")
-    public void closeConnection(FileDescriptor fd) {
-        // check for early return without locking
-        if (!mConnectionLookup.containsKey(fd)) {
-            // TODO(b/79994182): FileDescriptor does not implement toString() in fd
-            LOGGER.d("Connection doesn't exist, FileDescriptor: " + fd);
-            return;
-        }
-        synchronized (mLock) {
-            RfcommSocketConnection connection = mConnectionLookup.get(fd);
-            if (connection == null) {
-                // TODO(b/79994182): FileDescriptor does not implement toString() in fd
-                LOGGER.d("Connection doesn't exist, FileDescriptor: " + fd);
-                return;
-            }
-            int oldSize = mConnections.size();
-            FileDescriptor connectingFd = mFdMap.get(fd);
-            mConnectionLookup.remove(fd);
-            mConnectionLookup.remove(connectingFd);
-            mFdMap.remove(fd);
-            mFdMap.remove(connectingFd);
-            mConnections.remove(connection);
-            if (oldSize == 1) {
-                onConnectionStateChange(false);
-            }
-        }
-    }
-
-    public RfcommSocketConnection getConnection(FileDescriptor fd) {
-        return mConnectionLookup.get(fd);
-    }
-
-    public void encrypt() {
-        mIsEncrypted.set(true);
-    }
-
-    public boolean isEncrypted() {
-        return mIsEncrypted.get();
-    }
-
-    public boolean isConnected() {
-        return !mConnections.isEmpty();
-    }
-
-    private void onConnectionStateChange(boolean isConnected) {
-        for (Entry<String, RfcommDelegate.Callback> entry : mCallbacks.entrySet()) {
-            RfcommDelegate.Callback callback = entry.getValue();
-            String localAddress = entry.getKey();
-            callback.onConnectionStateChange(getRemoteAddress(localAddress), isConnected);
-        }
-    }
-
-    private String getRemoteAddress(String address) {
-        String remoteAddress = null;
-        for (String addr : mCallbacks.keySet()) {
-            if (!addr.equals(address)) {
-                remoteAddress = addr;
-                break;
-            }
-        }
-        return remoteAddress;
-    }
-
-    /**
-     * Represents a Rfcomm socket connection between two {@link android.bluetooth.BluetoothSocket}.
-     */
-    public static class RfcommSocketConnection {
-
-        final Map<String, BlockingQueue<Integer>> mIncomingDataMap; // address : incomingData
-
-        public RfcommSocketConnection(String address1, String address2) {
-            mIncomingDataMap = new ConcurrentHashMap<>();
-            mIncomingDataMap.put(address1, new LinkedBlockingQueue<Integer>());
-            mIncomingDataMap.put(address2, new LinkedBlockingQueue<Integer>());
-        }
-
-        public void write(String address, int b) throws InterruptedException {
-            mIncomingDataMap.get(address).put(b);
-        }
-
-        public int read(String address) throws InterruptedException {
-            return mIncomingDataMap.get(address).take();
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/RfcommDelegate.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/RfcommDelegate.java
deleted file mode 100644
index 3a4fdf6..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/RfcommDelegate.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
-
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelUuid;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BluetoothConstants;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.PageScanHandler.ConnectionRequest;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.PhysicalLink.RfcommSocketConnection;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.SdpHandler.ServiceRecord;
-import com.android.libraries.testing.deviceshadower.internal.common.Interrupter;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.errorprone.annotations.FormatMethod;
-
-import org.robolectric.util.ReflectionHelpers;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Delegate for Bluetooth Rfcommon operations, including creating service record, establishing
- * connection, and data communications.
- * <p>Socket connection with uuid is supported. Listen on port and connect to port are not
- * supported.</p>
- */
-public class RfcommDelegate {
-
-    private static final Logger LOGGER = Logger.create("RfcommDelegate");
-    private static final Object LOCK = new Object();
-
-    /**
-     * Callback for Rfcomm operations
-     */
-    public interface Callback {
-
-        void onConnectionStateChange(String remoteAddress, boolean isConnected);
-    }
-
-    public static void reset() {
-        PageScanHandler.reset();
-        FileDescriptorFactory.reset();
-    }
-
-    final Callback mCallback;
-    private final String mAddress;
-    private final Interrupter mInterrupter;
-    private final SdpHandler mSdpHandler;
-    private final PageScanHandler mPageScanHandler;
-    private final Map<String, PhysicalLink> mConnectionMap; // remoteAddress : physicalLink
-
-    public RfcommDelegate(String address, Callback callback, Interrupter interrupter) {
-        this.mAddress = address;
-        this.mCallback = callback;
-        this.mInterrupter = interrupter;
-        mSdpHandler = new SdpHandler(address);
-        mPageScanHandler = PageScanHandler.getInstance();
-        mConnectionMap = new ConcurrentHashMap<>();
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public ParcelFileDescriptor createSocketChannel(String serviceName, ParcelUuid uuid) {
-        ServiceRecord record = mSdpHandler.createServiceRecord(uuid.getUuid(), serviceName);
-        if (record == null) {
-            LOGGER.e(
-                    String.format("Address %s: failed to create socket channel, uuid: %s", mAddress,
-                            uuid));
-            return null;
-        }
-        try {
-            mPageScanHandler.writePort(record.mServerSocketFd, record.mPort);
-        } catch (InterruptedException e) {
-            LOGGER.e(String.format("Address %s: failed to write port to incoming data, fd: %s",
-                    mAddress,
-                    record.mServerSocketFd), e);
-            return null;
-        }
-        return parcelFileDescriptor(record.mServerSocketFd);
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public ParcelFileDescriptor connectSocket(String remoteAddress, UUID uuid) {
-        BlueletImpl remote = DeviceShadowEnvironmentImpl.getBlueletImpl(remoteAddress);
-        if (remote == null) {
-            LOGGER.e(String.format("Device %s is not defined.", remoteAddress));
-            return null;
-        }
-        ServiceRecord record = remote.getRfcommDelegate().mSdpHandler.lookupChannel(uuid);
-        if (record == null) {
-            LOGGER.e(String.format("Address %s: failed to connect socket, uuid: %s", mAddress,
-                    uuid));
-            return null;
-        }
-        FileDescriptor fd = FileDescriptorFactory.getInstance().createFileDescriptor(mAddress);
-        try {
-            mPageScanHandler.writePort(fd, record.mPort);
-        } catch (InterruptedException e) {
-            LOGGER.e(String.format("Address %s: failed to write port to incoming data, fd: %s",
-                    mAddress,
-                    fd), e);
-            return null;
-        }
-
-        // establish connection
-        try {
-            initiateConnectToServer(fd, record, remoteAddress);
-        } catch (IOException e) {
-            LOGGER.e(
-                    String.format("Address %s: fail to initiate connection to server, clientFd: %s",
-                            mAddress, fd), e);
-            return null;
-        }
-        return parcelFileDescriptor(fd);
-    }
-
-    /**
-     * Creates connection and unblocks server socket.
-     * <p>ShadowBluetoothSocket calls the method at the end of connect().</p>
-     */
-    public void finishPendingConnection(
-            String serverAddress, FileDescriptor clientFd, boolean isEncrypted) {
-        // update states
-        PhysicalLink physicalChannel = mConnectionMap.get(serverAddress);
-        if (physicalChannel == null) {
-            // use class level lock to ensure two RfcommDelegate hold reference to the same Physical
-            // Link
-            synchronized (LOCK) {
-                physicalChannel = mConnectionMap.get(serverAddress);
-                if (physicalChannel == null) {
-                    physicalChannel = new PhysicalLink(
-                            serverAddress,
-                            FileDescriptorFactory.getInstance().getAddress(clientFd));
-                    addPhysicalChannel(serverAddress, physicalChannel);
-                    BlueletImpl remote = DeviceShadowEnvironmentImpl.getBlueletImpl(serverAddress);
-                    remote.getRfcommDelegate().addPhysicalChannel(mAddress, physicalChannel);
-                }
-            }
-        }
-        physicalChannel.addConnection(clientFd, mPageScanHandler.getServerFd(clientFd));
-
-        if (isEncrypted) {
-            physicalChannel.encrypt();
-        }
-        mPageScanHandler.finishPendingConnection(clientFd);
-    }
-
-    /**
-     * Process the next {@link ConnectionRequest} to {@link android.bluetooth.BluetoothServerSocket}
-     * identified by serverSocketFd. This call will block until next connection request is
-     * available.
-     */
-    @SuppressWarnings("ObjectToString")
-    public FileDescriptor processNextConnectionRequest(FileDescriptor serverSocketFd)
-            throws IOException {
-        try {
-            return mPageScanHandler.processNextConnectionRequest(serverSocketFd);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e, "failed to process next connection request, serverSocketFd: %s",
-                            serverSocketFd),
-                    e);
-        }
-    }
-
-    /**
-     * Waits for a connection established.
-     * <p>ShadowBluetoothServerSocket calls the method at the end of accept(). Ensure that a
-     * connection is established when accept() returns.</p>
-     */
-    @SuppressWarnings("ObjectToString")
-    public void waitForConnectionEstablished(FileDescriptor clientFd) throws IOException {
-        try {
-            mPageScanHandler.waitForConnectionEstablished(clientFd);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e, "failed to wait for connection established. clientFd: %s",
-                            clientFd), e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public void write(String remoteAddress, FileDescriptor localFd, int b)
-            throws IOException {
-        checkInterrupt();
-        RfcommSocketConnection connection =
-                mConnectionMap.get(remoteAddress).getConnection(localFd);
-        if (connection == null) {
-            throw new IOException("closed");
-        }
-        try {
-            connection.write(remoteAddress, b);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e, "failed to write to target %s, fd: %s", remoteAddress,
-                            localFd), e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public int read(String remoteAddress, FileDescriptor localFd) throws IOException {
-        checkInterrupt();
-        // remoteAddress is null: 1. server socket, 2. client socket before connected
-        try {
-            if (remoteAddress == null) {
-                return mPageScanHandler.read(localFd);
-            }
-        } catch (InterruptedException e) {
-            throw new IOException(logError(e, "failed to read, fd: %s", localFd), e);
-        }
-
-        RfcommSocketConnection connection =
-                mConnectionMap.get(remoteAddress).getConnection(localFd);
-        if (connection == null) {
-            throw new IOException("closed");
-        }
-        try {
-            return connection.read(mAddress);
-        } catch (InterruptedException e) {
-            throw new IOException(logError(e, "failed to read, fd: %s", localFd), e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public void shutdownInput(String remoteAddress, FileDescriptor localFd)
-            throws IOException {
-        // remoteAddress is null: 1. server socket, 2. client socket before connected
-        try {
-            if (remoteAddress == null) {
-                mPageScanHandler.write(localFd, BluetoothConstants.SOCKET_CLOSE);
-                return;
-            }
-        } catch (InterruptedException e) {
-            throw new IOException(logError(e, "failed to shutdown input. fd: %s", localFd), e);
-        }
-
-        RfcommSocketConnection connection =
-                mConnectionMap.get(remoteAddress).getConnection(localFd);
-        if (connection == null) {
-            LOGGER.d(String.format("Address %s: Connection already closed. fd: %s.", mAddress,
-                    localFd));
-            return;
-        }
-        try {
-            connection.write(mAddress, BluetoothConstants.SOCKET_CLOSE);
-        } catch (InterruptedException e) {
-            throw new IOException(logError(e, "failed to shutdown input. fd: %s", localFd), e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public void shutdownOutput(String remoteAddress, FileDescriptor localFd)
-            throws IOException {
-        RfcommSocketConnection connection =
-                mConnectionMap.get(remoteAddress).getConnection(localFd);
-        if (connection == null) {
-            LOGGER.d(String.format("Address %s: Connection already closed. fd: %s.", mAddress,
-                    localFd));
-            return;
-        }
-        try {
-            connection.write(remoteAddress, BluetoothConstants.SOCKET_CLOSE);
-        } catch (InterruptedException e) {
-            throw new IOException(logError(e, "failed to shutdown output. fd: %s", localFd), e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public void closeServerSocket(FileDescriptor serverSocketFd) throws IOException {
-        // remove service record
-        UUID uuid = mSdpHandler.getUuid(serverSocketFd);
-        mSdpHandler.removeServiceRecord(uuid);
-        // unblock accept()
-        try {
-            mPageScanHandler.cancelServerSocket(serverSocketFd);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e, "failed to cancel server socket, serverSocketFd: %s",
-                            serverSocketFd),
-                    e);
-        }
-    }
-
-    public FileDescriptor getServerFd(FileDescriptor clientFd) {
-        return mPageScanHandler.getServerFd(clientFd);
-    }
-
-    @VisibleForTesting
-    public void addPhysicalChannel(String remoteAddress, PhysicalLink channel) {
-        mConnectionMap.put(remoteAddress, channel);
-    }
-
-    @SuppressWarnings("ObjectToString")
-    public void initiateConnectToClient(FileDescriptor clientFd, int port)
-            throws IOException {
-        checkInterrupt();
-        String clientAddress = FileDescriptorFactory.getInstance().getAddress(clientFd);
-        LOGGER.d(String.format("Address %s: init connection to %s, clientFd: %s",
-                mAddress, clientAddress, clientFd));
-        try {
-            mPageScanHandler.writeInitialConnectionInfo(clientFd, mAddress, port);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e,
-                            "failed to write initial connection info to %s, clientFd: %s",
-                            clientAddress, clientFd),
-                    e);
-        }
-    }
-
-    @SuppressWarnings("ObjectToString")
-    private void initiateConnectToServer(FileDescriptor clientFd, ServiceRecord serviceRecord,
-            String serverAddress) throws IOException {
-        checkInterrupt();
-        LOGGER.d(
-                String.format("Address %s: init connection to %s, serverSocketFd: %s, clientFd: %s",
-                        mAddress, serverAddress, serviceRecord.mServerSocketFd, clientFd));
-        try {
-            ConnectionRequest request = new ConnectionRequest(clientFd, mAddress, serverAddress,
-                    serviceRecord.mPort);
-            mPageScanHandler.postConnectionRequest(serviceRecord.mServerSocketFd, request);
-        } catch (InterruptedException e) {
-            throw new IOException(
-                    logError(e,
-                            "failed to post connection request, serverSocketFd: %s, "
-                                    + "clientFd: %s",
-                            serviceRecord.mServerSocketFd, clientFd),
-                    e);
-        }
-    }
-
-    public void checkInterrupt() throws IOException {
-        mInterrupter.checkInterrupt();
-    }
-
-    private ParcelFileDescriptor parcelFileDescriptor(FileDescriptor fd) {
-        return ReflectionHelpers.callConstructor(ParcelFileDescriptor.class,
-                ReflectionHelpers.ClassParameter.from(FileDescriptor.class, fd));
-    }
-
-    @FormatMethod
-    private String logError(Exception e, String msgTmpl, Object... args) {
-        String errMsg = String.format("Address %s: ", mAddress) + String.format(msgTmpl, args);
-        LOGGER.e(errMsg, e);
-        return errMsg;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/SdpHandler.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/SdpHandler.java
deleted file mode 100644
index dbe8651..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/bluetooth/connection/SdpHandler.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
-
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Sets;
-
-import java.io.FileDescriptor;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Encapsulates SDP operations including creating service record and allocating channel.
- * <p>Listen on port and connect on port are not supported. </p>
- */
-public class SdpHandler {
-
-    // intended to use "RfcommDelegate"
-    private static final Logger LOGGER = Logger.create("RfcommDelegate");
-
-    private final Object mLock;
-    private final String mAddress;
-    private final Map<UUID, ServiceRecord> mServiceRecords;
-    private final Map<FileDescriptor, UUID> mFdUuidMap;
-    private final Set<Integer> mAvailablePortPool;
-    private final Set<Integer> mInUsePortPool;
-
-    public SdpHandler(String address) {
-        mLock = new Object();
-        this.mAddress = address;
-        mServiceRecords = new ConcurrentHashMap<>();
-        mFdUuidMap = new ConcurrentHashMap<>();
-        mAvailablePortPool = Sets.newConcurrentHashSet();
-        mInUsePortPool = Sets.newConcurrentHashSet();
-        // 1 to 30 are valid RFCOMM port
-        for (int i = 1; i <= 30; i++) {
-            mAvailablePortPool.add(i);
-        }
-    }
-
-    public ServiceRecord createServiceRecord(UUID uuid, String serviceName) {
-        Preconditions.checkNotNull(uuid);
-        LOGGER.d(String.format("Address %s: createServiceRecord with uuid %s", mAddress, uuid));
-        if (isUuidRegistered(uuid) || !checkChannelAvailability()) {
-            return null;
-        }
-        synchronized (mLock) {
-            // ensure uuid is not registered and there's available channel
-            if (isUuidRegistered(uuid) || !checkChannelAvailability()) {
-                return null;
-            }
-            Iterator<Integer> available = mAvailablePortPool.iterator();
-            int port = available.next();
-            mAvailablePortPool.remove(port);
-            mInUsePortPool.add(port);
-            ServiceRecord record = new ServiceRecord(mAddress, serviceName, port);
-            mServiceRecords.put(uuid, record);
-            mFdUuidMap.put(record.mServerSocketFd, uuid);
-            PageScanHandler.getInstance().addServerSocket(record.mServerSocketFd);
-            return record;
-        }
-    }
-
-    public void removeServiceRecord(UUID uuid) {
-        Preconditions.checkNotNull(uuid);
-        LOGGER.d(String.format("Address %s: removeServiceRecord with uuid %s", mAddress, uuid));
-        if (!isUuidRegistered(uuid)) {
-            return;
-        }
-        synchronized (mLock) {
-            if (!isUuidRegistered(uuid)) {
-                return;
-            }
-            ServiceRecord record = mServiceRecords.get(uuid);
-            mServiceRecords.remove(uuid);
-            mInUsePortPool.remove(record.mPort);
-            mAvailablePortPool.add(record.mPort);
-            mFdUuidMap.remove(record.mServerSocketFd);
-        }
-    }
-
-    public ServiceRecord lookupChannel(UUID uuid) {
-        ServiceRecord record = mServiceRecords.get(uuid);
-        if (record == null) {
-            LOGGER.e(String.format("Address %s: uuid %s not registered.", mAddress, uuid));
-        }
-        return record;
-    }
-
-    public UUID getUuid(FileDescriptor serverSocketFd) {
-        return mFdUuidMap.get(serverSocketFd);
-    }
-
-    private boolean isUuidRegistered(UUID uuid) {
-        if (mServiceRecords.containsKey(uuid)) {
-            LOGGER.d(String.format("Address %s: Uuid %s in use.", mAddress, uuid));
-            return true;
-        }
-        LOGGER.d(String.format("Address %s: Uuid %s not registered.", mAddress, uuid));
-        return false;
-    }
-
-    private boolean checkChannelAvailability() {
-        if (mAvailablePortPool.isEmpty()) {
-            LOGGER.e(String.format("Address %s: No available channel.", mAddress));
-            return false;
-        }
-        return true;
-    }
-
-    static class ServiceRecord {
-
-        final FileDescriptor mServerSocketFd;
-        final String mServiceName;
-        final int mPort;
-
-        ServiceRecord(String address, String serviceName, int port) {
-            mServerSocketFd = FileDescriptorFactory.getInstance().createFileDescriptor(address);
-            this.mServiceName = serviceName;
-            this.mPort = port;
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/BroadcastManager.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/BroadcastManager.java
deleted file mode 100644
index 0b309ae..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/BroadcastManager.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.common;
-
-import android.content.BroadcastReceiver;
-import android.content.BroadcastReceiver.PendingResult;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build.VERSION;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowApplication;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Manager for broadcasting of one virtual Device Shadower device.
- *
- * <p>Inspired by {@link ShadowApplication} and {@link LocalBroadcastManager}.
- * <li>Broadcast permission is not supported until manifest is supported.
- * <li>Send Broadcast is asynchronous.
- */
-public class BroadcastManager {
-
-    private static final Logger LOGGER = Logger.create("BroadcastManager");
-
-    private static final Comparator<ReceiverRecord> RECEIVER_RECORD_COMPARATOR =
-            new Comparator<ReceiverRecord>() {
-                @Override
-                public int compare(ReceiverRecord o1, ReceiverRecord o2) {
-                    return o2.mIntentFilter.getPriority() - o1.mIntentFilter.getPriority();
-                }
-            };
-
-    private final Scheduler mScheduler;
-    private final Map<String, Intent> mStickyIntents;
-
-    @GuardedBy("mRegisteredReceivers")
-    private final Map<BroadcastReceiver, Set<String>> mRegisteredReceivers;
-
-    @GuardedBy("mRegisteredReceivers")
-    private final Map<String, List<ReceiverRecord>> mActions;
-
-    public BroadcastManager(Scheduler scheduler) {
-        this(
-                scheduler,
-                new HashMap<String, Intent>(),
-                new HashMap<BroadcastReceiver, Set<String>>(),
-                new HashMap<String, List<ReceiverRecord>>());
-    }
-
-    @VisibleForTesting
-    BroadcastManager(
-            Scheduler scheduler,
-            Map<String, Intent> stickyIntents,
-            Map<BroadcastReceiver, Set<String>> registeredReceivers,
-            Map<String, List<ReceiverRecord>> actions) {
-        this.mScheduler = scheduler;
-        this.mStickyIntents = stickyIntents;
-        this.mRegisteredReceivers = registeredReceivers;
-        this.mActions = actions;
-    }
-
-    /**
-     * Registers a {@link BroadcastReceiver} with given {@link Context}.
-     *
-     * @see Context#registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
-     */
-    @Nullable
-    public Intent registerReceiver(
-            @Nullable BroadcastReceiver receiver,
-            IntentFilter filter,
-            @Nullable String broadcastPermission,
-            @Nullable Handler handler,
-            Context context) {
-        // Ignore broadcastPermission before fully supporting manifest
-        Preconditions.checkNotNull(filter);
-        Preconditions.checkNotNull(context);
-        if (receiver != null) {
-            synchronized (mRegisteredReceivers) {
-                ReceiverRecord receiverRecord = new ReceiverRecord(receiver, filter, context,
-                        handler);
-                Set<String> actionSet = mRegisteredReceivers.get(receiver);
-                if (actionSet == null) {
-                    actionSet = new HashSet<>();
-                    mRegisteredReceivers.put(receiver, actionSet);
-                }
-                for (int i = 0; i < filter.countActions(); i++) {
-                    String action = filter.getAction(i);
-                    actionSet.add(action);
-                    List<ReceiverRecord> receiverRecords = mActions.get(action);
-                    if (receiverRecords == null) {
-                        receiverRecords = new ArrayList<>();
-                        mActions.put(action, receiverRecords);
-                    }
-                    receiverRecords.add(receiverRecord);
-                }
-            }
-        }
-        return processStickyIntents(receiver, filter, context);
-    }
-
-    // Broadcast all sticky intents matching the given IntentFilter.
-    @SuppressWarnings("FutureReturnValueIgnored")
-    @Nullable
-    private Intent processStickyIntents(
-            @Nullable final BroadcastReceiver receiver,
-            IntentFilter intentFilter,
-            final Context context) {
-        Intent result = null;
-        final List<Intent> matchedIntents = new ArrayList<>();
-        for (Intent intent : mStickyIntents.values()) {
-            if (match(intentFilter, intent)) {
-                if (result == null) {
-                    result = intent;
-                }
-                if (receiver == null) {
-                    return result;
-                }
-                matchedIntents.add(intent);
-            }
-        }
-        if (!matchedIntents.isEmpty()) {
-            mScheduler.post(
-                    NamedRunnable.create(
-                            "Broadcast.processStickyIntents",
-                            () -> {
-                                for (Intent intent : matchedIntents) {
-                                    receiver.onReceive(context, intent);
-                                }
-                            }));
-        }
-        return result;
-    }
-
-    /**
-     * Unregisters a {@link BroadcastReceiver}.
-     *
-     * @see Context#unregisterReceiver(BroadcastReceiver)
-     */
-    public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
-        synchronized (mRegisteredReceivers) {
-            if (!mRegisteredReceivers.containsKey(broadcastReceiver)) {
-                LOGGER.w("Receiver not registered: " + broadcastReceiver);
-                return;
-            }
-            Set<String> actionSet = mRegisteredReceivers.remove(broadcastReceiver);
-            for (String action : actionSet) {
-                List<ReceiverRecord> receiverRecords = mActions.get(action);
-                Iterator<ReceiverRecord> iterator = receiverRecords.iterator();
-                while (iterator.hasNext()) {
-                    if (iterator.next().mBroadcastReceiver == broadcastReceiver) {
-                        iterator.remove();
-                    }
-                }
-                if (receiverRecords.isEmpty()) {
-                    mActions.remove(action);
-                }
-            }
-        }
-    }
-
-    /**
-     * Sends sticky broadcast with given {@link Intent}. This call is asynchronous.
-     *
-     * @see Context#sendStickyBroadcast(Intent)
-     */
-    public void sendStickyBroadcast(Intent intent) {
-        mStickyIntents.put(intent.getAction(), intent);
-        sendBroadcast(intent, null /* broadcastPermission */);
-    }
-
-    /**
-     * Sends broadcast with given {@link Intent}. Receiver permission is not supported. This call is
-     * asynchronous.
-     *
-     * @see Context#sendBroadcast(Intent, String)
-     */
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void sendBroadcast(final Intent intent, @Nullable String receiverPermission) {
-        // Ignore permission matching before fully supporting manifest
-        final List<ReceiverRecord> receivers =
-                getMatchingReceivers(intent, false /* isOrdered */);
-        if (receivers.isEmpty()) {
-            return;
-        }
-        mScheduler.post(
-                NamedRunnable.create(
-                        "Broadcast.sendBroadcast",
-                        () -> {
-                            for (ReceiverRecord receiverRecord : receivers) {
-                                // Hacky: Call the shadow method, otherwise abort() NPEs after
-                                // calling onReceive().
-                                // TODO(b/200231384): Sending these, via context.sendBroadcast(),
-                                //  won't NPE...but it may not be possible on each simulated
-                                //  "device"'s main thread. Check if possible.
-                                BroadcastReceiver broadcastReceiver =
-                                        receiverRecord.mBroadcastReceiver;
-                                Shadows.shadowOf(broadcastReceiver)
-                                        .onReceive(receiverRecord.mContext, intent, /*abort=*/
-                                                new AtomicBoolean(false));
-                            }
-                        }));
-    }
-
-    /**
-     * Sends ordered broadcast with given {@link Intent}. Receiver permission is not supported. This
-     * call is asynchronous.
-     *
-     * @see Context#sendOrderedBroadcast(Intent, String)
-     */
-    public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission) {
-        sendOrderedBroadcast(
-                intent,
-                receiverPermission,
-                null /* resultReceiver */,
-                null /* handler */,
-                0 /* initialCode */,
-                null /* initialData */,
-                null /* initialExtras */,
-                null /* context */);
-    }
-
-    /**
-     * Sends ordered broadcast with given {@link Intent} and result {@link BroadcastReceiver}.
-     * Receiver permission is not supported. This call is asynchronous.
-     *
-     * @see Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
-     * Bundle)
-     */
-    @SuppressWarnings("FutureReturnValueIgnored")
-    public void sendOrderedBroadcast(
-            final Intent intent,
-            @Nullable String receiverPermission,
-            @Nullable BroadcastReceiver resultReceiver,
-            @Nullable Handler handler,
-            int initialCode,
-            @Nullable String initialData,
-            @Nullable Bundle initialExtras,
-            @Nullable Context context) {
-        // Ignore permission matching before fully supporting manifest
-        final List<ReceiverRecord> receivers =
-                getMatchingReceivers(intent, true /* isOrdered */);
-        if (receivers.isEmpty()) {
-            return;
-        }
-        if (resultReceiver != null) {
-            receivers.add(
-                    new ReceiverRecord(
-                            resultReceiver, null /* intentFilter */, context, handler));
-        }
-        mScheduler.post(
-                NamedRunnable.create(
-                        "Broadcast.sendOrderedBroadcast",
-                        () -> {
-                            postOrderedIntent(
-                                    receivers,
-                                    intent,
-                                    0 /* initialCode */,
-                                    null /* initialData */,
-                                    null /* initialExtras */);
-                        }));
-    }
-
-    @VisibleForTesting
-    void postOrderedIntent(
-            List<ReceiverRecord> receivers,
-            final Intent intent,
-            int initialCode,
-            @Nullable String initialData,
-            @Nullable Bundle initialExtras) {
-        final AtomicBoolean abort = new AtomicBoolean(false);
-        ListenableFuture<BroadcastResult> resultFuture =
-                Futures.immediateFuture(
-                        new BroadcastResult(initialCode, initialData, initialExtras));
-
-        for (ReceiverRecord receiverRecord : receivers) {
-            final BroadcastReceiver receiver = receiverRecord.mBroadcastReceiver;
-            final Context context = receiverRecord.mContext;
-            resultFuture =
-                    Futures.transformAsync(
-                            resultFuture,
-                            new AsyncFunction<BroadcastResult, BroadcastResult>() {
-                                @Override
-                                public ListenableFuture<BroadcastResult> apply(
-                                        BroadcastResult input) {
-                                    PendingResult result = newPendingResult(
-                                            input.mCode, input.mData, input.mExtras,
-                                            true /* isOrdered */);
-                                    ReflectionHelpers.callInstanceMethod(
-                                            receiver, "setPendingResult",
-                                            ClassParameter.from(PendingResult.class, result));
-                                    Shadows.shadowOf(receiver).onReceive(context, intent, abort);
-                                    return BroadcastResult.transform(result);
-                                }
-                            },
-                            MoreExecutors.directExecutor());
-        }
-        Futures.addCallback(
-                resultFuture,
-                new FutureCallback<BroadcastResult>() {
-                    @Override
-                    public void onSuccess(BroadcastResult result) {
-                        return;
-                    }
-
-                    @Override
-                    public void onFailure(Throwable t) {
-                        throw new RuntimeException(t);
-                    }
-                },
-                MoreExecutors.directExecutor());
-    }
-
-    private List<ReceiverRecord> getMatchingReceivers(Intent intent, boolean isOrdered) {
-        synchronized (mRegisteredReceivers) {
-            List<ReceiverRecord> result = new ArrayList<>();
-            if (!mActions.containsKey(intent.getAction())) {
-                return result;
-            }
-            Iterator<ReceiverRecord> iterator = mActions.get(intent.getAction()).iterator();
-            while (iterator.hasNext()) {
-                ReceiverRecord next = iterator.next();
-                if (match(next.mIntentFilter, intent)) {
-                    result.add(next);
-                }
-            }
-            if (isOrdered) {
-                Collections.sort(result, RECEIVER_RECORD_COMPARATOR);
-            }
-            return result;
-        }
-    }
-
-    private boolean match(IntentFilter intentFilter, Intent intent) {
-        // Action test
-        if (!intentFilter.matchAction(intent.getAction())) {
-            return false;
-        }
-        // Category test
-        if (intentFilter.matchCategories(intent.getCategories()) != null) {
-            return false;
-        }
-        // Data test
-        int matchResult =
-                intentFilter.matchData(intent.getType(), intent.getScheme(), intent.getData());
-        return matchResult != IntentFilter.NO_MATCH_TYPE
-                && matchResult != IntentFilter.NO_MATCH_DATA;
-    }
-
-    private static PendingResult newPendingResult(
-            int resultCode, String resultData, Bundle resultExtras, boolean isOrdered) {
-        ClassParameter<?>[] parameters;
-        // PendingResult constructor takes different parameters in different SDK levels.
-        if (VERSION.SDK_INT < 17) {
-            parameters =
-                    ClassParameter.fromComponentLists(
-                            new Class<?>[]{
-                                    int.class,
-                                    String.class,
-                                    Bundle.class,
-                                    int.class,
-                                    boolean.class,
-                                    boolean.class,
-                                    IBinder.class
-                            },
-                            new Object[]{
-                                    resultCode,
-                                    resultData,
-                                    resultExtras,
-                                    0 /* type */,
-                                    isOrdered,
-                                    false /* sticky */,
-                                    null /* IBinder */
-                            });
-        } else if (VERSION.SDK_INT < 23) {
-            parameters =
-                    ClassParameter.fromComponentLists(
-                            new Class<?>[]{
-                                    int.class,
-                                    String.class,
-                                    Bundle.class,
-                                    int.class,
-                                    boolean.class,
-                                    boolean.class,
-                                    IBinder.class,
-                                    int.class
-                            },
-                            new Object[]{
-                                    resultCode,
-                                    resultData,
-                                    resultExtras,
-                                    0 /* type */,
-                                    isOrdered,
-                                    false /* sticky */,
-                                    null /* IBinder */,
-                                    0 /* userId */
-                            });
-        } else {
-            parameters =
-                    ClassParameter.fromComponentLists(
-                            new Class<?>[]{
-                                    int.class,
-                                    String.class,
-                                    Bundle.class,
-                                    int.class,
-                                    boolean.class,
-                                    boolean.class,
-                                    IBinder.class,
-                                    int.class,
-                                    int.class
-                            },
-                            new Object[]{
-                                    resultCode,
-                                    resultData,
-                                    resultExtras,
-                                    0 /* type */,
-                                    isOrdered,
-                                    false /* sticky */,
-                                    null /* IBinder */,
-                                    0 /* userId */,
-                                    0 /* flags */
-                            });
-        }
-        return ReflectionHelpers.callConstructor(PendingResult.class, parameters);
-    }
-
-    /**
-     * Holder of broadcast result from previous receiver.
-     */
-    private static final class BroadcastResult {
-
-        private final int mCode;
-        private final String mData;
-        private final Bundle mExtras;
-
-        BroadcastResult(int code, String data, Bundle extras) {
-            this.mCode = code;
-            this.mData = data;
-            this.mExtras = extras;
-        }
-
-        private static ListenableFuture<BroadcastResult> transform(PendingResult result) {
-            return Futures.transform(
-                    Shadows.shadowOf(result).getFuture(),
-                    new Function<PendingResult, BroadcastResult>() {
-                        @Override
-                        public BroadcastResult apply(PendingResult input) {
-                            return new BroadcastResult(
-                                    input.getResultCode(), input.getResultData(),
-                                    input.getResultExtras(false));
-                        }
-                    },
-                    MoreExecutors.directExecutor());
-        }
-    }
-
-    /**
-     * Information of a registered BroadcastReceiver.
-     */
-    @VisibleForTesting
-    static final class ReceiverRecord {
-
-        final BroadcastReceiver mBroadcastReceiver;
-        final IntentFilter mIntentFilter;
-        final Context mContext;
-        final Handler mHandler;
-
-        @VisibleForTesting
-        ReceiverRecord(
-                BroadcastReceiver broadcastReceiver,
-                IntentFilter intentFilter,
-                Context context,
-                Handler handler) {
-            this.mBroadcastReceiver = broadcastReceiver;
-            this.mIntentFilter = intentFilter;
-            this.mContext = context;
-            this.mHandler = handler;
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/ContentDatabase.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/ContentDatabase.java
deleted file mode 100644
index 1f4d778..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/ContentDatabase.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.common;
-
-import android.database.Cursor;
-
-import org.robolectric.fakes.RoboCursor;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Simulate Sqlite database for Android content provider.
- */
-public class ContentDatabase {
-
-    private final List<String> mColumnNames;
-    private final List<List<Object>> mData;
-
-    public ContentDatabase(String... names) {
-        mColumnNames = Arrays.asList(names);
-        mData = new ArrayList<>();
-    }
-
-    public void addData(Object... items) {
-        mData.add(Arrays.asList(items));
-    }
-
-    public Cursor getCursor() {
-        RoboCursor cursor = new RoboCursor();
-        cursor.setColumnNames(mColumnNames);
-        Object[][] dataArr = new Object[mData.size()][mColumnNames.size()];
-        for (int i = 0; i < mData.size(); i++) {
-            dataArr[i] = new Object[mColumnNames.size()];
-            mData.get(i).toArray(dataArr[i]);
-        }
-        cursor.setResults(dataArr);
-        return cursor;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Interrupter.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Interrupter.java
deleted file mode 100644
index 66e9cb0..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Interrupter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.common;
-
-import com.android.libraries.testing.deviceshadower.Enums;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Interrupter sets and checks interruptible point, and interrupt operation by throwing
- * IOException.
- */
-public class Interrupter {
-
-    private final InheritableThreadLocal<Integer> mCurrentIdentifier;
-    private int mInterruptIdentifier;
-
-    private final Set<Enums.Operation> mInterruptOperations = new HashSet<>();
-
-    public Interrupter() {
-        mCurrentIdentifier = new InheritableThreadLocal<Integer>() {
-            @Override
-            protected Integer initialValue() {
-                return -1;
-            }
-        };
-    }
-
-    public void checkInterrupt() throws IOException {
-        if (mCurrentIdentifier.get() == mInterruptIdentifier) {
-            throw new IOException(
-                    "Bluetooth interrupted at identifier: " + mCurrentIdentifier.get());
-        }
-    }
-
-    public void setInterruptible(int identifier) {
-        mCurrentIdentifier.set(identifier);
-    }
-
-    public void interrupt(int identifier) {
-        mInterruptIdentifier = identifier;
-    }
-
-    public void addInterruptOperation(Enums.Operation operation) {
-        mInterruptOperations.add(operation);
-    }
-
-    public boolean shouldInterrupt(Enums.Operation operation) {
-        return mInterruptOperations.contains(operation);
-    }
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/NamedRunnable.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/NamedRunnable.java
deleted file mode 100644
index 4e84d71..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/NamedRunnable.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.common;
-
-/**
- * Runnable with a name defined.
- */
-public abstract class NamedRunnable implements Runnable {
-
-    private final String mName;
-
-    private NamedRunnable(String name) {
-        this.mName = name;
-    }
-
-    public static NamedRunnable create(String name, Runnable runnable) {
-        return new NamedRunnable(name) {
-            @Override
-            public void run() {
-                runnable.run();
-            }
-        };
-    }
-
-    @Override
-    public String toString() {
-        return mName;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Scheduler.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Scheduler.java
deleted file mode 100644
index 96e9b15..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/common/Scheduler.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.common;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Scheduler to post runnables to a single thread.
- */
-public class Scheduler {
-
-    private static final Logger LOGGER = Logger.create("Scheduler");
-
-    @GuardedBy("Scheduler.class")
-    private static int sTotalRunnables = 0;
-
-    private static CountDownLatch sCompleteLatch;
-
-    public Scheduler() {
-        this(null);
-    }
-
-    public Scheduler(String name) {
-        mExecutor =
-                Executors.newSingleThreadExecutor(
-                        r -> {
-                            Thread thread = Executors.defaultThreadFactory().newThread(r);
-                            if (name != null) {
-                                thread.setName(name);
-                            }
-                            return thread;
-                        });
-    }
-
-    public static boolean await(long timeoutMillis) throws InterruptedException {
-
-        synchronized (Scheduler.class) {
-            if (isComplete()) {
-                return true;
-            }
-            if (sCompleteLatch == null) {
-                sCompleteLatch = new CountDownLatch(1);
-            }
-        }
-
-        // TODO(b/200231384): solve potential NPE caused by race condition.
-        boolean result = sCompleteLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
-        synchronized (Scheduler.class) {
-            sCompleteLatch = null;
-        }
-        return result;
-    }
-
-    private final ExecutorService mExecutor;
-
-    @GuardedBy("this")
-    private final List<ScheduledRunnable> mRunnables = new ArrayList<>();
-
-    @GuardedBy("this")
-    private long mCurrentTimeMillis = 0;
-
-    @GuardedBy("this")
-    private List<ScheduledRunnable> mRunningRunnables = new ArrayList<>();
-
-    /**
-     * Post a {@link NamedRunnable} to scheduler.
-     *
-     * <p>Return value can be ignored because exception will be handled by {@link
-     * DeviceShadowEnvironmentImpl#catchInternalException}.
-     */
-    // @CanIgnoreReturnValue
-    public synchronized Future<?> post(NamedRunnable r) {
-        synchronized (Scheduler.class) {
-            sTotalRunnables++;
-        }
-        advance(0);
-        return mExecutor.submit(new ScheduledRunnable(r, mCurrentTimeMillis).mRunnable);
-    }
-
-    public synchronized void post(NamedRunnable r, long delayMillis) {
-        synchronized (Scheduler.class) {
-            sTotalRunnables++;
-        }
-        addRunnables(new ScheduledRunnable(r, mCurrentTimeMillis + delayMillis));
-        advance(0);
-    }
-
-    public synchronized void shutdown() {
-        mExecutor.shutdown();
-    }
-
-    @VisibleForTesting
-    synchronized void advance(long durationMillis) {
-        mCurrentTimeMillis += durationMillis;
-        while (mRunnables.size() > 0) {
-            ScheduledRunnable r = mRunnables.get(0);
-            if (r.mTimeMillis <= mCurrentTimeMillis) {
-                mRunnables.remove(0);
-                mExecutor.execute(r.mRunnable);
-            } else {
-                break;
-            }
-        }
-    }
-
-    private synchronized void addRunnables(ScheduledRunnable r) {
-        int index = 0;
-        while (index < mRunnables.size() && mRunnables.get(index).mTimeMillis <= r.mTimeMillis) {
-            index++;
-        }
-        mRunnables.add(index, r);
-    }
-
-    @VisibleForTesting
-    static synchronized boolean isComplete() {
-        return sTotalRunnables == 0;
-    }
-
-    // Can only be called by DeviceShadowEnvironmentImpl when reset.
-    public static synchronized void clear() {
-        sTotalRunnables = 0;
-    }
-
-    class ScheduledRunnable {
-
-        final NamedRunnable mRunnable;
-        final long mTimeMillis;
-
-        ScheduledRunnable(final NamedRunnable r, long timeMillis) {
-            this.mTimeMillis = timeMillis;
-            this.mRunnable =
-                    NamedRunnable.create(
-                            r.toString(),
-                            () -> {
-                                synchronized (Scheduler.this) {
-                                    Scheduler.this.mRunningRunnables.add(ScheduledRunnable.this);
-                                }
-
-                                try {
-                                    r.run();
-                                } catch (Exception e) {
-                                    LOGGER.e("Error in scheduler runnable " + r, e);
-                                    DeviceShadowEnvironmentImpl.catchInternalException(e);
-                                }
-
-                                synchronized (Scheduler.this) {
-                                    // Remove the last one.
-                                    Scheduler.this.mRunningRunnables.remove(
-                                            Scheduler.this.mRunningRunnables.size() - 1);
-                                }
-
-                                // If this is last runnable,
-                                // When this section runs before await:
-                                //   totalRunnable will be 0, await will return directly.
-                                // When this section runs after await:
-                                //   latch will not be null, count down will terminate await.
-
-                                // TODO(b/200231384): when there are two threads running at same
-                                // time, there will be a case when totalRunnable is 0, but another
-                                // thread pending to acquire Scheduler.class lock to post a
-                                // runnable. Hence, await here might not be correct in this case.
-                                synchronized (Scheduler.class) {
-                                    sTotalRunnables--;
-                                    if (isComplete()) {
-                                        if (sCompleteLatch != null) {
-                                            sCompleteLatch.countDown();
-                                        }
-                                    }
-                                }
-                            });
-        }
-
-        @Override
-        public String toString() {
-            return mRunnable.toString();
-        }
-    }
-
-    @Override
-    public synchronized String toString() {
-        return String.format(
-                "\t%d scheduled runnables %s\n\t%d still running or aborted %s",
-                mRunnables.size(), mRunnables, mRunningRunnables.size(), mRunningRunnables);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/INfcAdapterImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/INfcAdapterImpl.java
deleted file mode 100644
index 01dcac2..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/INfcAdapterImpl.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.nfc;
-
-import android.nfc.IAppCallback;
-import android.nfc.INfcAdapter;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-
-/**
- * Implementation of INfcAdapter
- */
-public class INfcAdapterImpl implements INfcAdapter {
-
-    public INfcAdapterImpl() {
-    }
-
-    @Override
-    public void setAppCallback(IAppCallback callback) {
-        DeviceShadowEnvironmentImpl.getLocalNfcletImpl().mAppCallback = callback;
-    }
-
-    @Override
-    public boolean enable() {
-        return DeviceShadowEnvironmentImpl.getLocalNfcletImpl().enable();
-    }
-
-    @Override
-    public boolean disable(boolean saveState) {
-        // We do not need to save state because test only run once.
-        return DeviceShadowEnvironmentImpl.getLocalNfcletImpl().disable();
-    }
-
-    @Override
-    public int getState() {
-        return DeviceShadowEnvironmentImpl.getLocalNfcletImpl().getState();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/NfcletImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/NfcletImpl.java
deleted file mode 100644
index 137f6b8..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/nfc/NfcletImpl.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.nfc;
-
-import android.content.Intent;
-import android.nfc.BeamShareData;
-import android.nfc.IAppCallback;
-import android.nfc.NdefMessage;
-import android.nfc.NfcAdapter;
-
-import com.android.libraries.testing.deviceshadower.Enums.NfcOperation;
-import com.android.libraries.testing.deviceshadower.Nfclet;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.common.Interrupter;
-import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Implementation of Nfclet.
- */
-public class NfcletImpl implements Nfclet {
-
-    private static final Logger LOGGER = Logger.create("NfcletImpl");
-
-    IAppCallback mAppCallback;
-    private final Interrupter mInterrupter;
-
-    @GuardedBy("this")
-    private int mCurrentState;
-
-    public NfcletImpl() {
-        mInterrupter = new Interrupter();
-        mCurrentState = NfcAdapter.STATE_OFF;
-    }
-
-    public void onNear(NfcletImpl remote) {
-        if (remote.mAppCallback != null) {
-            LOGGER.v("NFC receiver get beam share data from remote");
-            BeamShareData data = remote.mAppCallback.createBeamShareData();
-            DeviceShadowEnvironmentImpl.getLocalDeviceletImpl().getBroadcastManager()
-                    .sendBroadcast(createNdefDiscoveredIntent(data), null);
-        }
-        if (mAppCallback != null) {
-            LOGGER.v("NFC sender onNdefPushComplete");
-            mAppCallback.onNdefPushComplete();
-        }
-    }
-
-    public synchronized int getState() {
-        return mCurrentState;
-    }
-
-    public boolean enable() {
-        if (shouldInterrupt(NfcOperation.ENABLE)) {
-            return false;
-        }
-        LOGGER.v("Enable NFC Adapter");
-        updateState(NfcAdapter.STATE_TURNING_ON);
-        updateState(NfcAdapter.STATE_ON);
-        return true;
-    }
-
-    public boolean disable() {
-        if (shouldInterrupt(NfcOperation.DISABLE)) {
-            return false;
-        }
-        LOGGER.v("Disable NFC Adapter");
-        updateState(NfcAdapter.STATE_TURNING_OFF);
-        updateState(NfcAdapter.STATE_OFF);
-        return true;
-    }
-
-    @Override
-    public synchronized Nfclet setInitialState(int state) {
-        mCurrentState = state;
-        return this;
-    }
-
-    @Override
-    public Nfclet setInterruptOperation(NfcOperation operation) {
-        mInterrupter.addInterruptOperation(operation);
-        return this;
-    }
-
-    public boolean shouldInterrupt(NfcOperation operation) {
-        return mInterrupter.shouldInterrupt(operation);
-    }
-
-    private synchronized void updateState(int state) {
-        if (mCurrentState != state) {
-            mCurrentState = state;
-            DeviceShadowEnvironmentImpl.getLocalDeviceletImpl().getBroadcastManager()
-                    .sendBroadcast(createAdapterStateChangedIntent(state), null);
-        }
-    }
-
-    private Intent createAdapterStateChangedIntent(int state) {
-        Intent intent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
-        intent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, state);
-        return intent;
-    }
-
-    private Intent createNdefDiscoveredIntent(BeamShareData data) {
-        Intent intent = new Intent();
-        intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
-        intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{data.ndefMessage});
-        // TODO(b/200231384): uncomment when uri and mime type implemented.
-        // ndefUri = message.getRecords()[0].toUri();
-        // ndefMimeType = message.getRecords()[0].toMimeType();
-        return intent;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsContentProvider.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsContentProvider.java
deleted file mode 100644
index 6bc535b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsContentProvider.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.sms;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-
-/**
- * Content provider for SMS query.
- */
-public class SmsContentProvider extends ContentProvider {
-
-    public SmsContentProvider() {
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public Cursor query(
-            Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        return DeviceShadowEnvironmentImpl.getLocalSmsletImpl().getCursor(uri);
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsletImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsletImpl.java
deleted file mode 100644
index 00a581e..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/sms/SmsletImpl.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.sms;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.Telephony;
-
-import com.android.libraries.testing.deviceshadower.Smslet;
-import com.android.libraries.testing.deviceshadower.internal.common.ContentDatabase;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Implementation of SMS functionality.
- */
-public class SmsletImpl implements Smslet {
-
-    private final Map<Uri, ContentDatabase> mUriToDataMap;
-
-    public SmsletImpl() {
-        mUriToDataMap = new HashMap<>();
-        mUriToDataMap.put(
-                Telephony.Sms.Inbox.CONTENT_URI, new ContentDatabase(Telephony.Sms.Inbox.BODY));
-        mUriToDataMap.put(Telephony.Sms.Sent.CONTENT_URI,
-                new ContentDatabase(Telephony.Sms.Inbox.BODY));
-        // TODO(b/200231384): implement Outbox, Intents, Conversations.
-    }
-
-    @Override
-    public Smslet addSms(Uri contentUri, String body) {
-        mUriToDataMap.get(contentUri).addData(body);
-        return this;
-    }
-
-    public Cursor getCursor(Uri uri) {
-        return mUriToDataMap.get(uri).getCursor();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/GattHelper.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/GattHelper.java
deleted file mode 100644
index f45b125..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/GattHelper.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.utils;
-
-import android.bluetooth.le.AdvertiseData;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BluetoothConstants;
-
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.UUID;
-
-/**
- * Helper class for Gatt functionality.
- */
-public class GattHelper {
-
-    public static byte[] convertAdvertiseData(
-            AdvertiseData data, int txPowerLevel, String localName, boolean isConnectable) {
-        if (data == null) {
-            return new byte[0];
-        }
-        ByteArrayDataOutput result = ByteStreams.newDataOutput();
-        if (isConnectable) {
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_FLAGS,
-                    new byte[]{BluetoothConstants.FLAGS_IN_CONNECTABLE_PACKETS});
-        }
-        // tx power level is signed 8-bit int, range -100 to 20.
-        if (data.getIncludeTxPowerLevel()) {
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_TX_POWER_LEVEL,
-                    new byte[]{(byte) txPowerLevel});
-        }
-        // Local name
-        if (data.getIncludeDeviceName()) {
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_LOCAL_NAME_COMPLETE,
-                    localName.getBytes(Charset.defaultCharset()));
-        }
-        // Manufacturer data
-        SparseArray<byte[]> manufacturerData = data.getManufacturerSpecificData();
-        for (int i = 0; i < manufacturerData.size(); i++) {
-            int manufacturerId = manufacturerData.keyAt(i);
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_MANUFACTURER_SPECIFIC_DATA,
-                    parseManufacturerData(manufacturerId, manufacturerData.get(manufacturerId))
-            );
-        }
-        // Service data
-        Map<ParcelUuid, byte[]> serviceData = data.getServiceData();
-        for (Entry<ParcelUuid, byte[]> entry : serviceData.entrySet()) {
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_SERVICE_DATA,
-                    parseServiceData(entry.getKey().getUuid(), entry.getValue())
-            );
-        }
-        // Service UUID, 128-bit UUID in little endian
-        if (data.getServiceUuids() != null && !data.getServiceUuids().isEmpty()) {
-            ByteBuffer uuidBytes =
-                    ByteBuffer.allocate(data.getServiceUuids().size() * 16)
-                            .order(ByteOrder.LITTLE_ENDIAN);
-            for (ParcelUuid parcelUuid : data.getServiceUuids()) {
-                UUID uuid = parcelUuid.getUuid();
-                uuidBytes.putLong(uuid.getLeastSignificantBits())
-                        .putLong(uuid.getMostSignificantBits());
-            }
-            writeDataUnit(
-                    result,
-                    BluetoothConstants.DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE,
-                    uuidBytes.array()
-            );
-        }
-        return result.toByteArray();
-    }
-
-    private static byte[] parseServiceData(UUID uuid, byte[] serviceData) {
-        // First two bytes of the data are data UUID in little endian
-        int length = 2 + serviceData.length;
-        byte[] result = new byte[length];
-        // extract 16-bit UUID value
-        int uuidValue = (int) ((uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32);
-        result[0] = (byte) (uuidValue & 0xFF);
-        result[1] = (byte) ((uuidValue >> 8) & 0xFF);
-        System.arraycopy(serviceData, 0, result, 2, serviceData.length);
-        return result;
-
-    }
-
-    private static byte[] parseManufacturerData(int manufacturerId, byte[] manufacturerData) {
-        // First two bytes are manufacturer id in little endian.
-        int length = 2 + manufacturerData.length;
-        byte[] result = new byte[length];
-        result[0] = (byte) (manufacturerId & 0xFF);
-        result[1] = (byte) ((manufacturerId >> 8) & 0xFF);
-        System.arraycopy(manufacturerData, 0, result, 2, manufacturerData.length);
-        return result;
-    }
-
-    private static void writeDataUnit(ByteArrayDataOutput output, int type, byte[] data) {
-        // Length includes the length of the field type, which is 1 byte.
-        int length = 1 + data.length;
-        // Length and type are unsigned 8-bit int. Assume the values are valid.
-        output.write(length);
-        output.write(type);
-        output.write(data);
-    }
-
-    private GattHelper() {
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/Logger.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/Logger.java
deleted file mode 100644
index 31f7202..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/Logger.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.utils;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Logger class to provide formatted log for Device Shadower.
- *
- * <p>Log is formatted as "[TAG] [Keyword1, Keyword2 ...] Log Message Body".</p>
- */
-public class Logger {
-
-    private static final String TAG = "DeviceShadower";
-
-    private final String mTag;
-    private final String mPrefix;
-
-    public Logger(String tag, String... keywords) {
-        mTag = tag;
-        mPrefix = buildPrefix(keywords);
-    }
-
-    public static Logger create(String... keywords) {
-        return new Logger(TAG, keywords);
-    }
-
-    private static String buildPrefix(String... keywords) {
-        if (keywords.length == 0) {
-            return "";
-        }
-        return String.format(" [%s] ", TextUtils.join(", ", keywords));
-    }
-
-    /**
-     * @see Log#e(String, String)
-     */
-    public void e(String msg) {
-        Log.e(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#e(String, String, Throwable)
-     */
-    public void e(String msg, Throwable throwable) {
-        Log.e(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#d(String, String)
-     */
-    public void d(String msg) {
-        Log.d(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#d(String, String, Throwable)
-     */
-    public void d(String msg, Throwable throwable) {
-        Log.d(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#i(String, String)
-     */
-    public void i(String msg) {
-        Log.i(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#i(String, String, Throwable)
-     */
-    public void i(String msg, Throwable throwable) {
-        Log.i(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#v(String, String)
-     */
-    public void v(String msg) {
-        Log.v(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#v(String, String, Throwable)
-     */
-    public void v(String msg, Throwable throwable) {
-        Log.v(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#w(String, String)
-     */
-    public void w(String msg) {
-        Log.w(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#w(String, Throwable)
-     */
-    public void w(Throwable throwable) {
-        Log.w(mTag, null, throwable);
-    }
-
-    /**
-     * @see Log#w(String, String, Throwable)
-     */
-    public void w(String msg, Throwable throwable) {
-        Log.w(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#wtf(String, String)
-     */
-    public void wtf(String msg) {
-        Log.wtf(mTag, format(msg));
-    }
-
-    /**
-     * @see Log#wtf(String, String, Throwable)
-     */
-    public void wtf(String msg, Throwable throwable) {
-        Log.wtf(mTag, format(msg), throwable);
-    }
-
-    /**
-     * @see Log#isLoggable(String, int)
-     */
-    public boolean isLoggable(int level) {
-        return Log.isLoggable(mTag, level);
-    }
-
-    /**
-     * @see Log#println(int, String, String)
-     */
-    public int println(int priority, String msg) {
-        return Log.println(priority, mTag, format(msg));
-    }
-
-    private String format(String msg) {
-        return mPrefix + msg;
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/MacAddressGenerator.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/MacAddressGenerator.java
deleted file mode 100644
index f8d3193..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/internal/utils/MacAddressGenerator.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.internal.utils;
-
-import android.bluetooth.BluetoothAdapter;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Locale;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * A class which generates and converts valid Bluetooth MAC addresses.
- */
-public class MacAddressGenerator {
-
-    @GuardedBy("MacAddressGenerator.class")
-    private static MacAddressGenerator sInstance = new MacAddressGenerator();
-
-    @VisibleForTesting
-    public static synchronized void setInstanceForTest(MacAddressGenerator generator) {
-        sInstance = generator;
-    }
-
-    public static synchronized MacAddressGenerator get() {
-        return sInstance;
-    }
-
-    private long mLastAddress = 0x0L;
-
-    private MacAddressGenerator() {
-    }
-
-    public String generateMacAddress() {
-        byte[] bytes = generateMacAddressBytes();
-        return convertByteMacAddress(bytes);
-    }
-
-    public byte[] generateMacAddressBytes() {
-        long addr = mLastAddress++;
-        byte[] bytes = new byte[6];
-        for (int i = 5; i >= 0; i--) {
-            bytes[i] = (byte) (addr & 0xFF);
-            addr = addr >> 8;
-        }
-        return bytes;
-    }
-
-    public static byte[] convertStringMacAddress(String address) {
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            throw new IllegalArgumentException("Not a valid bluetooth mac hex string: " + address);
-        }
-        byte[] bytes = new byte[6];
-        String[] macValues = address.split(":");
-        for (int i = 0; i < bytes.length; i++) {
-            bytes[i] = Integer.decode("0x" + macValues[i]).byteValue();
-        }
-        return bytes;
-    }
-
-    public static String convertByteMacAddress(byte[] address) {
-        if (address == null || address.length != 6) {
-            throw new IllegalArgumentException("Bluetooth address must have 6 bytes");
-        }
-        return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
-                address[0], address[1], address[2], address[3], address[4], address[5]);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothA2dp.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothA2dp.java
deleted file mode 100644
index 344103b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothA2dp.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import static com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl.getBlueletImpl;
-import static com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl.getLocalBlueletImpl;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Shadow of the Bluetooth A2DP service.
- */
-@Implements(BluetoothA2dp.class)
-public class ShadowBluetoothA2dp {
-
-    /**
-     * Hidden in {@link BluetoothProfile}.
-     */
-    public static final int A2DP_SINK = 11;
-
-    private final Map<BluetoothDevice, Integer> mDeviceToConnectionState = new HashMap<>();
-    private Context mContext;
-    @RealObject
-    private BluetoothA2dp mRealObject;
-
-    public void __constructor__(Context context, ServiceListener l) {
-        this.mContext = context;
-        l.onServiceConnected(BluetoothProfile.A2DP, mRealObject);
-    }
-
-    @Implementation
-    public List<BluetoothDevice> getConnectedDevices() {
-        List<BluetoothDevice> result = new ArrayList<>();
-        for (BluetoothDevice device : mDeviceToConnectionState.keySet()) {
-            if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
-                result.add(device);
-            }
-        }
-        return result;
-    }
-
-    @Implementation
-    public int getConnectionState(BluetoothDevice device) {
-        return mDeviceToConnectionState.containsKey(device)
-                ? mDeviceToConnectionState.get(device)
-                : BluetoothProfile.STATE_DISCONNECTED;
-    }
-
-    @Implementation
-    public boolean connect(BluetoothDevice device) {
-        setConnectionState(BluetoothProfile.STATE_CONNECTING, device);
-        // Only successfully connect if the device is in the environment (i.e. nearby) and accepts
-        // connections.
-        BlueletImpl blueLet = getBlueletImpl(device.getAddress());
-        if (blueLet != null && !blueLet.getRefuseConnections()) {
-            setConnectionState(BluetoothProfile.STATE_CONNECTED, device);
-        } else {
-            // If the device isn't in the environment, still return true (no immediate failure, i.e.
-            // we're trying to connect) but send CONNECTING -> DISCONNECTED (like the OS does).
-            setConnectionState(BluetoothProfile.STATE_DISCONNECTED, device);
-        }
-        return true;
-    }
-
-    @Implementation
-    public void close() {
-    }
-
-    private void setConnectionState(int state, BluetoothDevice device) {
-        int previousState = getConnectionState(device);
-        mDeviceToConnectionState.put(device, state);
-
-        getLocalBlueletImpl()
-                .setProfileConnectionState(BluetoothProfile.A2DP, state, device.getAddress());
-        BlueletImpl remoteDevice = getBlueletImpl(device.getAddress());
-        if (remoteDevice != null) {
-            remoteDevice.setProfileConnectionState(A2DP_SINK, state, getLocalBlueletImpl().address);
-        }
-
-        mContext.sendBroadcast(
-                new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)
-                        .putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, previousState)
-                        .putExtra(BluetoothProfile.EXTRA_STATE, state)
-                        .putExtra(BluetoothDevice.EXTRA_DEVICE, device));
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothAdapter.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothAdapter.java
deleted file mode 100644
index 394afbc..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothAdapter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.AttributionSource;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BlueletImpl;
-import com.android.libraries.testing.deviceshadower.internal.utils.MacAddressGenerator;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow of {@link BluetoothAdapter} to be used with Device Shadower in Robolectric test.
- */
-@Implements(BluetoothAdapter.class)
-public class ShadowBluetoothAdapter {
-
-    @RealObject
-    BluetoothAdapter mRealAdapter;
-
-    public ShadowBluetoothAdapter() {
-    }
-
-    @Implementation
-    public static synchronized BluetoothAdapter getDefaultAdapter() {
-        // Add a device and set local devicelet in case no local bluelet set
-        if (!DeviceShadowEnvironmentImpl.hasLocalDeviceletImpl()) {
-            String address = MacAddressGenerator.get().generateMacAddress();
-            DeviceShadowEnvironmentImpl.addDevice(address);
-            DeviceShadowEnvironmentImpl.setLocalDevice(address);
-        }
-        BlueletImpl localBluelet = DeviceShadowEnvironmentImpl.getLocalBlueletImpl();
-        return localBluelet.getAdapter();
-    }
-
-    @Implementation
-    public static BluetoothAdapter createAdapter(AttributionSource attributionSource) {
-        // Add a device and set local devicelet in case no local bluelet set
-        if (!DeviceShadowEnvironmentImpl.hasLocalDeviceletImpl()) {
-            String address = MacAddressGenerator.get().generateMacAddress();
-            DeviceShadowEnvironmentImpl.addDevice(address);
-            DeviceShadowEnvironmentImpl.setLocalDevice(address);
-        }
-        BlueletImpl localBluelet = DeviceShadowEnvironmentImpl.getLocalBlueletImpl();
-        return localBluelet.getAdapter();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothDevice.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothDevice.java
deleted file mode 100644
index 247f46e..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothDevice.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.IBluetoothImpl;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Placeholder for BluetoothDevice improvements
- */
-@Implements(BluetoothDevice.class)
-public class ShadowBluetoothDevice {
-
-    @RealObject
-    private BluetoothDevice mBluetoothDevice;
-    private static final Map<String, Integer> sBondTransport = new HashMap<>();
-    private static Map<String, Boolean> sPairingConfirmation = new HashMap<>();
-
-    public ShadowBluetoothDevice() {
-    }
-
-    @Implementation
-    public boolean setPasskey(int passkey) {
-        return new IBluetoothImpl().setPasskey(mBluetoothDevice, passkey);
-    }
-
-    @Implementation
-    public boolean createBond(int transport) {
-        sBondTransport.put(mBluetoothDevice.getAddress(), transport);
-        return Shadow.directlyOn(
-                mBluetoothDevice,
-                BluetoothDevice.class,
-                "createBond",
-                ClassParameter.from(int.class, transport));
-    }
-
-    public static int getBondTransport(String address) {
-        return sBondTransport.containsKey(address)
-                ? sBondTransport.get(address)
-                : BluetoothDevice.TRANSPORT_AUTO;
-    }
-
-    @Implementation
-    public boolean setPairingConfirmation(boolean confirm) {
-        sPairingConfirmation.put(mBluetoothDevice.getAddress(), confirm);
-        return Shadow.directlyOn(
-                mBluetoothDevice,
-                BluetoothDevice.class,
-                "setPairingConfirmation",
-                ClassParameter.from(boolean.class, confirm));
-    }
-
-    /**
-     * Gets the confirmation value previously set with a call to {@link
-     * BluetoothDevice#setPairingConfirmation(boolean)}. Default is false.
-     */
-    public static boolean getPairingConfirmation(String address) {
-        return sPairingConfirmation.containsKey(address) && sPairingConfirmation.get(address);
-    }
-
-    /**
-     * Resets the confirmation values.
-     */
-    public static void resetPairingConfirmation() {
-        sPairingConfirmation = new HashMap<>();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothLeScanner.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothLeScanner.java
deleted file mode 100644
index 1f7da14..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothLeScanner.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.bluetooth.le.BluetoothLeScanner;
-
-import org.robolectric.annotation.Implements;
-
-/**
- * Shadow of {@link BluetoothLeScanner} to be used with Device Shadower in Robolectric test.
- */
-@Implements(BluetoothLeScanner.class)
-public class ShadowBluetoothLeScanner {
-
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothServerSocket.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothServerSocket.java
deleted file mode 100644
index bffcf32..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothServerSocket.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.bluetooth.BluetoothServerSocket;
-import android.bluetooth.BluetoothSocket;
-import android.net.LocalSocket;
-import android.os.ParcelFileDescriptor;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.RfcommDelegate;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Placeholder for BluetoothServerSocket updates
- */
-@Implements(BluetoothServerSocket.class)
-public class ShadowBluetoothServerSocket {
-
-    @RealObject
-    BluetoothServerSocket mRealServerSocket;
-
-    public ShadowBluetoothServerSocket() {
-    }
-
-    @Implementation
-    public BluetoothSocket accept(int timeout) throws IOException {
-        FileDescriptor serverSocketFd = getServerSocketFileDescriptor();
-        if (serverSocketFd == null) {
-            throw new IOException("socket is closed.");
-        }
-        RfcommDelegate local = getLocalRfcommDelegate();
-        local.checkInterrupt();
-        FileDescriptor clientFd = local.processNextConnectionRequest(serverSocketFd);
-        // configure the LocalSocket of the BluetoothServerSocket
-        BluetoothSocket internalSocket = ReflectionHelpers.getField(mRealServerSocket, "mSocket");
-        ShadowLocalSocket internalLocalSocket = getLocalSocketShadow(internalSocket);
-        internalLocalSocket.setAncillaryFd(local.getServerFd(clientFd));
-
-        // call original method
-        BluetoothSocket socket = Shadow.directlyOn(mRealServerSocket, BluetoothServerSocket.class,
-                "accept", ClassParameter.from(int.class, timeout));
-
-        // setup local socket of the returned BluetoothSocket
-        String remoteAddress = socket.getRemoteDevice().getAddress();
-        ShadowLocalSocket shadowLocalSocket = getLocalSocketShadow(socket);
-        shadowLocalSocket.setRemoteAddress(remoteAddress);
-        // init connection to client
-        local.initiateConnectToClient(clientFd, getPort());
-        local.waitForConnectionEstablished(clientFd);
-        return socket;
-    }
-
-    @Implementation
-    public void close() throws IOException {
-        getLocalRfcommDelegate().closeServerSocket(getServerSocketFileDescriptor());
-        Shadow.directlyOn(mRealServerSocket, BluetoothServerSocket.class, "close");
-    }
-
-    @VisibleForTesting
-    FileDescriptor getServerSocketFileDescriptor() {
-        BluetoothSocket socket = ReflectionHelpers.getField(mRealServerSocket, "mSocket");
-        ParcelFileDescriptor pfd = ReflectionHelpers.getField(socket, "mPfd");
-        if (pfd == null) {
-            return null;
-        }
-        return pfd.getFileDescriptor();
-    }
-
-    @VisibleForTesting
-    int getPort() {
-        BluetoothSocket socket = ReflectionHelpers.getField(mRealServerSocket, "mSocket");
-        return ReflectionHelpers.getField(socket, "mPort");
-    }
-
-    private ShadowLocalSocket getLocalSocketShadow(BluetoothSocket socket) {
-        LocalSocket localSocket = ReflectionHelpers.getField(socket, "mSocket");
-        return (ShadowLocalSocket) Shadow.extract(localSocket);
-    }
-
-    private RfcommDelegate getLocalRfcommDelegate() {
-        return DeviceShadowEnvironmentImpl.getLocalBlueletImpl().getRfcommDelegate();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothSocket.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothSocket.java
deleted file mode 100644
index 5d417cf..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowBluetoothSocket.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.bluetooth.BluetoothSocket;
-import android.os.ParcelFileDescriptor;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.RfcommDelegate;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Shadow implementation of a Bluetooth Socket
- */
-@Implements(BluetoothSocket.class)
-public class ShadowBluetoothSocket {
-
-    @RealObject
-    BluetoothSocket mRealSocket;
-
-    public ShadowBluetoothSocket() {
-    }
-
-    @Implementation
-    public void connect() throws IOException {
-        Shadow.directlyOn(mRealSocket, BluetoothSocket.class, "connect");
-
-        boolean isEncrypted = ReflectionHelpers.getField(mRealSocket, "mEncrypt");
-        FileDescriptor localFd =
-                ((ParcelFileDescriptor) ReflectionHelpers.getField(mRealSocket,
-                        "mPfd")).getFileDescriptor();
-        RfcommDelegate local = DeviceShadowEnvironmentImpl.getLocalBlueletImpl()
-                .getRfcommDelegate();
-        String remoteAddress = mRealSocket.getRemoteDevice().getAddress();
-        local.finishPendingConnection(remoteAddress, localFd, isEncrypted);
-
-        ShadowLocalSocket shadowLocalSocket = getLocalSocketShadow();
-        shadowLocalSocket.setRemoteAddress(remoteAddress);
-    }
-
-    @Implementation
-    public InputStream getInputStream() throws IOException {
-        ShadowLocalSocket socket = getLocalSocketShadow();
-        return socket.getInputStream();
-    }
-
-    @Implementation
-    public OutputStream getOutputStream() throws IOException {
-        ShadowLocalSocket socket = getLocalSocketShadow();
-        return socket.getOutputStream();
-    }
-
-    private ShadowLocalSocket getLocalSocketShadow() throws IOException {
-        try {
-            return (ShadowLocalSocket) Shadow.extract(
-                    ReflectionHelpers.getField(mRealSocket, "mSocket"));
-        } catch (NullPointerException e) {
-            throw new IOException(e);
-        }
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowLocalSocket.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowLocalSocket.java
deleted file mode 100644
index 5189330..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowLocalSocket.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.net.LocalSocket;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.BluetoothConstants;
-import com.android.libraries.testing.deviceshadower.internal.bluetooth.connection.RfcommDelegate;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Shadow implementation of a LocalSocket to make bluetooth connections function.
- */
-@Implements(LocalSocket.class)
-public class ShadowLocalSocket {
-
-    private String mRemoteAddress;
-    private FileDescriptor mFd;
-    private FileDescriptor mAncillaryFd;
-
-    public ShadowLocalSocket() {
-    }
-
-    public void __constructor__(FileDescriptor fd) {
-        this.mFd = fd;
-    }
-
-    @Implementation
-    public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
-        return new FileDescriptor[]{mAncillaryFd};
-    }
-
-    @Implementation
-    @SuppressWarnings("InputStreamSlowMultibyteRead")
-    public InputStream getInputStream() throws IOException {
-        final RfcommDelegate local = getLocalRfcommDelegate();
-        return new InputStream() {
-            @Override
-            public int read() throws IOException {
-                int res = local.read(mRemoteAddress, mFd);
-                if (res == BluetoothConstants.SOCKET_CLOSE) {
-                    throw new IOException("closed");
-                }
-                return res & 0xFF;
-            }
-        };
-    }
-
-    @Implementation
-    public OutputStream getOutputStream() throws IOException {
-        final RfcommDelegate local = getLocalRfcommDelegate();
-        return new OutputStream() {
-            @Override
-            public void write(int b) throws IOException {
-                local.write(mRemoteAddress, mFd, b);
-            }
-        };
-    }
-
-    @Implementation
-    public void setSoTimeout(int n) throws IOException {
-        // Nothing
-    }
-
-    @Implementation
-    public void shutdownInput() throws IOException {
-        getLocalRfcommDelegate().shutdownInput(mRemoteAddress, mFd);
-    }
-
-    @Implementation
-    public void shutdownOutput() throws IOException {
-        if (mRemoteAddress == null) {
-            return;
-        }
-        getLocalRfcommDelegate().shutdownOutput(mRemoteAddress, mFd);
-    }
-
-    void setAncillaryFd(FileDescriptor fd) {
-        mAncillaryFd = fd;
-    }
-
-    void setRemoteAddress(String address) {
-        mRemoteAddress = address;
-    }
-
-    @VisibleForTesting
-    void setFileDescriptorForTest(FileDescriptor fd) {
-        this.mFd = fd;
-    }
-
-    private RfcommDelegate getLocalRfcommDelegate() {
-        return DeviceShadowEnvironmentImpl.getLocalBlueletImpl().getRfcommDelegate();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowParcelFileDescriptor.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowParcelFileDescriptor.java
deleted file mode 100644
index 585939b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/bluetooth/ShadowParcelFileDescriptor.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.bluetooth;
-
-import android.os.ParcelFileDescriptor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Inert implementation of a ParcelFileDescriptor to make bluetooth connections function.
- */
-@Implements(ParcelFileDescriptor.class)
-public class ShadowParcelFileDescriptor {
-
-    private FileDescriptor mFd;
-
-    public ShadowParcelFileDescriptor() {
-    }
-
-    public void __constructor__(FileDescriptor fd) {
-        this.mFd = fd;
-    }
-
-    @Implementation
-    public FileDescriptor getFileDescriptor() {
-        return mFd;
-    }
-
-    @Implementation
-    public void close() throws IOException {
-        // Nothing
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/common/DeviceShadowContextImpl.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/common/DeviceShadowContextImpl.java
deleted file mode 100644
index 9bbcee7..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/common/DeviceShadowContextImpl.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.common;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.DeviceletImpl;
-import com.android.libraries.testing.deviceshadower.internal.common.BroadcastManager;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadows.ShadowContextImpl;
-
-import javax.annotation.Nullable;
-
-/**
- * Extends {@link ShadowContextImpl} to achieve automatic method redirection to correct virtual
- * device.
- *
- * <p>Supports:
- * <li>Broadcasting</li>
- * Includes send regular, regular sticky, ordered broadcast, and register/unregister receiver.
- * </p>
- */
-@Implements(className = "android.app.ContextImpl")
-public class DeviceShadowContextImpl extends ShadowContextImpl {
-
-    private static final String TAG = "DeviceShadowContextImpl";
-
-    @RealObject
-    private Context mContextImpl;
-
-    @Override
-    @Implementation
-    @Nullable
-    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        if (receiver == null) {
-            return null;
-        }
-        BroadcastManager manager = getLocalBroadcastManager();
-        if (manager == null) {
-            Log.w(TAG, "Receiver registered before any devices added: " + receiver);
-            return null;
-        }
-        return manager.registerReceiver(
-                receiver, filter, null /* permission */, null /* handler */, mContextImpl);
-    }
-
-    @Override
-    @Implementation
-    @Nullable
-    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
-            @Nullable String broadcastPermission, @Nullable Handler scheduler) {
-        return getLocalBroadcastManager().registerReceiver(
-                receiver, filter, broadcastPermission, scheduler, mContextImpl);
-    }
-
-    @Override
-    @Implementation
-    public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
-        getLocalBroadcastManager().unregisterReceiver(broadcastReceiver);
-    }
-
-    @Override
-    @Implementation
-    public void sendBroadcast(Intent intent) {
-        getLocalBroadcastManager().sendBroadcast(intent, null /* permission */);
-    }
-
-    @Override
-    @Implementation
-    public void sendBroadcast(Intent intent, @Nullable String receiverPermission) {
-        getLocalBroadcastManager().sendBroadcast(intent, receiverPermission);
-    }
-
-    @Override
-    @Implementation
-    public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission) {
-        getLocalBroadcastManager().sendOrderedBroadcast(intent, receiverPermission);
-    }
-
-    @Override
-    @Implementation
-    public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission,
-            @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
-            int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
-        getLocalBroadcastManager().sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
-                scheduler, initialCode, initialData, initialExtras, mContextImpl);
-    }
-
-    @Override
-    @Implementation
-    public void sendStickyBroadcast(Intent intent) {
-        getLocalBroadcastManager().sendStickyBroadcast(intent);
-    }
-
-    private BroadcastManager getLocalBroadcastManager() {
-        DeviceletImpl devicelet = DeviceShadowEnvironmentImpl.getLocalDeviceletImpl();
-        if (devicelet == null) {
-            return null;
-        }
-        return devicelet.getBroadcastManager();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/nfc/ShadowNfcAdapter.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/nfc/ShadowNfcAdapter.java
deleted file mode 100644
index e7112fb..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/shadows/nfc/ShadowNfcAdapter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.shadows.nfc;
-
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.content.Context;
-import android.nfc.NfcAdapter;
-
-import com.android.libraries.testing.deviceshadower.Enums.NfcOperation;
-import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
-import com.android.libraries.testing.deviceshadower.internal.nfc.INfcAdapterImpl;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-/**
- * Shadow implementation of Nfc Adapter.
- */
-@Implements(NfcAdapter.class)
-public class ShadowNfcAdapter {
-
-    @Implementation
-    public static NfcAdapter getDefaultAdapter(Context context) {
-        if (DeviceShadowEnvironmentImpl.getLocalNfcletImpl()
-                .shouldInterrupt(NfcOperation.GET_ADAPTER)) {
-            return null;
-        }
-        ReflectionHelpers.setStaticField(NfcAdapter.class, "sService", new INfcAdapterImpl());
-        return callConstructor(NfcAdapter.class, ClassParameter.from(Context.class, context));
-    }
-
-    // TODO(b/200231384): support state change.
-    public ShadowNfcAdapter() {
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BaseTestCase.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BaseTestCase.java
deleted file mode 100644
index 8a3c0e7..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BaseTestCase.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.testcases;
-
-import android.app.Application;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowLocalSocket;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowParcelFileDescriptor;
-import com.android.libraries.testing.deviceshadower.shadows.common.DeviceShadowContextImpl;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.internal.AssumptionViolatedException;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-/**
- * Base class for all DeviceShadower client.
- */
-@Config(
-        // sdk = 21,
-        shadows = {
-                DeviceShadowContextImpl.class,
-                ShadowParcelFileDescriptor.class,
-                ShadowLocalSocket.class
-        })
-public class BaseTestCase {
-
-    protected Application mContext = RuntimeEnvironment.application;
-
-    /**
-     * Test Watcher which logs test starting and finishing so log messages are easier to read.
-     */
-    @Rule
-    public TestWatcher watcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            super.succeeded(description);
-            logMessage(
-                    String.format("Test %s finished successfully.", description.getDisplayName()));
-        }
-
-        @Override
-        protected void failed(Throwable e, Description description) {
-            super.failed(e, description);
-            logMessage(String.format("Test %s failed.", description.getDisplayName()));
-        }
-
-        @Override
-        protected void skipped(AssumptionViolatedException e, Description description) {
-            super.skipped(e, description);
-            logMessage(String.format("Test %s is skipped.", description.getDisplayName()));
-        }
-
-        @Override
-        protected void starting(Description description) {
-            super.starting(description);
-            logMessage(String.format("Test %s started.", description.getDisplayName()));
-        }
-
-        @Override
-        protected void finished(Description description) {
-            super.finished(description);
-        }
-
-        private void logMessage(String message) {
-            System.out.println("\n*** " + message);
-        }
-    };
-
-    @Before
-    public void setUp() throws Exception {
-        DeviceShadowEnvironment.init();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        DeviceShadowEnvironment.reset();
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BluetoothTestCase.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BluetoothTestCase.java
deleted file mode 100644
index cddc6fe..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/BluetoothTestCase.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.testcases;
-
-import static org.robolectric.Shadows.shadowOf;
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothA2dp;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothAdapter;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothDevice;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothLeScanner;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothServerSocket;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothSocket;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-/**
- * Base class for Bluetooth Test
- */
-@Config(
-        shadows = {
-                ShadowBluetoothAdapter.class,
-                ShadowBluetoothDevice.class,
-                ShadowBluetoothLeScanner.class,
-                ShadowBluetoothSocket.class,
-                ShadowBluetoothServerSocket.class,
-                ShadowBluetoothA2dp.class
-        })
-public class BluetoothTestCase extends BaseTestCase {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        // TODO(b/28087747): Get bluetooth Manager from robolectric framework.
-        shadowOf(RuntimeEnvironment.application)
-                .setSystemService(
-                        Context.BLUETOOTH_SERVICE,
-                        callConstructor(BluetoothManager.class,
-                                ClassParameter.from(Context.class, mContext)));
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/Matchers.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/Matchers.java
deleted file mode 100644
index 3bfe43b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/Matchers.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.testcases;
-
-import static org.mockito.ArgumentMatchers.argThat;
-
-import android.bluetooth.BluetoothSocket;
-
-import org.mockito.ArgumentMatcher;
-
-/**
- * Convenient methods to create mockito matchers.
- */
-public class Matchers {
-
-    private Matchers() {
-    }
-
-    public static <T extends Exception> T exception(final Class<T> clazz, final String... msgs) {
-        return argThat(
-                new ArgumentMatcher<T>() {
-                    @Override
-                    public boolean matches(T obj) {
-                        if (!clazz.isInstance(obj)) {
-                            return false;
-                        }
-                        Throwable exception = clazz.cast(obj);
-                        for (String msg : msgs) {
-                            if (exception == null || !exception.getMessage().contains(msg)) {
-                                return false;
-                            }
-                            exception = exception.getCause();
-                        }
-                        return true;
-                    }
-                });
-    }
-
-    public static BluetoothSocket socket(final String addr) {
-        return argThat(
-                new ArgumentMatcher<BluetoothSocket>() {
-                    @Override
-                    public boolean matches(BluetoothSocket obj) {
-                        return ((BluetoothSocket) obj)
-                                .getRemoteDevice()
-                                .getAddress()
-                                .toUpperCase()
-                                .equals(addr.toUpperCase());
-                    }
-                });
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/NfcTestCase.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/NfcTestCase.java
deleted file mode 100644
index a80164b..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/NfcTestCase.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.testcases;
-
-import com.android.libraries.testing.deviceshadower.shadows.nfc.ShadowNfcAdapter;
-
-import org.robolectric.annotation.Config;
-
-/**
- * Base class for NFC Test
- */
-@Config(shadows = {ShadowNfcAdapter.class})
-public class NfcTestCase extends BaseTestCase {
-
-}
-
diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/SmsTestCase.java b/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/SmsTestCase.java
deleted file mode 100644
index edfcc6d..0000000
--- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/testcases/SmsTestCase.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2021 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.libraries.testing.deviceshadower.testcases;
-
-import android.content.pm.ProviderInfo;
-import android.provider.Telephony;
-
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironmentInternal;
-
-import org.robolectric.Robolectric;
-
-/**
- * Base class for SMS Test
- */
-public class SmsTestCase extends BaseTestCase {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        ProviderInfo info = new ProviderInfo();
-        info.authority = Telephony.Sms.CONTENT_URI.getAuthority();
-        Robolectric.buildContentProvider(
-                        DeviceShadowEnvironmentInternal.getSmsContentProviderClass())
-                .create(info);
-    }
-}
diff --git a/nearby/tests/robotests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java b/nearby/tests/robotests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
deleted file mode 100644
index 1ac2aaf..0000000
--- a/nearby/tests/robotests/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static org.robolectric.Shadows.shadowOf;
-
-import android.Manifest.permission;
-import android.bluetooth.BluetoothAdapter;
-
-import com.android.libraries.testing.deviceshadower.Bluelet.IoCapabilities;
-import com.android.libraries.testing.deviceshadower.DeviceShadowEnvironment;
-import com.android.libraries.testing.deviceshadower.shadows.bluetooth.ShadowBluetoothDevice;
-import com.android.libraries.testing.deviceshadower.testcases.BluetoothTestCase;
-
-import com.google.common.base.VerifyException;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * Tests for {@link BluetoothClassicPairer}.
- */
-@RunWith(RobolectricTestRunner.class)
-public class BluetoothClassicPairerTest extends BluetoothTestCase {
-
-    private static final String LOCAL_DEVICE_ADDRESS = "AA:AA:AA:AA:AA:01";
-
-    /**
-     * The remote device's Bluetooth Classic address.
-     */
-    private static final String REMOTE_DEVICE_PUBLIC_ADDRESS = "BB:BB:BB:BB:BB:0C";
-
-    private Preferences.Builder mPrefsBuilder;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mPrefsBuilder = Preferences.builder().setCreateBondTimeoutSeconds(10);
-
-        ShadowBluetoothDevice.resetPairingConfirmation();
-        shadowOf(mContext)
-                .grantPermissions(
-                        permission.BLUETOOTH, permission.BLUETOOTH_ADMIN,
-                        permission.BLUETOOTH_PRIVILEGED);
-
-        DeviceShadowEnvironment.addDevice(LOCAL_DEVICE_ADDRESS)
-                .bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON)
-                .setIoCapabilities(IoCapabilities.DISPLAY_YES_NO);
-        DeviceShadowEnvironment.addDevice(REMOTE_DEVICE_PUBLIC_ADDRESS)
-                .bluetooth()
-                .setAdapterInitialState(BluetoothAdapter.STATE_ON)
-                .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
-                .setIoCapabilities(IoCapabilities.DISPLAY_YES_NO);
-
-        // By default, code runs as if it's on this virtual "device".
-        DeviceShadowEnvironment.setLocalDevice(LOCAL_DEVICE_ADDRESS);
-    }
-
-    @Test
-    public void pair_setPairingConfirmationTrue_deviceBonded() throws Exception {
-    // TODO(b/217195327): replace deviceshadower with injector.
-    /*
-        AtomicReference<BluetoothDevice> targetRemoteDevice = new AtomicReference<>();
-        BluetoothClassicPairer bluetoothClassicPairer =
-                new BluetoothClassicPairer(
-                        mContext,
-                        BluetoothAdapter.getDefaultAdapter()
-                                .getRemoteDevice(REMOTE_DEVICE_PUBLIC_ADDRESS),
-                        mPrefsBuilder.build(),
-                        (BluetoothDevice remoteDevice, int key) -> {
-                            targetRemoteDevice.set(remoteDevice);
-                            // Confirms at remote device to pair with local one.
-                            setPairingConfirmationAtRemoteDevice(true);
-
-                            // Confirms to pair with remote device.
-                            remoteDevice.setPairingConfirmation(true);
-                        });
-
-        bluetoothClassicPairer.pair();
-
-        assertThat(targetRemoteDevice.get()).isNotNull();
-        assertThat(targetRemoteDevice.get().getAddress()).isEqualTo(REMOTE_DEVICE_PUBLIC_ADDRESS);
-        assertThat(targetRemoteDevice.get().getBondState()).isEqualTo(BluetoothDevice.BOND_BONDED);
-        assertThat(bluetoothClassicPairer.isPaired()).isTrue();
-    */
-    }
-
-    @Test
-    public void pair_setPairingConfirmationFalse_throwsExceptionDeviceNotBonded() throws Exception {
-    // TODO(b/217195327): replace deviceshadower with injector.
-    /*
-        AtomicReference<BluetoothDevice> targetRemoteDevice = new AtomicReference<>();
-        BluetoothClassicPairer bluetoothClassicPairer =
-                new BluetoothClassicPairer(
-                        mContext,
-                        BluetoothAdapter.getDefaultAdapter()
-                                .getRemoteDevice(REMOTE_DEVICE_PUBLIC_ADDRESS),
-                        mPrefsBuilder.build(),
-                        (BluetoothDevice remoteDevice, int key) -> {
-                            targetRemoteDevice.set(remoteDevice);
-                            // Confirms at remote device to pair with local one.
-                            setPairingConfirmationAtRemoteDevice(true);
-
-                            // Confirms NOT to pair with remote device.
-                            remoteDevice.setPairingConfirmation(false);
-                        });
-
-        assertThrows(PairingException.class, bluetoothClassicPairer::pair);
-
-        assertThat(targetRemoteDevice.get()).isNotNull();
-        assertThat(targetRemoteDevice.get().getAddress()).isEqualTo(REMOTE_DEVICE_PUBLIC_ADDRESS);
-        assertThat(targetRemoteDevice.get().getBondState()).isNotEqualTo(
-                BluetoothDevice.BOND_BONDED);
-        assertThat(bluetoothClassicPairer.isPaired()).isFalse();
-    */
-    }
-
-    @Test
-    public void pair_setPairingConfirmationIgnored_throwsExceptionDeviceNotBonded()
-            throws Exception {
-    // TODO(b/217195327): replace deviceshadower with injector.
-    /*
-        AtomicReference<BluetoothDevice> targetRemoteDevice = new AtomicReference<>();
-        BluetoothClassicPairer bluetoothClassicPairer =
-                new BluetoothClassicPairer(
-                        mContext,
-                        BluetoothAdapter.getDefaultAdapter()
-                                .getRemoteDevice(REMOTE_DEVICE_PUBLIC_ADDRESS),
-                        mPrefsBuilder.build(),
-                        (BluetoothDevice remoteDevice, int key) -> {
-                            targetRemoteDevice.set(remoteDevice);
-                            // Confirms at remote device to pair with local one.
-                            setPairingConfirmationAtRemoteDevice(true);
-
-                            // Ignores the setPairingConfirmation.
-                        });
-
-        assertThrows(PairingException.class, bluetoothClassicPairer::pair);
-        assertThat(targetRemoteDevice.get()).isNotNull();
-        assertThat(targetRemoteDevice.get().getAddress()).isEqualTo(REMOTE_DEVICE_PUBLIC_ADDRESS);
-        assertThat(targetRemoteDevice.get().getBondState()).isNotEqualTo(
-                BluetoothDevice.BOND_BONDED);
-        assertThat(bluetoothClassicPairer.isPaired()).isFalse();
-    */
-    }
-
-    private static void setPairingConfirmationAtRemoteDevice(boolean confirm) {
-        try {
-            DeviceShadowEnvironment.run(REMOTE_DEVICE_PUBLIC_ADDRESS,
-                    () -> BluetoothAdapter.getDefaultAdapter()
-                            .getRemoteDevice(LOCAL_DEVICE_ADDRESS)
-                            .setPairingConfirmation(confirm)).get();
-        } catch (InterruptedException | ExecutionException e) {
-            throw new VerifyException("failed to set pairing confirmation at remote device", e);
-        }
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
deleted file mode 100644
index 1d3653b..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * 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.server.nearby.common.ble;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.fail;
-
-import android.bluetooth.BluetoothDevice;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.server.nearby.common.ble.testing.FastPairTestData;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-@RunWith(AndroidJUnit4.class)
-public class BleFilterTest {
-
-
-    public static final ParcelUuid EDDYSTONE_SERVICE_DATA_PARCELUUID =
-            ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
-
-    private ParcelUuid mServiceDataUuid;
-    private BleSighting mBleSighting;
-    private BleFilter.Builder mFilterBuilder;
-
-    @Before
-    public void setUp() throws Exception {
-        // This is the service data UUID in TestData.sd1.
-        // Can't be static because of Robolectric.
-        mServiceDataUuid = ParcelUuid.fromString("000000E0-0000-1000-8000-00805F9B34FB");
-
-        byte[] bleRecordBytes =
-                new byte[]{
-                        0x02,
-                        0x01,
-                        0x1a, // advertising flags
-                        0x05,
-                        0x02,
-                        0x0b,
-                        0x11,
-                        0x0a,
-                        0x11, // 16 bit service uuids
-                        0x04,
-                        0x09,
-                        0x50,
-                        0x65,
-                        0x64, // setName
-                        0x02,
-                        0x0A,
-                        (byte) 0xec, // tx power level
-                        0x05,
-                        0x16,
-                        0x0b,
-                        0x11,
-                        0x50,
-                        0x64, // service data
-                        0x05,
-                        (byte) 0xff,
-                        (byte) 0xe0,
-                        0x00,
-                        0x02,
-                        0x15, // manufacturer specific data
-                        0x03,
-                        0x50,
-                        0x01,
-                        0x02, // an unknown data type won't cause trouble
-                };
-
-        mBleSighting = new BleSighting(null /* device */, bleRecordBytes,
-                -10, 1397545200000000L);
-        mFilterBuilder = new BleFilter.Builder();
-    }
-
-    @Test
-    public void setNameFilter() {
-        BleFilter filter = mFilterBuilder.setDeviceName("Ped").build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        filter = mFilterBuilder.setDeviceName("Pem").build();
-        assertThat(filter.matches(mBleSighting)).isFalse();
-    }
-
-    @Test
-    public void setServiceUuidFilter() {
-        BleFilter filter =
-                mFilterBuilder.setServiceUuid(
-                        ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"))
-                        .build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        filter =
-                mFilterBuilder.setServiceUuid(
-                        ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"))
-                        .build();
-        assertThat(filter.matches(mBleSighting)).isFalse();
-
-        filter =
-                mFilterBuilder
-                        .setServiceUuid(
-                                ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"),
-                                ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
-                        .build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-    }
-
-    @Test
-    public void setServiceDataFilter() {
-        byte[] setServiceData = new byte[]{0x50, 0x64};
-        ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
-        BleFilter filter = mFilterBuilder.setServiceData(serviceDataUuid, setServiceData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        byte[] emptyData = new byte[0];
-        filter = mFilterBuilder.setServiceData(serviceDataUuid, emptyData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        byte[] prefixData = new byte[]{0x50};
-        filter = mFilterBuilder.setServiceData(serviceDataUuid, prefixData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        byte[] nonMatchData = new byte[]{0x51, 0x64};
-        byte[] mask = new byte[]{(byte) 0x00, (byte) 0xFF};
-        filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData, mask).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData).build();
-        assertThat(filter.matches(mBleSighting)).isFalse();
-    }
-
-    @Test
-    public void manufacturerSpecificData() {
-        byte[] setManufacturerData = new byte[]{0x02, 0x15};
-        int manufacturerId = 0xE0;
-        BleFilter filter =
-                mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        byte[] emptyData = new byte[0];
-        filter = mFilterBuilder.setManufacturerData(manufacturerId, emptyData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        byte[] prefixData = new byte[]{0x02};
-        filter = mFilterBuilder.setManufacturerData(manufacturerId, prefixData).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        // Data and mask are nullable. Check that we still match when they're null.
-        filter = mFilterBuilder.setManufacturerData(manufacturerId,
-                null /* data */).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-        filter = mFilterBuilder.setManufacturerData(manufacturerId,
-                null /* data */, null /* mask */).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-
-        // Test data mask
-        byte[] nonMatchData = new byte[]{0x02, 0x14};
-        filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build();
-        assertThat(filter.matches(mBleSighting)).isFalse();
-        byte[] mask = new byte[]{(byte) 0xFF, (byte) 0x00};
-        filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build();
-        assertThat(filter.matches(mBleSighting)).isTrue();
-    }
-
-    @Test
-    public void manufacturerDataNotInBleRecord() {
-        byte[] bleRecord = FastPairTestData.adv_2;
-        // Verify manufacturer with no data
-        byte[] data = {(byte) 0xe0, (byte) 0x00};
-        BleFilter filter = mFilterBuilder.setManufacturerData(0x00e0, data).build();
-        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
-    }
-
-    @Test
-    public void manufacturerDataMaskNotInBleRecord() {
-        byte[] bleRecord = FastPairTestData.adv_2;
-
-        // Verify matching partial manufacturer with data and mask
-        byte[] data = {(byte) 0x15};
-        byte[] mask = {(byte) 0xff};
-
-        BleFilter filter = mFilterBuilder
-                .setManufacturerData(0x00e0, data, mask).build();
-        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
-    }
-
-
-    @Test
-    public void serviceData() throws Exception {
-        byte[] bleRecord = FastPairTestData.sd1;
-        byte[] serviceData = {(byte) 0x15};
-
-        // Verify manufacturer 2-byte UUID with no data
-        BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
-        assertMatches(filter, null, 0, bleRecord);
-    }
-
-    @Test
-    public void serviceDataNoMatch() {
-        byte[] bleRecord = FastPairTestData.sd1;
-        byte[] serviceData = {(byte) 0xe1, (byte) 0x00};
-
-        // Verify manufacturer 2-byte UUID with no data
-        BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
-        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
-    }
-
-    @Test
-    public void serviceDataMask() {
-        byte[] bleRecord = FastPairTestData.sd1;
-        BleFilter filter;
-
-        // Verify matching partial manufacturer with data and mask
-        byte[] serviceData1 = {(byte) 0x15};
-        byte[] mask1 = {(byte) 0xff};
-        filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData1, mask1).build();
-        assertMatches(filter, null, 0, bleRecord);
-    }
-
-    @Test
-    public void serviceDataMaskNoMatch() {
-        byte[] bleRecord = FastPairTestData.sd1;
-        BleFilter filter;
-
-        // Verify non-matching partial manufacturer with data and mask
-        byte[] serviceData2 = {(byte) 0xe0, (byte) 0x00, (byte) 0x10};
-        byte[] mask2 = {(byte) 0xff, (byte) 0xff, (byte) 0xff};
-        filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData2, mask2).build();
-        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void serviceDataMaskWithDifferentLength() {
-        // Different lengths for data and mask.
-        byte[] serviceData = {(byte) 0xe0, (byte) 0x00, (byte) 0x10};
-        byte[] mask = {(byte) 0xff, (byte) 0xff};
-
-        //expected.expect(IllegalArgumentException.class);
-
-        mFilterBuilder.setServiceData(mServiceDataUuid, serviceData, mask).build();
-    }
-
-
-    @Test
-    public void deviceNameTest() {
-        // Verify the name filter matches
-        byte[] bleRecord = FastPairTestData.adv_1;
-        BleFilter filter = mFilterBuilder.setDeviceName("Pedometer").build();
-        assertMatches(filter, null, 0, bleRecord);
-    }
-
-    @Test
-    public void deviceNameNoMatch() {
-        // Verify the name filter does not match
-        byte[] bleRecord = FastPairTestData.adv_1;
-        BleFilter filter = mFilterBuilder.setDeviceName("Foo").build();
-        assertThat(matches(filter, null, 0, bleRecord)).isFalse();
-    }
-
-    private static boolean matches(
-            BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecord) {
-        return filter.matches(new BleSighting(device,
-                bleRecord, rssi, 0 /* timestampNanos */));
-    }
-
-
-    private static void assertMatches(
-            BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecordBytes) {
-
-        // Device match.
-        if (filter.getDeviceAddress() != null
-                && (device == null || !filter.getDeviceAddress().equals(device.getAddress()))) {
-            fail("Filter specified a device address ("
-                    + filter.getDeviceAddress()
-                    + ") which doesn't match the actual value: ["
-                    + (device == null ? "null device" : device.getAddress())
-                    + "]");
-        }
-
-        // BLE record is null but there exist filters on it.
-        BleRecord bleRecord = BleRecord.parseFromBytes(bleRecordBytes);
-        if (bleRecord == null
-                && (filter.getDeviceName() != null
-                || filter.getServiceUuid() != null
-                || filter.getManufacturerData() != null
-                || filter.getServiceData() != null)) {
-            fail(
-                    "The bleRecordBytes given parsed to a null bleRecord, but the filter"
-                            + "has a non-null field which depends on the scan record");
-        }
-
-        // Local name match.
-        if (filter.getDeviceName() != null
-                && !filter.getDeviceName().equals(bleRecord.getDeviceName())) {
-            fail(
-                    "The filter's device name ("
-                            + filter.getDeviceName()
-                            + ") doesn't match the scan record device name ("
-                            + bleRecord.getDeviceName()
-                            + ")");
-        }
-
-        // UUID match.
-        if (filter.getServiceUuid() != null
-                && !matchesServiceUuids(filter.getServiceUuid(), filter.getServiceUuidMask(),
-                bleRecord.getServiceUuids())) {
-            fail("The filter specifies a service UUID but it doesn't match "
-                    + "what's in the scan record");
-        }
-
-        // Service data match
-        if (filter.getServiceDataUuid() != null
-                && !BleFilter.matchesPartialData(
-                filter.getServiceData(),
-                filter.getServiceDataMask(),
-                bleRecord.getServiceData(filter.getServiceDataUuid()))) {
-            fail(
-                    "The filter's service data doesn't match what's in the scan record.\n"
-                            + "Service data: "
-                            + byteString(filter.getServiceData())
-                            + "\n"
-                            + "Service data UUID: "
-                            + filter.getServiceDataUuid().toString()
-                            + "\n"
-                            + "Service data mask: "
-                            + byteString(filter.getServiceDataMask())
-                            + "\n"
-                            + "Scan record service data: "
-                            + byteString(bleRecord.getServiceData(filter.getServiceDataUuid()))
-                            + "\n"
-                            + "Scan record data map:\n"
-                            + byteString(bleRecord.getServiceData()));
-        }
-
-        // Manufacturer data match.
-        if (filter.getManufacturerId() >= 0
-                && !BleFilter.matchesPartialData(
-                filter.getManufacturerData(),
-                filter.getManufacturerDataMask(),
-                bleRecord.getManufacturerSpecificData(filter.getManufacturerId()))) {
-            fail(
-                    "The filter's manufacturer data doesn't match what's in the scan record.\n"
-                            + "Manufacturer ID: "
-                            + filter.getManufacturerId()
-                            + "\n"
-                            + "Manufacturer data: "
-                            + byteString(filter.getManufacturerData())
-                            + "\n"
-                            + "Manufacturer data mask: "
-                            + byteString(filter.getManufacturerDataMask())
-                            + "\n"
-                            + "Scan record manufacturer-specific data: "
-                            + byteString(bleRecord.getManufacturerSpecificData(
-                            filter.getManufacturerId()))
-                            + "\n"
-                            + "Manufacturer data array:\n"
-                            + byteString(bleRecord.getManufacturerSpecificData()));
-        }
-
-        // All filters match.
-        assertThat(
-                matches(filter, device, rssi, bleRecordBytes)).isTrue();
-    }
-
-
-    private static String byteString(byte[] bytes) {
-        if (bytes == null) {
-            return "[null]";
-        } else {
-            final char[] hexArray = "0123456789ABCDEF".toCharArray();
-            char[] hexChars = new char[bytes.length * 2];
-            for (int i = 0; i < bytes.length; i++) {
-                int v = bytes[i] & 0xFF;
-                hexChars[i * 2] = hexArray[v >>> 4];
-                hexChars[i * 2 + 1] = hexArray[v & 0x0F];
-            }
-            return new String(hexChars);
-        }
-    }
-
-    // Ref to beacon.decode.AppleBeaconDecoder.getFilterData
-    private static byte[] getFilterData(ParcelUuid uuid) {
-        byte[] data = new byte[18];
-        data[0] = (byte) 0x02;
-        data[1] = (byte) 0x15;
-        // Check if UUID is needed in data
-        if (uuid != null) {
-            // Convert UUID to array in big endian order
-            byte[] uuidBytes = uuidToByteArray(uuid);
-            for (int i = 0; i < 16; i++) {
-                // Adding uuid bytes in big-endian order to match iBeacon format
-                data[i + 2] = uuidBytes[i];
-            }
-        }
-        return data;
-    }
-
-    // Ref to beacon.decode.AppleBeaconDecoder.uuidToByteArray
-    private static byte[] uuidToByteArray(ParcelUuid uuid) {
-        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
-        bb.putLong(uuid.getUuid().getMostSignificantBits());
-        bb.putLong(uuid.getUuid().getLeastSignificantBits());
-        return bb.array();
-    }
-
-    private static boolean matchesServiceUuids(
-            ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids) {
-        if (uuid == null) {
-            return true;
-        }
-
-        for (ParcelUuid parcelUuid : uuids) {
-            UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
-            if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Check if the uuid pattern matches the particular service uuid.
-    private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
-        if (mask == null) {
-            return uuid.equals(data);
-        }
-        if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
-                != (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
-            return false;
-        }
-        return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits())
-                == (data.getMostSignificantBits() & mask.getMostSignificantBits()));
-    }
-
-    private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
-        StringBuilder builder = new StringBuilder();
-        for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
-            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
-            builder.append(entry.getKey().toString());
-            builder.append(" --> ");
-            builder.append(byteString(entry.getValue()));
-        }
-        return builder.toString();
-    }
-
-    private static String byteString(SparseArray<byte[]> bytesArray) {
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < bytesArray.size(); i++) {
-            builder.append(builder.toString().isEmpty() ? "  " : "\n  ");
-            builder.append(byteString(bytesArray.valueAt(i)));
-        }
-        return builder.toString();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
deleted file mode 100644
index 5da98e2..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Copyright (C) 2021 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.server.nearby.common.ble;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.Test;
-
-/** Test for Bluetooth LE {@link BleRecord}. */
-public class BleRecordTest {
-
-    // iBeacon (Apple) Packet 1
-    private static final byte[] BEACON = {
-            // Flags
-            (byte) 0x02,
-            (byte) 0x01,
-            (byte) 0x06,
-            // Manufacturer-specific data header
-            (byte) 0x1a,
-            (byte) 0xff,
-            (byte) 0x4c,
-            (byte) 0x00,
-            // iBeacon Type
-            (byte) 0x02,
-            // Frame length
-            (byte) 0x15,
-            // iBeacon Proximity UUID
-            (byte) 0xf7,
-            (byte) 0x82,
-            (byte) 0x6d,
-            (byte) 0xa6,
-            (byte) 0x4f,
-            (byte) 0xa2,
-            (byte) 0x4e,
-            (byte) 0x98,
-            (byte) 0x80,
-            (byte) 0x24,
-            (byte) 0xbc,
-            (byte) 0x5b,
-            (byte) 0x71,
-            (byte) 0xe0,
-            (byte) 0x89,
-            (byte) 0x3e,
-            // iBeacon Instance ID (Major/Minor)
-            (byte) 0x44,
-            (byte) 0xd0,
-            (byte) 0x25,
-            (byte) 0x22,
-            // Tx Power
-            (byte) 0xb3,
-            // RSP
-            (byte) 0x08,
-            (byte) 0x09,
-            (byte) 0x4b,
-            (byte) 0x6f,
-            (byte) 0x6e,
-            (byte) 0x74,
-            (byte) 0x61,
-            (byte) 0x6b,
-            (byte) 0x74,
-            (byte) 0x02,
-            (byte) 0x0a,
-            (byte) 0xf4,
-            (byte) 0x0a,
-            (byte) 0x16,
-            (byte) 0x0d,
-            (byte) 0xd0,
-            (byte) 0x74,
-            (byte) 0x6d,
-            (byte) 0x4d,
-            (byte) 0x6b,
-            (byte) 0x32,
-            (byte) 0x36,
-            (byte) 0x64,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00
-    };
-
-    // iBeacon (Apple) Packet 1
-    private static final byte[] SAME_BEACON = {
-            // Flags
-            (byte) 0x02,
-            (byte) 0x01,
-            (byte) 0x06,
-            // Manufacturer-specific data header
-            (byte) 0x1a,
-            (byte) 0xff,
-            (byte) 0x4c,
-            (byte) 0x00,
-            // iBeacon Type
-            (byte) 0x02,
-            // Frame length
-            (byte) 0x15,
-            // iBeacon Proximity UUID
-            (byte) 0xf7,
-            (byte) 0x82,
-            (byte) 0x6d,
-            (byte) 0xa6,
-            (byte) 0x4f,
-            (byte) 0xa2,
-            (byte) 0x4e,
-            (byte) 0x98,
-            (byte) 0x80,
-            (byte) 0x24,
-            (byte) 0xbc,
-            (byte) 0x5b,
-            (byte) 0x71,
-            (byte) 0xe0,
-            (byte) 0x89,
-            (byte) 0x3e,
-            // iBeacon Instance ID (Major/Minor)
-            (byte) 0x44,
-            (byte) 0xd0,
-            (byte) 0x25,
-            (byte) 0x22,
-            // Tx Power
-            (byte) 0xb3,
-            // RSP
-            (byte) 0x08,
-            (byte) 0x09,
-            (byte) 0x4b,
-            (byte) 0x6f,
-            (byte) 0x6e,
-            (byte) 0x74,
-            (byte) 0x61,
-            (byte) 0x6b,
-            (byte) 0x74,
-            (byte) 0x02,
-            (byte) 0x0a,
-            (byte) 0xf4,
-            (byte) 0x0a,
-            (byte) 0x16,
-            (byte) 0x0d,
-            (byte) 0xd0,
-            (byte) 0x74,
-            (byte) 0x6d,
-            (byte) 0x4d,
-            (byte) 0x6b,
-            (byte) 0x32,
-            (byte) 0x36,
-            (byte) 0x64,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00,
-            (byte) 0x00
-    };
-
-    // iBeacon (Apple) Packet 1 with a modified second field.
-    private static final byte[] OTHER_BEACON = {
-            (byte) 0x02, // Length of this Data
-            (byte) 0x02, // <<Flags>>
-            (byte) 0x04, // BR/EDR Not Supported.
-            // Apple Specific Data
-            26, // length of data that follows
-            (byte) 0xff, // <<Manufacturer Specific Data>>
-            // Company Identifier Code = Apple
-            (byte) 0x4c, // LSB
-            (byte) 0x00, // MSB
-            // iBeacon Header
-            0x02,
-            // iBeacon Length
-            0x15,
-            // UUID = PROXIMITY_NOW
-            // IEEE 128-bit UUID represented as UUID[15]: msb To UUID[0]: lsb
-            (byte) 0x14,
-            (byte) 0xe4,
-            (byte) 0xfd,
-            (byte) 0x9f, // UUID[15] - UUID[12]
-            (byte) 0x66,
-            (byte) 0x67,
-            (byte) 0x4c,
-            (byte) 0xcb, // UUID[11] - UUID[08]
-            (byte) 0xa6,
-            (byte) 0x1b,
-            (byte) 0x24,
-            (byte) 0xd0, // UUID[07] - UUID[04]
-            (byte) 0x9a,
-            (byte) 0xb1,
-            (byte) 0x7e,
-            (byte) 0x93, // UUID[03] - UUID[00]
-            // ID as an int (decimal) = 1297482358
-            (byte) 0x76, // Major H
-            (byte) 0x02, // Major L
-            (byte) 0x56, // Minor H
-            (byte) 0x4d, // Minor L
-            // Normalized Tx Power of -77dbm
-            (byte) 0xb3,
-            0x00, // Zero padding for testing
-    };
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEquals() {
-        BleRecord record = BleRecord.parseFromBytes(BEACON);
-        BleRecord record2 = BleRecord.parseFromBytes(SAME_BEACON);
-
-
-        assertThat(record).isEqualTo(record2);
-
-        // Different items.
-        record2 = BleRecord.parseFromBytes(OTHER_BEACON);
-        assertThat(record).isNotEqualTo(record2);
-        assertThat(record.hashCode()).isNotEqualTo(record2.hashCode());
-    }
-}
-
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
deleted file mode 100644
index 1ad04f8..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.server.nearby.common.ble.decode;
-
-import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
-import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_MODEL_ID;
-import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
-import static com.android.server.nearby.common.ble.testing.FastPairTestData.newFastPairRecord;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.server.nearby.common.ble.BleRecord;
-import com.android.server.nearby.util.Hex;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class FastPairDecoderTest {
-    private static final String LONG_MODEL_ID = "1122334455667788";
-    private final FastPairDecoder mDecoder = new FastPairDecoder();
-    // Bits 3-6 are model ID length bits = 0b1000 = 8
-    private static final byte LONG_MODEL_ID_HEADER = 0b00010000;
-    private static final String PADDED_LONG_MODEL_ID = "00001111";
-    // Bits 3-6 are model ID length bits = 0b0100 = 4
-    private static final byte PADDED_LONG_MODEL_ID_HEADER = 0b00001000;
-    private static final String TRIMMED_LONG_MODEL_ID = "001111";
-    private static final byte MODEL_ID_HEADER = 0b00000110;
-    private static final String MODEL_ID = "112233";
-    private static final byte BLOOM_FILTER_HEADER = 0b01100000;
-    private static final String BLOOM_FILTER = "112233445566";
-    private static final byte BLOOM_FILTER_SALT_HEADER = 0b00010001;
-    private static final String BLOOM_FILTER_SALT = "01";
-    private static final byte RANDOM_RESOLVABLE_DATA_HEADER = 0b01000110;
-    private static final String RANDOM_RESOLVABLE_DATA = "11223344";
-    private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
-
-
-    @Test
-    public void getModelId() {
-        assertThat(mDecoder.getBeaconIdBytes(parseFromBytes(getFastPairRecord())))
-                .isEqualTo(FAST_PAIR_MODEL_ID);
-        FastPairServiceData fastPairServiceData1 =
-                new FastPairServiceData(LONG_MODEL_ID_HEADER,
-                        LONG_MODEL_ID);
-        assertThat(
-                mDecoder.getBeaconIdBytes(
-                        newBleRecord(fastPairServiceData1.createServiceData())))
-                .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
-        FastPairServiceData fastPairServiceData =
-                new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER,
-                        PADDED_LONG_MODEL_ID);
-        assertThat(
-                mDecoder.getBeaconIdBytes(
-                        newBleRecord(fastPairServiceData.createServiceData())))
-                .isEqualTo(Hex.stringToBytes(TRIMMED_LONG_MODEL_ID));
-    }
-
-    @Test
-    public void getBloomFilter() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
-                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
-    }
-
-    @Test
-    public void getBloomFilter_smallModelId() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(null, MODEL_ID);
-        assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
-                .isNull();
-    }
-
-    @Test
-    public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
-        assertThat(
-                FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
-                .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
-    }
-
-    @Test
-    public void getRandomResolvableData_whenContainConnectionState() {
-        FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
-                MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
-        fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
-        assertThat(
-                FastPairDecoder.getRandomResolvableData(fastPairServiceData
-                                .createServiceData()))
-                .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
-    }
-
-    @Test
-    public void getBloomFilterNoNotification() {
-        FastPairServiceData fastPairServiceData =
-                new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
-        fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_NO_NOTIFICATION_HEADER);
-        fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
-        assertThat(FastPairDecoder.getBloomFilterNoNotification(fastPairServiceData
-                        .createServiceData())).isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
-    }
-
-    private static BleRecord newBleRecord(byte[] serviceDataBytes) {
-        return parseFromBytes(newFastPairRecord(serviceDataBytes));
-    }
-    class FastPairServiceData {
-        private Byte mHeader;
-        private String mModelId;
-        List<Byte> mExtraFieldHeaders = new ArrayList<>();
-        List<String> mExtraFields = new ArrayList<>();
-
-        FastPairServiceData(Byte header, String modelId) {
-            this.mHeader = header;
-            this.mModelId = modelId;
-        }
-        private byte[] createServiceData() {
-            if (mExtraFieldHeaders.size() != mExtraFields.size()) {
-                throw new RuntimeException("Number of headers and extra fields must match.");
-            }
-            byte[] serviceData =
-                    Bytes.concat(
-                            mHeader == null ? new byte[0] : new byte[] {mHeader},
-                            mModelId == null ? new byte[0] : Hex.stringToBytes(mModelId));
-            for (int i = 0; i < mExtraFieldHeaders.size(); i++) {
-                serviceData =
-                        Bytes.concat(
-                                serviceData,
-                                mExtraFieldHeaders.get(i) != null
-                                        ? new byte[] {mExtraFieldHeaders.get(i)}
-                                        : new byte[0],
-                                mExtraFields.get(i) != null
-                                        ? Hex.stringToBytes(mExtraFields.get(i))
-                                        : new byte[0]);
-            }
-            return serviceData;
-        }
-    }
-
-
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
deleted file mode 100644
index ebe72b3..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.server.nearby.common.ble.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class RangingUtilsTest {
-    // relative error to be used in comparing doubles
-    private static final double DELTA = 1e-5;
-
-    @Test
-    public void distanceFromRssi_getCorrectValue() {
-        // Distance expected to be 1.0 meters based on an RSSI/TxPower of -41dBm
-        // Using params: int rssi (dBm), int calibratedTxPower (dBm)
-        double distance = RangingUtils.distanceFromRssiAndTxPower(-82, -41);
-        assertThat(distance).isWithin(DELTA).of(1.0);
-
-        double distance2 = RangingUtils.distanceFromRssiAndTxPower(-111, -50);
-        assertThat(distance2).isWithin(DELTA).of(10.0);
-
-        //rssi txpower
-        double distance4 = RangingUtils.distanceFromRssiAndTxPower(-50, -29);
-        assertThat(distance4).isWithin(DELTA).of(0.1);
-    }
-
-    @Test
-    public void testRssiFromDistance() {
-        // RSSI expected at 1 meter based on the calibrated tx field of -41dBm
-        // Using params: distance (m), int calibratedTxPower (dBm),
-        int rssi = RangingUtils.rssiFromTargetDistance(1.0, -41);
-
-        assertThat(rssi).isEqualTo(-82);
-    }
-
-    @Test
-    public void testOutOfRange() {
-        double distance = RangingUtils.distanceFromRssiAndTxPower(-200, -41);
-        assertThat(distance).isWithin(DELTA).of(177.82794);
-
-        distance = RangingUtils.distanceFromRssiAndTxPower(200, -41);
-        assertThat(distance).isWithin(DELTA).of(0);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
deleted file mode 100644
index 35a45c0..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGeneratorTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.NoSuchAlgorithmException;
-
-/**
- * Unit tests for {@link AccountKeyGenerator}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AccountKeyGeneratorTest {
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void createAccountKey() throws NoSuchAlgorithmException {
-        byte[] accountKey = AccountKeyGenerator.createAccountKey();
-
-        assertThat(accountKey).hasLength(16);
-        assertThat(accountKey[0]).isEqualTo(AccountKeyCharacteristic.TYPE);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
deleted file mode 100644
index 28d2fca..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AdditionalDataEncoder.MAX_LENGTH_OF_DATA;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
-import static com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder.EXTRACT_HMAC_SIZE;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.GeneralSecurityException;
-
-/**
- * Unit tests for {@link AdditionalDataEncoder}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AdditionalDataEncoderTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decodeEncodedAdditionalDataPacket_mustGetSameRawData()
-            throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
-
-        byte[] encodedAdditionalDataPacket =
-                AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData);
-        byte[] additionalData =
-                AdditionalDataEncoder
-                        .decodeAdditionalDataPacket(secret, encodedAdditionalDataPacket);
-
-        assertThat(additionalData).isEqualTo(rawData);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToEncode_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Incorrect secret for encoding additional data packet");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToDecode_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        byte[] packet = base16().decode("01234567890123456789");
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AdditionalDataEncoder.decodeAdditionalDataPacket(secret, packet));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Incorrect secret for decoding additional data packet");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTooSmallPacketSize_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH];
-        byte[] packet = new byte[EXTRACT_HMAC_SIZE - 1];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AdditionalDataEncoder.decodeAdditionalDataPacket(secret, packet));
-
-        assertThat(exception).hasMessageThat().contains("Additional data packet size is incorrect");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTooLargePacketSize_mustThrowException() throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] packet = new byte[MAX_LENGTH_OF_DATA + EXTRACT_HMAC_SIZE + NONCE_SIZE + 1];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AdditionalDataEncoder.decodeAdditionalDataPacket(secret, packet));
-
-        assertThat(exception).hasMessageThat().contains("Additional data packet size is incorrect");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectHmacToDecode_mustThrowException() throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
-
-        byte[] additionalDataPacket = AdditionalDataEncoder
-                .encodeAdditionalDataPacket(secret, rawData);
-        additionalDataPacket[0] = (byte) ~additionalDataPacket[0];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AdditionalDataEncoder
-                                .decodeAdditionalDataPacket(secret, additionalDataPacket));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Verify HMAC failed, could be incorrect key or packet.");
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
deleted file mode 100644
index 7d86037..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryptionTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.KEY_LENGTH;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-/** Unit tests for {@link AesCtrMultpleBlockEncryption}. */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AesCtrMultipleBlockEncryptionTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decryptEncryptedData_nonBlockSizeAligned_mustEqualToPlaintext() throws Exception {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8); // The length is 31.
-
-        byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
-        byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
-
-        assertThat(decrypted).isEqualTo(plaintext);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decryptEncryptedData_blockSizeAligned_mustEqualToPlaintext() throws Exception {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] plaintext =
-                // The length is 32.
-                base16().decode("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
-
-        byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
-        byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
-
-        assertThat(decrypted).isEqualTo(plaintext);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void generateNonceTwice_mustBeDifferent() {
-        byte[] nonce1 = AesCtrMultipleBlockEncryption.generateNonce();
-        byte[] nonce2 = AesCtrMultipleBlockEncryption.generateNonce();
-
-        assertThat(nonce1).isNotEqualTo(nonce2);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void encryptedSamePlaintext_mustBeDifferentEncryptedResult() throws Exception {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
-
-        byte[] encrypted1 = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
-        byte[] encrypted2 = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
-
-        assertThat(encrypted1).isNotEqualTo(encrypted2);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void encryptData_mustBeDifferentToUnencrypted() throws Exception {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
-
-        byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
-
-        assertThat(encrypted).isNotEqualTo(plaintext);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToEncrypt_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH + 1];
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
-
-        IllegalArgumentException exception =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () -> AesCtrMultipleBlockEncryption.encrypt(secret, plaintext));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Incorrect key length for encryption, only supports 16-byte AES Key.");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToDecrypt_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
-
-        IllegalArgumentException exception =
-                assertThrows(
-                        IllegalArgumentException.class,
-                        () -> AesCtrMultipleBlockEncryption.decrypt(secret, plaintext));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Incorrect key length for encryption, only supports 16-byte AES Key.");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectDataSizeToDecrypt_mustThrowException()
-            throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
-
-        byte[] encryptedData = Arrays.copyOfRange(
-                AesCtrMultipleBlockEncryption.encrypt(secret, plaintext), /*from=*/ 0, NONCE_SIZE);
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData));
-
-        assertThat(exception).hasMessageThat().contains("Incorrect data length");
-    }
-
-    // Add some random tests that for a certain amount of random plaintext of random length to prove
-    // our encryption/decryption is correct. This is suggested by security team.
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decryptEncryptedRandomDataForCertainAmount_mustEqualToOriginalData()
-            throws Exception {
-        SecureRandom random = new SecureRandom();
-        for (int i = 0; i < 1000; i++) {
-            byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-            int dataLength = random.nextInt(64) + 1;
-            byte[] data = new byte[dataLength];
-            random.nextBytes(data);
-
-            byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, data);
-            byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
-
-            assertThat(decrypted).isEqualTo(data);
-        }
-    }
-
-    // Add some random tests that for a certain amount of random plaintext of random length to prove
-    // our encryption is correct. This is suggested by security team.
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void twoDistinctEncryptionOnSameRandomData_mustBeDifferentResult() throws Exception {
-        SecureRandom random = new SecureRandom();
-        for (int i = 0; i < 1000; i++) {
-            byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-            int dataLength = random.nextInt(64) + 1;
-            byte[] data = new byte[dataLength];
-            random.nextBytes(data);
-
-            byte[] encrypted1 = AesCtrMultipleBlockEncryption.encrypt(secret, data);
-            byte[] encrypted2 = AesCtrMultipleBlockEncryption.encrypt(secret, data);
-
-            assertThat(encrypted1).isNotEqualTo(encrypted2);
-        }
-    }
-
-    // Adds this test example on spec. Also we can easily change the parameters(e.g. secret, data,
-    // nonce) to clarify test results with partners.
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTestExampleToEncrypt_getCorrectResult() throws GeneralSecurityException {
-        byte[] secret = base16().decode("0123456789ABCDEF0123456789ABCDEF");
-        byte[] nonce = base16().decode("0001020304050607");
-
-        // "Someone's Google Headphone".getBytes(UTF_8) is
-        // base16().decode("536F6D656F6E65277320476F6F676C65204865616470686F6E65");
-        byte[] encryptedData =
-                AesCtrMultipleBlockEncryption.doAesCtr(
-                        secret,
-                        "Someone's Google Headphone".getBytes(UTF_8),
-                        nonce);
-
-        assertThat(encryptedData)
-                .isEqualTo(base16().decode("EE4A2483738052E44E9B2A145E5DDFAA44B9E5536AF438E1E5C6"));
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
deleted file mode 100644
index eccbd01..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryptionTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link AesEcbSingleBlockEncryption}. */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AesEcbSingleBlockEncryptionTest {
-
-    private static final byte[] PLAINTEXT = base16().decode("F30F4E786C59A7BBF3873B5A49BA97EA");
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void encryptDecryptSuccessful() throws Exception {
-        byte[] secret = AesEcbSingleBlockEncryption.generateKey();
-        byte[] encrypted = AesEcbSingleBlockEncryption.encrypt(secret, PLAINTEXT);
-        assertThat(encrypted).isNotEqualTo(PLAINTEXT);
-        byte[] decrypted = AesEcbSingleBlockEncryption.decrypt(secret, encrypted);
-        assertThat(decrypted).isEqualTo(PLAINTEXT);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void encryptionSizeLimitationEnforced() throws Exception {
-        byte[] secret = AesEcbSingleBlockEncryption.generateKey();
-        byte[] largePacket = Bytes.concat(PLAINTEXT, PLAINTEXT);
-        AesEcbSingleBlockEncryption.encrypt(secret, largePacket);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decryptionSizeLimitationEnforced() throws Exception {
-        byte[] secret = AesEcbSingleBlockEncryption.generateKey();
-        byte[] largePacket = Bytes.concat(PLAINTEXT, PLAINTEXT);
-        AesEcbSingleBlockEncryption.decrypt(secret, largePacket);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
deleted file mode 100644
index 6c95558..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddressTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.bluetooth.BluetoothAdapter;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link BluetoothAddress}. */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BluetoothAddressTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void maskBluetoothAddress_whenInputIsNull() {
-        assertThat(BluetoothAddress.maskBluetoothAddress(null)).isEqualTo("");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void maskBluetoothAddress_whenInputStringNotMatchFormat() {
-        assertThat(BluetoothAddress.maskBluetoothAddress("AA:BB:CC")).isEqualTo("AA:BB:CC");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void maskBluetoothAddress_whenInputStringMatchFormat() {
-        assertThat(BluetoothAddress.maskBluetoothAddress("AA:BB:CC:DD:EE:FF"))
-                .isEqualTo("XX:XX:XX:XX:EE:FF");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void maskBluetoothAddress_whenInputStringContainLowerCaseMatchFormat() {
-        assertThat(BluetoothAddress.maskBluetoothAddress("Aa:Bb:cC:dD:eE:Ff"))
-                .isEqualTo("XX:XX:XX:XX:EE:FF");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void maskBluetoothAddress_whenInputBluetoothDevice() {
-        assertThat(
-                BluetoothAddress.maskBluetoothAddress(
-                        BluetoothAdapter.getDefaultAdapter().getRemoteDevice("FF:EE:DD:CC:BB:AA")))
-                .isEqualTo("XX:XX:XX:XX:BB:AA");
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
deleted file mode 100644
index 0a56f2f..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.google.common.collect.Iterables;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-
-/** Unit tests for {@link BluetoothAudioPairer}. */
-@Presubmit
-@SmallTest
-public class BluetoothAudioPairerTest extends TestCase {
-
-    private static final byte[] SECRET = new byte[]{3, 0};
-    private static final boolean PRIVATE_INITIAL_PAIRING = false;
-    private static final String EVENT_NAME = "EVENT_NAME";
-    private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
-            .getRemoteDevice("11:22:33:44:55:66");
-    private static final int BOND_TIMEOUT_SECONDS = 1;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        initMocks(this);
-        BluetoothAudioPairer.enableTestMode();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testKeyBasedPairingInfoConstructor() {
-        assertThat(new BluetoothAudioPairer.KeyBasedPairingInfo(
-                SECRET,
-                null /* GattConnectionManager */,
-                PRIVATE_INITIAL_PAIRING)).isNotNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothAudioPairerConstructor() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        try {
-            assertThat(new BluetoothAudioPairer(
-                    context,
-                    BLUETOOTH_DEVICE,
-                    Preferences.builder().build(),
-                    new EventLoggerWrapper(new TestEventLogger()),
-                    null /* KeyBasePairingInfo */,
-                    null /*PasskeyConfirmationHandler */,
-                    new TimingLogger(EVENT_NAME, Preferences.builder().build()))).isNotNull();
-        } catch (PairingException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothAudioPairerUnpairNoCrash() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        try {
-            new BluetoothAudioPairer(
-                    context,
-                    BLUETOOTH_DEVICE,
-                    Preferences.builder().build(),
-                    new EventLoggerWrapper(new TestEventLogger()),
-                    null /* KeyBasePairingInfo */,
-                    null /*PasskeyConfirmationHandler */,
-                    new TimingLogger(EVENT_NAME, Preferences.builder().build())).unpair();
-        } catch (PairingException | InterruptedException | ExecutionException
-                | TimeoutException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothAudioPairerPairNoCrash() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        try {
-            new BluetoothAudioPairer(
-                    context,
-                    BLUETOOTH_DEVICE,
-                    Preferences.builder().setCreateBondTimeoutSeconds(BOND_TIMEOUT_SECONDS).build(),
-                    new EventLoggerWrapper(new TestEventLogger()),
-                    null /* KeyBasePairingInfo */,
-                    null /*PasskeyConfirmationHandler */,
-                    new TimingLogger(EVENT_NAME, Preferences.builder().build())).pair();
-        } catch (PairingException | InterruptedException | ExecutionException
-                | TimeoutException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothAudioPairerConnectNoCrash() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        try {
-            new BluetoothAudioPairer(
-                    context,
-                    BLUETOOTH_DEVICE,
-                    Preferences.builder().setCreateBondTimeoutSeconds(BOND_TIMEOUT_SECONDS).build(),
-                    new EventLoggerWrapper(new TestEventLogger()),
-                    null /* KeyBasePairingInfo */,
-                    null /*PasskeyConfirmationHandler */,
-                    new TimingLogger(EVENT_NAME, Preferences.builder().build()))
-                    .connect(Constants.A2DP_SINK_SERVICE_UUID, true /* enable pairing behavior */);
-        } catch (PairingException | InterruptedException | ExecutionException
-                | TimeoutException | ReflectionException e) {
-        }
-    }
-
-    static class TestEventLogger implements EventLogger {
-
-        private List<Item> mLogs = new ArrayList<>();
-
-        @Override
-        public void logEventSucceeded(Event event) {
-            mLogs.add(new Item(event));
-        }
-
-        @Override
-        public void logEventFailed(Event event, Exception e) {
-            mLogs.add(new ItemFailed(event, e));
-        }
-
-        List<Item> getErrorLogs() {
-            return mLogs.stream().filter(item -> item instanceof ItemFailed)
-                    .collect(Collectors.toList());
-        }
-
-        List<Item> getLogs() {
-            return mLogs;
-        }
-
-        List<Item> getLast() {
-            return mLogs.subList(mLogs.size() - 1, mLogs.size());
-        }
-
-        BluetoothDevice getDevice() {
-            return Iterables.getLast(mLogs).mEvent.getBluetoothDevice();
-        }
-
-        public static class Item {
-
-            final Event mEvent;
-
-            Item(Event event) {
-                this.mEvent = event;
-            }
-
-            @Override
-            public String toString() {
-                return "Item{" + "event=" + mEvent + '}';
-            }
-        }
-
-        public static class ItemFailed extends Item {
-
-            final Exception mException;
-
-            ItemFailed(Event event, Exception e) {
-                super(event);
-                this.mException = e;
-            }
-
-            @Override
-            public String toString() {
-                return "ItemFailed{" + "event=" + mEvent + ", exception=" + mException + '}';
-            }
-        }
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
deleted file mode 100644
index fa977ed..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuidsTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.UUID;
-
-/** Unit tests for {@link BluetoothUuids}. */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BluetoothUuidsTest {
-
-    // According to {@code android.bluetooth.BluetoothUuid}
-    private static final short A2DP_SINK_SHORT_UUID = (short) 0x110B;
-    private static final UUID A2DP_SINK_CHARACTERISTICS =
-            UUID.fromString("0000110B-0000-1000-8000-00805F9B34FB");
-
-    // According to {go/fastpair-128bit-gatt}, the short uuid locates at the 3rd and 4th bytes based
-    // on the Fast Pair custom GATT characteristics 128-bit UUIDs base -
-    // "FE2C0000-8366-4814-8EB0-01DE32100BEA".
-    private static final short CUSTOM_SHORT_UUID = (short) 0x9487;
-    private static final UUID CUSTOM_CHARACTERISTICS =
-            UUID.fromString("FE2C9487-8366-4814-8EB0-01DE32100BEA");
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void get16BitUuid() {
-        assertThat(BluetoothUuids.get16BitUuid(A2DP_SINK_CHARACTERISTICS))
-                .isEqualTo(A2DP_SINK_SHORT_UUID);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void is16BitUuid() {
-        assertThat(BluetoothUuids.is16BitUuid(A2DP_SINK_CHARACTERISTICS)).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void to128BitUuid() {
-        assertThat(BluetoothUuids.to128BitUuid(A2DP_SINK_SHORT_UUID))
-                .isEqualTo(A2DP_SINK_CHARACTERISTICS);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void toFastPair128BitUuid() {
-        assertThat(BluetoothUuids.toFastPair128BitUuid(CUSTOM_SHORT_UUID))
-                .isEqualTo(CUSTOM_CHARACTERISTICS);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
deleted file mode 100644
index f7ffa24..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
-import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.toFastPair128BitUuid;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-
-import java.util.UUID;
-
-/**
- * Unit tests for {@link Constants}.
- */
-public class ConstantsTest extends TestCase {
-
-    @Mock
-    private BluetoothGattConnection mMockGattConnection;
-
-    private static final UUID OLD_KEY_BASE_PAIRING_CHARACTERISTICS = to128BitUuid((short) 0x1234);
-
-    private static final UUID NEW_KEY_BASE_PAIRING_CHARACTERISTICS =
-            toFastPair128BitUuid((short) 0x1234);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        initMocks(this);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getId_whenSupportNewCharacteristics() throws BluetoothException {
-        when(mMockGattConnection.getCharacteristic(any(UUID.class), any(UUID.class)))
-                .thenReturn(new BluetoothGattCharacteristic(NEW_KEY_BASE_PAIRING_CHARACTERISTICS, 0,
-                        0));
-
-        assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
-                .isEqualTo(NEW_KEY_BASE_PAIRING_CHARACTERISTICS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getId_whenNotSupportNewCharacteristics() throws BluetoothException {
-        // {@link BluetoothGattConnection#getCharacteristic(UUID, UUID)} throws {@link
-        // BluetoothException} if the characteristic not found .
-        when(mMockGattConnection.getCharacteristic(any(UUID.class), any(UUID.class)))
-                .thenThrow(new BluetoothException(""));
-
-        assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
-                .isEqualTo(OLD_KEY_BASE_PAIRING_CHARACTERISTICS);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
deleted file mode 100644
index 3719783..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchangeTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.io.BaseEncoding.base64;
-import static com.google.common.primitives.Bytes.concat;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link EllipticCurveDiffieHellmanExchange}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class EllipticCurveDiffieHellmanExchangeTest {
-
-    public static final byte[] ANTI_SPOOF_PUBLIC_KEY = base64().decode(
-            "d2JTfvfdS6u7LmGfMOmco3C7ra3lW1k17AOly0LrBydDZURacfTYIMmo5K1ejfD9e8b6qHs"
-                    + "DTNzselhifi10kQ==");
-    public static final byte[] ANTI_SPOOF_PRIVATE_KEY =
-            base64().decode("Rn9GbLRPQTFc2O7WFVGkydzcUS9Tuj7R9rLh6EpLtuU=");
-
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void generateCommonKey() throws Exception {
-        EllipticCurveDiffieHellmanExchange bob = EllipticCurveDiffieHellmanExchange.create();
-        EllipticCurveDiffieHellmanExchange alice = EllipticCurveDiffieHellmanExchange.create();
-
-        assertThat(bob.getPublicKey()).isNotEqualTo(alice.getPublicKey());
-        assertThat(bob.getPrivateKey()).isNotEqualTo(alice.getPrivateKey());
-
-        assertThat(bob.generateSecret(alice.getPublicKey()))
-                .isEqualTo(alice.generateSecret(bob.getPublicKey()));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void generateCommonKey_withExistingPrivateKey() throws Exception {
-        EllipticCurveDiffieHellmanExchange bob = EllipticCurveDiffieHellmanExchange.create();
-        EllipticCurveDiffieHellmanExchange alice =
-                EllipticCurveDiffieHellmanExchange.create(ANTI_SPOOF_PRIVATE_KEY);
-
-        assertThat(alice.generateSecret(bob.getPublicKey()))
-                .isEqualTo(bob.generateSecret(ANTI_SPOOF_PUBLIC_KEY));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void generateCommonKey_soundcoreAntiSpoofingKey_generatedTooShort() throws Exception {
-        // This soundcore device has a public key that was generated which starts with 0x0. This was
-        // stripped out in our database, but this test confirms that adding that byte back fixes the
-        // issue and allows the generated secrets to match each other.
-        byte[] soundCorePublicKey = concat(new byte[]{0}, base64().decode(
-                "EYapuIsyw/nwHAdMxr12FCtAi4gY3EtuW06JuKDg4SA76IoIDVeol2vsGKy0Ea2Z00"
-                        + "ArOTiBDsk0L+4Xo9AA"));
-        byte[] soundCorePrivateKey = base64()
-                .decode("lW5idsrfX7cBC8kO/kKn3w3GXirqt9KnJoqXUcOMhjM=");
-        EllipticCurveDiffieHellmanExchange bob = EllipticCurveDiffieHellmanExchange.create();
-        EllipticCurveDiffieHellmanExchange alice =
-                EllipticCurveDiffieHellmanExchange.create(soundCorePrivateKey);
-
-        assertThat(alice.generateSecret(bob.getPublicKey()))
-                .isEqualTo(bob.generateSecret(soundCorePublicKey));
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
deleted file mode 100644
index 28e925f..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link Event}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class EventTest {
-
-    private static final String EXCEPTION_MESSAGE = "Test exception";
-    private static final long TIMESTAMP = 1234L;
-    private static final @EventCode int EVENT_CODE = EventCode.CREATE_BOND;
-    private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
-            .getRemoteDevice("11:22:33:44:55:66");
-    private static final Short PROFILE = (short) 1;
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void createAndReadFromParcel() {
-        Event event =
-                Event.builder()
-                        .setException(new Exception(EXCEPTION_MESSAGE))
-                        .setTimestamp(TIMESTAMP)
-                        .setEventCode(EVENT_CODE)
-                        .setBluetoothDevice(BLUETOOTH_DEVICE)
-                        .setProfile(PROFILE)
-                        .build();
-
-        Parcel parcel = Parcel.obtain();
-        event.writeToParcel(parcel, event.describeContents());
-        parcel.setDataPosition(0);
-        Event result = Event.CREATOR.createFromParcel(parcel);
-
-        assertThat(result.getException()).hasMessageThat()
-                .isEqualTo(event.getException().getMessage());
-        assertThat(result.getTimestamp()).isEqualTo(event.getTimestamp());
-        assertThat(result.getEventCode()).isEqualTo(event.getEventCode());
-        assertThat(result.getBluetoothDevice()).isEqualTo(event.getBluetoothDevice());
-        assertThat(result.getProfile()).isEqualTo(event.getProfile());
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
deleted file mode 100644
index a103a72..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.GATT_ERROR_CODE_TIMEOUT;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.appendMoreErrorCode;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyShort;
-import static org.mockito.Mockito.doNothing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.protobuf.ByteString;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-
-/**
- * Unit tests for {@link FastPairDualConnection}.
- */
-@Presubmit
-@SmallTest
-public class FastPairDualConnectionTest extends TestCase {
-
-    private static final String BLE_ADDRESS = "00:11:22:33:FF:EE";
-    private static final String MASKED_BLE_ADDRESS = "MASKED_BLE_ADDRESS";
-    private static final short[] PROFILES = {Constants.A2DP_SINK_SERVICE_UUID};
-    private static final int NUM_CONNECTION_ATTEMPTS = 1;
-    private static final boolean ENABLE_PAIRING_BEHAVIOR = true;
-    private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
-            .getRemoteDevice("11:22:33:44:55:66");
-    private static final String DEVICE_NAME = "DEVICE_NAME";
-    private static final byte[] ACCOUNT_KEY = new byte[]{1, 3};
-    private static final byte[] HASH_VALUE = new byte[]{7};
-
-    private TestEventLogger mEventLogger;
-    @Mock private TimingLogger mTimingLogger;
-    @Mock private BluetoothAudioPairer mBluetoothAudioPairer;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        BluetoothAudioPairer.enableTestMode();
-        FastPairDualConnection.enableTestMode();
-        MockitoAnnotations.initMocks(this);
-
-        doNothing().when(mBluetoothAudioPairer).connect(anyShort(), anyBoolean());
-        mEventLogger = new TestEventLogger();
-    }
-
-    private FastPairDualConnection newFastPairDualConnection(
-            String bleAddress, Preferences.Builder prefsBuilder) {
-        return new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                bleAddress,
-                prefsBuilder.build(),
-                mEventLogger,
-                mTimingLogger);
-    }
-
-    private FastPairDualConnection newFastPairDualConnection2(
-            String bleAddress, Preferences.Builder prefsBuilder) {
-        return new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                bleAddress,
-                prefsBuilder.build(),
-                mEventLogger);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testFastPairDualConnectionConstructor() {
-        assertThat(newFastPairDualConnection(BLE_ADDRESS, Preferences.builder())).isNotNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testFastPairDualConnectionConstructor2() {
-        assertThat(newFastPairDualConnection2(BLE_ADDRESS, Preferences.builder())).isNotNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAttemptConnectProfiles() {
-        try {
-            new FastPairDualConnection(
-                    ApplicationProvider.getApplicationContext(),
-                    BLE_ADDRESS,
-                    Preferences.builder().build(),
-                    mEventLogger,
-                    mTimingLogger)
-                    .attemptConnectProfiles(
-                            mBluetoothAudioPairer,
-                            MASKED_BLE_ADDRESS,
-                            PROFILES,
-                            NUM_CONNECTION_ATTEMPTS,
-                            ENABLE_PAIRING_BEHAVIOR);
-        } catch (PairingException e) {
-            // Mocked pair doesn't throw Pairing Exception.
-        }
-    }
-
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAppendMoreErrorCode_gattError() {
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
-                        new BluetoothGattException("Test", 133)))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED + 133);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
-                        new BluetoothGattException("Test", 257)))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED + 257);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED, new BluetoothException("Test")))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
-                        new BluetoothOperationTimeoutException("Test")))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED + GATT_ERROR_CODE_TIMEOUT);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST,
-                        new BluetoothGattException("Test", 41)))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST + 41);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST,
-                        new BluetoothGattException("Test", 788)))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST + 788);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST, new BluetoothException("Test")))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST);
-        assertThat(
-                appendMoreErrorCode(
-                        GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST,
-                        new BluetoothOperationTimeoutException("Test")))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST + GATT_ERROR_CODE_TIMEOUT);
-        assertThat(appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST, /* cause= */ null))
-                .isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testUnpairNotCrash() {
-        try {
-            new FastPairDualConnection(
-                    ApplicationProvider.getApplicationContext(),
-                    BLE_ADDRESS,
-                    Preferences.builder().build(),
-                    mEventLogger,
-                    mTimingLogger).unpair(BLUETOOTH_DEVICE);
-        } catch (ExecutionException | InterruptedException | ReflectionException
-                | TimeoutException | PairingException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSetFastPairHistory() {
-        new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().build(),
-                mEventLogger,
-                mTimingLogger).setFastPairHistory(ImmutableList.of());
-    }
-
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSetGetProviderDeviceName() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().build(),
-                mEventLogger,
-                mTimingLogger);
-        connection.setProviderDeviceName(DEVICE_NAME);
-        connection.getProviderDeviceName();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetExistingAccountKey() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().build(),
-                mEventLogger,
-                mTimingLogger);
-        connection.getExistingAccountKey();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testPair() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().setNumSdpAttempts(0)
-                        .setLogPairWithCachedModelId(false).build(),
-                mEventLogger,
-                mTimingLogger);
-        try {
-            connection.pair();
-        } catch (BluetoothException | InterruptedException | ReflectionException
-                | ExecutionException | TimeoutException | PairingException e) {
-        }
-    }
-
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetPublicAddress() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().setNumSdpAttempts(0)
-                        .setLogPairWithCachedModelId(false).build(),
-                mEventLogger,
-                mTimingLogger);
-        connection.getPublicAddress();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testShouldWriteAccountKeyForExistingCase() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().setNumSdpAttempts(0)
-                        .setLogPairWithCachedModelId(false).build(),
-                mEventLogger,
-                mTimingLogger);
-        connection.shouldWriteAccountKeyForExistingCase(ACCOUNT_KEY);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testReadFirmwareVersion() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().setNumSdpAttempts(0)
-                        .setLogPairWithCachedModelId(false).build(),
-                mEventLogger,
-                mTimingLogger);
-        try {
-            connection.readFirmwareVersion();
-        } catch (BluetoothException | InterruptedException | ExecutionException
-                | TimeoutException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHistoryItem() {
-        FastPairDualConnection connection = new FastPairDualConnection(
-                ApplicationProvider.getApplicationContext(),
-                BLE_ADDRESS,
-                Preferences.builder().setNumSdpAttempts(0)
-                        .setLogPairWithCachedModelId(false).build(),
-                mEventLogger,
-                mTimingLogger);
-        ImmutableList.Builder<FastPairHistoryItem> historyBuilder = ImmutableList.builder();
-        FastPairHistoryItem historyItem1 =
-                FastPairHistoryItem.create(
-                        ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(HASH_VALUE));
-        historyBuilder.add(historyItem1);
-
-        connection.setFastPairHistory(historyBuilder.build());
-        assertThat(connection.mPairedHistoryFinder.isInPairedHistory("11:22:33:44:55:88"))
-                .isFalse();
-    }
-
-    static class TestEventLogger implements EventLogger {
-
-        private List<Item> mLogs = new ArrayList<>();
-
-        @Override
-        public void logEventSucceeded(Event event) {
-            mLogs.add(new Item(event));
-        }
-
-        @Override
-        public void logEventFailed(Event event, Exception e) {
-            mLogs.add(new ItemFailed(event, e));
-        }
-
-        List<Item> getErrorLogs() {
-            return mLogs.stream().filter(item -> item instanceof ItemFailed)
-                    .collect(Collectors.toList());
-        }
-
-        List<Item> getLogs() {
-            return mLogs;
-        }
-
-        List<Item> getLast() {
-            return mLogs.subList(mLogs.size() - 1, mLogs.size());
-        }
-
-        BluetoothDevice getDevice() {
-            return Iterables.getLast(mLogs).mEvent.getBluetoothDevice();
-        }
-
-        public static class Item {
-
-            final Event mEvent;
-
-            Item(Event event) {
-                this.mEvent = event;
-            }
-
-            @Override
-            public String toString() {
-                return "Item{" + "event=" + mEvent + '}';
-            }
-        }
-
-        public static class ItemFailed extends Item {
-
-            final Exception mException;
-
-            ItemFailed(Event event, Exception e) {
-                super(event);
-                this.mException = e;
-            }
-
-            @Override
-            public String toString() {
-                return "ItemFailed{" + "event=" + mEvent + ", exception=" + mException + '}';
-            }
-        }
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
deleted file mode 100644
index b47fd89..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItemTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.primitives.Bytes.concat;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.hash.Hashing;
-import com.google.protobuf.ByteString;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link FastPairHistoryItem}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FastPairHistoryItemTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputMatchedPublicAddress_isMatchedReturnTrue() {
-        final byte[] accountKey = base16().decode("0123456789ABCDEF");
-        final byte[] publicAddress = BluetoothAddress.decode("11:22:33:44:55:66");
-        final byte[] hashValue =
-                Hashing.sha256().hashBytes(concat(accountKey, publicAddress)).asBytes();
-
-        FastPairHistoryItem historyItem =
-                FastPairHistoryItem
-                        .create(ByteString.copyFrom(accountKey), ByteString.copyFrom(hashValue));
-
-        assertThat(historyItem.isMatched(publicAddress)).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputNotMatchedPublicAddress_isMatchedReturnFalse() {
-        final byte[] accountKey = base16().decode("0123456789ABCDEF");
-        final byte[] publicAddress1 = BluetoothAddress.decode("11:22:33:44:55:66");
-        final byte[] publicAddress2 = BluetoothAddress.decode("11:22:33:44:55:77");
-        final byte[] hashValue =
-                Hashing.sha256().hashBytes(concat(accountKey, publicAddress1)).asBytes();
-
-        FastPairHistoryItem historyItem =
-                FastPairHistoryItem
-                        .create(ByteString.copyFrom(accountKey), ByteString.copyFrom(hashValue));
-
-        assertThat(historyItem.isMatched(publicAddress2)).isFalse();
-    }
-}
-
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java
deleted file mode 100644
index 2f80a30..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-
-import com.google.common.collect.ImmutableSet;
-
-import junit.framework.TestCase;
-
-import java.time.Duration;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Unit tests for {@link GattConnectionManager}.
- */
-@Presubmit
-@SmallTest
-public class GattConnectionManagerTest extends TestCase {
-
-    private static final String FAST_PAIR_ADDRESS = "BB:BB:BB:BB:BB:1E";
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        GattConnectionManager.enableTestMode();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectionManagerConstructor() throws Exception {
-        GattConnectionManager manager = createManager(Preferences.builder());
-        try {
-            manager.getConnection();
-        } catch (ExecutionException e) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIsNoRetryError() {
-        Preferences preferences =
-                Preferences.builder()
-                        .setGattConnectionAndSecretHandshakeNoRetryGattError(
-                                ImmutableSet.of(257, 999))
-                        .build();
-
-        assertThat(
-                GattConnectionManager.isNoRetryError(
-                        preferences, new BluetoothGattException("Test", 133)))
-                .isFalse();
-        assertThat(
-                GattConnectionManager.isNoRetryError(
-                        preferences, new BluetoothGattException("Test", 257)))
-                .isTrue();
-        assertThat(
-                GattConnectionManager.isNoRetryError(
-                        preferences, new BluetoothGattException("Test", 999)))
-                .isTrue();
-        assertThat(GattConnectionManager.isNoRetryError(
-                preferences, new BluetoothException("Test")))
-                .isFalse();
-        assertThat(
-                GattConnectionManager.isNoRetryError(
-                        preferences, new BluetoothOperationTimeoutException("Test")))
-                .isFalse();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetTimeoutNotOverShortRetryMaxSpentTimeGetShort() {
-        Preferences preferences = Preferences.builder().build();
-
-        assertThat(
-                createManager(Preferences.builder(), () -> {})
-                        .getTimeoutMs(
-                                preferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs() - 1))
-                .isEqualTo(preferences.getGattConnectShortTimeoutMs());
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetTimeoutOverShortRetryMaxSpentTimeGetLong() {
-        Preferences preferences = Preferences.builder().build();
-
-        assertThat(
-                createManager(Preferences.builder(), () -> {})
-                        .getTimeoutMs(
-                                preferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs() + 1))
-                .isEqualTo(preferences.getGattConnectLongTimeoutMs());
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetTimeoutRetryNotEnabledGetOrigin() {
-        Preferences preferences = Preferences.builder().build();
-
-        assertThat(
-                createManager(
-                        Preferences.builder().setRetryGattConnectionAndSecretHandshake(false),
-                        () -> {})
-                        .getTimeoutMs(0))
-                .isEqualTo(Duration.ofSeconds(
-                        preferences.getGattConnectionTimeoutSeconds()).toMillis());
-    }
-
-    private GattConnectionManager createManager(Preferences.Builder prefs) {
-        return createManager(prefs, () -> {});
-    }
-
-    private GattConnectionManager createManager(
-            Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) {
-        return createManager(prefs, toggleBluetooth,
-                /* fastPairSignalChecker= */ null);
-    }
-
-    private GattConnectionManager createManager(
-            Preferences.Builder prefs,
-            ToggleBluetoothTask toggleBluetooth,
-            @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
-        return new GattConnectionManager(
-                ApplicationProvider.getApplicationContext(),
-                prefs.build(),
-                new EventLoggerWrapper(null),
-                BluetoothAdapter.getDefaultAdapter(),
-                toggleBluetooth,
-                FAST_PAIR_ADDRESS,
-                new TimingLogger("GattConnectionManager", prefs.build()),
-                fastPairSignalChecker,
-                /* setMtu= */ false);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
deleted file mode 100644
index 670b2ca..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link HeadsetPiece}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HeadsetPieceTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void parcelAndUnparcel() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-        Parcel expectedParcel = Parcel.obtain();
-        headsetPiece.writeToParcel(expectedParcel, 0);
-        expectedParcel.setDataPosition(0);
-
-        HeadsetPiece fromParcel = HeadsetPiece.CREATOR.createFromParcel(expectedParcel);
-
-        assertThat(fromParcel).isEqualTo(headsetPiece);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void parcelAndUnparcel_nullImageContentUri() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().setImageContentUri(null).build();
-        Parcel expectedParcel = Parcel.obtain();
-        headsetPiece.writeToParcel(expectedParcel, 0);
-        expectedParcel.setDataPosition(0);
-
-        HeadsetPiece fromParcel = HeadsetPiece.CREATOR.createFromParcel(expectedParcel);
-
-        assertThat(fromParcel).isEqualTo(headsetPiece);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void equals() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().build();
-
-        assertThat(headsetPiece).isEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void equals_nullImageContentUri() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().setImageContentUri(null).build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().setImageContentUri(null).build();
-
-        assertThat(headsetPiece).isEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_differentLowLevelThreshold() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().setLowLevelThreshold(1).build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_differentBatteryLevel() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().setBatteryLevel(99).build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_differentImageUrl() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo =
-                createDefaultHeadset().setImageUrl("http://fake.image.path/different.png").build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_differentChargingState() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().setCharging(false).build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_differentImageContentUri() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo =
-                createDefaultHeadset().setImageContentUri(Uri.parse("content://different.png"))
-                        .build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notEquals_nullImageContentUri() {
-        HeadsetPiece headsetPiece = createDefaultHeadset().build();
-
-        HeadsetPiece compareTo = createDefaultHeadset().setImageContentUri(null).build();
-
-        assertThat(headsetPiece).isNotEqualTo(compareTo);
-    }
-
-    private static HeadsetPiece.Builder createDefaultHeadset() {
-        return HeadsetPiece.builder()
-                .setLowLevelThreshold(30)
-                .setBatteryLevel(18)
-                .setImageUrl("http://fake.image.path/image.png")
-                .setImageContentUri(Uri.parse("content://image.png"))
-                .setCharging(true);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void isLowBattery() {
-        HeadsetPiece headsetPiece =
-                HeadsetPiece.builder()
-                        .setLowLevelThreshold(30)
-                        .setBatteryLevel(18)
-                        .setImageUrl("http://fake.image.path/image.png")
-                        .setCharging(false)
-                        .build();
-
-        assertThat(headsetPiece.isBatteryLow()).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void isNotLowBattery() {
-        HeadsetPiece headsetPiece =
-                HeadsetPiece.builder()
-                        .setLowLevelThreshold(30)
-                        .setBatteryLevel(31)
-                        .setImageUrl("http://fake.image.path/image.png")
-                        .setCharging(false)
-                        .build();
-
-        assertThat(headsetPiece.isBatteryLow()).isFalse();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void isNotLowBattery_whileCharging() {
-        HeadsetPiece headsetPiece =
-                HeadsetPiece.builder()
-                        .setLowLevelThreshold(30)
-                        .setBatteryLevel(18)
-                        .setImageUrl("http://fake.image.path/image.png")
-                        .setCharging(true)
-                        .build();
-
-        assertThat(headsetPiece.isBatteryLow()).isFalse();
-    }
-}
-
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
deleted file mode 100644
index 8db3b97..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256Test.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.HmacSha256.HMAC_SHA256_BLOCK_SIZE;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.primitives.Bytes.concat;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.base.Preconditions;
-import com.google.common.hash.Hashing;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Random;
-
-/**
- * Unit tests for {@link HmacSha256}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HmacSha256Test {
-
-    private static final int EXTRACT_HMAC_SIZE = 8;
-    private static final byte OUTER_PADDING_BYTE = 0x5c;
-    private static final byte INNER_PADDING_BYTE = 0x36;
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void compareResultWithOurImplementation_mustBeIdentical()
-            throws GeneralSecurityException {
-        Random random = new Random(0xFE2C);
-
-        for (int i = 0; i < 1000; i++) {
-            byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-            // Avoid too small data size that may cause false alarm.
-            int dataLength = random.nextInt(64);
-            byte[] data = new byte[dataLength];
-            random.nextBytes(data);
-
-            assertThat(HmacSha256.build(secret, data)).isEqualTo(doHmacSha256(secret, data));
-        }
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToDecrypt_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        byte[] data = base16().decode("1234567890ABCDEF1234567890ABCDEF1234567890ABCD");
-
-        GeneralSecurityException exception =
-                assertThrows(GeneralSecurityException.class, () -> HmacSha256.build(secret, data));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Incorrect key length, should be the AES-128 key.");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTwoIdenticalArrays_compareTwoHmacMustReturnTrue() {
-        Random random = new Random(0x1237);
-        byte[] array1 = new byte[EXTRACT_HMAC_SIZE];
-        random.nextBytes(array1);
-        byte[] array2 = Arrays.copyOf(array1, array1.length);
-
-        assertThat(HmacSha256.compareTwoHMACs(array1, array2)).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTwoRandomArrays_compareTwoHmacMustReturnFalse() {
-        Random random = new Random(0xff);
-        byte[] array1 = new byte[EXTRACT_HMAC_SIZE];
-        random.nextBytes(array1);
-        byte[] array2 = new byte[EXTRACT_HMAC_SIZE];
-        random.nextBytes(array2);
-
-        assertThat(HmacSha256.compareTwoHMACs(array1, array2)).isFalse();
-    }
-
-    // HMAC-SHA256 may not be previously defined on Bluetooth platforms, so we explicitly create
-    // the code on test case. This will allow us to easily detect where partner implementation might
-    // have gone wrong or where our spec isn't clear enough.
-    static byte[] doHmacSha256(byte[] key, byte[] data) {
-
-        Preconditions.checkArgument(
-                key != null && key.length == KEY_LENGTH && data != null,
-                "Parameters can't be null.");
-
-        // Performs SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where
-        // key is the given secret extended to 64 bytes by concat(secret, ZEROS).
-        // opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c.
-        // ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36.
-        byte[] keyIpad = new byte[HMAC_SHA256_BLOCK_SIZE];
-        byte[] keyOpad = new byte[HMAC_SHA256_BLOCK_SIZE];
-
-        for (int i = 0; i < KEY_LENGTH; i++) {
-            keyIpad[i] = (byte) (key[i] ^ INNER_PADDING_BYTE);
-            keyOpad[i] = (byte) (key[i] ^ OUTER_PADDING_BYTE);
-        }
-        Arrays.fill(keyIpad, KEY_LENGTH, HMAC_SHA256_BLOCK_SIZE, INNER_PADDING_BYTE);
-        Arrays.fill(keyOpad, KEY_LENGTH, HMAC_SHA256_BLOCK_SIZE, OUTER_PADDING_BYTE);
-
-        byte[] innerSha256Result = Hashing.sha256().hashBytes(concat(keyIpad, data)).asBytes();
-        return Hashing.sha256().hashBytes(concat(keyOpad, innerSha256Result)).asBytes();
-    }
-
-    // Adds this test example on spec. Also we can easily change the parameters(e.g. secret, data)
-    // to clarify test results with partners.
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTestExampleToHmacSha256_getCorrectResult() {
-        byte[] secret = base16().decode("0123456789ABCDEF0123456789ABCDEF");
-        byte[] data =
-                base16().decode(
-                        "0001020304050607EE4A2483738052E44E9B2A145E5DDFAA44B9E5536AF438E1E5C6");
-
-        byte[] hmacResult = doHmacSha256(secret, data);
-
-        assertThat(hmacResult)
-                .isEqualTo(base16().decode(
-                        "55EC5E6055AF6E92618B7D8710D4413709AB5DA27CA26A66F52E5AD4E8209052"));
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
deleted file mode 100644
index d4c3342..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoderTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.EXTRACT_HMAC_SIZE;
-import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.SECTION_NONCE_LENGTH;
-
-import static com.google.common.primitives.Bytes.concat;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-/**
- * Unit tests for {@link MessageStreamHmacEncoder}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MessageStreamHmacEncoderTest {
-
-    private static final int ACCOUNT_KEY_LENGTH = 16;
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void encodeMessagePacket() throws GeneralSecurityException {
-        int messageLength = 2;
-        SecureRandom secureRandom = new SecureRandom();
-        byte[] accountKey = new byte[ACCOUNT_KEY_LENGTH];
-        secureRandom.nextBytes(accountKey);
-        byte[] data = new byte[messageLength];
-        secureRandom.nextBytes(data);
-        byte[] sectionNonce = new byte[SECTION_NONCE_LENGTH];
-        secureRandom.nextBytes(sectionNonce);
-
-        byte[] result = MessageStreamHmacEncoder
-                .encodeMessagePacket(accountKey, sectionNonce, data);
-
-        assertThat(result).hasLength(messageLength + SECTION_NONCE_LENGTH + EXTRACT_HMAC_SIZE);
-        // First bytes are raw message bytes.
-        assertThat(Arrays.copyOf(result, messageLength)).isEqualTo(data);
-        // Following by message nonce.
-        byte[] messageNonce =
-                Arrays.copyOfRange(result, messageLength, messageLength + SECTION_NONCE_LENGTH);
-        byte[] extractedHmac =
-                Arrays.copyOf(
-                        HmacSha256.buildWith64BytesKey(accountKey,
-                                concat(sectionNonce, messageNonce, data)),
-                        EXTRACT_HMAC_SIZE);
-        // Finally hash mac.
-        assertThat(Arrays.copyOfRange(result, messageLength + SECTION_NONCE_LENGTH, result.length))
-                .isEqualTo(extractedHmac);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void verifyHmac() throws GeneralSecurityException {
-        int messageLength = 2;
-        SecureRandom secureRandom = new SecureRandom();
-        byte[] accountKey = new byte[ACCOUNT_KEY_LENGTH];
-        secureRandom.nextBytes(accountKey);
-        byte[] data = new byte[messageLength];
-        secureRandom.nextBytes(data);
-        byte[] sectionNonce = new byte[SECTION_NONCE_LENGTH];
-        secureRandom.nextBytes(sectionNonce);
-        byte[] result = MessageStreamHmacEncoder
-                .encodeMessagePacket(accountKey, sectionNonce, data);
-
-        assertThat(MessageStreamHmacEncoder.verifyHmac(accountKey, sectionNonce, result)).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void verifyHmac_failedByAccountKey() throws GeneralSecurityException {
-        int messageLength = 2;
-        SecureRandom secureRandom = new SecureRandom();
-        byte[] accountKey = new byte[ACCOUNT_KEY_LENGTH];
-        secureRandom.nextBytes(accountKey);
-        byte[] data = new byte[messageLength];
-        secureRandom.nextBytes(data);
-        byte[] sectionNonce = new byte[SECTION_NONCE_LENGTH];
-        secureRandom.nextBytes(sectionNonce);
-        byte[] result = MessageStreamHmacEncoder
-                .encodeMessagePacket(accountKey, sectionNonce, data);
-        secureRandom.nextBytes(accountKey);
-
-        assertThat(MessageStreamHmacEncoder.verifyHmac(accountKey, sectionNonce, result)).isFalse();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void verifyHmac_failedBySectionNonce() throws GeneralSecurityException {
-        int messageLength = 2;
-        SecureRandom secureRandom = new SecureRandom();
-        byte[] accountKey = new byte[ACCOUNT_KEY_LENGTH];
-        secureRandom.nextBytes(accountKey);
-        byte[] data = new byte[messageLength];
-        secureRandom.nextBytes(data);
-        byte[] sectionNonce = new byte[SECTION_NONCE_LENGTH];
-        secureRandom.nextBytes(sectionNonce);
-        byte[] result = MessageStreamHmacEncoder
-                .encodeMessagePacket(accountKey, sectionNonce, data);
-        secureRandom.nextBytes(sectionNonce);
-
-        assertThat(MessageStreamHmacEncoder.verifyHmac(accountKey, sectionNonce, result)).isFalse();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
deleted file mode 100644
index d66d209..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoderTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE;
-import static com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder.EXTRACT_HMAC_SIZE;
-import static com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder.MAX_LENGTH_OF_NAME;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.security.GeneralSecurityException;
-
-/**
- * Unit tests for {@link NamingEncoder}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NamingEncoderTest {
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decodeEncodedNamingPacket_mustGetSameName() throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        String name = "Someone's Google Headphone";
-
-        byte[] encodedNamingPacket = NamingEncoder.encodeNamingPacket(secret, name);
-
-        assertThat(NamingEncoder.decodeNamingPacket(secret, encodedNamingPacket)).isEqualTo(name);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToEncode_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        String data = "Someone's Google Headphone";
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> NamingEncoder.encodeNamingPacket(secret, data));
-
-        assertThat(exception).hasMessageThat()
-                .contains("Incorrect secret for encoding name packet");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectKeySizeToDecode_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH - 1];
-        byte[] data = new byte[50];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> NamingEncoder.decodeNamingPacket(secret, data));
-
-        assertThat(exception).hasMessageThat()
-                .contains("Incorrect secret for decoding name packet");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTooSmallPacketSize_mustThrowException() {
-        byte[] secret = new byte[KEY_LENGTH];
-        byte[] data = new byte[EXTRACT_HMAC_SIZE - 1];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> NamingEncoder.decodeNamingPacket(secret, data));
-
-        assertThat(exception).hasMessageThat().contains("Naming packet size is incorrect");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputTooLargePacketSize_mustThrowException() throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        byte[] namingPacket = new byte[MAX_LENGTH_OF_NAME + EXTRACT_HMAC_SIZE + NONCE_SIZE + 1];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> NamingEncoder.decodeNamingPacket(secret, namingPacket));
-
-        assertThat(exception).hasMessageThat().contains("Naming packet size is incorrect");
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void inputIncorrectHmacToDecode_mustThrowException() throws GeneralSecurityException {
-        byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
-        String name = "Someone's Google Headphone";
-
-        byte[] encodedNamingPacket = NamingEncoder.encodeNamingPacket(secret, name);
-        encodedNamingPacket[0] = (byte) ~encodedNamingPacket[0];
-
-        GeneralSecurityException exception =
-                assertThrows(
-                        GeneralSecurityException.class,
-                        () -> NamingEncoder.decodeNamingPacket(secret, encodedNamingPacket));
-
-        assertThat(exception)
-                .hasMessageThat()
-                .contains("Verify HMAC failed, could be incorrect key or naming packet.");
-    }
-
-    // Adds this test example on spec. Also we can easily change the parameters(e.g. secret, naming
-    // packet) to clarify test results with partners.
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void decodeTestNamingPacket_mustGetSameName() throws GeneralSecurityException {
-        byte[] secret = base16().decode("0123456789ABCDEF0123456789ABCDEF");
-        byte[] namingPacket = base16().decode(
-                "55EC5E6055AF6E920001020304050607EE4A2483738052E44E9B2A145E5DDFAA44B9E5536AF438"
-                        + "E1E5C6");
-
-        assertThat(NamingEncoder.decodeNamingPacket(secret, namingPacket))
-                .isEqualTo("Someone's Google Headphone");
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
deleted file mode 100644
index b40a5a5..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java
+++ /dev/null
@@ -1,1277 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableSet;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link Preferences}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PreferencesTest {
-
-    private static final int FIRST_INT = 1505;
-    private static final int SECOND_INT = 1506;
-    private static final boolean FIRST_BOOL = true;
-    private static final boolean SECOND_BOOL = false;
-    private static final short FIRST_SHORT = 32;
-    private static final short SECOND_SHORT = 73;
-    private static final long FIRST_LONG = 9838L;
-    private static final long SECOND_LONG = 93935L;
-    private static final String FIRST_STRING = "FIRST_STRING";
-    private static final String SECOND_STRING = "SECOND_STRING";
-    private static final byte[] FIRST_BYTES = new byte[] {7, 9};
-    private static final byte[] SECOND_BYTES = new byte[] {2};
-    private static final ImmutableSet<Integer> FIRST_INT_SETS = ImmutableSet.of(6, 8);
-    private static final ImmutableSet<Integer> SECOND_INT_SETS = ImmutableSet.of(6, 8);
-    private static final Preferences.ExtraLoggingInformation FIRST_EXTRA_LOGGING_INFO =
-            Preferences.ExtraLoggingInformation.builder().setModelId("000006").build();
-    private static final Preferences.ExtraLoggingInformation SECOND_EXTRA_LOGGING_INFO =
-            Preferences.ExtraLoggingInformation.builder().setModelId("000007").build();
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattOperationTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setGattOperationTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getGattOperationTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getGattOperationTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattOperationTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getGattOperationTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectionTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectionTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getGattConnectionTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getGattConnectionTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectionTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getGattConnectionTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothToggleTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setBluetoothToggleTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getBluetoothToggleTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getBluetoothToggleTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBluetoothToggleTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getBluetoothToggleTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothToggleSleepSeconds() {
-        Preferences prefs =
-                Preferences.builder().setBluetoothToggleSleepSeconds(FIRST_INT).build();
-        assertThat(prefs.getBluetoothToggleSleepSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getBluetoothToggleSleepSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBluetoothToggleSleepSeconds(SECOND_INT).build();
-        assertThat(prefs2.getBluetoothToggleSleepSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testClassicDiscoveryTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setClassicDiscoveryTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getClassicDiscoveryTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getClassicDiscoveryTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setClassicDiscoveryTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getClassicDiscoveryTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumDiscoverAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumDiscoverAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumDiscoverAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumDiscoverAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumDiscoverAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumDiscoverAttempts()).isEqualTo(SECOND_INT);
-    }
-
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testDiscoveryRetrySleepSeconds() {
-        Preferences prefs =
-                Preferences.builder().setDiscoveryRetrySleepSeconds(FIRST_INT).build();
-        assertThat(prefs.getDiscoveryRetrySleepSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getDiscoveryRetrySleepSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setDiscoveryRetrySleepSeconds(SECOND_INT).build();
-        assertThat(prefs2.getDiscoveryRetrySleepSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSdpTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setSdpTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getSdpTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getSdpTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setSdpTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getSdpTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumSdpAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumSdpAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumSdpAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumSdpAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumSdpAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumSdpAttempts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumCreateBondAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumCreateBondAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumCreateBondAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumCreateBondAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumCreateBondAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumCreateBondAttempts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumConnectAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumConnectAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumConnectAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumConnectAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumConnectAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumConnectAttempts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumWriteAccountKeyAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumWriteAccountKeyAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumWriteAccountKeyAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumWriteAccountKeyAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumWriteAccountKeyAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumWriteAccountKeyAttempts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothStatePollingMillis() {
-        Preferences prefs =
-                Preferences.builder().setBluetoothStatePollingMillis(FIRST_INT).build();
-        assertThat(prefs.getBluetoothStatePollingMillis()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getBluetoothStatePollingMillis())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBluetoothStatePollingMillis(SECOND_INT).build();
-        assertThat(prefs2.getBluetoothStatePollingMillis()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumAttempts() {
-        Preferences prefs =
-                Preferences.builder().setNumAttempts(FIRST_INT).build();
-        assertThat(prefs.getNumAttempts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumAttempts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumAttempts(SECOND_INT).build();
-        assertThat(prefs2.getNumAttempts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRemoveBondTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setRemoveBondTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getRemoveBondTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getRemoveBondTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setRemoveBondTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getRemoveBondTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRemoveBondSleepMillis() {
-        Preferences prefs =
-                Preferences.builder().setRemoveBondSleepMillis(FIRST_INT).build();
-        assertThat(prefs.getRemoveBondSleepMillis()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getRemoveBondSleepMillis())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setRemoveBondSleepMillis(SECOND_INT).build();
-        assertThat(prefs2.getRemoveBondSleepMillis()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testCreateBondTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setCreateBondTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getCreateBondTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getCreateBondTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setCreateBondTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getCreateBondTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHidCreateBondTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setHidCreateBondTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getHidCreateBondTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getHidCreateBondTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setHidCreateBondTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getHidCreateBondTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testProxyTimeoutSeconds() {
-        Preferences prefs =
-                Preferences.builder().setProxyTimeoutSeconds(FIRST_INT).build();
-        assertThat(prefs.getProxyTimeoutSeconds()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getProxyTimeoutSeconds())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setProxyTimeoutSeconds(SECOND_INT).build();
-        assertThat(prefs2.getProxyTimeoutSeconds()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testWriteAccountKeySleepMillis() {
-        Preferences prefs =
-                Preferences.builder().setWriteAccountKeySleepMillis(FIRST_INT).build();
-        assertThat(prefs.getWriteAccountKeySleepMillis()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getWriteAccountKeySleepMillis())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setWriteAccountKeySleepMillis(SECOND_INT).build();
-        assertThat(prefs2.getWriteAccountKeySleepMillis()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testPairFailureCounts() {
-        Preferences prefs =
-                Preferences.builder().setPairFailureCounts(FIRST_INT).build();
-        assertThat(prefs.getPairFailureCounts()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getPairFailureCounts())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setPairFailureCounts(SECOND_INT).build();
-        assertThat(prefs2.getPairFailureCounts()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testCreateBondTransportType() {
-        Preferences prefs =
-                Preferences.builder().setCreateBondTransportType(FIRST_INT).build();
-        assertThat(prefs.getCreateBondTransportType()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getCreateBondTransportType())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setCreateBondTransportType(SECOND_INT).build();
-        assertThat(prefs2.getCreateBondTransportType()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectRetryTimeoutMillis() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectRetryTimeoutMillis(FIRST_INT).build();
-        assertThat(prefs.getGattConnectRetryTimeoutMillis()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getGattConnectRetryTimeoutMillis())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectRetryTimeoutMillis(SECOND_INT).build();
-        assertThat(prefs2.getGattConnectRetryTimeoutMillis()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNumSdpAttemptsAfterBonded() {
-        Preferences prefs =
-                Preferences.builder().setNumSdpAttemptsAfterBonded(FIRST_INT).build();
-        assertThat(prefs.getNumSdpAttemptsAfterBonded()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getNumSdpAttemptsAfterBonded())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setNumSdpAttemptsAfterBonded(SECOND_INT).build();
-        assertThat(prefs2.getNumSdpAttemptsAfterBonded()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSameModelIdPairedDeviceCount() {
-        Preferences prefs =
-                Preferences.builder().setSameModelIdPairedDeviceCount(FIRST_INT).build();
-        assertThat(prefs.getSameModelIdPairedDeviceCount()).isEqualTo(FIRST_INT);
-        assertThat(prefs.toBuilder().build().getSameModelIdPairedDeviceCount())
-                .isEqualTo(FIRST_INT);
-
-        Preferences prefs2 =
-                Preferences.builder().setSameModelIdPairedDeviceCount(SECOND_INT).build();
-        assertThat(prefs2.getSameModelIdPairedDeviceCount()).isEqualTo(SECOND_INT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIgnoreDiscoveryError() {
-        Preferences prefs =
-                Preferences.builder().setIgnoreDiscoveryError(FIRST_BOOL).build();
-        assertThat(prefs.getIgnoreDiscoveryError()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getIgnoreDiscoveryError())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setIgnoreDiscoveryError(SECOND_BOOL).build();
-        assertThat(prefs2.getIgnoreDiscoveryError()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testToggleBluetoothOnFailure() {
-        Preferences prefs =
-                Preferences.builder().setToggleBluetoothOnFailure(FIRST_BOOL).build();
-        assertThat(prefs.getToggleBluetoothOnFailure()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getToggleBluetoothOnFailure())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setToggleBluetoothOnFailure(SECOND_BOOL).build();
-        assertThat(prefs2.getToggleBluetoothOnFailure()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothStateUsesPolling() {
-        Preferences prefs =
-                Preferences.builder().setBluetoothStateUsesPolling(FIRST_BOOL).build();
-        assertThat(prefs.getBluetoothStateUsesPolling()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getBluetoothStateUsesPolling())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setBluetoothStateUsesPolling(SECOND_BOOL).build();
-        assertThat(prefs2.getBluetoothStateUsesPolling()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnableBrEdrHandover() {
-        Preferences prefs =
-                Preferences.builder().setEnableBrEdrHandover(FIRST_BOOL).build();
-        assertThat(prefs.getEnableBrEdrHandover()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnableBrEdrHandover())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnableBrEdrHandover(SECOND_BOOL).build();
-        assertThat(prefs2.getEnableBrEdrHandover()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testWaitForUuidsAfterBonding() {
-        Preferences prefs =
-                Preferences.builder().setWaitForUuidsAfterBonding(FIRST_BOOL).build();
-        assertThat(prefs.getWaitForUuidsAfterBonding()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getWaitForUuidsAfterBonding())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setWaitForUuidsAfterBonding(SECOND_BOOL).build();
-        assertThat(prefs2.getWaitForUuidsAfterBonding()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testReceiveUuidsAndBondedEventBeforeClose() {
-        Preferences prefs =
-                Preferences.builder().setReceiveUuidsAndBondedEventBeforeClose(FIRST_BOOL).build();
-        assertThat(prefs.getReceiveUuidsAndBondedEventBeforeClose()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getReceiveUuidsAndBondedEventBeforeClose())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setReceiveUuidsAndBondedEventBeforeClose(SECOND_BOOL).build();
-        assertThat(prefs2.getReceiveUuidsAndBondedEventBeforeClose()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRejectPhonebookAccess() {
-        Preferences prefs =
-                Preferences.builder().setRejectPhonebookAccess(FIRST_BOOL).build();
-        assertThat(prefs.getRejectPhonebookAccess()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getRejectPhonebookAccess())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setRejectPhonebookAccess(SECOND_BOOL).build();
-        assertThat(prefs2.getRejectPhonebookAccess()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRejectMessageAccess() {
-        Preferences prefs =
-                Preferences.builder().setRejectMessageAccess(FIRST_BOOL).build();
-        assertThat(prefs.getRejectMessageAccess()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getRejectMessageAccess())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setRejectMessageAccess(SECOND_BOOL).build();
-        assertThat(prefs2.getRejectMessageAccess()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRejectSimAccess() {
-        Preferences prefs =
-                Preferences.builder().setRejectSimAccess(FIRST_BOOL).build();
-        assertThat(prefs.getRejectSimAccess()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getRejectSimAccess())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setRejectSimAccess(SECOND_BOOL).build();
-        assertThat(prefs2.getRejectSimAccess()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSkipDisconnectingGattBeforeWritingAccountKey() {
-        Preferences prefs =
-                Preferences.builder().setSkipDisconnectingGattBeforeWritingAccountKey(FIRST_BOOL)
-                        .build();
-        assertThat(prefs.getSkipDisconnectingGattBeforeWritingAccountKey()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getSkipDisconnectingGattBeforeWritingAccountKey())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setSkipDisconnectingGattBeforeWritingAccountKey(SECOND_BOOL)
-                        .build();
-        assertThat(prefs2.getSkipDisconnectingGattBeforeWritingAccountKey()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testMoreEventLogForQuality() {
-        Preferences prefs =
-                Preferences.builder().setMoreEventLogForQuality(FIRST_BOOL).build();
-        assertThat(prefs.getMoreEventLogForQuality()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getMoreEventLogForQuality())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setMoreEventLogForQuality(SECOND_BOOL).build();
-        assertThat(prefs2.getMoreEventLogForQuality()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRetryGattConnectionAndSecretHandshake() {
-        Preferences prefs =
-                Preferences.builder().setRetryGattConnectionAndSecretHandshake(FIRST_BOOL).build();
-        assertThat(prefs.getRetryGattConnectionAndSecretHandshake()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getRetryGattConnectionAndSecretHandshake())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setRetryGattConnectionAndSecretHandshake(SECOND_BOOL).build();
-        assertThat(prefs2.getRetryGattConnectionAndSecretHandshake()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testRetrySecretHandshakeTimeout() {
-        Preferences prefs =
-                Preferences.builder().setRetrySecretHandshakeTimeout(FIRST_BOOL).build();
-        assertThat(prefs.getRetrySecretHandshakeTimeout()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getRetrySecretHandshakeTimeout())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setRetrySecretHandshakeTimeout(SECOND_BOOL).build();
-        assertThat(prefs2.getRetrySecretHandshakeTimeout()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testLogUserManualRetry() {
-        Preferences prefs =
-                Preferences.builder().setLogUserManualRetry(FIRST_BOOL).build();
-        assertThat(prefs.getLogUserManualRetry()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getLogUserManualRetry())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setLogUserManualRetry(SECOND_BOOL).build();
-        assertThat(prefs2.getLogUserManualRetry()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIsDeviceFinishCheckAddressFromCache() {
-        Preferences prefs =
-                Preferences.builder().setIsDeviceFinishCheckAddressFromCache(FIRST_BOOL).build();
-        assertThat(prefs.getIsDeviceFinishCheckAddressFromCache()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getIsDeviceFinishCheckAddressFromCache())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setIsDeviceFinishCheckAddressFromCache(SECOND_BOOL).build();
-        assertThat(prefs2.getIsDeviceFinishCheckAddressFromCache()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testLogPairWithCachedModelId() {
-        Preferences prefs =
-                Preferences.builder().setLogPairWithCachedModelId(FIRST_BOOL).build();
-        assertThat(prefs.getLogPairWithCachedModelId()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getLogPairWithCachedModelId())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setLogPairWithCachedModelId(SECOND_BOOL).build();
-        assertThat(prefs2.getLogPairWithCachedModelId()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testDirectConnectProfileIfModelIdInCache() {
-        Preferences prefs =
-                Preferences.builder().setDirectConnectProfileIfModelIdInCache(FIRST_BOOL).build();
-        assertThat(prefs.getDirectConnectProfileIfModelIdInCache()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getDirectConnectProfileIfModelIdInCache())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setDirectConnectProfileIfModelIdInCache(SECOND_BOOL).build();
-        assertThat(prefs2.getDirectConnectProfileIfModelIdInCache()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAcceptPasskey() {
-        Preferences prefs =
-                Preferences.builder().setAcceptPasskey(FIRST_BOOL).build();
-        assertThat(prefs.getAcceptPasskey()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getAcceptPasskey())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setAcceptPasskey(SECOND_BOOL).build();
-        assertThat(prefs2.getAcceptPasskey()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testProviderInitiatesBondingIfSupported() {
-        Preferences prefs =
-                Preferences.builder().setProviderInitiatesBondingIfSupported(FIRST_BOOL).build();
-        assertThat(prefs.getProviderInitiatesBondingIfSupported()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getProviderInitiatesBondingIfSupported())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setProviderInitiatesBondingIfSupported(SECOND_BOOL).build();
-        assertThat(prefs2.getProviderInitiatesBondingIfSupported()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAttemptDirectConnectionWhenPreviouslyBonded() {
-        Preferences prefs =
-                Preferences.builder()
-                        .setAttemptDirectConnectionWhenPreviouslyBonded(FIRST_BOOL).build();
-        assertThat(prefs.getAttemptDirectConnectionWhenPreviouslyBonded()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getAttemptDirectConnectionWhenPreviouslyBonded())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder()
-                        .setAttemptDirectConnectionWhenPreviouslyBonded(SECOND_BOOL).build();
-        assertThat(prefs2.getAttemptDirectConnectionWhenPreviouslyBonded()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAutomaticallyReconnectGattWhenNeeded() {
-        Preferences prefs =
-                Preferences.builder().setAutomaticallyReconnectGattWhenNeeded(FIRST_BOOL).build();
-        assertThat(prefs.getAutomaticallyReconnectGattWhenNeeded()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getAutomaticallyReconnectGattWhenNeeded())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setAutomaticallyReconnectGattWhenNeeded(SECOND_BOOL).build();
-        assertThat(prefs2.getAutomaticallyReconnectGattWhenNeeded()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSkipConnectingProfiles() {
-        Preferences prefs =
-                Preferences.builder().setSkipConnectingProfiles(FIRST_BOOL).build();
-        assertThat(prefs.getSkipConnectingProfiles()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getSkipConnectingProfiles())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setSkipConnectingProfiles(SECOND_BOOL).build();
-        assertThat(prefs2.getSkipConnectingProfiles()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIgnoreUuidTimeoutAfterBonded() {
-        Preferences prefs =
-                Preferences.builder().setIgnoreUuidTimeoutAfterBonded(FIRST_BOOL).build();
-        assertThat(prefs.getIgnoreUuidTimeoutAfterBonded()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getIgnoreUuidTimeoutAfterBonded())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setIgnoreUuidTimeoutAfterBonded(SECOND_BOOL).build();
-        assertThat(prefs2.getIgnoreUuidTimeoutAfterBonded()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSpecifyCreateBondTransportType() {
-        Preferences prefs =
-                Preferences.builder().setSpecifyCreateBondTransportType(FIRST_BOOL).build();
-        assertThat(prefs.getSpecifyCreateBondTransportType()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getSpecifyCreateBondTransportType())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setSpecifyCreateBondTransportType(SECOND_BOOL).build();
-        assertThat(prefs2.getSpecifyCreateBondTransportType()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIncreaseIntentFilterPriority() {
-        Preferences prefs =
-                Preferences.builder().setIncreaseIntentFilterPriority(FIRST_BOOL).build();
-        assertThat(prefs.getIncreaseIntentFilterPriority()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getIncreaseIntentFilterPriority())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setIncreaseIntentFilterPriority(SECOND_BOOL).build();
-        assertThat(prefs2.getIncreaseIntentFilterPriority()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEvaluatePerformance() {
-        Preferences prefs =
-                Preferences.builder().setEvaluatePerformance(FIRST_BOOL).build();
-        assertThat(prefs.getEvaluatePerformance()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEvaluatePerformance())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEvaluatePerformance(SECOND_BOOL).build();
-        assertThat(prefs2.getEvaluatePerformance()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnableNamingCharacteristic() {
-        Preferences prefs =
-                Preferences.builder().setEnableNamingCharacteristic(FIRST_BOOL).build();
-        assertThat(prefs.getEnableNamingCharacteristic()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnableNamingCharacteristic())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnableNamingCharacteristic(SECOND_BOOL).build();
-        assertThat(prefs2.getEnableNamingCharacteristic()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnableFirmwareVersionCharacteristic() {
-        Preferences prefs =
-                Preferences.builder().setEnableFirmwareVersionCharacteristic(FIRST_BOOL).build();
-        assertThat(prefs.getEnableFirmwareVersionCharacteristic()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnableFirmwareVersionCharacteristic())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnableFirmwareVersionCharacteristic(SECOND_BOOL).build();
-        assertThat(prefs2.getEnableFirmwareVersionCharacteristic()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testKeepSameAccountKeyWrite() {
-        Preferences prefs =
-                Preferences.builder().setKeepSameAccountKeyWrite(FIRST_BOOL).build();
-        assertThat(prefs.getKeepSameAccountKeyWrite()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getKeepSameAccountKeyWrite())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setKeepSameAccountKeyWrite(SECOND_BOOL).build();
-        assertThat(prefs2.getKeepSameAccountKeyWrite()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testIsRetroactivePairing() {
-        Preferences prefs =
-                Preferences.builder().setIsRetroactivePairing(FIRST_BOOL).build();
-        assertThat(prefs.getIsRetroactivePairing()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getIsRetroactivePairing())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setIsRetroactivePairing(SECOND_BOOL).build();
-        assertThat(prefs2.getIsRetroactivePairing()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSupportHidDevice() {
-        Preferences prefs =
-                Preferences.builder().setSupportHidDevice(FIRST_BOOL).build();
-        assertThat(prefs.getSupportHidDevice()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getSupportHidDevice())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setSupportHidDevice(SECOND_BOOL).build();
-        assertThat(prefs2.getSupportHidDevice()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnablePairingWhileDirectlyConnecting() {
-        Preferences prefs =
-                Preferences.builder().setEnablePairingWhileDirectlyConnecting(FIRST_BOOL).build();
-        assertThat(prefs.getEnablePairingWhileDirectlyConnecting()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnablePairingWhileDirectlyConnecting())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnablePairingWhileDirectlyConnecting(SECOND_BOOL).build();
-        assertThat(prefs2.getEnablePairingWhileDirectlyConnecting()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAcceptConsentForFastPairOne() {
-        Preferences prefs =
-                Preferences.builder().setAcceptConsentForFastPairOne(FIRST_BOOL).build();
-        assertThat(prefs.getAcceptConsentForFastPairOne()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getAcceptConsentForFastPairOne())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setAcceptConsentForFastPairOne(SECOND_BOOL).build();
-        assertThat(prefs2.getAcceptConsentForFastPairOne()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnable128BitCustomGattCharacteristicsId() {
-        Preferences prefs =
-                Preferences.builder().setEnable128BitCustomGattCharacteristicsId(FIRST_BOOL)
-                        .build();
-        assertThat(prefs.getEnable128BitCustomGattCharacteristicsId()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnable128BitCustomGattCharacteristicsId())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnable128BitCustomGattCharacteristicsId(SECOND_BOOL)
-                        .build();
-        assertThat(prefs2.getEnable128BitCustomGattCharacteristicsId()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnableSendExceptionStepToValidator() {
-        Preferences prefs =
-                Preferences.builder().setEnableSendExceptionStepToValidator(FIRST_BOOL).build();
-        assertThat(prefs.getEnableSendExceptionStepToValidator()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnableSendExceptionStepToValidator())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnableSendExceptionStepToValidator(SECOND_BOOL).build();
-        assertThat(prefs2.getEnableSendExceptionStepToValidator()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnableAdditionalDataTypeWhenActionOverBle() {
-        Preferences prefs =
-                Preferences.builder().setEnableAdditionalDataTypeWhenActionOverBle(FIRST_BOOL)
-                        .build();
-        assertThat(prefs.getEnableAdditionalDataTypeWhenActionOverBle()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnableAdditionalDataTypeWhenActionOverBle())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnableAdditionalDataTypeWhenActionOverBle(SECOND_BOOL)
-                        .build();
-        assertThat(prefs2.getEnableAdditionalDataTypeWhenActionOverBle()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testCheckBondStateWhenSkipConnectingProfiles() {
-        Preferences prefs =
-                Preferences.builder().setCheckBondStateWhenSkipConnectingProfiles(FIRST_BOOL)
-                        .build();
-        assertThat(prefs.getCheckBondStateWhenSkipConnectingProfiles()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getCheckBondStateWhenSkipConnectingProfiles())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setCheckBondStateWhenSkipConnectingProfiles(SECOND_BOOL)
-                        .build();
-        assertThat(prefs2.getCheckBondStateWhenSkipConnectingProfiles()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHandlePasskeyConfirmationByUi() {
-        Preferences prefs =
-                Preferences.builder().setHandlePasskeyConfirmationByUi(FIRST_BOOL).build();
-        assertThat(prefs.getHandlePasskeyConfirmationByUi()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getHandlePasskeyConfirmationByUi())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setHandlePasskeyConfirmationByUi(SECOND_BOOL).build();
-        assertThat(prefs2.getHandlePasskeyConfirmationByUi()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testEnablePairFlowShowUiWithoutProfileConnection() {
-        Preferences prefs =
-                Preferences.builder().setEnablePairFlowShowUiWithoutProfileConnection(FIRST_BOOL)
-                        .build();
-        assertThat(prefs.getEnablePairFlowShowUiWithoutProfileConnection()).isEqualTo(FIRST_BOOL);
-        assertThat(prefs.toBuilder().build().getEnablePairFlowShowUiWithoutProfileConnection())
-                .isEqualTo(FIRST_BOOL);
-
-        Preferences prefs2 =
-                Preferences.builder().setEnablePairFlowShowUiWithoutProfileConnection(SECOND_BOOL)
-                        .build();
-        assertThat(prefs2.getEnablePairFlowShowUiWithoutProfileConnection()).isEqualTo(SECOND_BOOL);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBrHandoverDataCharacteristicId() {
-        Preferences prefs =
-                Preferences.builder().setBrHandoverDataCharacteristicId(FIRST_SHORT).build();
-        assertThat(prefs.getBrHandoverDataCharacteristicId()).isEqualTo(FIRST_SHORT);
-        assertThat(prefs.toBuilder().build().getBrHandoverDataCharacteristicId())
-                .isEqualTo(FIRST_SHORT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBrHandoverDataCharacteristicId(SECOND_SHORT).build();
-        assertThat(prefs2.getBrHandoverDataCharacteristicId()).isEqualTo(SECOND_SHORT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothSigDataCharacteristicId() {
-        Preferences prefs =
-                Preferences.builder().setBluetoothSigDataCharacteristicId(FIRST_SHORT).build();
-        assertThat(prefs.getBluetoothSigDataCharacteristicId()).isEqualTo(FIRST_SHORT);
-        assertThat(prefs.toBuilder().build().getBluetoothSigDataCharacteristicId())
-                .isEqualTo(FIRST_SHORT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBluetoothSigDataCharacteristicId(SECOND_SHORT).build();
-        assertThat(prefs2.getBluetoothSigDataCharacteristicId()).isEqualTo(SECOND_SHORT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testFirmwareVersionCharacteristicId() {
-        Preferences prefs =
-                Preferences.builder().setFirmwareVersionCharacteristicId(FIRST_SHORT).build();
-        assertThat(prefs.getFirmwareVersionCharacteristicId()).isEqualTo(FIRST_SHORT);
-        assertThat(prefs.toBuilder().build().getFirmwareVersionCharacteristicId())
-                .isEqualTo(FIRST_SHORT);
-
-        Preferences prefs2 =
-                Preferences.builder().setFirmwareVersionCharacteristicId(SECOND_SHORT).build();
-        assertThat(prefs2.getFirmwareVersionCharacteristicId()).isEqualTo(SECOND_SHORT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBrTransportBlockDataDescriptorId() {
-        Preferences prefs =
-                Preferences.builder().setBrTransportBlockDataDescriptorId(FIRST_SHORT).build();
-        assertThat(prefs.getBrTransportBlockDataDescriptorId()).isEqualTo(FIRST_SHORT);
-        assertThat(prefs.toBuilder().build().getBrTransportBlockDataDescriptorId())
-                .isEqualTo(FIRST_SHORT);
-
-        Preferences prefs2 =
-                Preferences.builder().setBrTransportBlockDataDescriptorId(SECOND_SHORT).build();
-        assertThat(prefs2.getBrTransportBlockDataDescriptorId()).isEqualTo(SECOND_SHORT);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectShortTimeoutMs() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectShortTimeoutMs(FIRST_LONG).build();
-        assertThat(prefs.getGattConnectShortTimeoutMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getGattConnectShortTimeoutMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectShortTimeoutMs(SECOND_LONG).build();
-        assertThat(prefs2.getGattConnectShortTimeoutMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectLongTimeoutMs() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectLongTimeoutMs(FIRST_LONG).build();
-        assertThat(prefs.getGattConnectLongTimeoutMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getGattConnectLongTimeoutMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectLongTimeoutMs(SECOND_LONG).build();
-        assertThat(prefs2.getGattConnectLongTimeoutMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectShortTimeoutRetryMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectShortTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
-                        .build();
-        assertThat(prefs.getGattConnectShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getGattConnectShortTimeoutRetryMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectShortTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
-                        .build();
-        assertThat(prefs2.getGattConnectShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testAddressRotateRetryMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder().setAddressRotateRetryMaxSpentTimeMs(FIRST_LONG).build();
-        assertThat(prefs.getAddressRotateRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getAddressRotateRetryMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setAddressRotateRetryMaxSpentTimeMs(SECOND_LONG).build();
-        assertThat(prefs2.getAddressRotateRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testPairingRetryDelayMs() {
-        Preferences prefs =
-                Preferences.builder().setPairingRetryDelayMs(FIRST_LONG).build();
-        assertThat(prefs.getPairingRetryDelayMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getPairingRetryDelayMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setPairingRetryDelayMs(SECOND_LONG).build();
-        assertThat(prefs2.getPairingRetryDelayMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeShortTimeoutMs() {
-        Preferences prefs =
-                Preferences.builder().setSecretHandshakeShortTimeoutMs(FIRST_LONG).build();
-        assertThat(prefs.getSecretHandshakeShortTimeoutMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeShortTimeoutMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeShortTimeoutMs(SECOND_LONG).build();
-        assertThat(prefs2.getSecretHandshakeShortTimeoutMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeLongTimeoutMs() {
-        Preferences prefs =
-                Preferences.builder().setSecretHandshakeLongTimeoutMs(FIRST_LONG).build();
-        assertThat(prefs.getSecretHandshakeLongTimeoutMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeLongTimeoutMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeLongTimeoutMs(SECOND_LONG).build();
-        assertThat(prefs2.getSecretHandshakeLongTimeoutMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder().setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
-                        .build();
-        assertThat(prefs.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
-                        .build();
-        assertThat(prefs2.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs())
-                .isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeLongTimeoutRetryMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder().setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(FIRST_LONG)
-                        .build();
-        assertThat(prefs.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(SECOND_LONG)
-                        .build();
-        assertThat(prefs2.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs())
-                .isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeRetryAttempts() {
-        Preferences prefs =
-                Preferences.builder().setSecretHandshakeRetryAttempts(FIRST_LONG).build();
-        assertThat(prefs.getSecretHandshakeRetryAttempts()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeRetryAttempts())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeRetryAttempts(SECOND_LONG).build();
-        assertThat(prefs2.getSecretHandshakeRetryAttempts()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSecretHandshakeRetryGattConnectionMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder()
-                        .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(FIRST_LONG).build();
-        assertThat(prefs.getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(
-                        SECOND_LONG).build();
-        assertThat(prefs2.getSecretHandshakeRetryGattConnectionMaxSpentTimeMs())
-                .isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSignalLostRetryMaxSpentTimeMs() {
-        Preferences prefs =
-                Preferences.builder().setSignalLostRetryMaxSpentTimeMs(FIRST_LONG).build();
-        assertThat(prefs.getSignalLostRetryMaxSpentTimeMs()).isEqualTo(FIRST_LONG);
-        assertThat(prefs.toBuilder().build().getSignalLostRetryMaxSpentTimeMs())
-                .isEqualTo(FIRST_LONG);
-
-        Preferences prefs2 =
-                Preferences.builder().setSignalLostRetryMaxSpentTimeMs(SECOND_LONG).build();
-        assertThat(prefs2.getSignalLostRetryMaxSpentTimeMs()).isEqualTo(SECOND_LONG);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testCachedDeviceAddress() {
-        Preferences prefs =
-                Preferences.builder().setCachedDeviceAddress(FIRST_STRING).build();
-        assertThat(prefs.getCachedDeviceAddress()).isEqualTo(FIRST_STRING);
-        assertThat(prefs.toBuilder().build().getCachedDeviceAddress())
-                .isEqualTo(FIRST_STRING);
-
-        Preferences prefs2 =
-                Preferences.builder().setCachedDeviceAddress(SECOND_STRING).build();
-        assertThat(prefs2.getCachedDeviceAddress()).isEqualTo(SECOND_STRING);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testPossibleCachedDeviceAddress() {
-        Preferences prefs =
-                Preferences.builder().setPossibleCachedDeviceAddress(FIRST_STRING).build();
-        assertThat(prefs.getPossibleCachedDeviceAddress()).isEqualTo(FIRST_STRING);
-        assertThat(prefs.toBuilder().build().getPossibleCachedDeviceAddress())
-                .isEqualTo(FIRST_STRING);
-
-        Preferences prefs2 =
-                Preferences.builder().setPossibleCachedDeviceAddress(SECOND_STRING).build();
-        assertThat(prefs2.getPossibleCachedDeviceAddress()).isEqualTo(SECOND_STRING);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSupportedProfileUuids() {
-        Preferences prefs =
-                Preferences.builder().setSupportedProfileUuids(FIRST_BYTES).build();
-        assertThat(prefs.getSupportedProfileUuids()).isEqualTo(FIRST_BYTES);
-        assertThat(prefs.toBuilder().build().getSupportedProfileUuids())
-                .isEqualTo(FIRST_BYTES);
-
-        Preferences prefs2 =
-                Preferences.builder().setSupportedProfileUuids(SECOND_BYTES).build();
-        assertThat(prefs2.getSupportedProfileUuids()).isEqualTo(SECOND_BYTES);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGattConnectionAndSecretHandshakeNoRetryGattError() {
-        Preferences prefs =
-                Preferences.builder().setGattConnectionAndSecretHandshakeNoRetryGattError(
-                        FIRST_INT_SETS).build();
-        assertThat(prefs.getGattConnectionAndSecretHandshakeNoRetryGattError())
-                .isEqualTo(FIRST_INT_SETS);
-        assertThat(prefs.toBuilder().build().getGattConnectionAndSecretHandshakeNoRetryGattError())
-                .isEqualTo(FIRST_INT_SETS);
-
-        Preferences prefs2 =
-                Preferences.builder().setGattConnectionAndSecretHandshakeNoRetryGattError(
-                        SECOND_INT_SETS).build();
-        assertThat(prefs2.getGattConnectionAndSecretHandshakeNoRetryGattError())
-                .isEqualTo(SECOND_INT_SETS);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testExtraLoggingInformation() {
-        Preferences prefs =
-                Preferences.builder().setExtraLoggingInformation(FIRST_EXTRA_LOGGING_INFO).build();
-        assertThat(prefs.getExtraLoggingInformation()).isEqualTo(FIRST_EXTRA_LOGGING_INFO);
-        assertThat(prefs.toBuilder().build().getExtraLoggingInformation())
-                .isEqualTo(FIRST_EXTRA_LOGGING_INFO);
-
-        Preferences prefs2 =
-                Preferences.builder().setExtraLoggingInformation(SECOND_EXTRA_LOGGING_INFO).build();
-        assertThat(prefs2.getExtraLoggingInformation()).isEqualTo(SECOND_EXTRA_LOGGING_INFO);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
deleted file mode 100644
index 4672905..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TimingLoggerTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming;
-import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.Timing;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link TimingLogger}.
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TimingLoggerTest {
-
-    private final Preferences mPrefs = Preferences.builder().setEvaluatePerformance(true).build();
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void logPairedTiming() {
-        String label = "start";
-        TimingLogger timingLogger = new TimingLogger("paired", mPrefs);
-        timingLogger.start(label);
-        SystemClock.sleep(1000);
-        timingLogger.end();
-
-        assertThat(timingLogger.getTimings()).hasSize(2);
-
-        // Calculate execution time and only store result at "start" timing.
-        // Expected output:
-        // <pre>
-        //  I/FastPair: paired [Exclusive time] / [Total time]
-        //  I/FastPair:   start 1000ms
-        //  I/FastPair: paired end, 1000ms
-        // </pre>
-        timingLogger.dump();
-
-        assertPairedTiming(label, timingLogger.getTimings().get(0),
-                timingLogger.getTimings().get(1));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void logScopedTiming() {
-        String label = "scopedTiming";
-        TimingLogger timingLogger = new TimingLogger("scoped", mPrefs);
-        try (ScopedTiming scopedTiming = new ScopedTiming(timingLogger, label)) {
-            SystemClock.sleep(1000);
-        }
-
-        assertThat(timingLogger.getTimings()).hasSize(2);
-
-        // Calculate execution time and only store result at "start" timings.
-        // Expected output:
-        // <pre>
-        //  I/FastPair: scoped [Exclusive time] / [Total time]
-        //  I/FastPair:   scopedTiming 1000ms
-        //  I/FastPair: scoped end, 1000ms
-        // </pre>
-        timingLogger.dump();
-
-        assertPairedTiming(label, timingLogger.getTimings().get(0),
-                timingLogger.getTimings().get(1));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void logOrderedTiming() {
-        String label1 = "t1";
-        String label2 = "t2";
-        TimingLogger timingLogger = new TimingLogger("ordered", mPrefs);
-        try (ScopedTiming t1 = new ScopedTiming(timingLogger, label1)) {
-            SystemClock.sleep(1000);
-        }
-        try (ScopedTiming t2 = new ScopedTiming(timingLogger, label2)) {
-            SystemClock.sleep(1000);
-        }
-
-        assertThat(timingLogger.getTimings()).hasSize(4);
-
-        // Calculate execution time and only store result at "start" timings.
-        // Expected output:
-        // <pre>
-        //  I/FastPair: ordered [Exclusive time] / [Total time]
-        //  I/FastPair:   t1 1000ms
-        //  I/FastPair:   t2 1000ms
-        //  I/FastPair: ordered end, 2000ms
-        // </pre>
-        timingLogger.dump();
-
-        // We expect get timings in this order: t1 start, t1 end, t2 start, t2 end.
-        Timing start1 = timingLogger.getTimings().get(0);
-        Timing end1 = timingLogger.getTimings().get(1);
-        Timing start2 = timingLogger.getTimings().get(2);
-        Timing end2 = timingLogger.getTimings().get(3);
-
-        // Verify the paired timings.
-        assertPairedTiming(label1, start1, end1);
-        assertPairedTiming(label2, start2, end2);
-
-        // Verify the order and total time.
-        assertOrderedTiming(start1, start2);
-        assertThat(start1.getExclusiveTime() + start2.getExclusiveTime())
-                .isEqualTo(timingLogger.getTotalTime());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void logNestedTiming() {
-        String labelOuter = "outer";
-        String labelInner1 = "inner1";
-        String labelInner1Inner1 = "inner1inner1";
-        String labelInner2 = "inner2";
-        TimingLogger timingLogger = new TimingLogger("nested", mPrefs);
-        try (ScopedTiming outer = new ScopedTiming(timingLogger, labelOuter)) {
-            SystemClock.sleep(1000);
-            try (ScopedTiming inner1 = new ScopedTiming(timingLogger, labelInner1)) {
-                SystemClock.sleep(1000);
-                try (ScopedTiming inner1inner1 = new ScopedTiming(timingLogger,
-                        labelInner1Inner1)) {
-                    SystemClock.sleep(1000);
-                }
-            }
-            try (ScopedTiming inner2 = new ScopedTiming(timingLogger, labelInner2)) {
-                SystemClock.sleep(1000);
-            }
-        }
-
-        assertThat(timingLogger.getTimings()).hasSize(8);
-
-        // Calculate execution time and only store result at "start" timing.
-        // Expected output:
-        // <pre>
-        //  I/FastPair: nested [Exclusive time] / [Total time]
-        //  I/FastPair:   outer 1000ms / 4000ms
-        //  I/FastPair:     inner1 1000ms / 2000ms
-        //  I/FastPair:       inner1inner1 1000ms
-        //  I/FastPair:     inner2 1000ms
-        //  I/FastPair: nested end, 4000ms
-        // </pre>
-        timingLogger.dump();
-
-        // We expect get timings in this order: outer start, inner1 start, inner1inner1 start,
-        // inner1inner1 end, inner1 end, inner2 start, inner2 end, outer end.
-        Timing startOuter = timingLogger.getTimings().get(0);
-        Timing startInner1 = timingLogger.getTimings().get(1);
-        Timing startInner1Inner1 = timingLogger.getTimings().get(2);
-        Timing endInner1Inner1 = timingLogger.getTimings().get(3);
-        Timing endInner1 = timingLogger.getTimings().get(4);
-        Timing startInner2 = timingLogger.getTimings().get(5);
-        Timing endInner2 = timingLogger.getTimings().get(6);
-        Timing endOuter = timingLogger.getTimings().get(7);
-
-        // Verify the paired timings.
-        assertPairedTiming(labelOuter, startOuter, endOuter);
-        assertPairedTiming(labelInner1, startInner1, endInner1);
-        assertPairedTiming(labelInner1Inner1, startInner1Inner1, endInner1Inner1);
-        assertPairedTiming(labelInner2, startInner2, endInner2);
-
-        // Verify the order and total time.
-        assertOrderedTiming(startOuter, startInner1);
-        assertOrderedTiming(startInner1, startInner1Inner1);
-        assertOrderedTiming(startInner1Inner1, startInner2);
-        assertThat(
-                startOuter.getExclusiveTime() + startInner1.getTotalTime() + startInner2
-                        .getTotalTime())
-                .isEqualTo(timingLogger.getTotalTime());
-
-        // Verify the nested execution time.
-        assertThat(startInner1Inner1.getTotalTime()).isAtMost(startInner1.getTotalTime());
-        assertThat(startInner1.getTotalTime() + startInner2.getTotalTime())
-                .isAtMost(startOuter.getTotalTime());
-    }
-
-    private void assertPairedTiming(String label, Timing start, Timing end) {
-        assertThat(start.isStartTiming()).isTrue();
-        assertThat(start.getName()).isEqualTo(label);
-        assertThat(end.isEndTiming()).isTrue();
-        assertThat(end.getTimestamp()).isAtLeast(start.getTimestamp());
-
-        assertThat(start.getExclusiveTime() > 0).isTrue();
-        assertThat(start.getTotalTime()).isAtLeast(start.getExclusiveTime());
-        assertThat(end.getExclusiveTime() == 0).isTrue();
-        assertThat(end.getTotalTime() == 0).isTrue();
-    }
-
-    private void assertOrderedTiming(Timing t1, Timing t2) {
-        assertThat(t1.isStartTiming()).isTrue();
-        assertThat(t2.isStartTiming()).isTrue();
-        assertThat(t2.getTimestamp()).isAtLeast(t1.getTimestamp());
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java
deleted file mode 100644
index 80bde63..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnectionTest.java
+++ /dev/null
@@ -1,848 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.gatt;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.BluetoothStatusCodes;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.common.bluetooth.BluetoothConsts;
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.ReservedUuids;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.OperationType;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.SynchronousOperation;
-
-import junit.framework.TestCase;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Captor;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Unit tests for {@link BluetoothGattConnection}.
- */
-public class BluetoothGattConnectionTest extends TestCase {
-
-    private static final UUID SERVICE_UUID = UUID.randomUUID();
-    private static final UUID CHARACTERISTIC_UUID = UUID.randomUUID();
-    private static final UUID DESCRIPTOR_UUID = UUID.randomUUID();
-    private static final byte[] DATA = "data".getBytes();
-    private static final int RSSI = -63;
-    private static final int CONNECTION_PRIORITY = 128;
-    private static final int MTU_REQUEST = 512;
-    private static final BluetoothGattHelper.ConnectionOptions CONNECTION_OPTIONS =
-            BluetoothGattHelper.ConnectionOptions.builder().build();
-
-    @Mock
-    private BluetoothGattWrapper mMockBluetoothGattWrapper;
-    @Mock
-    private BluetoothDevice mMockBluetoothDevice;
-    @Mock
-    private BluetoothOperationExecutor mMockBluetoothOperationExecutor;
-    @Mock
-    private BluetoothGattService mMockBluetoothGattService;
-    @Mock
-    private BluetoothGattService mMockBluetoothGattService2;
-    @Mock
-    private BluetoothGattCharacteristic mMockBluetoothGattCharacteristic;
-    @Mock
-    private BluetoothGattCharacteristic mMockBluetoothGattCharacteristic2;
-    @Mock
-    private BluetoothGattDescriptor mMockBluetoothGattDescriptor;
-    @Mock
-    private BluetoothGattConnection.CharacteristicChangeListener mMockCharChangeListener;
-    @Mock
-    private BluetoothGattConnection.ChangeObserver mMockChangeObserver;
-    @Mock
-    private BluetoothGattConnection.ConnectionCloseListener mMockConnectionCloseListener;
-
-    @Captor
-    private ArgumentCaptor<Operation<?>> mOperationCaptor;
-    @Captor
-    private ArgumentCaptor<SynchronousOperation<?>> mSynchronousOperationCaptor;
-    @Captor
-    private ArgumentCaptor<BluetoothGattCharacteristic> mCharacteristicCaptor;
-    @Captor
-    private ArgumentCaptor<BluetoothGattDescriptor> mDescriptorCaptor;
-
-    private BluetoothGattConnection mBluetoothGattConnection;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        initMocks(this);
-
-        mBluetoothGattConnection = new BluetoothGattConnection(
-                mMockBluetoothGattWrapper,
-                mMockBluetoothOperationExecutor,
-                CONNECTION_OPTIONS);
-        mBluetoothGattConnection.onConnected();
-
-        when(mMockBluetoothGattWrapper.getDevice()).thenReturn(mMockBluetoothDevice);
-        when(mMockBluetoothGattWrapper.discoverServices()).thenReturn(true);
-        when(mMockBluetoothGattWrapper.refresh()).thenReturn(true);
-        when(mMockBluetoothGattWrapper.readCharacteristic(mMockBluetoothGattCharacteristic))
-                .thenReturn(true);
-        when(mMockBluetoothGattWrapper
-                .writeCharacteristic(ArgumentMatchers.<BluetoothGattCharacteristic>any(), any(),
-                        anyInt()))
-                .thenReturn(BluetoothStatusCodes.SUCCESS);
-        when(mMockBluetoothGattWrapper.readDescriptor(mMockBluetoothGattDescriptor))
-                .thenReturn(true);
-        when(mMockBluetoothGattWrapper.writeDescriptor(
-                ArgumentMatchers.<BluetoothGattDescriptor>any(), any()))
-                .thenReturn(BluetoothStatusCodes.SUCCESS);
-        when(mMockBluetoothGattWrapper.readRemoteRssi()).thenReturn(true);
-        when(mMockBluetoothGattWrapper.requestConnectionPriority(CONNECTION_PRIORITY))
-                .thenReturn(true);
-        when(mMockBluetoothGattWrapper.requestMtu(MTU_REQUEST)).thenReturn(true);
-        when(mMockBluetoothGattWrapper.getServices())
-                .thenReturn(Arrays.asList(mMockBluetoothGattService));
-        when(mMockBluetoothGattService.getUuid()).thenReturn(SERVICE_UUID);
-        when(mMockBluetoothGattService.getCharacteristics())
-                .thenReturn(Arrays.asList(mMockBluetoothGattCharacteristic));
-        when(mMockBluetoothGattCharacteristic.getUuid()).thenReturn(CHARACTERISTIC_UUID);
-        when(mMockBluetoothGattCharacteristic.getProperties())
-                .thenReturn(
-                        BluetoothGattCharacteristic.PROPERTY_NOTIFY
-                                | BluetoothGattCharacteristic.PROPERTY_WRITE);
-        BluetoothGattDescriptor clientConfigDescriptor =
-                new BluetoothGattDescriptor(
-                        ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION,
-                        BluetoothGattDescriptor.PERMISSION_WRITE);
-        when(mMockBluetoothGattCharacteristic.getDescriptor(
-                ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION))
-                .thenReturn(clientConfigDescriptor);
-        when(mMockBluetoothGattCharacteristic.getDescriptors())
-                .thenReturn(Arrays.asList(mMockBluetoothGattDescriptor, clientConfigDescriptor));
-        when(mMockBluetoothGattDescriptor.getUuid()).thenReturn(DESCRIPTOR_UUID);
-        when(mMockBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getDevice() {
-        BluetoothDevice result = mBluetoothGattConnection.getDevice();
-
-        assertThat(result).isEqualTo(mMockBluetoothDevice);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getConnectionOptions() {
-        BluetoothGattHelper.ConnectionOptions result = mBluetoothGattConnection
-                .getConnectionOptions();
-
-        assertThat(result).isSameInstanceAs(CONNECTION_OPTIONS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_isConnected_false_beforeConnection() {
-        mBluetoothGattConnection = new BluetoothGattConnection(
-                mMockBluetoothGattWrapper,
-                mMockBluetoothOperationExecutor,
-                CONNECTION_OPTIONS);
-
-        boolean result = mBluetoothGattConnection.isConnected();
-
-        assertThat(result).isFalse();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_isConnected_true_afterConnection() {
-        boolean result = mBluetoothGattConnection.isConnected();
-
-        assertThat(result).isTrue();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_isConnected_false_afterDisconnection() {
-        mBluetoothGattConnection.onClosed();
-
-        boolean result = mBluetoothGattConnection.isConnected();
-
-        assertThat(result).isFalse();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getService_notDiscovered() throws Exception {
-        BluetoothGattService result = mBluetoothGattConnection.getService(SERVICE_UUID);
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor)
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-
-        assertThat(result).isEqualTo(mMockBluetoothGattService);
-        verify(mMockBluetoothGattWrapper).discoverServices();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getService_alreadyDiscovered() throws Exception {
-        mBluetoothGattConnection.getService(SERVICE_UUID);
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-        reset(mMockBluetoothOperationExecutor);
-
-        BluetoothGattService result = mBluetoothGattConnection.getService(SERVICE_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattService);
-        // Verify that service discovery has been done only once
-        verifyNoMoreInteractions(mMockBluetoothOperationExecutor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getService_notFound() throws Exception {
-        when(mMockBluetoothGattWrapper.getServices()).thenReturn(
-                Arrays.<BluetoothGattService>asList());
-
-        try {
-            mBluetoothGattConnection.getService(SERVICE_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getService_moreThanOne() throws Exception {
-        when(mMockBluetoothGattWrapper.getServices())
-                .thenReturn(Arrays.asList(mMockBluetoothGattService, mMockBluetoothGattService));
-
-        try {
-            mBluetoothGattConnection.getService(SERVICE_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getCharacteristic() throws Exception {
-        BluetoothGattCharacteristic result =
-                mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattCharacteristic);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getCharacteristic_notFound() throws Exception {
-        when(mMockBluetoothGattService.getCharacteristics())
-                .thenReturn(Arrays.<BluetoothGattCharacteristic>asList());
-
-        try {
-            mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getCharacteristic_moreThanOne() throws Exception {
-        when(mMockBluetoothGattService.getCharacteristics())
-                .thenReturn(
-                        Arrays.asList(mMockBluetoothGattCharacteristic,
-                                mMockBluetoothGattCharacteristic));
-
-        try {
-            mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getCharacteristic_moreThanOneService() throws Exception {
-        // Add a new service with the same service UUID as our existing one, but add a different
-        // characteristic inside of it.
-        when(mMockBluetoothGattWrapper.getServices())
-                .thenReturn(Arrays.asList(mMockBluetoothGattService, mMockBluetoothGattService2));
-        when(mMockBluetoothGattService2.getUuid()).thenReturn(SERVICE_UUID);
-        when(mMockBluetoothGattService2.getCharacteristics())
-                .thenReturn(Arrays.asList(mMockBluetoothGattCharacteristic2));
-        when(mMockBluetoothGattCharacteristic2.getUuid())
-                .thenReturn(
-                        new UUID(
-                                CHARACTERISTIC_UUID.getMostSignificantBits(),
-                                CHARACTERISTIC_UUID.getLeastSignificantBits() + 1));
-        when(mMockBluetoothGattCharacteristic2.getProperties())
-                .thenReturn(
-                        BluetoothGattCharacteristic.PROPERTY_NOTIFY
-                                | BluetoothGattCharacteristic.PROPERTY_WRITE);
-
-        mBluetoothGattConnection.getCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getDescriptor() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getDescriptors())
-                .thenReturn(Arrays.asList(mMockBluetoothGattDescriptor));
-
-        BluetoothGattDescriptor result =
-                mBluetoothGattConnection
-                        .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattDescriptor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getDescriptor_notFound() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getDescriptors())
-                .thenReturn(Arrays.<BluetoothGattDescriptor>asList());
-
-        try {
-            mBluetoothGattConnection
-                    .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getDescriptor_moreThanOne() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getDescriptors())
-                .thenReturn(
-                        Arrays.asList(mMockBluetoothGattDescriptor, mMockBluetoothGattDescriptor));
-
-        try {
-            mBluetoothGattConnection
-                    .getDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<>(
-                        mMockBluetoothOperationExecutor, OperationType.NOTIFICATION_CHANGE)))
-                .thenReturn(mMockChangeObserver);
-
-        mBluetoothGattConnection.discoverServices();
-
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor)
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).discoverServices();
-        verify(mMockBluetoothGattWrapper, never()).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices_serviceChange() throws Exception {
-        when(mMockBluetoothGattWrapper.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE))
-                .thenReturn(mMockBluetoothGattService);
-        when(mMockBluetoothGattService
-                .getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE))
-                .thenReturn(mMockBluetoothGattCharacteristic);
-
-        mBluetoothGattConnection.discoverServices();
-
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor, times(2))
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        verify(mMockBluetoothGattWrapper).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices_SelfDefinedServiceDynamic() throws Exception {
-        when(mMockBluetoothGattWrapper.getService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE))
-                .thenReturn(mMockBluetoothGattService);
-        when(mMockBluetoothGattService
-                .getCharacteristic(BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC))
-                .thenReturn(mMockBluetoothGattCharacteristic);
-
-        mBluetoothGattConnection.discoverServices();
-
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor, times(2))
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        verify(mMockBluetoothGattWrapper).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices_refreshWithGattErrorOnMncAbove() throws Exception {
-        if (VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP_MR1) {
-            return;
-        }
-        mBluetoothGattConnection.discoverServices();
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-
-        doThrow(new BluetoothGattException("fail", BluetoothGattConnection.GATT_ERROR))
-                .doReturn(null)
-                .when(mMockBluetoothOperationExecutor)
-                .execute(isA(Operation.class),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor, times(2))
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        verify(mMockBluetoothGattWrapper).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices_refreshWithGattInternalErrorOnMncAbove() throws Exception {
-        if (VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP_MR1) {
-            return;
-        }
-        mBluetoothGattConnection.discoverServices();
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-
-        doThrow(new BluetoothGattException("fail", BluetoothGattConnection.GATT_INTERNAL_ERROR))
-                .doReturn(null)
-                .when(mMockBluetoothOperationExecutor)
-                .execute(isA(Operation.class),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        mSynchronousOperationCaptor.getValue().call();
-        verify(mMockBluetoothOperationExecutor, times(2))
-                .execute(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.SLOW_OPERATION_TIMEOUT_MILLIS));
-        verify(mMockBluetoothGattWrapper).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_discoverServices_dynamicServices_notBonded() throws Exception {
-        when(mMockBluetoothGattWrapper.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE))
-                .thenReturn(mMockBluetoothGattService);
-        when(mMockBluetoothGattService
-                .getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE))
-                .thenReturn(mMockBluetoothGattCharacteristic);
-        when(mMockBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
-
-        mBluetoothGattConnection.discoverServices();
-
-        verify(mMockBluetoothGattWrapper, never()).refresh();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_readCharacteristic() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<byte[]>(
-                        OperationType.READ_CHARACTERISTIC,
-                        mMockBluetoothGattWrapper,
-                        mMockBluetoothGattCharacteristic),
-                BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS))
-                .thenReturn(DATA);
-
-        byte[] result = mBluetoothGattConnection
-                .readCharacteristic(mMockBluetoothGattCharacteristic);
-
-        assertThat(result).isEqualTo(DATA);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).readCharacteristic(mMockBluetoothGattCharacteristic);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_readCharacteristic_by_uuid() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<byte[]>(
-                        OperationType.READ_CHARACTERISTIC,
-                        mMockBluetoothGattWrapper,
-                        mMockBluetoothGattCharacteristic),
-                BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS))
-                .thenReturn(DATA);
-
-        byte[] result = mBluetoothGattConnection
-                .readCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID);
-
-        assertThat(result).isEqualTo(DATA);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).readCharacteristic(mMockBluetoothGattCharacteristic);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_writeCharacteristic() throws Exception {
-        BluetoothGattCharacteristic characteristic =
-                new BluetoothGattCharacteristic(
-                        CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE, 0);
-        mBluetoothGattConnection.writeCharacteristic(characteristic, DATA);
-
-        verify(mMockBluetoothOperationExecutor)
-                .execute(mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeCharacteristic(mCharacteristicCaptor.capture(),
-                eq(DATA), eq(characteristic.getWriteType()));
-        BluetoothGattCharacteristic writtenCharacteristic = mCharacteristicCaptor.getValue();
-        assertThat(writtenCharacteristic.getUuid()).isEqualTo(CHARACTERISTIC_UUID);
-        assertThat(writtenCharacteristic).isEqualTo(characteristic);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_writeCharacteristic_by_uuid() throws Exception {
-        mBluetoothGattConnection.writeCharacteristic(SERVICE_UUID, CHARACTERISTIC_UUID, DATA);
-
-        verify(mMockBluetoothOperationExecutor)
-                .execute(mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeCharacteristic(mCharacteristicCaptor.capture(),
-                eq(DATA), anyInt());
-        BluetoothGattCharacteristic writtenCharacteristic = mCharacteristicCaptor.getValue();
-        assertThat(writtenCharacteristic.getUuid()).isEqualTo(CHARACTERISTIC_UUID);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_readDescriptor() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<byte[]>(
-                        OperationType.READ_DESCRIPTOR, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattDescriptor),
-                BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS))
-                .thenReturn(DATA);
-
-        byte[] result = mBluetoothGattConnection.readDescriptor(mMockBluetoothGattDescriptor);
-
-        assertThat(result).isEqualTo(DATA);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).readDescriptor(mMockBluetoothGattDescriptor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_readDescriptor_by_uuid() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<byte[]>(
-                        OperationType.READ_DESCRIPTOR, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattDescriptor),
-                BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS))
-                .thenReturn(DATA);
-
-        byte[] result =
-                mBluetoothGattConnection
-                        .readDescriptor(SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID);
-
-        assertThat(result).isEqualTo(DATA);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).readDescriptor(mMockBluetoothGattDescriptor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_writeDescriptor() throws Exception {
-        BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0);
-        mBluetoothGattConnection.writeDescriptor(descriptor, DATA);
-
-        verify(mMockBluetoothOperationExecutor)
-                .execute(mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), eq(DATA));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid()).isEqualTo(DESCRIPTOR_UUID);
-        assertThat(writtenDescriptor).isEqualTo(descriptor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_writeDescriptor_by_uuid() throws Exception {
-        mBluetoothGattConnection.writeDescriptor(
-                SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID, DATA);
-
-        verify(mMockBluetoothOperationExecutor)
-                .execute(mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(), eq(DATA));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid()).isEqualTo(DESCRIPTOR_UUID);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_readRemoteRssi() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<Integer>(OperationType.READ_RSSI, mMockBluetoothGattWrapper),
-                BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS))
-                .thenReturn(RSSI);
-
-        int result = mBluetoothGattConnection.readRemoteRssi();
-
-        assertThat(result).isEqualTo(RSSI);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(
-                        mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).readRemoteRssi();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getMaxDataPacketSize() throws Exception {
-        int result = mBluetoothGattConnection.getMaxDataPacketSize();
-
-        assertThat(result).isEqualTo(mBluetoothGattConnection.getMtu() - 3);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSetNotificationEnabled_indication_enable() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getProperties())
-                .thenReturn(BluetoothGattCharacteristic.PROPERTY_INDICATE);
-
-        mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, true);
-
-        verify(mMockBluetoothGattWrapper)
-                .setCharacteristicNotification(mMockBluetoothGattCharacteristic, true);
-        verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(),
-                eq(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid())
-                .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_getNotificationEnabled_notification_enable() throws Exception {
-        mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, true);
-
-        verify(mMockBluetoothGattWrapper)
-                .setCharacteristicNotification(mMockBluetoothGattCharacteristic, true);
-        verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(),
-                eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid())
-                .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_setNotificationEnabled_indication_disable() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getProperties())
-                .thenReturn(BluetoothGattCharacteristic.PROPERTY_INDICATE);
-
-        mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, false);
-
-        verify(mMockBluetoothGattWrapper)
-                .setCharacteristicNotification(mMockBluetoothGattCharacteristic, false);
-        verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(),
-                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid())
-                .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_setNotificationEnabled_notification_disable() throws Exception {
-        mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic, false);
-
-        verify(mMockBluetoothGattWrapper)
-                .setCharacteristicNotification(mMockBluetoothGattCharacteristic, false);
-        verify(mMockBluetoothOperationExecutor).execute(mOperationCaptor.capture(), anyLong());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).writeDescriptor(mDescriptorCaptor.capture(),
-                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
-        BluetoothGattDescriptor writtenDescriptor = mDescriptorCaptor.getValue();
-        assertThat(writtenDescriptor.getUuid())
-                .isEqualTo(ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_setNotificationEnabled_failure() throws Exception {
-        when(mMockBluetoothGattCharacteristic.getProperties())
-                .thenReturn(BluetoothGattCharacteristic.PROPERTY_READ);
-
-        try {
-            mBluetoothGattConnection.setNotificationEnabled(mMockBluetoothGattCharacteristic,
-                    true);
-            fail("BluetoothException was expected");
-        } catch (BluetoothException expected) {
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_enableNotification_Uuid() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<>(
-                        mMockBluetoothOperationExecutor,
-                        OperationType.NOTIFICATION_CHANGE,
-                        mMockBluetoothGattCharacteristic)))
-                .thenReturn(mMockChangeObserver);
-        mBluetoothGattConnection.enableNotification(SERVICE_UUID, CHARACTERISTIC_UUID);
-
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mSynchronousOperationCaptor.capture());
-        ((ChangeObserver) mSynchronousOperationCaptor.getValue().call())
-                .setListener(mMockCharChangeListener);
-        mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA);
-        verify(mMockCharChangeListener).onValueChange(DATA);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_enableNotification() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<>(
-                        mMockBluetoothOperationExecutor,
-                        OperationType.NOTIFICATION_CHANGE,
-                        mMockBluetoothGattCharacteristic)))
-                .thenReturn(mMockChangeObserver);
-        mBluetoothGattConnection.enableNotification(mMockBluetoothGattCharacteristic);
-
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mSynchronousOperationCaptor.capture());
-        ((ChangeObserver) mSynchronousOperationCaptor.getValue().call())
-                .setListener(mMockCharChangeListener);
-
-        mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA);
-
-        verify(mMockCharChangeListener).onValueChange(DATA);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_enableNotification_observe() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<>(
-                        mMockBluetoothOperationExecutor,
-                        OperationType.NOTIFICATION_CHANGE,
-                        mMockBluetoothGattCharacteristic)))
-                .thenReturn(mMockChangeObserver);
-        mBluetoothGattConnection.enableNotification(mMockBluetoothGattCharacteristic);
-
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mSynchronousOperationCaptor.capture());
-        ChangeObserver changeObserver = (ChangeObserver) mSynchronousOperationCaptor.getValue()
-                .call();
-        mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA);
-        assertThat(changeObserver.waitForUpdate(TimeUnit.SECONDS.toMillis(1))).isEqualTo(DATA);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_disableNotification_Uuid() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<>(
-                        OperationType.NOTIFICATION_CHANGE, mMockBluetoothGattCharacteristic)))
-                .thenReturn(mMockChangeObserver);
-        mBluetoothGattConnection
-                .enableNotification(SERVICE_UUID, CHARACTERISTIC_UUID)
-                .setListener(mMockCharChangeListener);
-
-        mBluetoothGattConnection.disableNotification(SERVICE_UUID, CHARACTERISTIC_UUID);
-
-        mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA);
-        verify(mMockCharChangeListener, never()).onValueChange(DATA);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_disableNotification() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new SynchronousOperation<ChangeObserver>(
-                        OperationType.NOTIFICATION_CHANGE, mMockBluetoothGattCharacteristic)))
-                .thenReturn(mMockChangeObserver);
-        mBluetoothGattConnection
-                .enableNotification(mMockBluetoothGattCharacteristic)
-                .setListener(mMockCharChangeListener);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-
-        mBluetoothGattConnection.disableNotification(mMockBluetoothGattCharacteristic);
-        verify(mMockBluetoothOperationExecutor).execute(mSynchronousOperationCaptor.capture());
-        mSynchronousOperationCaptor.getValue().call();
-
-        mBluetoothGattConnection.onCharacteristicChanged(mMockBluetoothGattCharacteristic, DATA);
-        verify(mMockCharChangeListener, never()).onValueChange(DATA);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_addCloseListener() throws Exception {
-        mBluetoothGattConnection.addCloseListener(mMockConnectionCloseListener);
-
-        mBluetoothGattConnection.onClosed();
-        verify(mMockConnectionCloseListener).onClose();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_removeCloseListener() throws Exception {
-        mBluetoothGattConnection.addCloseListener(mMockConnectionCloseListener);
-
-        mBluetoothGattConnection.removeCloseListener(mMockConnectionCloseListener);
-
-        mBluetoothGattConnection.onClosed();
-        verify(mMockConnectionCloseListener, never()).onClose();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_close() throws Exception {
-        mBluetoothGattConnection.close();
-
-        verify(mMockBluetoothOperationExecutor)
-                .execute(mOperationCaptor.capture(),
-                        eq(BluetoothGattConnection.OPERATION_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_onClosed() throws Exception {
-        mBluetoothGattConnection.onClosed();
-
-        verify(mMockBluetoothOperationExecutor, never())
-                .execute(mOperationCaptor.capture(), anyLong());
-        verify(mMockBluetoothGattWrapper).close();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java
deleted file mode 100644
index 7c20be1..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelperTest.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.gatt;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.content.Context;
-import android.os.ParcelUuid;
-import android.test.mock.MockContext;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.ConnectionOptions;
-import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.OperationType;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanCallback;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanResult;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-
-import junit.framework.TestCase;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-import java.util.UUID;
-
-/**
- * Unit tests for {@link BluetoothGattHelper}.
- */
-public class BluetoothGattHelperTest extends TestCase {
-
-    private static final UUID SERVICE_UUID = UUID.randomUUID();
-    private static final int GATT_STATUS = 1234;
-    private static final Operation<BluetoothDevice> SCANNING_OPERATION =
-            new Operation<BluetoothDevice>(OperationType.SCAN);
-    private static final byte[] CHARACTERISTIC_VALUE = "characteristic_value".getBytes();
-    private static final byte[] DESCRIPTOR_VALUE = "descriptor_value".getBytes();
-    private static final int RSSI = -63;
-    private static final int MTU = 50;
-    private static final long CONNECT_TIMEOUT_MILLIS = 5000;
-
-    private Context mMockApplicationContext = new MockContext();
-    @Mock
-    private BluetoothAdapter mMockBluetoothAdapter;
-    @Mock
-    private BluetoothLeScanner mMockBluetoothLeScanner;
-    @Mock
-    private BluetoothOperationExecutor mMockBluetoothOperationExecutor;
-    @Mock
-    private BluetoothDevice mMockBluetoothDevice;
-    @Mock
-    private BluetoothGattConnection mMockBluetoothGattConnection;
-    @Mock
-    private BluetoothGattWrapper mMockBluetoothGattWrapper;
-    @Mock
-    private BluetoothGattCharacteristic mMockBluetoothGattCharacteristic;
-    @Mock
-    private BluetoothGattDescriptor mMockBluetoothGattDescriptor;
-    @Mock
-    private ScanResult mMockScanResult;
-
-    @Captor
-    private ArgumentCaptor<Operation<?>> mOperationCaptor;
-    @Captor
-    private ArgumentCaptor<ScanSettings> mScanSettingsCaptor;
-
-    private BluetoothGattHelper mBluetoothGattHelper;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        initMocks(this);
-
-        mBluetoothGattHelper = new BluetoothGattHelper(
-                mMockApplicationContext,
-                mMockBluetoothAdapter,
-                mMockBluetoothOperationExecutor);
-
-        when(mMockBluetoothAdapter.getBluetoothLeScanner()).thenReturn(mMockBluetoothLeScanner);
-        when(mMockBluetoothOperationExecutor.executeNonnull(SCANNING_OPERATION,
-                BluetoothGattHelper.LOW_LATENCY_SCAN_MILLIS)).thenReturn(mMockBluetoothDevice);
-        when(mMockBluetoothOperationExecutor.executeNonnull(SCANNING_OPERATION)).thenReturn(
-                mMockBluetoothDevice);
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<BluetoothGattConnection>(OperationType.CONNECT, mMockBluetoothDevice),
-                CONNECT_TIMEOUT_MILLIS))
-                .thenReturn(mMockBluetoothGattConnection);
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<BluetoothGattConnection>(OperationType.CONNECT,
-                        mMockBluetoothDevice)))
-                .thenReturn(mMockBluetoothGattConnection);
-        when(mMockBluetoothGattCharacteristic.getValue()).thenReturn(CHARACTERISTIC_VALUE);
-        when(mMockBluetoothGattDescriptor.getValue()).thenReturn(DESCRIPTOR_VALUE);
-        when(mMockScanResult.getDevice()).thenReturn(mMockBluetoothDevice);
-        when(mMockBluetoothGattWrapper.getDevice()).thenReturn(mMockBluetoothDevice);
-        when(mMockBluetoothDevice.connectGatt(eq(mMockApplicationContext), anyBoolean(),
-                eq(mBluetoothGattHelper.mBluetoothGattCallback))).thenReturn(
-                mMockBluetoothGattWrapper);
-        when(mMockBluetoothDevice.connectGatt(eq(mMockApplicationContext), anyBoolean(),
-                eq(mBluetoothGattHelper.mBluetoothGattCallback), anyInt()))
-                .thenReturn(mMockBluetoothGattWrapper);
-        when(mMockBluetoothGattConnection.getConnectionOptions())
-                .thenReturn(ConnectionOptions.builder().build());
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_success_lowLatency() throws Exception {
-        BluetoothGattConnection result = mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-        verify(mMockBluetoothOperationExecutor, atLeastOnce())
-                .executeNonnull(mOperationCaptor.capture(),
-                        anyLong());
-        for (Operation<?> operation : mOperationCaptor.getAllValues()) {
-            operation.run();
-        }
-        verify(mMockBluetoothLeScanner).startScan(eq(Arrays.asList(
-                new ScanFilter.Builder().setServiceUuid(new ParcelUuid(SERVICE_UUID)).build())),
-                mScanSettingsCaptor.capture(), eq(mBluetoothGattHelper.mScanCallback));
-        assertThat(mScanSettingsCaptor.getValue().getScanMode()).isEqualTo(
-                ScanSettings.SCAN_MODE_LOW_LATENCY);
-        verify(mMockBluetoothLeScanner).stopScan(mBluetoothGattHelper.mScanCallback);
-        verifyNoMoreInteractions(mMockBluetoothLeScanner);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_success_lowPower() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(SCANNING_OPERATION,
-                BluetoothGattHelper.LOW_LATENCY_SCAN_MILLIS)).thenThrow(
-                new BluetoothOperationTimeoutException("Timeout"));
-
-        BluetoothGattConnection result = mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-        verify(mMockBluetoothOperationExecutor).executeNonnull(mOperationCaptor.capture());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothLeScanner).startScan(eq(Arrays.asList(
-                new ScanFilter.Builder().setServiceUuid(new ParcelUuid(SERVICE_UUID)).build())),
-                mScanSettingsCaptor.capture(), eq(mBluetoothGattHelper.mScanCallback));
-        assertThat(mScanSettingsCaptor.getValue().getScanMode()).isEqualTo(
-                ScanSettings.SCAN_MODE_LOW_POWER);
-        verify(mMockBluetoothLeScanner, times(2)).stopScan(mBluetoothGattHelper.mScanCallback);
-        verifyNoMoreInteractions(mMockBluetoothLeScanner);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_success_afterRetry() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<BluetoothGattConnection>(OperationType.CONNECT, mMockBluetoothDevice),
-                BluetoothGattHelper.LOW_LATENCY_SCAN_MILLIS))
-                .thenThrow(new BluetoothException("first attempt fails!"))
-                .thenReturn(mMockBluetoothGattConnection);
-
-        BluetoothGattConnection result = mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_failure_scanning() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(SCANNING_OPERATION,
-                BluetoothGattHelper.LOW_LATENCY_SCAN_MILLIS)).thenThrow(
-                new BluetoothException("Scanning failed"));
-
-        try {
-            mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-            fail("BluetoothException expected");
-        } catch (BluetoothException e) {
-            // expected
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_failure_connecting() throws Exception {
-        when(mMockBluetoothOperationExecutor.executeNonnull(
-                new Operation<BluetoothGattConnection>(OperationType.CONNECT, mMockBluetoothDevice),
-                CONNECT_TIMEOUT_MILLIS))
-                .thenThrow(new BluetoothException("Connect failed"));
-
-        try {
-            mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-            fail("BluetoothException expected");
-        } catch (BluetoothException e) {
-            // expected
-        }
-        verify(mMockBluetoothOperationExecutor, times(3))
-                .executeNonnull(
-                        new Operation<BluetoothGattConnection>(OperationType.CONNECT,
-                                mMockBluetoothDevice),
-                        CONNECT_TIMEOUT_MILLIS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_autoConnect_uuid_failure_noBle() throws Exception {
-        when(mMockBluetoothAdapter.getBluetoothLeScanner()).thenReturn(null);
-
-        try {
-            mBluetoothGattHelper.autoConnect(SERVICE_UUID);
-            fail("BluetoothException expected");
-        } catch (BluetoothException e) {
-            // expected
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_connect() throws Exception {
-        BluetoothGattConnection result = mBluetoothGattHelper.connect(mMockBluetoothDevice);
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), eq(CONNECT_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothDevice).connectGatt(mMockApplicationContext, false,
-                mBluetoothGattHelper.mBluetoothGattCallback,
-                android.bluetooth.BluetoothDevice.TRANSPORT_LE);
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper).getDevice())
-                .isEqualTo(mMockBluetoothDevice);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_connect_withOptionAutoConnect_success() throws Exception {
-        BluetoothGattConnection result = mBluetoothGattHelper
-                .connect(
-                        mMockBluetoothDevice,
-                        ConnectionOptions.builder()
-                                .setAutoConnect(true)
-                                .build());
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-        verify(mMockBluetoothOperationExecutor).executeNonnull(mOperationCaptor.capture());
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothDevice).connectGatt(mMockApplicationContext, true,
-                mBluetoothGattHelper.mBluetoothGattCallback,
-                android.bluetooth.BluetoothDevice.TRANSPORT_LE);
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)
-                .getConnectionOptions())
-                .isEqualTo(ConnectionOptions.builder()
-                        .setAutoConnect(true)
-                        .build());
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_connect_withOptionAutoConnect_failure_nullResult() throws Exception {
-        when(mMockBluetoothDevice.connectGatt(eq(mMockApplicationContext), anyBoolean(),
-                eq(mBluetoothGattHelper.mBluetoothGattCallback),
-                eq(android.bluetooth.BluetoothDevice.TRANSPORT_LE))).thenReturn(null);
-
-        try {
-            mBluetoothGattHelper.connect(
-                    mMockBluetoothDevice,
-                    ConnectionOptions.builder()
-                            .setAutoConnect(true)
-                            .build());
-            verify(mMockBluetoothOperationExecutor).executeNonnull(mOperationCaptor.capture());
-            mOperationCaptor.getValue().run();
-            fail("BluetoothException expected");
-        } catch (BluetoothException e) {
-            // expected
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_connect_withOptionRequestConnectionPriority_success() throws Exception {
-        // Operation succeeds on the 3rd try.
-        when(mMockBluetoothGattWrapper
-                .requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH))
-                .thenReturn(false)
-                .thenReturn(false)
-                .thenReturn(true);
-
-        BluetoothGattConnection result = mBluetoothGattHelper
-                .connect(
-                        mMockBluetoothDevice,
-                        ConnectionOptions.builder()
-                                .setConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
-                                .build());
-
-        assertThat(result).isEqualTo(mMockBluetoothGattConnection);
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), eq(CONNECT_TIMEOUT_MILLIS));
-        mOperationCaptor.getValue().run();
-        verify(mMockBluetoothDevice).connectGatt(mMockApplicationContext, false,
-                mBluetoothGattHelper.mBluetoothGattCallback,
-                android.bluetooth.BluetoothDevice.TRANSPORT_LE);
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)
-                .getConnectionOptions())
-                .isEqualTo(ConnectionOptions.builder()
-                        .setConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
-                        .build());
-        verify(mMockBluetoothGattWrapper, times(3))
-                .requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_connect_cancel() throws Exception {
-        mBluetoothGattHelper.connect(mMockBluetoothDevice);
-
-        verify(mMockBluetoothOperationExecutor)
-                .executeNonnull(mOperationCaptor.capture(), eq(CONNECT_TIMEOUT_MILLIS));
-        Operation<?> operation = mOperationCaptor.getValue();
-        operation.run();
-        operation.cancel();
-
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_connected_success()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<>(OperationType.CONNECT, mMockBluetoothDevice),
-                BluetoothGatt.GATT_SUCCESS,
-                mMockBluetoothGattConnection);
-        verify(mMockBluetoothGattConnection).onConnected();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_connected_success_withMtuOption()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.getConnectionOptions())
-                .thenReturn(BluetoothGattHelper.ConnectionOptions.builder()
-                        .setMtu(MTU)
-                        .build());
-        when(mMockBluetoothGattWrapper.requestMtu(MTU)).thenReturn(true);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED);
-
-        verifyZeroInteractions(mMockBluetoothOperationExecutor);
-        verify(mMockBluetoothGattConnection, never()).onConnected();
-        verify(mMockBluetoothGattWrapper).requestMtu(MTU);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_connected_success_failMtuOption()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.getConnectionOptions())
-                .thenReturn(BluetoothGattHelper.ConnectionOptions.builder()
-                        .setMtu(MTU)
-                        .build());
-        when(mMockBluetoothGattWrapper.requestMtu(MTU)).thenReturn(false);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED);
-
-        verify(mMockBluetoothOperationExecutor).notifyFailure(
-                eq(new Operation<>(OperationType.CONNECT, mMockBluetoothDevice)),
-                any(BluetoothException.class));
-        verify(mMockBluetoothGattConnection, never()).onConnected();
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_connected_unexpectedSuccess()
-            throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED);
-
-        verifyZeroInteractions(mMockBluetoothOperationExecutor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_connected_failure()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-
-        mBluetoothGattHelper.mBluetoothGattCallback
-                .onConnectionStateChange(
-                        mMockBluetoothGattWrapper,
-                        BluetoothGatt.GATT_FAILURE,
-                        BluetoothGatt.STATE_CONNECTED);
-
-        verify(mMockBluetoothOperationExecutor)
-                .notifyCompletion(
-                        new Operation<>(OperationType.CONNECT, mMockBluetoothDevice),
-                        BluetoothGatt.GATT_FAILURE,
-                        null);
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_disconnected_unexpectedSuccess()
-            throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback
-                .onConnectionStateChange(
-                        mMockBluetoothGattWrapper,
-                        BluetoothGatt.GATT_SUCCESS,
-                        BluetoothGatt.STATE_DISCONNECTED);
-
-        verifyZeroInteractions(mMockBluetoothOperationExecutor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_disconnected_notConnected()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(false);
-
-        mBluetoothGattHelper.mBluetoothGattCallback
-                .onConnectionStateChange(
-                        mMockBluetoothGattWrapper,
-                        GATT_STATUS,
-                        BluetoothGatt.STATE_DISCONNECTED);
-
-        verify(mMockBluetoothOperationExecutor)
-                .notifyCompletion(
-                        new Operation<>(OperationType.CONNECT, mMockBluetoothDevice),
-                        GATT_STATUS,
-                        null);
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_disconnected_success()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(true);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_DISCONNECTED);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<>(OperationType.DISCONNECT, mMockBluetoothDevice),
-                BluetoothGatt.GATT_SUCCESS);
-        verify(mMockBluetoothGattConnection).onClosed();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onConnectionStateChange_disconnected_failure()
-            throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(true);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onConnectionStateChange(
-                mMockBluetoothGattWrapper,
-                BluetoothGatt.GATT_FAILURE, BluetoothGatt.STATE_DISCONNECTED);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<>(OperationType.DISCONNECT, mMockBluetoothDevice),
-                BluetoothGatt.GATT_FAILURE);
-        verify(mMockBluetoothGattConnection).onClosed();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onServicesDiscovered() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onServicesDiscovered(mMockBluetoothGattWrapper,
-                GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<Void>(OperationType.DISCOVER_SERVICES_INTERNAL,
-                        mMockBluetoothGattWrapper),
-                GATT_STATUS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onCharacteristicRead() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onCharacteristicRead(mMockBluetoothGattWrapper,
-                mMockBluetoothGattCharacteristic, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(new Operation<byte[]>(
-                        OperationType.READ_CHARACTERISTIC, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattCharacteristic),
-                GATT_STATUS, CHARACTERISTIC_VALUE);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onCharacteristicWrite() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onCharacteristicWrite(mMockBluetoothGattWrapper,
-                mMockBluetoothGattCharacteristic, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(new Operation<Void>(
-                        OperationType.WRITE_CHARACTERISTIC, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattCharacteristic),
-                GATT_STATUS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onDescriptorRead() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onDescriptorRead(mMockBluetoothGattWrapper,
-                mMockBluetoothGattDescriptor, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(new Operation<byte[]>(
-                        OperationType.READ_DESCRIPTOR, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattDescriptor),
-                GATT_STATUS,
-                DESCRIPTOR_VALUE);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onDescriptorWrite() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onDescriptorWrite(mMockBluetoothGattWrapper,
-                mMockBluetoothGattDescriptor, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(new Operation<Void>(
-                        OperationType.WRITE_DESCRIPTOR, mMockBluetoothGattWrapper,
-                        mMockBluetoothGattDescriptor),
-                GATT_STATUS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onReadRemoteRssi() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onReadRemoteRssi(mMockBluetoothGattWrapper,
-                RSSI, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<Integer>(OperationType.READ_RSSI, mMockBluetoothGattWrapper),
-                GATT_STATUS, RSSI);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onReliableWriteCompleted() throws Exception {
-        mBluetoothGattHelper.mBluetoothGattCallback.onReliableWriteCompleted(
-                mMockBluetoothGattWrapper,
-                GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<Void>(OperationType.WRITE_RELIABLE, mMockBluetoothGattWrapper),
-                GATT_STATUS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onMtuChanged() throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(true);
-
-        mBluetoothGattHelper.mBluetoothGattCallback
-                .onMtuChanged(mMockBluetoothGattWrapper, MTU, GATT_STATUS);
-
-        verify(mMockBluetoothOperationExecutor).notifyCompletion(
-                new Operation<>(OperationType.CHANGE_MTU, mMockBluetoothGattWrapper), GATT_STATUS,
-                MTU);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothGattCallback_onMtuChangedDuringConnection_success() throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(false);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onMtuChanged(
-                mMockBluetoothGattWrapper, MTU, BluetoothGatt.GATT_SUCCESS);
-
-        verify(mMockBluetoothGattConnection).onConnected();
-        verify(mMockBluetoothOperationExecutor)
-                .notifyCompletion(
-                        new Operation<>(OperationType.CONNECT, mMockBluetoothDevice),
-                        BluetoothGatt.GATT_SUCCESS,
-                        mMockBluetoothGattConnection);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testBluetoothGattCallback_onMtuChangedDuringConnection_fail() throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-        when(mMockBluetoothGattConnection.isConnected()).thenReturn(false);
-
-        mBluetoothGattHelper.mBluetoothGattCallback
-                .onMtuChanged(mMockBluetoothGattWrapper, MTU, GATT_STATUS);
-
-        verify(mMockBluetoothGattConnection).onConnected();
-        verify(mMockBluetoothOperationExecutor)
-                .notifyCompletion(
-                        new Operation<>(OperationType.CONNECT, mMockBluetoothDevice),
-                        GATT_STATUS,
-                        mMockBluetoothGattConnection);
-        verify(mMockBluetoothGattWrapper).disconnect();
-        verify(mMockBluetoothGattWrapper).close();
-        assertThat(mBluetoothGattHelper.mConnections.get(mMockBluetoothGattWrapper)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_BluetoothGattCallback_onCharacteristicChanged() throws Exception {
-        mBluetoothGattHelper.mConnections.put(mMockBluetoothGattWrapper,
-                mMockBluetoothGattConnection);
-
-        mBluetoothGattHelper.mBluetoothGattCallback.onCharacteristicChanged(
-                mMockBluetoothGattWrapper,
-                mMockBluetoothGattCharacteristic);
-
-        verify(mMockBluetoothGattConnection).onCharacteristicChanged(
-                mMockBluetoothGattCharacteristic,
-                CHARACTERISTIC_VALUE);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_ScanCallback_onScanFailed() throws Exception {
-        mBluetoothGattHelper.mScanCallback.onScanFailed(ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
-
-        verify(mMockBluetoothOperationExecutor).notifyFailure(
-                eq(new Operation<BluetoothDevice>(OperationType.SCAN)),
-                isA(BluetoothException.class));
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_ScanCallback_onScanResult() throws Exception {
-        mBluetoothGattHelper.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
-                mMockScanResult);
-
-        verify(mMockBluetoothOperationExecutor).notifySuccess(
-                new Operation<BluetoothDevice>(OperationType.SCAN), mMockBluetoothDevice);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
deleted file mode 100644
index 47182c3..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.util;
-
-import static com.android.server.nearby.common.bluetooth.util.BluetoothGattUtils.getMessageForStatusCode;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.bluetooth.BluetoothGatt;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableSet;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.UUID;
-
-/** Unit tests for {@link BluetoothGattUtils}. */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BluetoothGattUtilsTest {
-    private static final UUID TEST_UUID = UUID.randomUUID();
-    private static final ImmutableSet<String> GATT_HIDDEN_CONSTANTS = ImmutableSet.of(
-            "GATT_WRITE_REQUEST_BUSY", "GATT_WRITE_REQUEST_FAIL", "GATT_WRITE_REQUEST_SUCCESS");
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetMessageForStatusCode() throws Exception {
-        Field[] publicFields = BluetoothGatt.class.getFields();
-        for (Field field : publicFields) {
-            if ((field.getModifiers() & Modifier.STATIC) == 0
-                    || field.getDeclaringClass() != BluetoothGatt.class) {
-                continue;
-            }
-            String fieldName = field.getName();
-            if (!fieldName.startsWith("GATT_") || GATT_HIDDEN_CONSTANTS.contains(fieldName)) {
-                continue;
-            }
-            int fieldValue = (Integer) field.get(null);
-            assertThat(getMessageForStatusCode(fieldValue)).isEqualTo(fieldName);
-        }
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java
deleted file mode 100644
index 7b3ebab..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutorTest.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.bluetooth.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.bluetooth.BluetoothGatt;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.common.bluetooth.BluetoothException;
-import com.android.server.nearby.common.bluetooth.testability.NonnullProvider;
-import com.android.server.nearby.common.bluetooth.testability.TimeProvider;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation;
-import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.SynchronousOperation;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-
-import java.util.Arrays;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-
-/**
- * Unit tests for {@link BluetoothOperationExecutor}.
- */
-public class BluetoothOperationExecutorTest extends TestCase {
-
-    private static final String OPERATION_RESULT = "result";
-    private static final String EXCEPTION_REASON = "exception";
-    private static final long TIME = 1234;
-    private static final long TIMEOUT = 121212;
-
-    @Mock
-    private NonnullProvider<BlockingQueue<Object>> mMockBlockingQueueProvider;
-    @Mock
-    private TimeProvider mMockTimeProvider;
-    @Mock
-    private BlockingQueue<Object> mMockBlockingQueue;
-    @Mock
-    private Semaphore mMockSemaphore;
-    @Mock
-    private Operation<String> mMockStringOperation;
-    @Mock
-    private Operation<Void> mMockVoidOperation;
-    @Mock
-    private Future<Object> mMockFuture;
-    @Mock
-    private Future<Object> mMockFuture2;
-
-    private BluetoothOperationExecutor mBluetoothOperationExecutor;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        initMocks(this);
-
-        when(mMockBlockingQueueProvider.get()).thenReturn(mMockBlockingQueue);
-        when(mMockSemaphore.tryAcquire()).thenReturn(true);
-        when(mMockTimeProvider.getTimeMillis()).thenReturn(TIME);
-
-        mBluetoothOperationExecutor =
-                new BluetoothOperationExecutor(mMockSemaphore, mMockTimeProvider,
-                        mMockBlockingQueueProvider);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testExecute() throws Exception {
-        when(mMockBlockingQueue.take()).thenReturn(OPERATION_RESULT);
-
-        String result = mBluetoothOperationExecutor.execute(mMockStringOperation);
-
-        verify(mMockStringOperation).execute(mBluetoothOperationExecutor);
-        assertThat(result).isEqualTo(OPERATION_RESULT);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testExecuteWithTimeout() throws Exception {
-        when(mMockBlockingQueue.poll(TIMEOUT, TimeUnit.MILLISECONDS)).thenReturn(OPERATION_RESULT);
-
-        String result = mBluetoothOperationExecutor.execute(mMockStringOperation, TIMEOUT);
-
-        verify(mMockStringOperation).execute(mBluetoothOperationExecutor);
-        assertThat(result).isEqualTo(OPERATION_RESULT);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testSchedule() throws Exception {
-        when(mMockBlockingQueue.poll(TIMEOUT, TimeUnit.MILLISECONDS)).thenReturn(OPERATION_RESULT);
-
-        Future<String> result = mBluetoothOperationExecutor.schedule(mMockStringOperation);
-
-        verify(mMockStringOperation).execute(mBluetoothOperationExecutor);
-        assertThat(result.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(OPERATION_RESULT);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testScheduleOtherOperationInProgress() throws Exception {
-        when(mMockSemaphore.tryAcquire()).thenReturn(false);
-        when(mMockBlockingQueue.poll(TIMEOUT, TimeUnit.MILLISECONDS)).thenReturn(OPERATION_RESULT);
-
-        Future<String> result = mBluetoothOperationExecutor.schedule(mMockStringOperation);
-
-        verify(mMockStringOperation, never()).run();
-
-        when(mMockSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)).thenReturn(true);
-
-        assertThat(result.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(OPERATION_RESULT);
-        verify(mMockStringOperation).execute(mBluetoothOperationExecutor);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifySuccessWithResult() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<String> future = mBluetoothOperationExecutor.schedule(mMockStringOperation);
-
-        mBluetoothOperationExecutor.notifySuccess(mMockStringOperation, OPERATION_RESULT);
-
-        assertThat(future.get(1, TimeUnit.MILLISECONDS)).isEqualTo(OPERATION_RESULT);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifySuccessTwice() throws Exception {
-        BlockingQueue<Object> resultQueue = new LinkedBlockingDeque<Object>();
-        when(mMockBlockingQueueProvider.get()).thenReturn(resultQueue);
-        Future<String> future = mBluetoothOperationExecutor.schedule(mMockStringOperation);
-
-        mBluetoothOperationExecutor.notifySuccess(mMockStringOperation, OPERATION_RESULT);
-
-        assertThat(future.get(1, TimeUnit.MILLISECONDS)).isEqualTo(OPERATION_RESULT);
-
-        // the second notification should be ignored
-        mBluetoothOperationExecutor.notifySuccess(mMockStringOperation, OPERATION_RESULT);
-        assertThat(resultQueue).isEmpty();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifySuccessWithNullResult() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<String> future = mBluetoothOperationExecutor.schedule(mMockStringOperation);
-
-        mBluetoothOperationExecutor.notifySuccess(mMockStringOperation, null);
-
-        assertThat(future.get(1, TimeUnit.MILLISECONDS)).isNull();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifySuccess() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<Void> future = mBluetoothOperationExecutor.schedule(mMockVoidOperation);
-
-        mBluetoothOperationExecutor.notifySuccess(mMockVoidOperation);
-
-        future.get(1, TimeUnit.MILLISECONDS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifyCompletionSuccess() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<Void> future = mBluetoothOperationExecutor.schedule(mMockVoidOperation);
-
-        mBluetoothOperationExecutor
-                .notifyCompletion(mMockVoidOperation, BluetoothGatt.GATT_SUCCESS);
-
-        future.get(1, TimeUnit.MILLISECONDS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifyCompletionFailure() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<Void> future = mBluetoothOperationExecutor.schedule(mMockVoidOperation);
-
-        mBluetoothOperationExecutor
-                .notifyCompletion(mMockVoidOperation, BluetoothGatt.GATT_FAILURE);
-
-        try {
-            BluetoothOperationExecutor.getResult(future, 1);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException e) {
-            //expected
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testNotifyFailure() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(new LinkedBlockingDeque<Object>());
-        Future<Void> future = mBluetoothOperationExecutor.schedule(mMockVoidOperation);
-
-        mBluetoothOperationExecutor
-                .notifyFailure(mMockVoidOperation, new BluetoothException("test"));
-
-        try {
-            BluetoothOperationExecutor.getResult(future, 1);
-            fail("Expected BluetoothException");
-        } catch (BluetoothException e) {
-            //expected
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testWaitFor() throws Exception {
-        mBluetoothOperationExecutor.waitFor(Arrays.asList(mMockFuture, mMockFuture2));
-
-        verify(mMockFuture).get();
-        verify(mMockFuture2).get();
-    }
-
-    @SuppressWarnings("unchecked")
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testWaitForWithTimeout() throws Exception {
-        mBluetoothOperationExecutor.waitFor(
-                Arrays.asList(mMockFuture, mMockFuture2),
-                TIMEOUT);
-
-        verify(mMockFuture).get(TIMEOUT, TimeUnit.MILLISECONDS);
-        verify(mMockFuture2).get(TIMEOUT, TimeUnit.MILLISECONDS);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetResult() throws Exception {
-        when(mMockFuture.get()).thenReturn(OPERATION_RESULT);
-
-        Object result = BluetoothOperationExecutor.getResult(mMockFuture);
-
-        assertThat(result).isEqualTo(OPERATION_RESULT);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testGetResultWithTimeout() throws Exception {
-        when(mMockFuture.get(TIMEOUT, TimeUnit.MILLISECONDS)).thenThrow(new TimeoutException());
-
-        try {
-            BluetoothOperationExecutor.getResult(mMockFuture, TIMEOUT);
-            fail("Expected BluetoothOperationTimeoutException");
-        } catch (BluetoothOperationTimeoutException e) {
-            //expected
-        }
-        verify(mMockFuture).cancel(true);
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_SynchronousOperation_execute() throws Exception {
-        when(mMockBlockingQueueProvider.get()).thenReturn(mMockBlockingQueue);
-        SynchronousOperation<String> synchronousOperation = new SynchronousOperation<String>() {
-            @Override
-            public String call() throws BluetoothException {
-                return OPERATION_RESULT;
-            }
-        };
-
-        @SuppressWarnings("unused") // future return.
-        Future<?> possiblyIgnoredError = mBluetoothOperationExecutor.schedule(synchronousOperation);
-
-        verify(mMockBlockingQueue).add(OPERATION_RESULT);
-        verify(mMockSemaphore).release();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_SynchronousOperation_exception() throws Exception {
-        final BluetoothException exception = new BluetoothException(EXCEPTION_REASON);
-        when(mMockBlockingQueueProvider.get()).thenReturn(mMockBlockingQueue);
-        SynchronousOperation<String> synchronousOperation = new SynchronousOperation<String>() {
-            @Override
-            public String call() throws BluetoothException {
-                throw exception;
-            }
-        };
-
-        @SuppressWarnings("unused") // future return.
-        Future<?> possiblyIgnoredError = mBluetoothOperationExecutor.schedule(synchronousOperation);
-
-        verify(mMockBlockingQueue).add(exception);
-        verify(mMockSemaphore).release();
-    }
-
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void test_AsynchronousOperation_exception() throws Exception {
-        final BluetoothException exception = new BluetoothException(EXCEPTION_REASON);
-        when(mMockBlockingQueueProvider.get()).thenReturn(mMockBlockingQueue);
-        Operation<String> operation = new Operation<String>() {
-            @Override
-            public void run() throws BluetoothException {
-                throw exception;
-            }
-        };
-
-        @SuppressWarnings("unused") // future return.
-        Future<?> possiblyIgnoredError = mBluetoothOperationExecutor.schedule(operation);
-
-        verify(mMockBlockingQueue).add(exception);
-        verify(mMockSemaphore).release();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
deleted file mode 100644
index 70dcec8..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.common.eventloop;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SdkSuppress;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-public class EventLoopTest {
-    private static final String TAG = "EventLoopTest";
-
-    private final EventLoop mEventLoop = EventLoop.newInstance(TAG);
-    private final List<Integer> mExecutedRunnables = new ArrayList<>();
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
-    /*
-    @Test
-    public void remove() {
-        mEventLoop.postRunnable(new NumberedRunnable(0));
-        NumberedRunnable runnableToAddAndRemove = new NumberedRunnable(1);
-        mEventLoop.postRunnable(runnableToAddAndRemove);
-        mEventLoop.removeRunnable(runnableToAddAndRemove);
-        mEventLoop.postRunnable(new NumberedRunnable(2));
-
-        assertThat(mExecutedRunnables).containsExactly(0, 2);
-    }
-    */
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void isPosted() {
-        NumberedRunnable runnable = new NumberedRunnable(0);
-        mEventLoop.postRunnableDelayed(runnable, 10 * 1000L);
-        assertThat(mEventLoop.isPosted(runnable)).isTrue();
-        mEventLoop.removeRunnable(runnable);
-        assertThat(mEventLoop.isPosted(runnable)).isFalse();
-
-        // Let a runnable execute, then verify that it's not posted.
-        mEventLoop.postRunnable(runnable);
-        assertThat(mEventLoop.isPosted(runnable)).isTrue();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void postAndWaitAfterDestroy() throws InterruptedException {
-        mEventLoop.destroy();
-        mEventLoop.postAndWait(new NumberedRunnable(0));
-
-        assertThat(mExecutedRunnables).isEmpty();
-    }
-
-
-    private class NumberedRunnable extends NamedRunnable {
-        private final int mId;
-
-        private NumberedRunnable(int id) {
-            super("NumberedRunnable:" + id);
-            this.mId = id;
-        }
-
-        @Override
-        public void run() {
-            // Note: when running in robolectric, this is not actually executed on a different
-            // thread, it's executed in the same thread the test runs in, so this is safe.
-            mExecutedRunnables.add(mId);
-        }
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
deleted file mode 100644
index 346a961..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.server.nearby.fastpair;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.nearby.FastPairDevice;
-
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
-import com.android.server.nearby.provider.FastPairDataProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import service.proto.Rpcs;
-
-public class FastPairAdvHandlerTest {
-    @Mock
-    private Context mContext;
-    @Mock
-    private FastPairDataProvider mFastPairDataProvider;
-    @Mock
-    private FastPairHalfSheetManager mFastPairHalfSheetManager;
-    @Mock
-    private FastPairNotificationManager mFastPairNotificationManager;
-    private static final String BLUETOOTH_ADDRESS = "AA:BB:CC:DD";
-    private static final int CLOSE_RSSI = -80;
-    private static final int FAR_AWAY_RSSI = -120;
-    private static final int TX_POWER = -70;
-    private static final byte[] INITIAL_BYTE_ARRAY = new byte[]{0x01, 0x02, 0x03};
-
-    LocatorContextWrapper mLocatorContextWrapper;
-    FastPairAdvHandler mFastPairAdvHandler;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mLocatorContextWrapper = new LocatorContextWrapper(mContext);
-        mLocatorContextWrapper.getLocator().overrideBindingForTest(
-                FastPairHalfSheetManager.class, mFastPairHalfSheetManager
-        );
-        mLocatorContextWrapper.getLocator().overrideBindingForTest(
-                FastPairNotificationManager.class, mFastPairNotificationManager
-        );
-        when(mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any()))
-                .thenReturn(Rpcs.GetObservedDeviceResponse.getDefaultInstance());
-        mFastPairAdvHandler = new FastPairAdvHandler(mLocatorContextWrapper, mFastPairDataProvider);
-    }
-
-    @Test
-    public void testInitialBroadcast() {
-        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .setData(INITIAL_BYTE_ARRAY)
-                .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                .setRssi(CLOSE_RSSI)
-                .setTxPower(TX_POWER)
-                .build();
-
-        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
-
-        verify(mFastPairHalfSheetManager).showHalfSheet(any());
-    }
-
-    @Test
-    public void testInitialBroadcast_farAway_notShowHalfSheet() {
-        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .setData(INITIAL_BYTE_ARRAY)
-                .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                .setRssi(FAR_AWAY_RSSI)
-                .setTxPower(TX_POWER)
-                .build();
-
-        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
-
-        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
-    }
-
-    @Test
-    public void testSubsequentBroadcast() {
-        byte[] fastPairRecordWithBloomFilter =
-                new byte[]{
-                        (byte) 0x02,
-                        (byte) 0x01,
-                        (byte) 0x02, // Flags
-                        (byte) 0x02,
-                        (byte) 0x0A,
-                        (byte) 0xEB, // Tx Power (-20)
-                        (byte) 0x0B,
-                        (byte) 0x16,
-                        (byte) 0x2C,
-                        (byte) 0xFE, // FastPair Service Data
-                        (byte) 0x00, // Flags (model ID length = 3)
-                        (byte) 0x40, // Account key hash flags (length = 4, type = 0)
-                        (byte) 0x11,
-                        (byte) 0x22,
-                        (byte) 0x33,
-                        (byte) 0x44, // Account key hash (0x11223344)
-                        (byte) 0x11, // Account key salt flags (length = 1, type = 1)
-                        (byte) 0x55, // Account key salt
-                };
-        FastPairDevice fastPairDevice = new FastPairDevice.Builder()
-                .setData(fastPairRecordWithBloomFilter)
-                .setBluetoothAddress(BLUETOOTH_ADDRESS)
-                .setRssi(CLOSE_RSSI)
-                .setTxPower(TX_POWER)
-                .build();
-
-        mFastPairAdvHandler.handleBroadcast(fastPairDevice);
-
-        verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
deleted file mode 100644
index 26d1847..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-public class FastPairManagerTest {
-    private FastPairManager mFastPairManager;
-    @Mock private Context mContext;
-    private LocatorContextWrapper mLocatorContextWrapper;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mLocatorContextWrapper = new LocatorContextWrapper(mContext);
-        mFastPairManager = new FastPairManager(mLocatorContextWrapper);
-        when(mContext.getContentResolver()).thenReturn(
-                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testFastPairInit() {
-        mFastPairManager.initiate();
-
-        verify(mContext, times(1)).registerReceiver(any(), any());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testFastPairCleanUp() {
-        mFastPairManager.cleanUp();
-
-        verify(mContext, times(1)).unregisterReceiver(any());
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/ModuleTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/ModuleTest.java
deleted file mode 100644
index bb4e3d0..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/ModuleTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 src.com.android.server.nearby.fastpair;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.common.eventloop.EventLoop;
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.FastPairAdvHandler;
-import com.android.server.nearby.fastpair.FastPairModule;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Clock;
-
-import src.com.android.server.nearby.fastpair.testing.MockingLocator;
-
-public class ModuleTest {
-    private Locator mLocator;
-
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLocator = MockingLocator.withMocksOnly(ApplicationProvider.getApplicationContext());
-        mLocator.bind(new FastPairModule());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void genericConstructor() {
-        assertThat(mLocator.get(FastPairCacheManager.class)).isNotNull();
-        assertThat(mLocator.get(FootprintsDeviceManager.class)).isNotNull();
-        assertThat(mLocator.get(EventLoop.class)).isNotNull();
-        assertThat(mLocator.get(FastPairHalfSheetManager.class)).isNotNull();
-        assertThat(mLocator.get(FastPairAdvHandler.class)).isNotNull();
-        assertThat(mLocator.get(Clock.class)).isNotNull();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void genericDestroy() {
-        mLocator.destroy();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
deleted file mode 100644
index fdda6f7..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2021 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.server.nearby.fastpair.cache;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SdkSuppress;
-
-import com.google.protobuf.ByteString;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import service.proto.Cache;
-
-public class FastPairCacheManagerTest {
-
-    private static final String MODEL_ID = "001";
-    private static final String MODEL_ID2 = "002";
-    private static final String APP_NAME = "APP_NAME";
-    private static final String MAC_ADDRESS = "00:11:22:33";
-    private static final ByteString ACCOUNT_KEY = ByteString.copyFromUtf8("axgs");
-    private static final String MAC_ADDRESS_B = "00:11:22:44";
-    private static final ByteString ACCOUNT_KEY_B = ByteString.copyFromUtf8("axgb");
-
-    @Mock
-    DiscoveryItem mDiscoveryItem;
-    @Mock
-    DiscoveryItem mDiscoveryItem2;
-    @Mock
-    Cache.StoredFastPairItem mStoredFastPairItem;
-    Cache.StoredDiscoveryItem mStoredDiscoveryItem = Cache.StoredDiscoveryItem.newBuilder()
-            .setTriggerId(MODEL_ID)
-            .setAppName(APP_NAME).build();
-    Cache.StoredDiscoveryItem mStoredDiscoveryItem2 = Cache.StoredDiscoveryItem.newBuilder()
-            .setTriggerId(MODEL_ID2)
-            .setAppName(APP_NAME).build();
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void notSaveRetrieveInfo() {
-        Context mContext = ApplicationProvider.getApplicationContext();
-        when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
-        when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
-
-        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
-
-        assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID).getAppName())
-                .isNotEqualTo(APP_NAME);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void saveRetrieveInfo() {
-        Context mContext = ApplicationProvider.getApplicationContext();
-        when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
-        when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
-        when(mDiscoveryItem2.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem2);
-        when(mDiscoveryItem2.getTriggerId()).thenReturn(MODEL_ID2);
-
-        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
-        fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem);
-        assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID).getAppName())
-                .isEqualTo(APP_NAME);
-        assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(1);
-
-        fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem2);
-        assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID2).getAppName())
-                .isEqualTo(APP_NAME);
-        assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(2);
-        fastPairCacheManager.cleanUp();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void saveRetrieveInfoStoredFastPairItem() {
-        Context mContext = ApplicationProvider.getApplicationContext();
-        Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
-                .setMacAddress(MAC_ADDRESS)
-                .setAccountKey(ACCOUNT_KEY)
-                .build();
-
-
-        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
-        fastPairCacheManager.putStoredFastPairItem(storedFastPairItem);
-
-        assertThat(fastPairCacheManager.getStoredFastPairItemFromMacAddress(
-                MAC_ADDRESS).getAccountKey())
-                .isEqualTo(ACCOUNT_KEY);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void checkGetAllFastPairItems() {
-        Context mContext = ApplicationProvider.getApplicationContext();
-        Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
-                .setMacAddress(MAC_ADDRESS)
-                .setAccountKey(ACCOUNT_KEY)
-                .build();
-        Cache.StoredFastPairItem storedFastPairItemB = Cache.StoredFastPairItem.newBuilder()
-                .setMacAddress(MAC_ADDRESS_B)
-                .setAccountKey(ACCOUNT_KEY_B)
-                .build();
-
-        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
-        fastPairCacheManager.putStoredFastPairItem(storedFastPairItem);
-        fastPairCacheManager.putStoredFastPairItem(storedFastPairItemB);
-
-        assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
-                .isEqualTo(2);
-
-        fastPairCacheManager.removeStoredFastPairItem(MAC_ADDRESS_B);
-
-        assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
-                .isEqualTo(1);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
deleted file mode 100644
index 58e4c47..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.server.nearby.fastpair.halfsheet;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
-
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.FastPairController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import service.proto.Cache;
-
-public class FastPairHalfSheetManagerTest {
-    private static final String BLEADDRESS = "11:22:44:66";
-    private static final String NAME = "device_name";
-    private FastPairHalfSheetManager mFastPairHalfSheetManager;
-    private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
-    @Mock
-    LocatorContextWrapper mContextWrapper;
-    @Mock
-    ResolveInfo mResolveInfo;
-    @Mock
-    PackageManager mPackageManager;
-    @Mock
-    Locator mLocator;
-    @Mock
-    FastPairController mFastPairController;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
-                .setAddress(BLEADDRESS)
-                .setDeviceName(NAME)
-                .build();
-    }
-
-    @Test
-    public void verifyFastPairHalfSheetManagerBehavior() {
-        mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
-        ResolveInfo resolveInfo = new ResolveInfo();
-        List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
-        mPackageManager = mock(PackageManager.class);
-        when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
-        resolveInfo.activityInfo = new ActivityInfo();
-        ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.sourceDir = "/apex/com.android.tethering";
-        applicationInfo.packageName = "test.package";
-        resolveInfo.activityInfo.applicationInfo = applicationInfo;
-        resolveInfoList.add(resolveInfo);
-        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
-        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
-
-        mFastPairHalfSheetManager =
-                new FastPairHalfSheetManager(mContextWrapper);
-
-        when(mContextWrapper.getLocator()).thenReturn(mLocator);
-
-        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-
-        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
-
-        verify(mContextWrapper, atLeastOnce())
-                .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
-    }
-
-    @Test
-    public void verifyFastPairHalfSheetManagerHalfSheetApkNotValidBehavior() {
-        mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
-        ResolveInfo resolveInfo = new ResolveInfo();
-        List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
-        mPackageManager = mock(PackageManager.class);
-        when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
-        resolveInfo.activityInfo = new ActivityInfo();
-        ApplicationInfo applicationInfo = new ApplicationInfo();
-        // application directory is wrong
-        applicationInfo.sourceDir = "/apex/com.android.nearby";
-        applicationInfo.packageName = "test.package";
-        resolveInfo.activityInfo.applicationInfo = applicationInfo;
-        resolveInfoList.add(resolveInfo);
-        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
-        when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
-
-        mFastPairHalfSheetManager =
-                new FastPairHalfSheetManager(mContextWrapper);
-
-        when(mContextWrapper.getLocator()).thenReturn(mLocator);
-
-        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-
-        mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
-
-        verify(mContextWrapper, never())
-                .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
deleted file mode 100644
index 2ade5f2..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.server.nearby.fastpair.pairinghandler;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import androidx.annotation.Nullable;
-
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
-import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
-import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
-import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
-
-import com.google.protobuf.ByteString;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Clock;
-
-import service.proto.Cache;
-import service.proto.Rpcs;
-
-public class PairingProgressHandlerBaseTest {
-    @Mock
-    Locator mLocator;
-    @Mock
-    LocatorContextWrapper mContextWrapper;
-    @Mock
-    Clock mClock;
-    @Mock
-    FastPairCacheManager mFastPairCacheManager;
-    @Mock
-    FootprintsDeviceManager mFootprintsDeviceManager;
-    private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
-
-    @Before
-    public void setup() {
-
-        MockitoAnnotations.initMocks(this);
-        when(mContextWrapper.getLocator()).thenReturn(mLocator);
-        mLocator.overrideBindingForTest(FastPairCacheManager.class,
-                mFastPairCacheManager);
-        mLocator.overrideBindingForTest(Clock.class, mClock);
-    }
-
-    @Test
-    public void createHandler_halfSheetSubsequentPairing_notificationPairingHandlerCreated() {
-
-        DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
-        discoveryItem.setStoredItemForTest(
-                discoveryItem.getStoredItemForTest().toBuilder()
-                        .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
-                        .setFastPairInformation(
-                                Cache.FastPairInformation.newBuilder()
-                                        .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
-                        .build());
-
-        PairingProgressHandlerBase progressHandler =
-                createProgressHandler(ACCOUNT_KEY, discoveryItem, /* isRetroactivePair= */ false);
-
-        assertThat(progressHandler).isInstanceOf(NotificationPairingProgressHandler.class);
-    }
-
-    @Test
-    public void createHandler_halfSheetInitialPairing_halfSheetPairingHandlerCreated() {
-        // No account key
-        DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
-        discoveryItem.setStoredItemForTest(
-                discoveryItem.getStoredItemForTest().toBuilder()
-                        .setFastPairInformation(
-                                Cache.FastPairInformation.newBuilder()
-                                        .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
-                        .build());
-
-        PairingProgressHandlerBase progressHandler =
-                createProgressHandler(null, discoveryItem, /* isRetroactivePair= */ false);
-
-        assertThat(progressHandler).isInstanceOf(HalfSheetPairingProgressHandler.class);
-    }
-
-    private PairingProgressHandlerBase createProgressHandler(
-            @Nullable byte[] accountKey, DiscoveryItem fastPairItem, boolean isRetroactivePair) {
-        FastPairNotificationManager fastPairNotificationManager =
-                new FastPairNotificationManager(mContextWrapper, fastPairItem, true);
-        FastPairHalfSheetManager fastPairHalfSheetManager =
-                new FastPairHalfSheetManager(mContextWrapper);
-        mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager);
-        PairingProgressHandlerBase pairingProgressHandlerBase =
-                PairingProgressHandlerBase.create(
-                        mContextWrapper,
-                        fastPairItem,
-                        fastPairItem.getAppPackageName(),
-                        accountKey,
-                        mFootprintsDeviceManager,
-                        fastPairNotificationManager,
-                        fastPairHalfSheetManager,
-                        isRetroactivePair);
-        return pairingProgressHandlerBase;
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
deleted file mode 100644
index c406e47..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.server.nearby.fastpair.testing;
-
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-
-import service.proto.Cache;
-
-public class FakeDiscoveryItems {
-    public static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
-    public static final long DEFAULT_TIMESTAMP = 1000000000L;
-    public static final String DEFAULT_DESCRIPITON = "description";
-    public static final String TRIGGER_ID = "trigger.id";
-    private static final String FAST_PAIR_ID = "id";
-    private static final int RSSI = -80;
-    private static final int TX_POWER = -10;
-    public static DiscoveryItem newFastPairDiscoveryItem(LocatorContextWrapper contextWrapper) {
-        return new DiscoveryItem(contextWrapper, newFastPairDeviceStoredItem());
-    }
-
-    public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem() {
-        return newFastPairDeviceStoredItem(TRIGGER_ID);
-    }
-
-    public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem(String triggerId) {
-        Cache.StoredDiscoveryItem.Builder item = Cache.StoredDiscoveryItem.newBuilder();
-        item.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
-        item.setId(FAST_PAIR_ID);
-        item.setDescription(DEFAULT_DESCRIPITON);
-        item.setTriggerId(triggerId);
-        item.setMacAddress(DEFAULT_MAC_ADDRESS);
-        item.setFirstObservationTimestampMillis(DEFAULT_TIMESTAMP);
-        item.setLastObservationTimestampMillis(DEFAULT_TIMESTAMP);
-        item.setRssi(RSSI);
-        item.setTxPower(TX_POWER);
-        return item.build();
-    }
-
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
deleted file mode 100644
index b261b26..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 src.com.android.server.nearby.fastpair.testing;
-
-import android.content.Context;
-
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.LocatorContextWrapper;
-
-/** A locator for tests that, by default, installs mocks for everything that's requested of it. */
-public class MockingLocator extends Locator {
-    private final LocatorContextWrapper mLocatorContextWrapper;
-
-    /**
-     * Creates a MockingLocator with the explicit bindings already configured on the given locator.
-     */
-    public static MockingLocator withBindings(Context context, Locator locator) {
-        Locator mockingLocator = new Locator(context);
-        mockingLocator.bind(new MockingModule());
-        locator.attachParent(mockingLocator);
-        return new MockingLocator(context, locator);
-    }
-
-    /** Creates a MockingLocator with no explicit bindings. */
-    public static MockingLocator withMocksOnly(Context context) {
-        return withBindings(context, new Locator(context));
-    }
-
-    @SuppressWarnings("nullness") // due to passing in this before initialized.
-    private MockingLocator(Context context, Locator locator) {
-        super(context, locator);
-        this.mLocatorContextWrapper = new LocatorContextWrapper(context, this);
-    }
-
-    /** Returns a LocatorContextWrapper with this Locator attached. */
-    public LocatorContextWrapper getContextForTest() {
-        return mLocatorContextWrapper;
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingModule.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingModule.java
deleted file mode 100644
index 7938c55..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingModule.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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 src.com.android.server.nearby.fastpair.testing;
-
-import android.content.Context;
-
-import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.common.locator.Module;
-
-
-import org.mockito.Mockito;
-
-/** Module for tests that just provides mocks for anything that's requested of it. */
-public class MockingModule extends Module {
-
-    @Override
-    public void configure(Context context, Class<?> type, Locator locator) {
-        configureMock(type, locator);
-    }
-
-    private <T> void configureMock(Class<T> type, Locator locator) {
-        T mock = Mockito.mock(type);
-        locator.bind(type, mock);
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
deleted file mode 100644
index eeea319..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * 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.server.nearby.provider;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.accounts.Account;
-import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
-import android.nearby.aidl.FastPairDeviceMetadataParcel;
-import android.nearby.aidl.FastPairDiscoveryItemParcel;
-import android.nearby.aidl.FastPairEligibleAccountParcel;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
-
-import com.google.protobuf.ByteString;
-
-import org.junit.Test;
-
-import java.util.List;
-
-import service.proto.Cache;
-import service.proto.Data;
-import service.proto.FastPairString.FastPairStrings;
-import service.proto.Rpcs;
-
-public class UtilsTest {
-
-    private static final String ASSISTANT_SETUP_HALFSHEET = "ASSISTANT_SETUP_HALFSHEET";
-    private static final String ASSISTANT_SETUP_NOTIFICATION = "ASSISTANT_SETUP_NOTIFICATION";
-    private static final int BLE_TX_POWER = 5;
-    private static final String CONFIRM_PIN_DESCRIPTION = "CONFIRM_PIN_DESCRIPTION";
-    private static final String CONFIRM_PIN_TITLE = "CONFIRM_PIN_TITLE";
-    private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
-            "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
-    private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
-            "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
-    private static final int DEVICE_TYPE = 1;
-    private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
-            "DOWNLOAD_COMPANION_APP_DESCRIPTION";
-    private static final Account ELIGIBLE_ACCOUNT_1 = new Account("abc@google.com", "type1");
-    private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
-            "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
-    private static final String FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
-            "FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION";
-    private static final byte[] IMAGE = new byte[]{7, 9};
-    private static final String IMAGE_URL = "IMAGE_URL";
-    private static final String INITIAL_NOTIFICATION_DESCRIPTION =
-            "INITIAL_NOTIFICATION_DESCRIPTION";
-    private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
-            "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
-    private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
-    private static final String INTENT_URI = "INTENT_URI";
-    private static final String LOCALE = "LOCALE";
-    private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
-    private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
-            "RETRO_ACTIVE_PAIRING_DESCRIPTION";
-    private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
-    private static final String SYNC_CONTACT_DESCRPTION = "SYNC_CONTACT_DESCRPTION";
-    private static final String SYNC_CONTACTS_TITLE = "SYNC_CONTACTS_TITLE";
-    private static final String SYNC_SMS_DESCRIPTION = "SYNC_SMS_DESCRIPTION";
-    private static final String SYNC_SMS_TITLE = "SYNC_SMS_TITLE";
-    private static final float TRIGGER_DISTANCE = 111;
-    private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
-    private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
-            "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
-    private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
-            "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
-    private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
-    private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
-    private static final String UPDATE_COMPANION_APP_DESCRIPTION =
-            "UPDATE_COMPANION_APP_DESCRIPTION";
-    private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
-            "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
-    private static final byte[] ACCOUNT_KEY = new byte[]{3};
-    private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[]{2, 8};
-    private static final byte[] ANTI_SPOOFING_KEY = new byte[]{4, 5, 6};
-    private static final String ACTION_URL = "ACTION_URL";
-    private static final int ACTION_URL_TYPE = 1;
-    private static final String APP_NAME = "APP_NAME";
-    private static final int ATTACHMENT_TYPE = 1;
-    private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[]{5, 7};
-    private static final byte[] BLE_RECORD_BYTES = new byte[]{2, 4};
-    private static final int DEBUG_CATEGORY = 1;
-    private static final String DEBUG_MESSAGE = "DEBUG_MESSAGE";
-    private static final String DESCRIPTION = "DESCRIPTION";
-    private static final String DEVICE_NAME = "DEVICE_NAME";
-    private static final String DISPLAY_URL = "DISPLAY_URL";
-    private static final String ENTITY_ID = "ENTITY_ID";
-    private static final String FEATURE_GRAPHIC_URL = "FEATURE_GRAPHIC_URL";
-    private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
-    private static final String GROUP_ID = "GROUP_ID";
-    private static final String ICON_FIFE_URL = "ICON_FIFE_URL";
-    private static final byte[] ICON_PNG = new byte[]{2, 5};
-    private static final String ID = "ID";
-    private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
-    private static final int LAST_USER_EXPERIENCE = 1;
-    private static final long LOST_MILLIS = 393284L;
-    private static final String MAC_ADDRESS = "MAC_ADDRESS";
-    private static final String NAME = "NAME";
-    private static final String PACKAGE_NAME = "PACKAGE_NAME";
-    private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
-    private static final int RSSI = 9;
-    private static final int STATE = 1;
-    private static final String TITLE = "TITLE";
-    private static final String TRIGGER_ID = "TRIGGER_ID";
-    private static final int TX_POWER = 63;
-    private static final int TYPE = 1;
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHappyPathConvertToFastPairDevicesWithAccountKey() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = {
-                genHappyPathFastPairAccountkeyDeviceMetadataParcel()};
-
-        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
-                Utils.convertToFastPairDevicesWithAccountKey(array);
-        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
-        assertThat(deviceWithAccountKey.get(0)).isEqualTo(
-                genHappyPathFastPairDeviceWithAccountKey());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairDevicesWithAccountKeyWithNullArray() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = null;
-        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(0);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairDevicesWithAccountKeyWithNullElement() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = {null};
-        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(0);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairDevicesWithAccountKeyWithEmptyElementNoCrash() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = {
-                genEmptyFastPairAccountkeyDeviceMetadataParcel()};
-
-        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
-                Utils.convertToFastPairDevicesWithAccountKey(array);
-        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairDevicesWithAccountKeyWithEmptyMetadataDiscoveryNoCrash() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = {
-                genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem()};
-
-        List<Data.FastPairDeviceWithAccountKey> deviceWithAccountKey =
-                Utils.convertToFastPairDevicesWithAccountKey(array);
-        assertThat(deviceWithAccountKey.size()).isEqualTo(1);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairDevicesWithAccountKeyWithMixedArrayElements() {
-        FastPairAccountKeyDeviceMetadataParcel[] array = {
-                null,
-                genHappyPathFastPairAccountkeyDeviceMetadataParcel(),
-                genEmptyFastPairAccountkeyDeviceMetadataParcel(),
-                genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem()};
-
-        assertThat(Utils.convertToFastPairDevicesWithAccountKey(array).size()).isEqualTo(3);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHappyPathConvertToAccountList() {
-        FastPairEligibleAccountParcel[] array = {genHappyPathFastPairEligibleAccountParcel()};
-
-        List<Account> accountList = Utils.convertToAccountList(array);
-        assertThat(accountList.size()).isEqualTo(1);
-        assertThat(accountList.get(0)).isEqualTo(ELIGIBLE_ACCOUNT_1);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToAccountListNullArray() {
-        FastPairEligibleAccountParcel[] array = null;
-
-        List<Account> accountList = Utils.convertToAccountList(array);
-        assertThat(accountList.size()).isEqualTo(0);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToAccountListWithNullElement() {
-        FastPairEligibleAccountParcel[] array = {null};
-
-        List<Account> accountList = Utils.convertToAccountList(array);
-        assertThat(accountList.size()).isEqualTo(0);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToAccountListWithEmptyElementNotCrash() {
-        FastPairEligibleAccountParcel[] array =
-                {genEmptyFastPairEligibleAccountParcel()};
-
-        List<Account> accountList = Utils.convertToAccountList(array);
-        assertThat(accountList.size()).isEqualTo(0);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToAccountListWithMixedArrayElements() {
-        FastPairEligibleAccountParcel[] array = {
-                genHappyPathFastPairEligibleAccountParcel(),
-                genEmptyFastPairEligibleAccountParcel(),
-                null,
-                genHappyPathFastPairEligibleAccountParcel()};
-
-        List<Account> accountList = Utils.convertToAccountList(array);
-        assertThat(accountList.size()).isEqualTo(2);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHappyPathConvertToGetObservedDeviceResponse() {
-        Rpcs.GetObservedDeviceResponse response =
-                Utils.convertToGetObservedDeviceResponse(
-                        genHappyPathFastPairAntispoofKeyDeviceMetadataParcel());
-        assertThat(response).isEqualTo(genHappyPathObservedDeviceResponse());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToGetObservedDeviceResponseWithNullInput() {
-        assertThat(Utils.convertToGetObservedDeviceResponse(null))
-                .isEqualTo(null);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToGetObservedDeviceResponseWithEmptyInputNotCrash() {
-        Utils.convertToGetObservedDeviceResponse(
-                genEmptyFastPairAntispoofKeyDeviceMetadataParcel());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToGetObservedDeviceResponseWithEmptyDeviceMetadataNotCrash() {
-        Utils.convertToGetObservedDeviceResponse(
-                genFastPairAntispoofKeyDeviceMetadataParcelWithEmptyDeviceMetadata());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testHappyPathConvertToFastPairAccountKeyDeviceMetadata() {
-        FastPairAccountKeyDeviceMetadataParcel metadataParcel =
-                Utils.convertToFastPairAccountKeyDeviceMetadata(genHappyPathFastPairUploadInfo());
-        ensureHappyPathAsExpected(metadataParcel);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairAccountKeyDeviceMetadataWithNullInput() {
-        assertThat(Utils.convertToFastPairAccountKeyDeviceMetadata(null)).isEqualTo(null);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testConvertToFastPairAccountKeyDeviceMetadataWithEmptyFieldsNotCrash() {
-        Utils.convertToFastPairAccountKeyDeviceMetadata(
-                new FastPairUploadInfo(
-                        null /* discoveryItem */,
-                        null /* accountKey */,
-                        null /* sha256AccountKeyPublicAddress */));
-    }
-
-    private static FastPairUploadInfo genHappyPathFastPairUploadInfo() {
-        return new FastPairUploadInfo(
-                genHappyPathStoredDiscoveryItem(),
-                ByteString.copyFrom(ACCOUNT_KEY),
-                ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
-
-    }
-
-    private static void ensureHappyPathAsExpected(
-            FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
-        assertThat(metadataParcel).isNotNull();
-        assertThat(metadataParcel.deviceAccountKey).isEqualTo(ACCOUNT_KEY);
-        assertThat(metadataParcel.sha256DeviceAccountKeyPublicAddress)
-                .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
-        ensureHappyPathAsExpected(metadataParcel.metadata);
-        ensureHappyPathAsExpected(metadataParcel.discoveryItem);
-    }
-
-    private static void ensureHappyPathAsExpected(FastPairDeviceMetadataParcel metadataParcel) {
-        assertThat(metadataParcel).isNotNull();
-
-        assertThat(metadataParcel.connectSuccessCompanionAppInstalled).isEqualTo(
-                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
-        assertThat(metadataParcel.connectSuccessCompanionAppNotInstalled).isEqualTo(
-                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
-
-        assertThat(metadataParcel.deviceType).isEqualTo(DEVICE_TYPE);
-
-        assertThat(metadataParcel.failConnectGoToSettingsDescription).isEqualTo(
-                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
-
-        assertThat(metadataParcel.initialNotificationDescription).isEqualTo(
-                INITIAL_NOTIFICATION_DESCRIPTION);
-        assertThat(metadataParcel.initialNotificationDescriptionNoAccount).isEqualTo(
-                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
-        assertThat(metadataParcel.initialPairingDescription).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
-
-
-        assertThat(metadataParcel.retroactivePairingDescription).isEqualTo(
-                RETRO_ACTIVE_PAIRING_DESCRIPTION);
-
-        assertThat(metadataParcel.subsequentPairingDescription).isEqualTo(
-                SUBSEQUENT_PAIRING_DESCRIPTION);
-
-        assertThat(metadataParcel.trueWirelessImageUrlCase).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
-        assertThat(metadataParcel.trueWirelessImageUrlLeftBud).isEqualTo(
-                TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
-        assertThat(metadataParcel.trueWirelessImageUrlRightBud).isEqualTo(
-                TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
-        assertThat(metadataParcel.waitLaunchCompanionAppDescription).isEqualTo(
-                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
-
-        /* do we need upload this? */
-        // assertThat(metadataParcel.locale).isEqualTo(LOCALE);
-        // assertThat(metadataParcel.name).isEqualTo(NAME);
-        // assertThat(metadataParcel.downloadCompanionAppDescription).isEqualTo(
-        //        DOWNLOAD_COMPANION_APP_DESCRIPTION);
-        // assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
-        //        OPEN_COMPANION_APP_DESCRIPTION);
-        // assertThat(metadataParcel.triggerDistance).isWithin(DELTA).of(TRIGGER_DISTANCE);
-        // assertThat(metadataParcel.unableToConnectDescription).isEqualTo(
-        //        UNABLE_TO_CONNECT_DESCRIPTION);
-        // assertThat(metadataParcel.unableToConnectTitle).isEqualTo(UNABLE_TO_CONNECT_TITLE);
-        // assertThat(metadataParcel.updateCompanionAppDescription).isEqualTo(
-        //        UPDATE_COMPANION_APP_DESCRIPTION);
-
-        // assertThat(metadataParcel.bleTxPower).isEqualTo(BLE_TX_POWER);
-        // assertThat(metadataParcel.image).isEqualTo(IMAGE);
-        // assertThat(metadataParcel.imageUrl).isEqualTo(IMAGE_URL);
-        // assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
-    }
-
-    private static void ensureHappyPathAsExpected(FastPairDiscoveryItemParcel itemParcel) {
-        assertThat(itemParcel.actionUrl).isEqualTo(ACTION_URL);
-        assertThat(itemParcel.actionUrlType).isEqualTo(ACTION_URL_TYPE);
-        assertThat(itemParcel.appName).isEqualTo(APP_NAME);
-        assertThat(itemParcel.authenticationPublicKeySecp256r1)
-                .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
-        assertThat(itemParcel.description).isEqualTo(DESCRIPTION);
-        assertThat(itemParcel.deviceName).isEqualTo(DEVICE_NAME);
-        assertThat(itemParcel.displayUrl).isEqualTo(DISPLAY_URL);
-        assertThat(itemParcel.firstObservationTimestampMillis)
-                .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
-        assertThat(itemParcel.iconFifeUrl).isEqualTo(ICON_FIFE_URL);
-        assertThat(itemParcel.iconPng).isEqualTo(ICON_PNG);
-        assertThat(itemParcel.id).isEqualTo(ID);
-        assertThat(itemParcel.lastObservationTimestampMillis)
-                .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
-        assertThat(itemParcel.macAddress).isEqualTo(MAC_ADDRESS);
-        assertThat(itemParcel.packageName).isEqualTo(PACKAGE_NAME);
-        assertThat(itemParcel.pendingAppInstallTimestampMillis)
-                .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
-        assertThat(itemParcel.rssi).isEqualTo(RSSI);
-        assertThat(itemParcel.state).isEqualTo(STATE);
-        assertThat(itemParcel.title).isEqualTo(TITLE);
-        assertThat(itemParcel.triggerId).isEqualTo(TRIGGER_ID);
-        assertThat(itemParcel.txPower).isEqualTo(TX_POWER);
-    }
-
-    private static FastPairEligibleAccountParcel genHappyPathFastPairEligibleAccountParcel() {
-        FastPairEligibleAccountParcel parcel = new FastPairEligibleAccountParcel();
-        parcel.account = ELIGIBLE_ACCOUNT_1;
-        parcel.optIn = true;
-
-        return parcel;
-    }
-
-    private static FastPairEligibleAccountParcel genEmptyFastPairEligibleAccountParcel() {
-        return new FastPairEligibleAccountParcel();
-    }
-
-    private static FastPairDeviceMetadataParcel genEmptyFastPairDeviceMetadataParcel() {
-        return new FastPairDeviceMetadataParcel();
-    }
-
-    private static FastPairDiscoveryItemParcel genEmptyFastPairDiscoveryItemParcel() {
-        return new FastPairDiscoveryItemParcel();
-    }
-
-    private static FastPairAccountKeyDeviceMetadataParcel
-            genEmptyFastPairAccountkeyDeviceMetadataParcel() {
-        return new FastPairAccountKeyDeviceMetadataParcel();
-    }
-
-    private static FastPairAccountKeyDeviceMetadataParcel
-            genFastPairAccountkeyDeviceMetadataParcelWithEmptyMetadataDiscoveryItem() {
-        FastPairAccountKeyDeviceMetadataParcel parcel =
-                new FastPairAccountKeyDeviceMetadataParcel();
-        parcel.metadata = genEmptyFastPairDeviceMetadataParcel();
-        parcel.discoveryItem = genEmptyFastPairDiscoveryItemParcel();
-
-        return parcel;
-    }
-
-    private static FastPairAccountKeyDeviceMetadataParcel
-            genHappyPathFastPairAccountkeyDeviceMetadataParcel() {
-        FastPairAccountKeyDeviceMetadataParcel parcel =
-                new FastPairAccountKeyDeviceMetadataParcel();
-        parcel.deviceAccountKey = ACCOUNT_KEY;
-        parcel.metadata = genHappyPathFastPairDeviceMetadataParcel();
-        parcel.sha256DeviceAccountKeyPublicAddress = SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS;
-        parcel.discoveryItem = genHappyPathFastPairDiscoveryItemParcel();
-
-        return parcel;
-    }
-
-    private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
-        FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
-
-        parcel.bleTxPower = BLE_TX_POWER;
-        parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
-        parcel.connectSuccessCompanionAppNotInstalled =
-                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
-        parcel.deviceType = DEVICE_TYPE;
-        parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
-        parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
-        parcel.image = IMAGE;
-        parcel.imageUrl = IMAGE_URL;
-        parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
-        parcel.initialNotificationDescriptionNoAccount =
-                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
-        parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
-        parcel.intentUri = INTENT_URI;
-        parcel.name = NAME;
-        parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
-        parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
-        parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
-        parcel.triggerDistance = TRIGGER_DISTANCE;
-        parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
-        parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
-        parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
-        parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
-        parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
-        parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
-        parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
-
-        return parcel;
-    }
-
-    private static Cache.StoredDiscoveryItem genHappyPathStoredDiscoveryItem() {
-        Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder =
-                Cache.StoredDiscoveryItem.newBuilder();
-        storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
-        storedDiscoveryItemBuilder.setActionUrlType(Cache.ResolvedUrlType.WEBPAGE);
-        storedDiscoveryItemBuilder.setAppName(APP_NAME);
-        storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
-                ByteString.copyFrom(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1));
-        storedDiscoveryItemBuilder.setDescription(DESCRIPTION);
-        storedDiscoveryItemBuilder.setDeviceName(DEVICE_NAME);
-        storedDiscoveryItemBuilder.setDisplayUrl(DISPLAY_URL);
-        storedDiscoveryItemBuilder.setFirstObservationTimestampMillis(
-                FIRST_OBSERVATION_TIMESTAMP_MILLIS);
-        storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
-        storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
-        storedDiscoveryItemBuilder.setId(ID);
-        storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
-                LAST_OBSERVATION_TIMESTAMP_MILLIS);
-        storedDiscoveryItemBuilder.setMacAddress(MAC_ADDRESS);
-        storedDiscoveryItemBuilder.setPackageName(PACKAGE_NAME);
-        storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis(
-                PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
-        storedDiscoveryItemBuilder.setRssi(RSSI);
-        storedDiscoveryItemBuilder.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
-        storedDiscoveryItemBuilder.setTitle(TITLE);
-        storedDiscoveryItemBuilder.setTriggerId(TRIGGER_ID);
-        storedDiscoveryItemBuilder.setTxPower(TX_POWER);
-
-        FastPairStrings.Builder stringsBuilder = FastPairStrings.newBuilder();
-        stringsBuilder.setPairingFinishedCompanionAppInstalled(
-                CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
-        stringsBuilder.setPairingFinishedCompanionAppNotInstalled(
-                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
-        stringsBuilder.setPairingFailDescription(
-                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
-        stringsBuilder.setTapToPairWithAccount(
-                INITIAL_NOTIFICATION_DESCRIPTION);
-        stringsBuilder.setTapToPairWithoutAccount(
-                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
-        stringsBuilder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
-        stringsBuilder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
-        stringsBuilder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
-        stringsBuilder.setWaitAppLaunchDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
-        storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build());
-
-        Cache.FastPairInformation.Builder fpInformationBuilder =
-                Cache.FastPairInformation.newBuilder();
-        Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
-                Rpcs.TrueWirelessHeadsetImages.newBuilder();
-        imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE);
-        imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
-        imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
-        fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build());
-        fpInformationBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES);
-
-        storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build());
-        storedDiscoveryItemBuilder.setTxPower(TX_POWER);
-
-        storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
-        storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
-        storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
-
-        return storedDiscoveryItemBuilder.build();
-    }
-
-    private static Data.FastPairDeviceWithAccountKey genHappyPathFastPairDeviceWithAccountKey() {
-        Data.FastPairDeviceWithAccountKey.Builder fpDeviceBuilder =
-                Data.FastPairDeviceWithAccountKey.newBuilder();
-        fpDeviceBuilder.setAccountKey(ByteString.copyFrom(ACCOUNT_KEY));
-        fpDeviceBuilder.setSha256AccountKeyPublicAddress(
-                ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
-        fpDeviceBuilder.setDiscoveryItem(genHappyPathStoredDiscoveryItem());
-
-        return fpDeviceBuilder.build();
-    }
-
-    private static FastPairDiscoveryItemParcel genHappyPathFastPairDiscoveryItemParcel() {
-        FastPairDiscoveryItemParcel parcel = new FastPairDiscoveryItemParcel();
-        parcel.actionUrl = ACTION_URL;
-        parcel.actionUrlType = ACTION_URL_TYPE;
-        parcel.appName = APP_NAME;
-        parcel.authenticationPublicKeySecp256r1 = AUTHENTICATION_PUBLIC_KEY_SEC_P256R1;
-        parcel.description = DESCRIPTION;
-        parcel.deviceName = DEVICE_NAME;
-        parcel.displayUrl = DISPLAY_URL;
-        parcel.firstObservationTimestampMillis = FIRST_OBSERVATION_TIMESTAMP_MILLIS;
-        parcel.iconFifeUrl = ICON_FIFE_URL;
-        parcel.iconPng = ICON_PNG;
-        parcel.id = ID;
-        parcel.lastObservationTimestampMillis = LAST_OBSERVATION_TIMESTAMP_MILLIS;
-        parcel.macAddress = MAC_ADDRESS;
-        parcel.packageName = PACKAGE_NAME;
-        parcel.pendingAppInstallTimestampMillis = PENDING_APP_INSTALL_TIMESTAMP_MILLIS;
-        parcel.rssi = RSSI;
-        parcel.state = STATE;
-        parcel.title = TITLE;
-        parcel.triggerId = TRIGGER_ID;
-        parcel.txPower = TX_POWER;
-
-        return parcel;
-    }
-
-    private static Rpcs.GetObservedDeviceResponse genHappyPathObservedDeviceResponse() {
-        Rpcs.Device.Builder deviceBuilder = Rpcs.Device.newBuilder();
-        deviceBuilder.setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder()
-                .setPublicKey(ByteString.copyFrom(ANTI_SPOOFING_KEY))
-                .build());
-        Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
-                Rpcs.TrueWirelessHeadsetImages.newBuilder();
-        imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
-        imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
-        imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE);
-        deviceBuilder.setTrueWirelessImages(imagesBuilder.build());
-        deviceBuilder.setImageUrl(IMAGE_URL);
-        deviceBuilder.setIntentUri(INTENT_URI);
-        deviceBuilder.setName(NAME);
-        deviceBuilder.setBleTxPower(BLE_TX_POWER);
-        deviceBuilder.setTriggerDistance(TRIGGER_DISTANCE);
-        deviceBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES);
-
-        return Rpcs.GetObservedDeviceResponse.newBuilder()
-                .setDevice(deviceBuilder.build())
-                .setImage(ByteString.copyFrom(IMAGE))
-                .setStrings(Rpcs.ObservedDeviceStrings.newBuilder()
-                        .setConnectSuccessCompanionAppInstalled(
-                                CONNECT_SUCCESS_COMPANION_APP_INSTALLED)
-                        .setConnectSuccessCompanionAppNotInstalled(
-                                CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED)
-                        .setDownloadCompanionAppDescription(
-                                DOWNLOAD_COMPANION_APP_DESCRIPTION)
-                        .setFailConnectGoToSettingsDescription(
-                                FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION)
-                        .setInitialNotificationDescription(
-                                INITIAL_NOTIFICATION_DESCRIPTION)
-                        .setInitialNotificationDescriptionNoAccount(
-                                INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT)
-                        .setInitialPairingDescription(
-                                INITIAL_PAIRING_DESCRIPTION)
-                        .setOpenCompanionAppDescription(
-                                OPEN_COMPANION_APP_DESCRIPTION)
-                        .setRetroactivePairingDescription(
-                                RETRO_ACTIVE_PAIRING_DESCRIPTION)
-                        .setSubsequentPairingDescription(
-                                SUBSEQUENT_PAIRING_DESCRIPTION)
-                        .setUnableToConnectDescription(
-                                UNABLE_TO_CONNECT_DESCRIPTION)
-                        .setUnableToConnectTitle(
-                                UNABLE_TO_CONNECT_TITLE)
-                        .setUpdateCompanionAppDescription(
-                                UPDATE_COMPANION_APP_DESCRIPTION)
-                        .setWaitLaunchCompanionAppDescription(
-                                WAIT_LAUNCH_COMPANION_APP_DESCRIPTION)
-                        .build())
-                .build();
-    }
-
-    private static FastPairAntispoofKeyDeviceMetadataParcel
-            genHappyPathFastPairAntispoofKeyDeviceMetadataParcel() {
-        FastPairAntispoofKeyDeviceMetadataParcel parcel =
-                new FastPairAntispoofKeyDeviceMetadataParcel();
-        parcel.antispoofPublicKey = ANTI_SPOOFING_KEY;
-        parcel.deviceMetadata = genHappyPathFastPairDeviceMetadataParcel();
-
-        return parcel;
-    }
-
-    private static FastPairAntispoofKeyDeviceMetadataParcel
-            genFastPairAntispoofKeyDeviceMetadataParcelWithEmptyDeviceMetadata() {
-        FastPairAntispoofKeyDeviceMetadataParcel parcel =
-                new FastPairAntispoofKeyDeviceMetadataParcel();
-        parcel.antispoofPublicKey = ANTI_SPOOFING_KEY;
-        parcel.deviceMetadata = genEmptyFastPairDeviceMetadataParcel();
-
-        return parcel;
-    }
-
-    private static FastPairAntispoofKeyDeviceMetadataParcel
-            genEmptyFastPairAntispoofKeyDeviceMetadataParcel() {
-        return new FastPairAntispoofKeyDeviceMetadataParcel();
-    }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
deleted file mode 100644
index f098600..0000000
--- a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.server.nearby.util;
-
-import static com.google.common.io.BaseEncoding.base16;
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.protobuf.ByteString;
-
-import org.junit.Test;
-
-import service.proto.Cache;
-import service.proto.FastPairString.FastPairStrings;
-import service.proto.Rpcs;
-import service.proto.Rpcs.GetObservedDeviceResponse;
-
-public final class DataUtilsTest {
-    private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
-    private static final String APP_PACKAGE = "test_package";
-    private static final String APP_ACTION_URL =
-            "intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
-                    + "package=to_be_set;"
-                    + "component=to_be_set;"
-                    + "to_be_set%3AEXTRA_COMPANION_APP="
-                    + APP_PACKAGE
-                    + ";end";
-    private static final long DEVICE_ID = 12;
-    private static final String DEVICE_NAME = "My device";
-    private static final byte[] DEVICE_PUBLIC_KEY = base16().decode("0123456789ABCDEF");
-    private static final String DEVICE_COMPANY = "Company name";
-    private static final byte[] DEVICE_IMAGE = new byte[] {0x00, 0x01, 0x10, 0x11};
-    private static final String DEVICE_IMAGE_URL = "device_image_url";
-    private static final String AUTHORITY = "com.android.test";
-    private static final String SIGNATURE_HASH = "as8dfbyu2duas7ikanvklpaclo2";
-    private static final String ACCOUNT = "test@gmail.com";
-
-    private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION = "message 1";
-    private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT = "message 2";
-    private static final String MESSAGE_INIT_PAIR_DESCRIPTION = "message 3 %s";
-    private static final String MESSAGE_COMPANION_INSTALLED = "message 4";
-    private static final String MESSAGE_COMPANION_NOT_INSTALLED = "message 5";
-    private static final String MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION = "message 6";
-    private static final String MESSAGE_RETROACTIVE_PAIR_DESCRIPTION = "message 7";
-    private static final String MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = "message 8";
-    private static final String MESSAGE_FAIL_CONNECT_DESCRIPTION = "message 9";
-    private static final String MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
-            "message 10";
-    private static final String MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION = "message 11";
-    private static final String MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION = "message 12";
-
-    @Test
-    public void test_toScanFastPairStoreItem_withAccount() {
-        Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
-        assertThat(item.getAddress()).isEqualTo(BLUETOOTH_ADDRESS);
-        assertThat(item.getActionUrl()).isEqualTo(APP_ACTION_URL);
-        assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
-        assertThat(item.getIconPng()).isEqualTo(ByteString.copyFrom(DEVICE_IMAGE));
-        assertThat(item.getIconFifeUrl()).isEqualTo(DEVICE_IMAGE_URL);
-        assertThat(item.getAntiSpoofingPublicKey())
-                .isEqualTo(ByteString.copyFrom(DEVICE_PUBLIC_KEY));
-
-        FastPairStrings strings = item.getFastPairStrings();
-        assertThat(strings.getTapToPairWithAccount()).isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION);
-        assertThat(strings.getTapToPairWithoutAccount())
-                .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
-        assertThat(strings.getInitialPairingDescription())
-                .isEqualTo(String.format(MESSAGE_INIT_PAIR_DESCRIPTION, DEVICE_NAME));
-        assertThat(strings.getPairingFinishedCompanionAppInstalled())
-                .isEqualTo(MESSAGE_COMPANION_INSTALLED);
-        assertThat(strings.getPairingFinishedCompanionAppNotInstalled())
-                .isEqualTo(MESSAGE_COMPANION_NOT_INSTALLED);
-        assertThat(strings.getSubsequentPairingDescription())
-                .isEqualTo(MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION);
-        assertThat(strings.getRetroactivePairingDescription())
-                .isEqualTo(MESSAGE_RETROACTIVE_PAIR_DESCRIPTION);
-        assertThat(strings.getWaitAppLaunchDescription())
-                .isEqualTo(MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
-        assertThat(strings.getPairingFailDescription())
-                .isEqualTo(MESSAGE_FAIL_CONNECT_DESCRIPTION);
-    }
-
-    @Test
-    public void test_toScanFastPairStoreItem_withoutAccount() {
-        Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, /* account= */ null);
-        FastPairStrings strings = item.getFastPairStrings();
-        assertThat(strings.getInitialPairingDescription())
-                .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
-    }
-
-    @Test
-    public void test_toString() {
-        Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
-                createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
-        FastPairStrings strings = item.getFastPairStrings();
-
-        assertThat(DataUtils.toString(strings))
-                .isEqualTo("FastPairStrings[tapToPairWithAccount=message 1, "
-                        + "tapToPairWithoutAccount=message 2, "
-                        + "initialPairingDescription=message 3 " + DEVICE_NAME + ", "
-                        + "pairingFinishedCompanionAppInstalled=message 4, "
-                        + "pairingFinishedCompanionAppNotInstalled=message 5, "
-                        + "subsequentPairingDescription=message 6, "
-                        + "retroactivePairingDescription=message 7, "
-                        + "waitAppLaunchDescription=message 8, "
-                        + "pairingFailDescription=message 9]");
-    }
-
-    private static GetObservedDeviceResponse createObservedDeviceResponse() {
-        return GetObservedDeviceResponse.newBuilder()
-                .setDevice(
-                        Rpcs.Device.newBuilder()
-                                .setId(DEVICE_ID)
-                                .setName(DEVICE_NAME)
-                                .setAntiSpoofingKeyPair(
-                                        Rpcs.AntiSpoofingKeyPair
-                                                .newBuilder()
-                                                .setPublicKey(
-                                                        ByteString.copyFrom(DEVICE_PUBLIC_KEY)))
-                                .setIntentUri(APP_ACTION_URL)
-                                .setDataOnlyConnection(true)
-                                .setAssistantSupported(false)
-                                .setCompanionDetail(
-                                        Rpcs.CompanionAppDetails.newBuilder()
-                                                .setAuthority(AUTHORITY)
-                                                .setCertificateHash(SIGNATURE_HASH)
-                                                .build())
-                                .setCompanyName(DEVICE_COMPANY)
-                                .setImageUrl(DEVICE_IMAGE_URL))
-                .setImage(ByteString.copyFrom(DEVICE_IMAGE))
-                .setStrings(
-                        Rpcs.ObservedDeviceStrings.newBuilder()
-                                .setInitialNotificationDescription(MESSAGE_INIT_NOTIFY_DESCRIPTION)
-                                .setInitialNotificationDescriptionNoAccount(
-                                        MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT)
-                                .setInitialPairingDescription(MESSAGE_INIT_PAIR_DESCRIPTION)
-                                .setConnectSuccessCompanionAppInstalled(MESSAGE_COMPANION_INSTALLED)
-                                .setConnectSuccessCompanionAppNotInstalled(
-                                        MESSAGE_COMPANION_NOT_INSTALLED)
-                                .setSubsequentPairingDescription(
-                                        MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION)
-                                .setRetroactivePairingDescription(
-                                        MESSAGE_RETROACTIVE_PAIR_DESCRIPTION)
-                                .setWaitLaunchCompanionAppDescription(
-                                        MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION)
-                                .setFailConnectGoToSettingsDescription(
-                                        MESSAGE_FAIL_CONNECT_DESCRIPTION))
-                .build();
-    }
-}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 73feee4..fc680d9 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -76,6 +76,19 @@
 }
 
 static Status initPrograms(const char* cg2_path) {
+    // This code was mainlined in T, so this should be trivially satisfied.
+    if (!modules::sdklevel::IsAtLeastT()) abort();
+
+    // S requires eBPF support which was only added in 4.9, so this should be satisfied.
+    if (!bpf::isAtLeastKernelVersion(4, 9, 0)) abort();
+
+    // U bumps the kernel requirement up to 4.14
+    if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) abort();
+
+    // V bumps the kernel requirement up to 4.19
+    if (modules::sdklevel::IsAtLeastV() && !bpf::isAtLeastKernelVersion(4, 19, 0)) abort();
+
+    // U mandates this mount point (though it should also be the case on T)
     if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) abort();
 
     unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index dfaf8cf..6e7b8d2 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -25,7 +25,7 @@
 java_library {
     name: "service-remoteauth-pre-jarjar",
     srcs: [":remoteauth-service-srcs"],
-    required: ["libremoteauth_jni_rust_defaults"],
+    required: ["libremoteauth_jni_rust"],
     defaults: [
         "enable-remoteauth-targets",
         "framework-system-server-module-defaults",
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthConnectionCache.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthConnectionCache.java
new file mode 100644
index 0000000..49481a2
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthConnectionCache.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.connectivity.Connection;
+import com.android.server.remoteauth.connectivity.ConnectionException;
+import com.android.server.remoteauth.connectivity.ConnectionInfo;
+import com.android.server.remoteauth.connectivity.ConnectivityManager;
+import com.android.server.remoteauth.connectivity.EventListener;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Manages caching of remote devices {@link ConnectionInfo} and {@link Connection}.
+ *
+ * <p>Allows mapping between {@link ConnectionInfo#getConnectionId()} to {@link ConnectionInfo} and
+ * {@link Connection}
+ */
+public class RemoteAuthConnectionCache {
+    public static final String TAG = "RemoteAuthConCache";
+    private final Map<Integer, ConnectionInfo> mConnectionInfoMap = new ConcurrentHashMap<>();
+    private final Map<Integer, Connection> mConnectionMap = new ConcurrentHashMap<>();
+
+    private final ConnectivityManager mConnectivityManager;
+
+    public RemoteAuthConnectionCache(@NonNull ConnectivityManager connectivityManager) {
+        Preconditions.checkNotNull(connectivityManager);
+        this.mConnectivityManager = connectivityManager;
+    }
+
+    /** Returns the {@link ConnectivityManager}. */
+    ConnectivityManager getConnectivityManager() {
+        return mConnectivityManager;
+    }
+
+    /**
+     * Associates the connectionId with {@link ConnectionInfo}. Updates association with new value
+     * if already exists
+     *
+     * @param connectionInfo of the remote device
+     */
+    public void setConnectionInfo(@NonNull ConnectionInfo connectionInfo) {
+        Preconditions.checkNotNull(connectionInfo);
+        mConnectionInfoMap.put(connectionInfo.getConnectionId(), connectionInfo);
+    }
+
+    /** Returns {@link ConnectionInfo} associated with connectionId. */
+    public ConnectionInfo getConnectionInfo(int connectionId) {
+        return mConnectionInfoMap.get(connectionId);
+    }
+
+    /**
+     * Associates the connectionId with {@link Connection}. Updates association with new value if
+     * already exists
+     *
+     * @param connection to the remote device
+     */
+    public void setConnection(@NonNull Connection connection) {
+        Preconditions.checkNotNull(connection);
+        mConnectionMap.put(connection.getConnectionInfo().getConnectionId(), connection);
+    }
+
+    /**
+     * Returns {@link Connection} associated with connectionId. Uses {@link ConnectivityManager} to
+     * create and associate with new {@link Connection}, if mapping doesn't exist
+     *
+     * @param connectionId of the remote device
+     */
+    public Connection getConnection(int connectionId) {
+        return mConnectionMap.computeIfAbsent(
+                connectionId,
+                id -> {
+                    ConnectionInfo connectionInfo = getConnectionInfo(id);
+                    if (null == connectionInfo) {
+                        // TODO: Try accessing DB to fetch by connectionId
+                        Log.e(TAG, String.format("Unknown connectionId: %d", connectionId));
+                        return null;
+                    }
+                    try {
+                        Connection connection =
+                                mConnectivityManager.connect(
+                                        connectionInfo,
+                                        new EventListener() {
+                                            @Override
+                                            public void onDisconnect(
+                                                    @NonNull ConnectionInfo connectionInfo) {
+                                                removeConnection(connectionInfo.getConnectionId());
+                                                Log.i(
+                                                        TAG,
+                                                        String.format(
+                                                                "Disconnected from: %d",
+                                                                connectionInfo.getConnectionId()));
+                                            }
+                                        });
+                        if (null == connection) {
+                            Log.e(TAG, String.format("Failed to connect: %d", connectionId));
+                            return null;
+                        }
+                        return connection;
+                    } catch (ConnectionException e) {
+                        Log.e(
+                                TAG,
+                                String.format("Failed to create connection to %d.", connectionId),
+                                e);
+                        return null;
+                    }
+                });
+    }
+
+    /**
+     * Removes {@link Connection} from cache.
+     *
+     * @param connectionId of the remote device
+     */
+    public void removeConnection(int connectionId) {
+        if (null != mConnectionMap.remove(connectionId)) {
+            Log.i(
+                    TAG,
+                    String.format("Connection associated with id: %d was removed", connectionId));
+        }
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthPlatform.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthPlatform.java
new file mode 100644
index 0000000..a61aeff
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthPlatform.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import android.util.Log;
+
+import com.android.server.remoteauth.connectivity.Connection;
+import com.android.server.remoteauth.jni.INativeRemoteAuthService;
+
+/** Implementation of the {@link INativeRemoteAuthService.IPlatform} interface. */
+public class RemoteAuthPlatform implements INativeRemoteAuthService.IPlatform {
+    public static final String TAG = "RemoteAuthPlatform";
+    private final RemoteAuthConnectionCache mConnectionCache;
+
+    public RemoteAuthPlatform(RemoteAuthConnectionCache connectionCache) {
+        mConnectionCache = connectionCache;
+    }
+
+    /**
+     * Sends message to the remote device via {@link Connection} created by
+     * {@link com.android.server.remoteauth.connectivity.ConnectivityManager}.
+     *
+     * @param connectionId connection ID of the {@link android.remoteauth.RemoteAuthenticator}
+     * @param request payload of the request
+     * @param callback to be used to pass the response result
+     * @return true if succeeded, false otherwise.
+     * @hide
+     */
+    @Override
+    public boolean sendRequest(int connectionId, byte[] request, ResponseCallback callback) {
+        Connection connection = mConnectionCache.getConnection(connectionId);
+        if (null == connection) {
+            Log.e(TAG, String.format("Failed to get a connection for: %d", connectionId));
+            return false;
+        }
+        connection.sendRequest(
+                request,
+                new Connection.MessageResponseCallback() {
+                    @Override
+                    public void onSuccess(byte[] buffer) {
+                        callback.onSuccess(buffer);
+                    }
+
+                    @Override
+                    public void onFailure(@Connection.ErrorCode int errorCode) {
+                        callback.onFailure(errorCode);
+                    }
+                });
+        return true;
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
index 9374ace..619785f 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
@@ -65,7 +65,7 @@
     }
 
     private static void checkPermission(Context context, String permission) {
-        context.enforceCallingOrSelfPermission(permission,
-                "Must have " + permission + " permission.");
+        context.enforceCallingOrSelfPermission(
+                permission, "Must have " + permission + " permission.");
     }
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java
index d07adb1..8ec851a 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java
@@ -21,7 +21,7 @@
 /**
  * Listens to the events from underlying transport.
  */
-interface EventListener {
+public interface EventListener {
     /** Called when remote device is disconnected from the underlying transport. */
     void onDisconnect(@NonNull ConnectionInfo connectionInfo);
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
index f79ec7e..4aaa760 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
@@ -34,10 +34,10 @@
          * @param connectionId connection ID of the {@link android.remoteauth.RemoteAuthenticator}
          * @param request payload of the request
          * @param callback to be used to pass the response result
-         *
+         * @return true if succeeded, false otherwise.
          * @hide
          */
-        void sendRequest(int connectionId, byte[] request, ResponseCallback callback);
+        boolean sendRequest(int connectionId, byte[] request, ResponseCallback callback);
 
         /**
          * Interface for a callback to send a response back.
@@ -49,7 +49,6 @@
              * Invoked when message sending succeeds.
              *
              * @param response contains response
-             *
              * @hide
              */
             void onSuccess(byte[] response);
@@ -58,7 +57,6 @@
              * Invoked when message sending fails.
              *
              * @param errorCode indicating the error
-             *
              * @hide
              */
             void onFailure(int errorCode);
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java
index 39c2a74..a676562 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.remoteauth.jni;
 
+import android.util.Log;
+
 import com.android.internal.annotations.Keep;
 import com.android.server.remoteauth.jni.INativeRemoteAuthService.IPlatform;
 
@@ -53,12 +55,13 @@
      *     platform
      * @param platformHandle a handle associated with the platform object, used to pass the response
      *     to the specific platform
-     *
      * @hide
      */
     @Keep
     public void sendRequest(
             int connectionId, byte[] request, long responseHandle, long platformHandle) {
+        Log.d(TAG, String.format("sendRequest with connectionId: %d, rh: %d, ph: %d",
+                connectionId, responseHandle, platformHandle));
         mPlatform.sendRequest(
                 connectionId,
                 request,
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
index 3ae9838..4bf930c 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
@@ -21,6 +21,7 @@
 package com.android.server.remoteauth.jni;
 
 import com.android.internal.annotations.Keep;
+
 /**
  * Exception thrown by native platform rust implementation of {@link
  * com.android.server.remoteauth.RemoteAuthService}.
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index e6e8a43..a95a8fb 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -30,6 +30,12 @@
     host_supported: true,
 }
 
+rust_ffi_shared {
+    name: "libremoteauth_jni_rust",
+    defaults: ["libremoteauth_jni_rust_defaults"],
+    rustlibs: [],
+}
+
 rust_test {
     name: "libremoteauth_jni_rust_tests",
     defaults: ["libremoteauth_jni_rust_defaults"],
diff --git a/remoteauth/service/jni/src/lib.rs b/remoteauth/service/jni/src/lib.rs
index a816c94..c6f8c72 100644
--- a/remoteauth/service/jni/src/lib.rs
+++ b/remoteauth/service/jni/src/lib.rs
@@ -21,5 +21,7 @@
 mod unique_jvm;
 mod utils;
 
+/// Implementation of JNI platform functionality.
 pub mod remoteauth_jni_android_platform;
+/// Implementation of JNI protocol functionality.
 pub mod remoteauth_jni_android_protocol;
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index 1967c1a..0a189f2 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! Implementation of JNI platform functionality.
 use crate::jnames::{SEND_REQUEST_MNAME, SEND_REQUEST_MSIG};
 use crate::unique_jvm;
 use anyhow::anyhow;
@@ -73,11 +74,15 @@
     HANDLE_MAPPING.lock().unwrap().insert(handle, Arc::clone(&item));
 }
 
+/// Reports a response from remote device.
 pub trait ResponseCallback {
+    /// Invoked upon successful response
     fn on_response(&mut self, response: Vec<u8>);
+    /// Invoked upon failure
     fn on_error(&mut self, error_code: i32);
 }
 
+/// Trait to platform functionality
 pub trait Platform {
     /// Send a binary message to the remote with the given connection id and return the response.
     fn send_request(
@@ -89,6 +94,7 @@
 }
 //////////////////////////////////
 
+/// Implementation of Platform trait
 pub struct JavaPlatform {
     platform_handle: i64,
     vm: &'static Arc<JavaVM>,
@@ -99,7 +105,7 @@
 }
 
 impl JavaPlatform {
-    // Method to create JavaPlatform
+    /// Creates JavaPlatform and associates with unique handle id
     pub fn create(
         java_platform_native: JObject<'_>,
     ) -> Result<Arc<Mutex<impl Platform>>, JNIError> {
@@ -219,6 +225,7 @@
     }
 }
 
+/// Returns successful response from remote device
 #[no_mangle]
 pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_success(
     env: JNIEnv,
@@ -250,6 +257,7 @@
     }
 }
 
+/// Notifies about failure to receive a response from remote device
 #[no_mangle]
 pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_error(
     env: JNIEnv,
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
index 1f73207..ac2eb8c 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+//! Implementation of JNI protocol functionality.
 use crate::unique_jvm;
 use crate::utils::get_boolean_result;
 use jni::objects::JObject;
 use jni::sys::jboolean;
 use jni::JNIEnv;
 
+/// Initialize native library. Captures Java VM:
 #[no_mangle]
 pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_init(
     env: JNIEnv,
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthConnectionCacheTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthConnectionCacheTest.java
new file mode 100644
index 0000000..00f35d3
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthConnectionCacheTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.connectivity.Connection;
+import com.android.server.remoteauth.connectivity.ConnectionException;
+import com.android.server.remoteauth.connectivity.ConnectionInfo;
+import com.android.server.remoteauth.connectivity.ConnectivityManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit test for {@link com.android.server.remoteauth.RemoteAuthConnectionCache} */
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthConnectionCacheTest {
+    @Mock private Connection mConnection;
+    @Mock private ConnectionInfo mConnectionInfo;
+    @Mock private ConnectivityManager mConnectivityManager;
+    private RemoteAuthConnectionCache mConnectionCache;
+
+    private static final int CONNECTION_ID = 1;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(CONNECTION_ID).when(mConnectionInfo).getConnectionId();
+        doReturn(mConnectionInfo).when(mConnection).getConnectionInfo();
+        mConnectionCache = new RemoteAuthConnectionCache(mConnectivityManager);
+    }
+
+    @Test
+    public void testCreateCache_managerIsNull() {
+        assertThrows(NullPointerException.class, () -> new RemoteAuthConnectionCache(null));
+    }
+
+    @Test
+    public void testGetManager_managerExists() {
+        assertEquals(mConnectivityManager, mConnectionCache.getConnectivityManager());
+    }
+
+    @Test
+    public void testSetConnectionInfo_infoIsNull() {
+        assertThrows(NullPointerException.class, () -> mConnectionCache.setConnectionInfo(null));
+    }
+
+    @Test
+    public void testSetConnectionInfo_infoIsValid() {
+        mConnectionCache.setConnectionInfo(mConnectionInfo);
+
+        assertEquals(mConnectionInfo, mConnectionCache.getConnectionInfo(CONNECTION_ID));
+    }
+
+    @Test
+    public void testSetConnection_connectionIsNull() {
+        assertThrows(NullPointerException.class, () -> mConnectionCache.setConnection(null));
+    }
+
+    @Test
+    public void testGetConnection_connectionAlreadyExists() {
+        mConnectionCache.setConnection(mConnection);
+
+        assertEquals(mConnection, mConnectionCache.getConnection(CONNECTION_ID));
+    }
+
+    @Test
+    public void testGetConnection_connectionInfoDoesntExists() {
+        assertNull(mConnectionCache.getConnection(CONNECTION_ID));
+    }
+
+    @Test
+    public void testGetConnection_failedToConnect() {
+        mConnectionCache.setConnectionInfo(mConnectionInfo);
+        doReturn(null).when(mConnectivityManager).connect(eq(mConnectionInfo), anyObject());
+
+        assertNull(mConnectionCache.getConnection(CONNECTION_ID));
+    }
+
+    @Test
+    public void testGetConnection_failedToConnectException() {
+        mConnectionCache.setConnectionInfo(mConnectionInfo);
+        doThrow(ConnectionException.class)
+                .when(mConnectivityManager)
+                .connect(eq(mConnectionInfo), anyObject());
+
+        assertNull(mConnectionCache.getConnection(CONNECTION_ID));
+    }
+
+    @Test
+    public void testGetConnection_connectionSucceed() {
+        mConnectionCache.setConnectionInfo(mConnectionInfo);
+        doReturn(mConnection).when(mConnectivityManager).connect(eq(mConnectionInfo), anyObject());
+
+        assertEquals(mConnection, mConnectionCache.getConnection(CONNECTION_ID));
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthPlatformTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthPlatformTest.java
new file mode 100644
index 0000000..8975d52
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthPlatformTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.connectivity.Connection;
+import com.android.server.remoteauth.jni.INativeRemoteAuthService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit test for {@link com.android.server.remoteauth.RemoteAuthPlatform} */
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthPlatformTest {
+    @Mock private Connection mConnection;
+    @Mock private RemoteAuthConnectionCache mConnectionCache;
+    private RemoteAuthPlatform mPlatform;
+    private static final int CONNECTION_ID = 1;
+    private static final byte[] REQUEST = new byte[] {(byte) 0x01};
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mPlatform = new RemoteAuthPlatform(mConnectionCache);
+    }
+
+    @Test
+    public void testSendRequest_connectionIsNull() {
+        doReturn(null).when(mConnectionCache).getConnection(anyInt());
+        assertFalse(
+                mPlatform.sendRequest(
+                        CONNECTION_ID,
+                        REQUEST,
+                        new INativeRemoteAuthService.IPlatform.ResponseCallback() {
+                            @Override
+                            public void onSuccess(byte[] response) {}
+
+                            @Override
+                            public void onFailure(int errorCode) {}
+                        }));
+    }
+
+    @Test
+    public void testSendRequest_connectionExists() {
+        doReturn(mConnection).when(mConnectionCache).getConnection(anyInt());
+        assertTrue(
+                mPlatform.sendRequest(
+                        CONNECTION_ID,
+                        REQUEST,
+                        new INativeRemoteAuthService.IPlatform.ResponseCallback() {
+                            @Override
+                            public void onSuccess(byte[] response) {}
+
+                            @Override
+                            public void onFailure(int errorCode) {}
+                        }));
+        verify(mConnection, times(1)).sendRequest(eq(REQUEST), anyObject());
+    }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 08527a3..7e2d2f4 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -91,6 +91,10 @@
 java_library {
     name: "service-connectivity-mdns-standalone-build-test",
     sdk_version: "core_platform",
+    min_sdk_version: "21",
+    lint: {
+        error_checks: ["NewApi"],
+    },
     srcs: [
         "src/com/android/server/connectivity/mdns/**/*.java",
         ":framework-connectivity-t-mdns-standalone-build-sources",
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 597c06f..42a922d 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -24,26 +24,30 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ConnectivityStatsLog;
 
+import java.util.Random;
+
 /**
  * Class to record the NetworkNsdReported into statsd. Each client should create this class to
  * report its data.
  */
 public class NetworkNsdReportedMetrics {
-    // Whether this client is using legacy backend.
-    private final boolean mIsLegacy;
+    // The upper bound for the random number used in metrics data sampling determines the possible
+    // sample rate.
+    private static final int RANDOM_NUMBER_UPPER_BOUND = 1000;
     // The client id.
     private final int mClientId;
     private final Dependencies mDependencies;
+    private final Random mRandom;
 
-    public NetworkNsdReportedMetrics(boolean isLegacy, int clientId) {
-        this(isLegacy, clientId, new Dependencies());
+    public NetworkNsdReportedMetrics(int clientId) {
+        this(clientId, new Dependencies());
     }
 
     @VisibleForTesting
-    NetworkNsdReportedMetrics(boolean isLegacy, int clientId, Dependencies dependencies) {
-        mIsLegacy = isLegacy;
+    NetworkNsdReportedMetrics(int clientId, Dependencies dependencies) {
         mClientId = clientId;
         mDependencies = dependencies;
+        mRandom = dependencies.makeRandomGenerator();
     }
 
     /**
@@ -67,26 +71,40 @@
                     event.getFoundCallbackCount(),
                     event.getLostCallbackCount(),
                     event.getRepliedRequestsCount(),
-                    event.getSentQueryCount());
+                    event.getSentQueryCount(),
+                    event.getSentPacketCount(),
+                    event.getConflictDuringProbingCount(),
+                    event.getConflictAfterProbingCount(),
+                    event.getRandomNumber());
+        }
+
+        /**
+         * @see Random
+         */
+        public Random makeRandomGenerator() {
+            return new Random();
         }
     }
 
-    private Builder makeReportedBuilder() {
+    private Builder makeReportedBuilder(boolean isLegacy, int transactionId) {
         final Builder builder = NetworkNsdReported.newBuilder();
-        builder.setIsLegacy(mIsLegacy);
+        builder.setIsLegacy(isLegacy);
         builder.setClientId(mClientId);
+        builder.setRandomNumber(mRandom.nextInt(RANDOM_NUMBER_UPPER_BOUND));
+        builder.setTransactionId(transactionId);
         return builder;
     }
 
     /**
      * Report service registration succeeded metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service registration.
      * @param durationMs The duration of service registration success.
      */
-    public void reportServiceRegistrationSucceeded(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceRegistrationSucceeded(boolean isLegacy, int transactionId,
+            long durationMs) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_REGISTER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTERED);
         builder.setEventDurationMillisec(durationMs);
@@ -96,12 +114,13 @@
     /**
      * Report service registration failed metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service registration.
      * @param durationMs The duration of service registration failed.
      */
-    public void reportServiceRegistrationFailed(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceRegistrationFailed(boolean isLegacy, int transactionId,
+            long durationMs) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_REGISTER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED);
         builder.setEventDurationMillisec(durationMs);
@@ -111,27 +130,36 @@
     /**
      * Report service unregistration success metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service registration.
      * @param durationMs The duration of service stayed registered.
+     * @param repliedRequestsCount The replied request count of this service before unregistered it.
+     * @param sentPacketCount The total sent packet count of this service before unregistered it.
+     * @param conflictDuringProbingCount The number of conflict during probing.
+     * @param conflictAfterProbingCount The number of conflict after probing.
      */
-    public void reportServiceUnregistration(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceUnregistration(boolean isLegacy, int transactionId, long durationMs,
+            int repliedRequestsCount, int sentPacketCount, int conflictDuringProbingCount,
+            int conflictAfterProbingCount) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_REGISTER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_UNREGISTERED);
         builder.setEventDurationMillisec(durationMs);
-        // TODO: Report repliedRequestsCount
+        builder.setRepliedRequestsCount(repliedRequestsCount);
+        builder.setSentPacketCount(sentPacketCount);
+        builder.setConflictDuringProbingCount(conflictDuringProbingCount);
+        builder.setConflictAfterProbingCount(conflictAfterProbingCount);
         mDependencies.statsWrite(builder.build());
     }
 
     /**
      * Report service discovery started metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service discovery.
      */
-    public void reportServiceDiscoveryStarted(int transactionId) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceDiscoveryStarted(boolean isLegacy, int transactionId) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_DISCOVER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STARTED);
         mDependencies.statsWrite(builder.build());
@@ -140,12 +168,13 @@
     /**
      * Report service discovery failed metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service discovery.
      * @param durationMs The duration of service discovery failed.
      */
-    public void reportServiceDiscoveryFailed(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceDiscoveryFailed(boolean isLegacy, int transactionId,
+            long durationMs) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_DISCOVER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_FAILED);
         builder.setEventDurationMillisec(durationMs);
@@ -155,6 +184,7 @@
     /**
      * Report service discovery stop metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service discovery.
      * @param durationMs The duration of discovering services.
      * @param foundCallbackCount The count of found service callbacks before stop discovery.
@@ -162,10 +192,9 @@
      * @param servicesCount The count of found services.
      * @param sentQueryCount The count of sent queries before stop discovery.
      */
-    public void reportServiceDiscoveryStop(int transactionId, long durationMs,
+    public void reportServiceDiscoveryStop(boolean isLegacy, int transactionId, long durationMs,
             int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_DISCOVER);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STOP);
         builder.setEventDurationMillisec(durationMs);
@@ -179,15 +208,15 @@
     /**
      * Report service resolution success metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service resolution.
      * @param durationMs The duration of resolving services.
      * @param isServiceFromCache Whether the resolved service is from cache.
      * @param sentQueryCount The count of sent queries during resolving.
      */
-    public void reportServiceResolved(int transactionId, long durationMs,
+    public void reportServiceResolved(boolean isLegacy, int transactionId, long durationMs,
             boolean isServiceFromCache, int sentQueryCount) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_RESOLVE);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLVED);
         builder.setEventDurationMillisec(durationMs);
@@ -199,12 +228,13 @@
     /**
      * Report service resolution failed metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service resolution.
      * @param durationMs The duration of service resolution failed.
      */
-    public void reportServiceResolutionFailed(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceResolutionFailed(boolean isLegacy, int transactionId,
+            long durationMs) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_RESOLVE);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_FAILED);
         builder.setEventDurationMillisec(durationMs);
@@ -214,12 +244,12 @@
     /**
      * Report service resolution stop metric data.
      *
+     * @param isLegacy Whether this call is using legacy backend.
      * @param transactionId The transaction id of service resolution.
      * @param durationMs The duration before stop resolving the service.
      */
-    public void reportServiceResolutionStop(int transactionId, long durationMs) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+    public void reportServiceResolutionStop(boolean isLegacy, int transactionId, long durationMs) {
+        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
         builder.setType(NsdEventType.NET_RESOLVE);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP);
         builder.setEventDurationMillisec(durationMs);
@@ -232,8 +262,8 @@
      * @param transactionId The transaction id of service info callback registration.
      */
     public void reportServiceInfoCallbackRegistered(int transactionId) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+        // service info callback is always using new backend.
+        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
         builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTERED);
         mDependencies.statsWrite(builder.build());
@@ -245,8 +275,8 @@
      * @param transactionId The transaction id of service callback registration.
      */
     public void reportServiceInfoCallbackRegistrationFailed(int transactionId) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+        // service info callback is always using new backend.
+        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
         builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTRATION_FAILED);
         mDependencies.statsWrite(builder.build());
@@ -265,8 +295,8 @@
     public void reportServiceInfoCallbackUnregistered(int transactionId, long durationMs,
             int updateCallbackCount, int lostCallbackCount, boolean isServiceFromCache,
             int sentQueryCount) {
-        final Builder builder = makeReportedBuilder();
-        builder.setTransactionId(transactionId);
+        // service info callback is always using new backend.
+        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
         builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
         builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_UNREGISTERED);
         builder.setEventDurationMillisec(durationMs);
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 27e97f1..468d7bd 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -26,14 +26,16 @@
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
 import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
-
 import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
 import static com.android.networkstack.apishim.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
+import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
 import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
@@ -58,6 +60,7 @@
 import android.net.nsd.OffloadServiceInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -87,6 +90,7 @@
 import com.android.server.connectivity.mdns.ExecutorProvider;
 import com.android.server.connectivity.mdns.MdnsAdvertiser;
 import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
+import com.android.server.connectivity.mdns.MdnsFeatureFlags;
 import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
 import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
 import com.android.server.connectivity.mdns.MdnsSearchOptions;
@@ -118,6 +122,7 @@
  *
  * @hide
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class NsdService extends INsdManager.Stub {
     private static final String TAG = "NsdService";
     private static final String MDNS_TAG = "mDnsConnector";
@@ -606,7 +611,7 @@
                             final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
                             final NetworkNsdReportedMetrics metrics =
                                     mDeps.makeNetworkNsdReportedMetrics(
-                                            !arg.useJavaBackend, (int) mClock.elapsedRealtime());
+                                            (int) mClock.elapsedRealtime());
                             cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
                                     mServiceLogs.forSubComponent(tag), metrics);
                             mClients.put(arg.connector, cInfo);
@@ -630,8 +635,8 @@
                     case NsdManager.DISCOVER_SERVICES:
                         cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
-                            cInfo.onDiscoverServicesFailedImmediately(
-                                    clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            cInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                         }
                        break;
                     case NsdManager.STOP_DISCOVERY:
@@ -644,8 +649,8 @@
                     case NsdManager.REGISTER_SERVICE:
                         cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
-                            cInfo.onRegisterServiceFailedImmediately(
-                                    clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            cInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                         }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
@@ -658,8 +663,8 @@
                     case NsdManager.RESOLVE_SERVICE:
                         cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
-                            cInfo.onResolveServiceFailedImmediately(
-                                    clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            cInfo.onResolveServiceFailedImmediately(clientRequestId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                         }
                         break;
                     case NsdManager.STOP_RESOLUTION:
@@ -724,13 +729,15 @@
                 return false;
             }
 
-            private void storeLegacyRequestMap(int clientRequestId, int transactionId,
+            private ClientRequest storeLegacyRequestMap(int clientRequestId, int transactionId,
                     ClientInfo clientInfo, int what, long startTimeMs) {
-                clientInfo.mClientRequests.put(clientRequestId,
-                        new LegacyClientRequest(transactionId, what, startTimeMs));
+                final LegacyClientRequest request =
+                        new LegacyClientRequest(transactionId, what, startTimeMs);
+                clientInfo.mClientRequests.put(clientRequestId, request);
                 mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
                 // Remove the cleanup event because here comes a new request.
                 cancelStop();
+                return request;
             }
 
             private void storeAdvertiserRequestMap(int clientRequestId, int transactionId,
@@ -756,13 +763,15 @@
                 }
             }
 
-            private void storeDiscoveryManagerRequestMap(int clientRequestId, int transactionId,
-                    MdnsListener listener, ClientInfo clientInfo,
+            private ClientRequest storeDiscoveryManagerRequestMap(int clientRequestId,
+                    int transactionId, MdnsListener listener, ClientInfo clientInfo,
                     @Nullable Network requestedNetwork) {
-                clientInfo.mClientRequests.put(clientRequestId, new DiscoveryManagerRequest(
-                        transactionId, listener, requestedNetwork, mClock.elapsedRealtime()));
+                final DiscoveryManagerRequest request = new DiscoveryManagerRequest(transactionId,
+                        listener, requestedNetwork, mClock.elapsedRealtime());
+                clientInfo.mClientRequests.put(clientRequestId, request);
                 mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
                 updateMulticastLock();
+                return request;
             }
 
             /**
@@ -804,8 +813,8 @@
                         }
 
                         if (requestLimitReached(clientInfo)) {
-                            clientInfo.onDiscoverServicesFailedImmediately(
-                                    clientRequestId, NsdManager.FAILURE_MAX_LIMIT);
+                            clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+                                    NsdManager.FAILURE_MAX_LIMIT, true /* isLegacy */);
                             break;
                         }
 
@@ -819,8 +828,8 @@
                                 || mDeps.isMdnsDiscoveryManagerEnabled(mContext)
                                 || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
-                                clientInfo.onDiscoverServicesFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
                                 break;
                             }
 
@@ -840,10 +849,10 @@
                             }
                             mMdnsDiscoveryManager.registerListener(
                                     listenServiceType, listener, optionsBuilder.build());
-                            storeDiscoveryManagerRequestMap(clientRequestId, transactionId,
-                                    listener, clientInfo, info.getNetwork());
-                            clientInfo.onDiscoverServicesStarted(
-                                    clientRequestId, info, transactionId);
+                            final ClientRequest request = storeDiscoveryManagerRequestMap(
+                                    clientRequestId, transactionId, listener, clientInfo,
+                                    info.getNetwork());
+                            clientInfo.onDiscoverServicesStarted(clientRequestId, info, request);
                             clientInfo.log("Register a DiscoveryListener " + transactionId
                                     + " for service type:" + listenServiceType);
                         } else {
@@ -853,14 +862,15 @@
                                     Log.d(TAG, "Discover " + msg.arg2 + " " + transactionId
                                             + info.getServiceType());
                                 }
-                                storeLegacyRequestMap(clientRequestId, transactionId, clientInfo,
-                                        msg.what, mClock.elapsedRealtime());
+                                final ClientRequest request = storeLegacyRequestMap(clientRequestId,
+                                        transactionId, clientInfo, msg.what,
+                                        mClock.elapsedRealtime());
                                 clientInfo.onDiscoverServicesStarted(
-                                        clientRequestId, info, transactionId);
+                                        clientRequestId, info, request);
                             } else {
                                 stopServiceDiscovery(transactionId);
-                                clientInfo.onDiscoverServicesFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                             }
                         }
                         break;
@@ -916,8 +926,8 @@
                         }
 
                         if (requestLimitReached(clientInfo)) {
-                            clientInfo.onRegisterServiceFailedImmediately(
-                                    clientRequestId, NsdManager.FAILURE_MAX_LIMIT);
+                            clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                    NsdManager.FAILURE_MAX_LIMIT, true /* isLegacy */);
                             break;
                         }
 
@@ -932,8 +942,8 @@
                                 || useAdvertiserForType(registerServiceType)) {
                             if (registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
-                                clientInfo.onRegisterServiceFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
                                 break;
                             }
                             serviceInfo.setServiceType(registerServiceType);
@@ -960,8 +970,8 @@
                                 // Return success after mDns reports success
                             } else {
                                 unregisterService(transactionId);
-                                clientInfo.onRegisterServiceFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                             }
 
                         }
@@ -990,16 +1000,19 @@
                         // Note isMdnsAdvertiserEnabled may have changed to false at this point,
                         // so this needs to check the type of the original request to unregister
                         // instead of looking at the flag value.
-                        final long stopTimeMs = mClock.elapsedRealtime();
                         if (request instanceof AdvertiserClientRequest) {
+                            final AdvertiserMetrics metrics =
+                                    mAdvertiser.getAdvertiserMetrics(transactionId);
                             mAdvertiser.removeService(transactionId);
-                            clientInfo.onUnregisterServiceSucceeded(clientRequestId, transactionId,
-                                    request.calculateRequestDurationMs(stopTimeMs));
+                            clientInfo.onUnregisterServiceSucceeded(
+                                    clientRequestId, request, metrics);
                         } else {
                             if (unregisterService(transactionId)) {
-                                clientInfo.onUnregisterServiceSucceeded(clientRequestId,
-                                        transactionId,
-                                        request.calculateRequestDurationMs(stopTimeMs));
+                                clientInfo.onUnregisterServiceSucceeded(clientRequestId, request,
+                                        new AdvertiserMetrics(NO_PACKET /* repliedRequestsCount */,
+                                                NO_PACKET /* sentPacketCount */,
+                                                0 /* conflictDuringProbingCount */,
+                                                0 /* conflictAfterProbingCount */));
                             } else {
                                 clientInfo.onUnregisterServiceFailed(
                                         clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -1029,8 +1042,8 @@
                                 ||  mDeps.isMdnsDiscoveryManagerEnabled(mContext)
                                 || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
-                                clientInfo.onResolveServiceFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onResolveServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
                                 break;
                             }
                             final String resolveServiceType = serviceType + ".local";
@@ -1052,8 +1065,8 @@
                                     + " for service type:" + resolveServiceType);
                         } else {
                             if (clientInfo.mResolvedService != null) {
-                                clientInfo.onResolveServiceFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_ALREADY_ACTIVE);
+                                clientInfo.onResolveServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_ALREADY_ACTIVE, true /* isLegacy */);
                                 break;
                             }
 
@@ -1063,8 +1076,8 @@
                                 storeLegacyRequestMap(clientRequestId, transactionId, clientInfo,
                                         msg.what, mClock.elapsedRealtime());
                             } else {
-                                clientInfo.onResolveServiceFailedImmediately(
-                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onResolveServiceFailedImmediately(clientRequestId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */);
                             }
                         }
                         break;
@@ -1274,21 +1287,21 @@
                     }
                     case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
                         clientInfo.onDiscoverServicesFailed(clientRequestId,
-                                NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         break;
                     case IMDnsEventListener.SERVICE_REGISTERED: {
                         final RegistrationInfo info = (RegistrationInfo) obj;
                         final String name = info.serviceName;
                         servInfo = new NsdServiceInfo(name, null /* serviceType */);
-                        clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo,
-                                transactionId,
-                                request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+                        clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo, request);
                         break;
                     }
                     case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
                         clientInfo.onRegisterServiceFailed(clientRequestId,
-                                NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         break;
                     case IMDnsEventListener.SERVICE_RESOLVED: {
@@ -1326,7 +1339,8 @@
                                     NsdManager.RESOLVE_SERVICE, request.mStartTimeMs);
                         } else {
                             clientInfo.onResolveServiceFailed(clientRequestId,
-                                    NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                    transactionId,
                                     request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                             clientInfo.mResolvedService = null;
                         }
@@ -1337,7 +1351,8 @@
                         stopResolveService(transactionId);
                         removeRequestMap(clientRequestId, transactionId, clientInfo);
                         clientInfo.onResolveServiceFailed(clientRequestId,
-                                NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         clientInfo.mResolvedService = null;
                         break;
@@ -1346,7 +1361,8 @@
                         stopGetAddrInfo(transactionId);
                         removeRequestMap(clientRequestId, transactionId, clientInfo);
                         clientInfo.onResolveServiceFailed(clientRequestId,
-                                NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         clientInfo.mResolvedService = null;
                         break;
@@ -1373,7 +1389,8 @@
                                     clientRequestId, clientInfo.mResolvedService, request);
                         } else {
                             clientInfo.onResolveServiceFailed(clientRequestId,
-                                    NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, true /* isLegacy */,
+                                    transactionId,
                                     request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         }
                         stopGetAddrInfo(transactionId);
@@ -1490,7 +1507,8 @@
                         } else {
                             // No address. Notify resolution failure.
                             clientInfo.onResolveServiceFailed(clientRequestId,
-                                    NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */,
+                                    transactionId,
                                     request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         }
 
@@ -1669,7 +1687,10 @@
         mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper(),
                 LOGGER.forSubComponent("MdnsSocketProvider"), new SocketRequestMonitor());
         // Netlink monitor starts on boot, and intentionally never stopped, to ensure that all
-        // address events are received.
+        // address events are received. When the netlink monitor starts, any IP addresses already
+        // on the interfaces will not be seen. In practice, the network will not connect at boot
+        // time As a result, all the netlink message should be observed if the netlink monitor
+        // starts here.
         handler.post(mMdnsSocketProvider::startNetLinkMonitor);
 
         // NsdService is started after ActivityManager (startOtherServices in SystemServer, vs.
@@ -1687,8 +1708,11 @@
         mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
                 mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
         handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
+        MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder().setIsMdnsOffloadFeatureEnabled(
+                mDeps.isTetheringFeatureNotChickenedOut(
+                        MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD)).build();
         mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
-                new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"));
+                new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags);
         mClock = deps.makeClock();
     }
 
@@ -1705,8 +1729,7 @@
          */
         public boolean isMdnsDiscoveryManagerEnabled(Context context) {
             return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
-                    NAMESPACE_TETHERING, MDNS_DISCOVERY_MANAGER_VERSION,
-                    DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
+                    MDNS_DISCOVERY_MANAGER_VERSION);
         }
 
         /**
@@ -1717,8 +1740,7 @@
          */
         public boolean isMdnsAdvertiserEnabled(Context context) {
             return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
-                    NAMESPACE_TETHERING, MDNS_ADVERTISER_VERSION,
-                    DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
+                    MDNS_ADVERTISER_VERSION);
         }
 
         /**
@@ -1735,8 +1757,14 @@
          * @see DeviceConfigUtils#isTetheringFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String feature) {
-            return DeviceConfigUtils.isTetheringFeatureEnabled(context, NAMESPACE_TETHERING,
-                    feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, feature);
+        }
+
+        /**
+         * @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut
+         */
+        public boolean isTetheringFeatureNotChickenedOut(String feature) {
+            return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(feature);
         }
 
         /**
@@ -1753,8 +1781,9 @@
          */
         public MdnsAdvertiser makeMdnsAdvertiser(
                 @NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
-                @NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog) {
-            return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog);
+                @NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog,
+                MdnsFeatureFlags featureFlags) {
+            return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog, featureFlags);
         }
 
         /**
@@ -1783,9 +1812,8 @@
         /**
          * @see NetworkNsdReportedMetrics
          */
-        public NetworkNsdReportedMetrics makeNetworkNsdReportedMetrics(
-                boolean isLegacy, int clientId) {
-            return new NetworkNsdReportedMetrics(isLegacy, clientId);
+        public NetworkNsdReportedMetrics makeNetworkNsdReportedMetrics(int clientId) {
+            return new NetworkNsdReportedMetrics(clientId);
         }
 
         /**
@@ -1945,8 +1973,7 @@
             // historical behavior.
             final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
             final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
-            clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo, transactionId,
-                    request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+            clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo, request);
         }
 
         @Override
@@ -1957,8 +1984,8 @@
             final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
             if (clientRequestId < 0) return;
             final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
-            clientInfo.onRegisterServiceFailed(clientRequestId, errorCode, transactionId,
-                    request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+            clientInfo.onRegisterServiceFailed(clientRequestId, errorCode, false /* isLegacy */,
+                    transactionId, request.calculateRequestDurationMs(mClock.elapsedRealtime()));
         }
 
         @Override
@@ -2475,14 +2502,14 @@
                 if (request instanceof DiscoveryManagerRequest) {
                     final MdnsListener listener = unregisterMdnsListenerFromRequest(request);
                     if (listener instanceof DiscoveryListener) {
-                        mMetrics.reportServiceDiscoveryStop(transactionId,
+                        mMetrics.reportServiceDiscoveryStop(false /* isLegacy */, transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()),
                                 request.getFoundServiceCount(),
                                 request.getLostServiceCount(),
                                 request.getServicesCount(),
                                 request.getSentQueryCount());
                     } else if (listener instanceof ResolutionListener) {
-                        mMetrics.reportServiceResolutionStop(transactionId,
+                        mMetrics.reportServiceResolutionStop(false /* isLegacy */, transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                     } else if (listener instanceof ServiceInfoListener) {
                         mMetrics.reportServiceInfoCallbackUnregistered(transactionId,
@@ -2496,9 +2523,14 @@
                 }
 
                 if (request instanceof AdvertiserClientRequest) {
+                    final AdvertiserMetrics metrics =
+                            mAdvertiser.getAdvertiserMetrics(transactionId);
                     mAdvertiser.removeService(transactionId);
-                    mMetrics.reportServiceUnregistration(transactionId,
-                            request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+                    mMetrics.reportServiceUnregistration(false /* isLegacy */, transactionId,
+                            request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+                            metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
+                            metrics.mConflictDuringProbingCount,
+                            metrics.mConflictAfterProbingCount);
                     continue;
                 }
 
@@ -2509,7 +2541,7 @@
                 switch (((LegacyClientRequest) request).mRequestCode) {
                     case NsdManager.DISCOVER_SERVICES:
                         stopServiceDiscovery(transactionId);
-                        mMetrics.reportServiceDiscoveryStop(transactionId,
+                        mMetrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()),
                                 request.getFoundServiceCount(),
                                 request.getLostServiceCount(),
@@ -2518,13 +2550,17 @@
                         break;
                     case NsdManager.RESOLVE_SERVICE:
                         stopResolveService(transactionId);
-                        mMetrics.reportServiceResolutionStop(transactionId,
+                        mMetrics.reportServiceResolutionStop(true /* isLegacy */, transactionId,
                                 request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         break;
                     case NsdManager.REGISTER_SERVICE:
                         unregisterService(transactionId);
-                        mMetrics.reportServiceUnregistration(transactionId,
-                                request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+                        mMetrics.reportServiceUnregistration(true /* isLegacy */, transactionId,
+                                request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+                                NO_PACKET /* repliedRequestsCount */,
+                                NO_PACKET /* sentPacketCount */,
+                                0 /* conflictDuringProbingCount */,
+                                0 /* conflictAfterProbingCount */);
                         break;
                     default:
                         break;
@@ -2568,21 +2604,29 @@
             mClientLogs.log(message);
         }
 
-        void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info, int transactionId) {
-            mMetrics.reportServiceDiscoveryStarted(transactionId);
+        private static boolean isLegacyClientRequest(@NonNull ClientRequest request) {
+            return !(request instanceof DiscoveryManagerRequest)
+                    && !(request instanceof AdvertiserClientRequest);
+        }
+
+        void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info,
+                ClientRequest request) {
+            mMetrics.reportServiceDiscoveryStarted(
+                    isLegacyClientRequest(request), request.mTransactionId);
             try {
                 mCb.onDiscoverServicesStarted(listenerKey, info);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
             }
         }
-        void onDiscoverServicesFailedImmediately(int listenerKey, int error) {
-            onDiscoverServicesFailed(listenerKey, error, NO_TRANSACTION, 0L /* durationMs */);
+        void onDiscoverServicesFailedImmediately(int listenerKey, int error, boolean isLegacy) {
+            onDiscoverServicesFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
+                    0L /* durationMs */);
         }
 
-        void onDiscoverServicesFailed(int listenerKey, int error, int transactionId,
-                long durationMs) {
-            mMetrics.reportServiceDiscoveryFailed(transactionId, durationMs);
+        void onDiscoverServicesFailed(int listenerKey, int error, boolean isLegacy,
+                int transactionId, long durationMs) {
+            mMetrics.reportServiceDiscoveryFailed(isLegacy, transactionId, durationMs);
             try {
                 mCb.onDiscoverServicesFailed(listenerKey, error);
             } catch (RemoteException e) {
@@ -2618,6 +2662,7 @@
 
         void onStopDiscoverySucceeded(int listenerKey, ClientRequest request) {
             mMetrics.reportServiceDiscoveryStop(
+                    isLegacyClientRequest(request),
                     request.mTransactionId,
                     request.calculateRequestDurationMs(mClock.elapsedRealtime()),
                     request.getFoundServiceCount(),
@@ -2631,13 +2676,14 @@
             }
         }
 
-        void onRegisterServiceFailedImmediately(int listenerKey, int error) {
-            onRegisterServiceFailed(listenerKey, error, NO_TRANSACTION, 0L /* durationMs */);
+        void onRegisterServiceFailedImmediately(int listenerKey, int error, boolean isLegacy) {
+            onRegisterServiceFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
+                    0L /* durationMs */);
         }
 
-        void onRegisterServiceFailed(int listenerKey, int error, int transactionId,
-                long durationMs) {
-            mMetrics.reportServiceRegistrationFailed(transactionId, durationMs);
+        void onRegisterServiceFailed(int listenerKey, int error, boolean isLegacy,
+                int transactionId, long durationMs) {
+            mMetrics.reportServiceRegistrationFailed(isLegacy, transactionId, durationMs);
             try {
                 mCb.onRegisterServiceFailed(listenerKey, error);
             } catch (RemoteException e) {
@@ -2645,9 +2691,11 @@
             }
         }
 
-        void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info, int transactionId,
-                long durationMs) {
-            mMetrics.reportServiceRegistrationSucceeded(transactionId, durationMs);
+        void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info,
+                ClientRequest request) {
+            mMetrics.reportServiceRegistrationSucceeded(isLegacyClientRequest(request),
+                    request.mTransactionId,
+                    request.calculateRequestDurationMs(mClock.elapsedRealtime()));
             try {
                 mCb.onRegisterServiceSucceeded(listenerKey, info);
             } catch (RemoteException e) {
@@ -2663,8 +2711,13 @@
             }
         }
 
-        void onUnregisterServiceSucceeded(int listenerKey, int transactionId, long durationMs) {
-            mMetrics.reportServiceUnregistration(transactionId, durationMs);
+        void onUnregisterServiceSucceeded(int listenerKey, ClientRequest request,
+                AdvertiserMetrics metrics) {
+            mMetrics.reportServiceUnregistration(isLegacyClientRequest(request),
+                    request.mTransactionId,
+                    request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+                    metrics.mRepliedRequestsCount, metrics.mSentPacketCount,
+                    metrics.mConflictDuringProbingCount, metrics.mConflictAfterProbingCount);
             try {
                 mCb.onUnregisterServiceSucceeded(listenerKey);
             } catch (RemoteException e) {
@@ -2672,13 +2725,14 @@
             }
         }
 
-        void onResolveServiceFailedImmediately(int listenerKey, int error) {
-            onResolveServiceFailed(listenerKey, error, NO_TRANSACTION, 0L /* durationMs */);
+        void onResolveServiceFailedImmediately(int listenerKey, int error, boolean isLegacy) {
+            onResolveServiceFailed(listenerKey, error, isLegacy, NO_TRANSACTION,
+                    0L /* durationMs */);
         }
 
-        void onResolveServiceFailed(int listenerKey, int error, int transactionId,
-                long durationMs) {
-            mMetrics.reportServiceResolutionFailed(transactionId, durationMs);
+        void onResolveServiceFailed(int listenerKey, int error, boolean isLegacy,
+                int transactionId, long durationMs) {
+            mMetrics.reportServiceResolutionFailed(isLegacy, transactionId, durationMs);
             try {
                 mCb.onResolveServiceFailed(listenerKey, error);
             } catch (RemoteException e) {
@@ -2689,6 +2743,7 @@
         void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info,
                 ClientRequest request) {
             mMetrics.reportServiceResolved(
+                    isLegacyClientRequest(request),
                     request.mTransactionId,
                     request.calculateRequestDurationMs(mClock.elapsedRealtime()),
                     request.isServiceFromCache(),
@@ -2710,6 +2765,7 @@
 
         void onStopResolutionSucceeded(int listenerKey, ClientRequest request) {
             mMetrics.reportServiceResolutionStop(
+                    isLegacyClientRequest(request),
                     request.mTransactionId,
                     request.calculateRequestDurationMs(mClock.elapsedRealtime()));
             try {
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index fa3b646..1582fb6 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -19,6 +19,7 @@
 import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.INVALID_TRANSACTION_ID;
 
 import android.annotation.NonNull;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Pair;
 
@@ -220,7 +221,9 @@
             throws IOException {
         DatagramPacket packet = packetWriter.getPacket(address);
         if (expectUnicastResponse) {
-            if (requestSender instanceof MdnsMultinetworkSocketClient) {
+            // MdnsMultinetworkSocketClient is only available on T+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                    && requestSender instanceof MdnsMultinetworkSocketClient) {
                 ((MdnsMultinetworkSocketClient) requestSender).sendPacketRequestingUnicastResponse(
                         packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
             } else {
@@ -228,7 +231,8 @@
                         packet, onlyUseIpv6OnIpv6OnlyNetworks);
             }
         } else {
-            if (requestSender instanceof MdnsMultinetworkSocketClient) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                    && requestSender instanceof MdnsMultinetworkSocketClient) {
                 ((MdnsMultinetworkSocketClient) requestSender)
                         .sendPacketRequestingMulticastResponse(
                                 packet, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
diff --git a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
index 161669b..5d75b48 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
@@ -17,7 +17,8 @@
 package com.android.server.connectivity.mdns;
 
 import android.annotation.NonNull;
-import android.util.ArraySet;
+
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
@@ -30,7 +31,7 @@
 public class ExecutorProvider {
 
     private final Set<ScheduledExecutorService> serviceTypeClientSchedulerExecutors =
-            new ArraySet<>();
+            MdnsUtils.newSet();
 
     /** Returns a new {@link ScheduledExecutorService} instance. */
     public ScheduledExecutorService newServiceTypeClientSchedulerExecutor() {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index ce05a84..a946bca 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -16,16 +16,19 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.net.LinkAddress;
 import android.net.Network;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
 import android.net.nsd.OffloadEngine;
 import android.net.nsd.OffloadServiceInfo;
+import android.os.Build;
 import android.os.Looper;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -49,6 +52,7 @@
  *
  * All methods except the constructor must be called on the looper thread.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsAdvertiser {
     private static final String TAG = MdnsAdvertiser.class.getSimpleName();
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -77,6 +81,7 @@
     @NonNull private final SharedLog mSharedLog;
     private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
             new ArrayMap<>();
+    private final MdnsFeatureFlags mMdnsFeatureFlags;
 
     /**
      * Dependencies for {@link MdnsAdvertiser}, useful for testing.
@@ -139,22 +144,21 @@
                 mSharedLog.wtf("Register succeeded for unknown registration");
                 return;
             }
+            if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+                final String interfaceName = advertiser.getSocketInterfaceName();
+                final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+                        mInterfaceOffloadServices.computeIfAbsent(interfaceName,
+                                k -> new ArrayList<>());
+                // Remove existing offload services from cache for update.
+                existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId);
 
-            final String interfaceName = advertiser.getSocketInterfaceName();
-            final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
-                    mInterfaceOffloadServices.computeIfAbsent(
-                            interfaceName, k -> new ArrayList<>());
-            // Remove existing offload services from cache for update.
-            existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId);
-
-            byte[] rawOffloadPacket = advertiser.getRawOffloadPayload(serviceId);
-            final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService(
-                    serviceId,
-                    registration,
-                    rawOffloadPacket);
-            existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
-            mCb.onOffloadStartOrUpdate(interfaceName,
-                    newOffloadServiceInfoWrapper.mOffloadServiceInfo);
+                byte[] rawOffloadPacket = advertiser.getRawOffloadPayload(serviceId);
+                final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService(
+                        serviceId, registration, rawOffloadPacket);
+                existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
+                mCb.onOffloadStartOrUpdate(interfaceName,
+                        newOffloadServiceInfoWrapper.mOffloadServiceInfo);
+            }
 
             // Wait for all current interfaces to be done probing before notifying of success.
             if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return;
@@ -180,13 +184,16 @@
                 // (with the old, conflicting, actually not used name as argument... The new
                 // implementation will send callbacks with the new name).
                 registration.mNotifiedRegistrationSuccess = false;
+                registration.mConflictAfterProbingCount++;
 
                 // The service was done probing, just reset it to probing state (RFC6762 9.)
                 forAllAdvertisers(a -> {
                     if (!a.maybeRestartProbingForConflict(serviceId)) {
                         return;
                     }
-                    maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId);
+                    if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+                        maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId);
+                    }
                 });
                 return;
             }
@@ -195,6 +202,7 @@
             registration.updateForConflict(
                     registration.makeNewServiceInfoForConflict(1 /* renameCount */),
                     1 /* renameCount */);
+            registration.mConflictDuringProbingCount++;
 
             // Keep renaming if the new name conflicts in local registrations
             updateRegistrationUntilNoConflict((net, adv) -> adv.hasRegistration(registration),
@@ -277,12 +285,12 @@
          */
         boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
             final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket);
-            if (removedAdvertiser != null) {
+            if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled && removedAdvertiser != null) {
                 final String interfaceName = removedAdvertiser.getSocketInterfaceName();
-                // If the interface is destroyed, stop all hardware offloading on that interface.
+                // If the interface is destroyed, stop all hardware offloading on that
+                // interface.
                 final List<OffloadServiceInfoWrapper> offloadServiceInfoWrappers =
-                        mInterfaceOffloadServices.remove(
-                                interfaceName);
+                        mInterfaceOffloadServices.remove(interfaceName);
                 if (offloadServiceInfoWrappers != null) {
                     for (OffloadServiceInfoWrapper offloadServiceInfoWrapper :
                             offloadServiceInfoWrappers) {
@@ -356,10 +364,28 @@
                 final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i);
                 advertiser.removeService(id);
 
-                maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id);
+                if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+                    maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id);
+                }
             }
         }
 
+        int getServiceRepliedRequestsCount(int id) {
+            int repliedRequestsCount = NO_PACKET;
+            for (int i = 0; i < mAdvertisers.size(); i++) {
+                repliedRequestsCount += mAdvertisers.valueAt(i).getServiceRepliedRequestsCount(id);
+            }
+            return repliedRequestsCount;
+        }
+
+        int getSentPacketCount(int id) {
+            int sentPacketCount = NO_PACKET;
+            for (int i = 0; i < mAdvertisers.size(); i++) {
+                sentPacketCount += mAdvertisers.valueAt(i).getSentPacketCount(id);
+            }
+            return sentPacketCount;
+        }
+
         @Override
         public void onSocketCreated(@NonNull SocketKey socketKey,
                 @NonNull MdnsInterfaceSocket socket,
@@ -400,25 +426,28 @@
                 return;
             }
             advertiser.updateAddresses(addresses);
-            // Update address should trigger offload packet update.
-            final String interfaceName = advertiser.getSocketInterfaceName();
-            final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
-                    mInterfaceOffloadServices.get(interfaceName);
-            if (existingOffloadServiceInfoWrappers == null) {
-                return;
+
+            if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
+                // Update address should trigger offload packet update.
+                final String interfaceName = advertiser.getSocketInterfaceName();
+                final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+                        mInterfaceOffloadServices.get(interfaceName);
+                if (existingOffloadServiceInfoWrappers == null) {
+                    return;
+                }
+                final List<OffloadServiceInfoWrapper> updatedOffloadServiceInfoWrappers =
+                        new ArrayList<>(existingOffloadServiceInfoWrappers.size());
+                for (OffloadServiceInfoWrapper oldWrapper : existingOffloadServiceInfoWrappers) {
+                    OffloadServiceInfoWrapper newWrapper = new OffloadServiceInfoWrapper(
+                            oldWrapper.mServiceId,
+                            oldWrapper.mOffloadServiceInfo.withOffloadPayload(
+                                    advertiser.getRawOffloadPayload(oldWrapper.mServiceId))
+                    );
+                    updatedOffloadServiceInfoWrappers.add(newWrapper);
+                    mCb.onOffloadStartOrUpdate(interfaceName, newWrapper.mOffloadServiceInfo);
+                }
+                mInterfaceOffloadServices.put(interfaceName, updatedOffloadServiceInfoWrappers);
             }
-            final List<OffloadServiceInfoWrapper> updatedOffloadServiceInfoWrappers =
-                    new ArrayList<>(existingOffloadServiceInfoWrappers.size());
-            for (OffloadServiceInfoWrapper oldWrapper : existingOffloadServiceInfoWrappers) {
-                OffloadServiceInfoWrapper newWrapper = new OffloadServiceInfoWrapper(
-                        oldWrapper.mServiceId,
-                        oldWrapper.mOffloadServiceInfo.withOffloadPayload(
-                                advertiser.getRawOffloadPayload(oldWrapper.mServiceId))
-                );
-                updatedOffloadServiceInfoWrappers.add(newWrapper);
-                mCb.onOffloadStartOrUpdate(interfaceName, newWrapper.mOffloadServiceInfo);
-            }
-            mInterfaceOffloadServices.put(interfaceName, updatedOffloadServiceInfoWrappers);
         }
     }
 
@@ -444,6 +473,8 @@
         private NsdServiceInfo mServiceInfo;
         @Nullable
         private final String mSubtype;
+        int mConflictDuringProbingCount;
+        int mConflictAfterProbingCount;
 
         private Registration(@NonNull NsdServiceInfo serviceInfo, @Nullable String subtype) {
             this.mOriginalName = serviceInfo.getServiceName();
@@ -555,21 +586,41 @@
                 @NonNull OffloadServiceInfo offloadServiceInfo);
     }
 
+    /**
+     * Data class of avdverting metrics.
+     */
+    public static class AdvertiserMetrics {
+        public final int mRepliedRequestsCount;
+        public final int mSentPacketCount;
+        public final int mConflictDuringProbingCount;
+        public final int mConflictAfterProbingCount;
+
+        public AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount,
+                int conflictDuringProbingCount, int conflictAfterProbingCount) {
+            mRepliedRequestsCount = repliedRequestsCount;
+            mSentPacketCount = sentPacketCount;
+            mConflictDuringProbingCount = conflictDuringProbingCount;
+            mConflictAfterProbingCount = conflictAfterProbingCount;
+        }
+    }
+
     public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
-            @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog) {
-        this(looper, socketProvider, cb, new Dependencies(), sharedLog);
+            @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog,
+            @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
+        this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags);
     }
 
     @VisibleForTesting
     MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
             @NonNull AdvertiserCallback cb, @NonNull Dependencies deps,
-            @NonNull SharedLog sharedLog) {
+            @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags) {
         mLooper = looper;
         mCb = cb;
         mSocketProvider = socketProvider;
         mDeps = deps;
         mDeviceHostName = deps.generateHostname();
         mSharedLog = sharedLog;
+        mMdnsFeatureFlags = mDnsFeatureFlags;
     }
 
     private void checkThread() {
@@ -637,6 +688,34 @@
         }
     }
 
+    /**
+     * Get advertising metrics.
+     *
+     * @param id ID used when registering.
+     * @return The advertising metrics includes replied requests count, send packet count, conflict
+     *         count during/after probing.
+     */
+    public AdvertiserMetrics getAdvertiserMetrics(int id) {
+        checkThread();
+        final Registration registration = mRegistrations.get(id);
+        if (registration == null) {
+            return new AdvertiserMetrics(
+                    NO_PACKET /* repliedRequestsCount */,
+                    NO_PACKET /* sentPacketCount */,
+                    0 /* conflictDuringProbingCount */,
+                    0 /* conflictAfterProbingCount */);
+        }
+        int repliedRequestsCount = NO_PACKET;
+        int sentPacketCount = NO_PACKET;
+        for (int i = 0; i < mAdvertiserRequests.size(); i++) {
+            repliedRequestsCount +=
+                    mAdvertiserRequests.valueAt(i).getServiceRepliedRequestsCount(id);
+            sentPacketCount += mAdvertiserRequests.valueAt(i).getSentPacketCount(id);
+        }
+        return new AdvertiserMetrics(repliedRequestsCount, sentPacketCount,
+                registration.mConflictDuringProbingCount, registration.mConflictAfterProbingCount);
+    }
+
     private static <K, V> boolean any(@NonNull ArrayMap<K, V> map,
             @NonNull BiPredicate<K, V> predicate) {
         for (int i = 0; i < map.size(); i++) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
index fd2c32e..d9bc643 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
+import android.os.Build;
 import android.os.Looper;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -31,6 +33,7 @@
  *
  * This allows maintaining other hosts' caches up-to-date. See RFC6762 8.3.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsAnnouncer extends MdnsPacketRepeater<MdnsAnnouncer.BaseAnnouncementInfo> {
     private static final long ANNOUNCEMENT_INITIAL_DELAY_MS = 1000L;
     @VisibleForTesting
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
index 0c32cf1..1251170 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -37,6 +37,7 @@
     public static final int FLAG_TRUNCATED = 0x0200;
     public static final int QCLASS_INTERNET = 0x0001;
     public static final int QCLASS_UNICAST = 0x8000;
+    public static final int NO_PACKET = 0;
     public static final String SUBTYPE_LABEL = "_sub";
     public static final String SUBTYPE_PREFIX = "_";
     private static final String MDNS_IPV4_HOST_ADDRESS = "224.0.0.251";
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index d55098c..24e9fa8 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -35,6 +35,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
@@ -102,8 +103,12 @@
         }
 
         public void remove(@NonNull MdnsServiceTypeClient client) {
-            final int index = clients.indexOfValue(client);
-            clients.removeAt(index);
+            for (int i = 0; i < clients.size(); ++i) {
+                if (Objects.equals(client, clients.valueAt(i))) {
+                    clients.removeAt(i);
+                    break;
+                }
+            }
         }
 
         public boolean isEmpty() {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
new file mode 100644
index 0000000..9840409
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.server.connectivity.mdns;
+
+/**
+ * The class that contains mDNS feature flags;
+ */
+public class MdnsFeatureFlags {
+    /**
+     * The feature flag for control whether the  mDNS offload is enabled or not.
+     */
+    public static final String NSD_FORCE_DISABLE_MDNS_OFFLOAD = "nsd_force_disable_mdns_offload";
+
+    // Flag for offload feature
+    public final boolean mIsMdnsOffloadFeatureEnabled;
+
+    /**
+     * The constructor for {@link MdnsFeatureFlags}.
+     */
+    public MdnsFeatureFlags(boolean isOffloadFeatureEnabled) {
+        mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
+    }
+
+
+    /** Returns a {@link Builder} for {@link MdnsFeatureFlags}. */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** A builder to create {@link MdnsFeatureFlags}. */
+    public static final class Builder {
+
+        private boolean mIsMdnsOffloadFeatureEnabled;
+
+        /**
+         * The constructor for {@link Builder}.
+         */
+        public Builder() {
+            mIsMdnsOffloadFeatureEnabled = false;
+        }
+
+        /**
+         * Set if the mDNS offload  feature is enabled.
+         */
+        public Builder setIsMdnsOffloadFeatureEnabled(boolean isMdnsOffloadFeatureEnabled) {
+            mIsMdnsOffloadFeatureEnabled = isMdnsOffloadFeatureEnabled;
+            return this;
+        }
+
+        /**
+         * Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
+         */
+        public MdnsFeatureFlags build() {
+            return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled);
+        }
+
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 61a2c5e..40dfd57 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -16,10 +16,14 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.net.LinkAddress;
 import android.net.nsd.NsdServiceInfo;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 
@@ -37,6 +41,7 @@
 /**
  * A class that handles advertising services on a {@link MdnsInterfaceSocket} tied to an interface.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHandler {
     private static final boolean DBG = MdnsAdvertiser.DBG;
     @VisibleForTesting
@@ -93,8 +98,11 @@
     /**
      * Callbacks from {@link MdnsProber}.
      */
-    private class ProbingCallback implements
-            PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+    private class ProbingCallback implements PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+        @Override
+        public void onSent(int index, @NonNull MdnsProber.ProbingInfo info, int sentPacketCount) {
+            mRecordRepository.onProbingSent(info.getServiceId(), sentPacketCount);
+        }
         @Override
         public void onFinished(MdnsProber.ProbingInfo info) {
             final MdnsAnnouncer.AnnouncementInfo announcementInfo;
@@ -118,8 +126,8 @@
      */
     private class AnnouncingCallback implements PacketRepeaterCallback<BaseAnnouncementInfo> {
         @Override
-        public void onSent(int index, @NonNull BaseAnnouncementInfo info) {
-            mRecordRepository.onAdvertisementSent(info.getServiceId());
+        public void onSent(int index, @NonNull BaseAnnouncementInfo info, int sentPacketCount) {
+            mRecordRepository.onAdvertisementSent(info.getServiceId(), sentPacketCount);
         }
 
         @Override
@@ -260,6 +268,22 @@
     }
 
     /**
+     * Get the replied request count from given service id.
+     */
+    public int getServiceRepliedRequestsCount(int id) {
+        if (!mRecordRepository.hasActiveService(id)) return NO_PACKET;
+        return mRecordRepository.getServiceRepliedRequestsCount(id);
+    }
+
+    /**
+     * Get the total sent packet count from given service id.
+     */
+    public int getSentPacketCount(int id) {
+        if (!mRecordRepository.hasActiveService(id)) return NO_PACKET;
+        return mRecordRepository.getSentPacketCount(id);
+    }
+
+    /**
      * Update interface addresses used to advertise.
      *
      * This causes new address records to be announced.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 534f8d0..63dd703 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -20,8 +20,10 @@
 import static com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV6_ADDRESS;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresApi;
 import android.net.LinkAddress;
 import android.net.util.SocketUtils;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
@@ -49,6 +51,7 @@
  * @see MulticastSocket for javadoc of each public method.
  * @see MulticastSocket for javadoc of each public method.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsInterfaceSocket {
     private static final String TAG = MdnsInterfaceSocket.class.getSimpleName();
     @NonNull private final MulticastSocket mMulticastSocket;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 2ef7368..4ba6912 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -20,8 +20,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.net.LinkAddress;
 import android.net.Network;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
@@ -40,6 +42,7 @@
  *
  *  * <p>This class is not thread safe.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsMultinetworkSocketClient implements MdnsSocketClientBase {
     private static final String TAG = MdnsMultinetworkSocketClient.class.getSimpleName();
     private static final boolean DBG = MdnsDiscoveryManager.DBG;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
index 27002b9..7fa3f84 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.util.Log;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -206,9 +205,6 @@
 
             default: {
                 try {
-                    if (MdnsAdvertiser.DBG) {
-                        Log.i(TAG, "Skipping parsing of record of unhandled type " + type);
-                    }
                     skipMdnsRecord(reader, isQuestion);
                     return null;
                 } catch (IOException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index 644560c..fd0f5c9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -21,6 +21,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -34,6 +36,7 @@
  * A class used to send several packets at given time intervals.
  * @param <T> The type of the request providing packet repeating parameters.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
     private static final boolean DBG = MdnsAdvertiser.DBG;
     private static final InetSocketAddress[] ALL_ADDRS = new InetSocketAddress[] {
@@ -59,7 +62,7 @@
         /**
          * Called when a packet was sent.
          */
-        default void onSent(int index, @NonNull T info) {}
+        default void onSent(int index, @NonNull T info, int sentPacketCount) {}
 
         /**
          * Called when the {@link MdnsPacketRepeater} is done sending packets.
@@ -114,9 +117,10 @@
             }
             // Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
             // send when the socket has not joined the relevant group.
+            int sentPacketCount = 0;
             for (InetSocketAddress destination : ALL_ADDRS) {
                 try {
-                    mReplySender.sendNow(packet, destination);
+                    sentPacketCount += mReplySender.sendNow(packet, destination);
                 } catch (IOException e) {
                     mSharedLog.e("Error sending packet to " + destination, e);
                 }
@@ -135,7 +139,7 @@
 
             // Call onSent after scheduling the next run, to allow the callback to cancel it
             if (mCb != null) {
-                mCb.onSent(index, request);
+                mCb.onSent(index, request, sentPacketCount);
             }
         }
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
index ba37f32..f2b562a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -17,6 +17,8 @@
 package com.android.server.connectivity.mdns;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.os.Build;
 import android.os.Looper;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +35,7 @@
  *
  * TODO: implement receiving replies and handling conflicts.
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsProber extends MdnsPacketRepeater<MdnsProber.ProbingInfo> {
     private static final long CONFLICT_RETRY_DELAY_MS = 5_000L;
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index e25d7e1..f532372 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
@@ -175,13 +177,23 @@
         public boolean exiting = false;
 
         /**
+         * The replied query packet count of this service.
+         */
+        public int repliedServiceCount = NO_PACKET;
+
+        /**
+         * The sent packet count of this service (including announcements and probes).
+         */
+        public int sentPacketCount = NO_PACKET;
+
+        /**
          * Create a ServiceRegistration for dns-sd service registration (RFC6763).
          *
          * @param deviceHostname Hostname of the device (for the interface used)
          * @param serviceInfo Service to advertise
          */
         ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
-                @Nullable String subtype) {
+                @Nullable String subtype, int repliedServiceCount, int sentPacketCount) {
             this.serviceInfo = serviceInfo;
             this.subtype = subtype;
 
@@ -254,6 +266,8 @@
                     true /* sharedName */, true /* probing */));
 
             this.allRecords = Collections.unmodifiableList(allRecords);
+            this.repliedServiceCount = repliedServiceCount;
+            this.sentPacketCount = sentPacketCount;
         }
 
         void setProbing(boolean probing) {
@@ -316,7 +330,8 @@
         }
 
         final ServiceRegistration registration = new ServiceRegistration(
-                mDeviceHostname, serviceInfo, subtype);
+                mDeviceHostname, serviceInfo, subtype, NO_PACKET /* repliedServiceCount */,
+                NO_PACKET /* sentPacketCount */);
         mServices.put(serviceId, registration);
 
         // Remove existing exiting service
@@ -384,7 +399,9 @@
                 r -> new MdnsPointerRecord(
                         r.record.getName(),
                         0L /* receiptTimeMillis */,
-                        true /* cacheFlush */,
+                        // RFC6762#10.1, the cache flush bit should be false for existing
+                        // announcement. Otherwise, the record will be deleted immediately.
+                        false /* cacheFlush */,
                         0L /* ttlMillis */,
                         r.record.getPointer()));
 
@@ -406,6 +423,24 @@
     }
 
     /**
+     * @return The replied request count of the service.
+     */
+    public int getServiceRepliedRequestsCount(int id) {
+        final ServiceRegistration service = mServices.get(id);
+        if (service == null) return NO_PACKET;
+        return service.repliedServiceCount;
+    }
+
+    /**
+     * @return The total sent packet count of the service.
+     */
+    public int getSentPacketCount(int id) {
+        final ServiceRegistration service = mServices.get(id);
+        if (service == null) return NO_PACKET;
+        return service.sentPacketCount;
+    }
+
+    /**
      * Remove all services from the repository
      * @return IDs of the removed services
      */
@@ -472,9 +507,12 @@
             for (int i = 0; i < mServices.size(); i++) {
                 final ServiceRegistration registration = mServices.valueAt(i);
                 if (registration.exiting) continue;
-                addReplyFromService(question, registration.allRecords, registration.ptrRecords,
+                if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
                         registration.srvRecord, registration.txtRecord, replyUnicast, now,
-                        answerInfo, additionalAnswerRecords);
+                        answerInfo, additionalAnswerRecords)) {
+                    registration.repliedServiceCount++;
+                    registration.sentPacketCount++;
+                }
             }
         }
 
@@ -527,7 +565,7 @@
     /**
      * Add answers and additional answers for a question, from a ServiceRegistration.
      */
-    private void addReplyFromService(@NonNull MdnsRecord question,
+    private boolean addReplyFromService(@NonNull MdnsRecord question,
             @NonNull List<RecordInfo<?>> serviceRecords,
             @Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
             @Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
@@ -596,7 +634,7 @@
         }
 
         // No more records to add if no answer
-        if (answerInfo.size() == answersStartIndex) return;
+        if (answerInfo.size() == answersStartIndex) return false;
 
         final List<RecordInfo<?>> additionalAnswerInfo = new ArrayList<>();
         // RFC6763 12.1: if including PTR record, include the SRV and TXT records it names
@@ -626,6 +664,7 @@
         addNsecRecordsForUniqueNames(additionalAnswerRecords,
                 answerInfo.listIterator(answersStartIndex),
                 additionalAnswerInfo.listIterator());
+        return true;
     }
 
     /**
@@ -862,8 +901,8 @@
         final ServiceRegistration existing = mServices.get(serviceId);
         if (existing == null) return null;
 
-        final ServiceRegistration newService = new ServiceRegistration(
-                mDeviceHostname, newInfo, existing.subtype);
+        final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
+                existing.subtype, existing.repliedServiceCount, existing.sentPacketCount);
         mServices.put(serviceId, newService);
         return makeProbingInfo(serviceId, newService.srvRecord.record);
     }
@@ -871,7 +910,7 @@
     /**
      * Called when {@link MdnsAdvertiser} sent an advertisement for the given service.
      */
-    public void onAdvertisementSent(int serviceId) {
+    public void onAdvertisementSent(int serviceId, int sentPacketCount) {
         final ServiceRegistration registration = mServices.get(serviceId);
         if (registration == null) return;
 
@@ -880,9 +919,20 @@
             record.lastSentTimeMs = now;
             record.lastAdvertisedTimeMs = now;
         }
+        registration.sentPacketCount += sentPacketCount;
     }
 
     /**
+     * Called when {@link MdnsAdvertiser} sent a probing for the given service.
+     */
+    public void onProbingSent(int serviceId, int sentPacketCount) {
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) return;
+        registration.sentPacketCount += sentPacketCount;
+    }
+
+
+    /**
      * Compute:
      * 2001:db8::1 --> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa
      *
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index cf7a464..3d64b5a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -19,6 +19,8 @@
 import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresApi;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -41,9 +43,13 @@
  *
  * TODO: implement sending after a delay, combining queued replies and duplicate answer suppression
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsReplySender {
     private static final boolean DBG = MdnsAdvertiser.DBG;
     private static final int MSG_SEND = 1;
+    private static final int PACKET_NOT_SENT = 0;
+    private static final int PACKET_SENT = 1;
+
     @NonNull
     private final MdnsInterfaceSocket mSocket;
     @NonNull
@@ -79,16 +85,17 @@
      *
      * Must be called on the looper thread used by the {@link MdnsReplySender}.
      */
-    public void sendNow(@NonNull MdnsPacket packet, @NonNull InetSocketAddress destination)
+    public int sendNow(@NonNull MdnsPacket packet, @NonNull InetSocketAddress destination)
             throws IOException {
         ensureRunningOnHandlerThread(mHandler);
         if (!((destination.getAddress() instanceof Inet6Address && mSocket.hasJoinedIpv6())
                 || (destination.getAddress() instanceof Inet4Address && mSocket.hasJoinedIpv4()))) {
             // Skip sending if the socket has not joined the v4/v6 group (there was no address)
-            return;
+            return PACKET_NOT_SENT;
         }
         final byte[] outBuffer = MdnsUtils.createRawDnsPacket(mPacketCreationBuffer, packet);
         mSocket.send(new DatagramPacket(outBuffer, 0, outBuffer.length, destination));
+        return PACKET_SENT;
     }
 
     /** Get the packetCreationBuffer */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 2f10bde..a3cc0eb 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.net.Network;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Pair;
 
 import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -29,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /** A class that decodes mDNS responses from UDP packets. */
 public class MdnsResponseDecoder {
@@ -125,7 +125,7 @@
      *                     2) A copy of the original responses with some of them have records
      *                     update or only contains receive time updated.
      */
-    public Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentResponses(
+    public Pair<Set<MdnsResponse>, ArrayList<MdnsResponse>> augmentResponses(
             @NonNull MdnsPacket mdnsPacket,
             @NonNull Collection<MdnsResponse> existingResponses, int interfaceIndex,
             @Nullable Network network) {
@@ -136,7 +136,7 @@
         records.addAll(mdnsPacket.authorityRecords);
         records.addAll(mdnsPacket.additionalRecords);
 
-        final ArraySet<MdnsResponse> modified = new ArraySet<>();
+        final Set<MdnsResponse> modified = MdnsUtils.newSet();
         final ArrayList<MdnsResponse> responses = new ArrayList<>(existingResponses.size());
         final ArrayMap<MdnsResponse, MdnsResponse> augmentedToOriginal = new ArrayMap<>();
         for (MdnsResponse existing : existingResponses) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index f09596d..63835d9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -22,7 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
-import android.util.ArraySet;
+
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -46,11 +47,11 @@
                 public MdnsSearchOptions createFromParcel(Parcel source) {
                     return new MdnsSearchOptions(
                             source.createStringArrayList(),
-                            source.readBoolean(),
-                            source.readBoolean(),
+                            source.readInt() == 1,
+                            source.readInt() == 1,
                             source.readParcelable(null),
                             source.readString(),
-                            source.readBoolean(),
+                            source.readInt() == 1,
                             source.readInt());
                 }
 
@@ -165,11 +166,11 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeStringList(subtypes);
-        out.writeBoolean(isPassiveMode);
-        out.writeBoolean(removeExpiredService);
+        out.writeInt(isPassiveMode ? 1 : 0);
+        out.writeInt(removeExpiredService ? 1 : 0);
         out.writeParcelable(mNetwork, 0);
         out.writeString(resolveInstanceName);
-        out.writeBoolean(onlyUseIpv6OnIpv6OnlyNetworks);
+        out.writeInt(onlyUseIpv6OnIpv6OnlyNetworks ? 1 : 0);
         out.writeInt(numOfQueriesBeforeBackoff);
     }
 
@@ -184,7 +185,7 @@
         private String resolveInstanceName;
 
         private Builder() {
-            subtypes = new ArraySet<>();
+            subtypes = MdnsUtils.newSet();
         }
 
         /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 861d8d1..bbe8f4c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -27,7 +27,6 @@
 import android.os.Message;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -42,6 +41,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
 
 /**
@@ -414,11 +414,11 @@
                 currentList.add(additionalResponse);
             }
         }
-        final Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
+        final Pair<Set<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
                 responseDecoder.augmentResponses(packet, currentList,
                         socketKey.getInterfaceIndex(), socketKey.getNetwork());
 
-        final ArraySet<MdnsResponse> modifiedResponse = augmentedResult.first;
+        final Set<MdnsResponse> modifiedResponse = augmentedResult.first;
         final ArrayList<MdnsResponse> allResponses = augmentedResult.second;
 
         for (MdnsResponse response : allResponses) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
index d690032..c51811b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -128,9 +128,13 @@
      * cannot be determined, returns -1.
      */
     public int getInterfaceIndex() {
+        if (multicastSocket.isClosed()) {
+            sharedLog.e("Socket is closed");
+            return -1;
+        }
         try {
             return multicastSocket.getNetworkInterface().getIndex();
-        } catch (SocketException e) {
+        } catch (SocketException | NullPointerException e) {
             sharedLog.e("Failed to retrieve interface index for socket.", e);
             return -1;
         }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 23c5a4d..5c9ec09 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -19,12 +19,12 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
 import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
 import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresApi;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -41,6 +41,7 @@
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
@@ -67,6 +68,7 @@
  * to their default value (0, false or null).
  *
  */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class MdnsSocketProvider {
     private static final String TAG = MdnsSocketProvider.class.getSimpleName();
     private static final boolean DBG = MdnsDiscoveryManager.DBG;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
index 63119ac..3cd77a4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
@@ -22,9 +22,9 @@
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.system.Os;
-import android.util.ArraySet;
 
 import com.android.net.module.util.FdEventsReader;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.FileDescriptor;
 import java.net.InetSocketAddress;
@@ -39,7 +39,7 @@
     @NonNull
     private final Handler mHandler;
     @NonNull
-    private final Set<PacketHandler> mPacketHandlers = new ArraySet<>();
+    private final Set<PacketHandler> mPacketHandlers = MdnsUtils.newSet();
 
     interface PacketHandler {
         void handlePacket(byte[] recvbuf, int length, InetSocketAddress src);
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index c1c9c42..0dcc560 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Network;
+import android.os.Build;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -34,6 +35,8 @@
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Mdns utility functions.
@@ -58,6 +61,17 @@
     }
 
     /**
+     * Create a ArraySet or HashSet based on the sdk version.
+     */
+    public static <Type> Set<Type> newSet() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return new ArraySet<>();
+        } else {
+            return new HashSet<>();
+        }
+    }
+
+    /**
      * Convert the array of labels to DNS case-insensitive lowercase.
      */
     public static String[] toDnsLabelsLowerCase(@NonNull String[] labels) {
@@ -142,7 +156,7 @@
 
     /*** Check whether the target network matches any of the current networks */
     public static boolean isAnyNetworkMatched(@Nullable Network targetNetwork,
-            ArraySet<Network> currentNetworks) {
+            Set<Network> currentNetworks) {
         if (targetNetwork == null) {
             return !currentNetworks.isEmpty();
         }
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index c46eada..25e59d5 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -1745,7 +1745,7 @@
             // information. This is because no caller needs this information for now, and it
             // makes it easier to change the implementation later by using the histories in the
             // recorder.
-            return stats.clearInterfaces();
+            return stats.withoutInterfaces();
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 9ced44e..50a0635 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -187,15 +187,6 @@
     mTc.setPermissionForUids(permission, data);
 }
 
-static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
-    int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
-    if (fd < 0) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
-        return;
-    }
-    mTc.dump(fd, verbose);
-}
-
 static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
     return -bpf::synchronizeKernelRCU();
 }
@@ -232,8 +223,6 @@
     (void*)native_swapActiveStatsMap},
     {"native_setPermissionForUids", "(I[I)V",
     (void*)native_setPermissionForUids},
-    {"native_dump", "(Ljava/io/FileDescriptor;Z)V",
-    (void*)native_dump},
     {"native_synchronizeKernelRCU", "()I",
     (void*)native_synchronizeKernelRCU},
 };
diff --git a/service/libconnectivity/include/connectivity_native.h b/service/libconnectivity/include/connectivity_native.h
index 5a2509a..f4676a9 100644
--- a/service/libconnectivity/include/connectivity_native.h
+++ b/service/libconnectivity/include/connectivity_native.h
@@ -78,7 +78,7 @@
  * @param count Pointer to the size of the ports array; the value will be set to the total number of
  *              blocked ports, which may be larger than the ports array that was filled.
  */
-int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count)
+int AConnectivityNative_getPortsBlockedForBind(in_port_t* _Nonnull ports, size_t* _Nonnull count)
     __INTRODUCED_IN(__ANDROID_API_U__);
 
 __END_DECLS
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 3828389..8cd698e 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -46,7 +46,6 @@
 
 #include "TrafficController.h"
 #include "bpf/BpfMap.h"
-#include "netdutils/DumpWriter.h"
 
 namespace android {
 namespace net {
@@ -55,10 +54,8 @@
 using base::unique_fd;
 using bpf::BpfMap;
 using bpf::synchronizeKernelRCU;
-using netdutils::DumpWriter;
 using netdutils::NetlinkListener;
 using netdutils::NetlinkListenerInterface;
-using netdutils::ScopedIndent;
 using netdutils::Slice;
 using netdutils::sSyscalls;
 using netdutils::Status;
@@ -576,13 +573,5 @@
     }
 }
 
-void TrafficController::dump(int fd, bool verbose __unused) {
-    std::lock_guard guard(mMutex);
-    DumpWriter dw(fd);
-
-    ScopedIndent indentTop(dw);
-    dw.println("TrafficController");
-}
-
 }  // namespace net
 }  // namespace android
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index d610d25..86cf50a 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -22,7 +22,6 @@
 #include "android-base/thread_annotations.h"
 #include "bpf/BpfMap.h"
 #include "netd.h"
-#include "netdutils/DumpWriter.h"
 #include "netdutils/NetlinkListener.h"
 #include "netdutils/StatusOr.h"
 
@@ -55,8 +54,6 @@
     netdutils::Status updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
                                           FirewallType type) EXCLUDES(mMutex);
 
-    void dump(int fd, bool verbose) EXCLUDES(mMutex);
-
     netdutils::Status replaceRulesInMap(UidOwnerMatchType match, const std::vector<int32_t>& uids)
             EXCLUDES(mMutex);
 
diff --git a/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java b/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java
new file mode 100644
index 0000000..93d1d5d
--- /dev/null
+++ b/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.metrics;
+
+import android.annotation.NonNull;
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import com.android.modules.utils.HandlerExecutor;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * A class to register, sample and send connectivity state metrics.
+ */
+public class ConnectivitySampleMetricsHelper implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = ConnectivitySampleMetricsHelper.class.getSimpleName();
+
+    final Supplier<StatsEvent> mDelegate;
+
+    /**
+     * Start collecting metrics.
+     * @param context some context to get services
+     * @param connectivityServiceHandler the connectivity service handler
+     * @param atomTag the tag to collect metrics from
+     * @param delegate a method returning data when called on the handler thread
+     */
+    // Unfortunately it seems essentially impossible to unit test this method. The only thing
+    // to test is that there is a call to setPullAtomCallback, but StatsManager is final and
+    // can't be mocked without mockito-extended. Using mockito-extended in FrameworksNetTests
+    // would have a very large impact on performance, while splitting the unit test for this
+    // class in a separate target would make testing very hard to manage. Therefore, there
+    // can unfortunately be no unit tests for this method, but at least it is very simple.
+    public static void start(@NonNull final Context context,
+            @NonNull final Handler connectivityServiceHandler,
+            final int atomTag,
+            @NonNull final Supplier<StatsEvent> delegate) {
+        final ConnectivitySampleMetricsHelper metrics =
+                new ConnectivitySampleMetricsHelper(delegate);
+        final StatsManager mgr = context.getSystemService(StatsManager.class);
+        if (null == mgr) return; // No metrics for you
+        mgr.setPullAtomCallback(atomTag, null /* metadata */,
+                new HandlerExecutor(connectivityServiceHandler), metrics);
+    }
+
+    public ConnectivitySampleMetricsHelper(@NonNull final Supplier<StatsEvent> delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public int onPullAtom(final int atomTag, final List<StatsEvent> data) {
+        Log.d(TAG, "Sampling data for atom : " + atomTag);
+        data.add(mDelegate.get());
+        return StatsManager.PULL_SUCCESS;
+    }
+}
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 99afb90..ecc0377 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -64,6 +64,18 @@
 
   // Record sent query count before stopped discovery
   optional int32 sent_query_count = 12;
+
+  // Record sent packet count before unregistered service
+  optional int32 sent_packet_count = 13;
+
+  // Record number of conflict during probing
+  optional int32 conflict_during_probing_count = 14;
+
+  // Record number of conflict after probing
+  optional int32 conflict_after_probing_count = 15;
+
+  // The random number between 0 ~ 999 for sampling
+  optional int32 random_number = 16;
 }
 
 /**
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 62520dc..4b24aaf 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -295,13 +295,6 @@
             return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
                     uidOwnerMapSize, uidPermissionMapSize);
         }
-
-        /**
-         * Call native_dump
-         */
-        public void nativeDump(final FileDescriptor fd, final boolean verbose) {
-            native_dump(fd, verbose);
-        }
     }
 
     /** Constructor used after T that doesn't need to use netd anymore. */
@@ -1030,7 +1023,8 @@
                     EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
                     + " devices, use dumpsys netd trafficcontroller instead.");
         }
-        mDeps.nativeDump(fd, verbose);
+
+        pw.println("TrafficController");  // required by CTS testDumpBpfNetMaps
 
         pw.println();
         pw.println("sEnableJavaBpfMap: " + sEnableJavaBpfMap);
@@ -1106,8 +1100,5 @@
     private native void native_setPermissionForUids(int permissions, int[] uids);
 
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    private static native void native_dump(FileDescriptor fd, boolean verbose);
-
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static native int native_synchronizeKernelRCU();
 }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 60523dd..85507f6 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -77,6 +77,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
@@ -93,17 +94,16 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.VPN_UID;
-import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 import static android.system.OsConstants.ETH_P_ALL;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
 
-import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
 import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
 import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
+import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
 
 import static java.util.Map.Entry;
 
@@ -237,6 +237,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.stats.connectivity.MeteredState;
+import android.stats.connectivity.RequestType;
+import android.stats.connectivity.ValidatedState;
 import android.sysprop.NetworkProperties;
 import android.system.ErrnoException;
 import android.telephony.TelephonyManager;
@@ -249,6 +252,7 @@
 import android.util.Range;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.StatsEvent;
 
 import androidx.annotation.RequiresApi;
 
@@ -257,6 +261,16 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
+import com.android.metrics.ConnectionDurationForTransports;
+import com.android.metrics.ConnectionDurationPerTransports;
+import com.android.metrics.ConnectivitySampleMetricsHelper;
+import com.android.metrics.ConnectivityStateSample;
+import com.android.metrics.NetworkCountForTransports;
+import com.android.metrics.NetworkCountPerTransports;
+import com.android.metrics.NetworkDescription;
+import com.android.metrics.NetworkList;
+import com.android.metrics.NetworkRequestCount;
+import com.android.metrics.RequestCountForType;
 import com.android.modules.utils.BasicShellCommandHandler;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
@@ -339,6 +353,7 @@
 import java.util.SortedSet;
 import java.util.StringJoiner;
 import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -1424,8 +1439,7 @@
          * @see DeviceConfigUtils#isTetheringFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String name) {
-            return DeviceConfigUtils.isTetheringFeatureEnabled(context, NAMESPACE_TETHERING, name,
-                    TETHERING_MODULE_NAME, false /* defaultValue */);
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, name);
         }
 
         /**
@@ -2343,6 +2357,134 @@
         return out;
     }
 
+    // Because StatsEvent is not usable in tests (everything inside it is hidden), this
+    // method is used to convert a ConnectivityStateSample into a StatsEvent, so that tests
+    // can call sampleConnectivityState and make the checks on it.
+    @NonNull
+    private StatsEvent sampleConnectivityStateToStatsEvent() {
+        final ConnectivityStateSample sample = sampleConnectivityState();
+        return ConnectivityStatsLog.buildStatsEvent(
+                ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE,
+                sample.getNetworkCountPerTransports().toByteArray(),
+                sample.getConnectionDurationPerTransports().toByteArray(),
+                sample.getNetworkRequestCount().toByteArray(),
+                sample.getNetworks().toByteArray());
+    }
+
+    /**
+     * Gather and return a snapshot of the current connectivity state, to be used as a sample.
+     *
+     * This is used for metrics. These snapshots will be sampled and constitute a base for
+     * statistics about connectivity state of devices.
+     */
+    @VisibleForTesting
+    @NonNull
+    public ConnectivityStateSample sampleConnectivityState() {
+        ensureRunningOnConnectivityServiceThread();
+        final ConnectivityStateSample.Builder builder = ConnectivityStateSample.newBuilder();
+        builder.setNetworkCountPerTransports(sampleNetworkCount(mNetworkAgentInfos));
+        builder.setConnectionDurationPerTransports(sampleConnectionDuration(mNetworkAgentInfos));
+        builder.setNetworkRequestCount(sampleNetworkRequestCount(mNetworkRequests.values()));
+        builder.setNetworks(sampleNetworks(mNetworkAgentInfos));
+        return builder.build();
+    }
+
+    private static NetworkCountPerTransports sampleNetworkCount(
+            @NonNull final ArraySet<NetworkAgentInfo> nais) {
+        final SparseIntArray countPerTransports = new SparseIntArray();
+        for (final NetworkAgentInfo nai : nais) {
+            int transports = (int) nai.networkCapabilities.getTransportTypesInternal();
+            countPerTransports.put(transports, 1 + countPerTransports.get(transports, 0));
+        }
+        final NetworkCountPerTransports.Builder builder = NetworkCountPerTransports.newBuilder();
+        for (int i = countPerTransports.size() - 1; i >= 0; --i) {
+            final NetworkCountForTransports.Builder c = NetworkCountForTransports.newBuilder();
+            c.setTransportTypes(countPerTransports.keyAt(i));
+            c.setNetworkCount(countPerTransports.valueAt(i));
+            builder.addNetworkCountForTransports(c);
+        }
+        return builder.build();
+    }
+
+    private static ConnectionDurationPerTransports sampleConnectionDuration(
+            @NonNull final ArraySet<NetworkAgentInfo> nais) {
+        final ConnectionDurationPerTransports.Builder builder =
+                ConnectionDurationPerTransports.newBuilder();
+        for (final NetworkAgentInfo nai : nais) {
+            final ConnectionDurationForTransports.Builder c =
+                    ConnectionDurationForTransports.newBuilder();
+            c.setTransportTypes((int) nai.networkCapabilities.getTransportTypesInternal());
+            final long durationMillis = SystemClock.elapsedRealtime() - nai.getConnectedTime();
+            final long millisPerSecond = TimeUnit.SECONDS.toMillis(1);
+            // Add millisPerSecond/2 to round up or down to the nearest value
+            c.setDurationSec((int) ((durationMillis + millisPerSecond / 2) / millisPerSecond));
+            builder.addConnectionDurationForTransports(c);
+        }
+        return builder.build();
+    }
+
+    private static NetworkRequestCount sampleNetworkRequestCount(
+            @NonNull final Collection<NetworkRequestInfo> nris) {
+        final NetworkRequestCount.Builder builder = NetworkRequestCount.newBuilder();
+        final SparseIntArray countPerType = new SparseIntArray();
+        for (final NetworkRequestInfo nri : nris) {
+            final int type;
+            if (Process.SYSTEM_UID == nri.mAsUid) {
+                // The request is filed "as" the system, so it's the system on its own behalf.
+                type = RequestType.RT_SYSTEM.getNumber();
+            } else if (Process.SYSTEM_UID == nri.mUid) {
+                // The request is filed by the system as some other app, so it's the system on
+                // behalf of an app.
+                type = RequestType.RT_SYSTEM_ON_BEHALF_OF_APP.getNumber();
+            } else {
+                // Not the system, so it's an app requesting on its own behalf.
+                type = RequestType.RT_APP.getNumber();
+            }
+            countPerType.put(type, countPerType.get(type, 0));
+        }
+        for (int i = countPerType.size() - 1; i >= 0; --i) {
+            final RequestCountForType.Builder r = RequestCountForType.newBuilder();
+            r.setRequestType(RequestType.forNumber(countPerType.keyAt(i)));
+            r.setRequestCount(countPerType.valueAt(i));
+            builder.addRequestCountForType(r);
+        }
+        return builder.build();
+    }
+
+    private static NetworkList sampleNetworks(@NonNull final ArraySet<NetworkAgentInfo> nais) {
+        final NetworkList.Builder builder = NetworkList.newBuilder();
+        for (final NetworkAgentInfo nai : nais) {
+            final NetworkCapabilities nc = nai.networkCapabilities;
+            final NetworkDescription.Builder d = NetworkDescription.newBuilder();
+            d.setTransportTypes((int) nc.getTransportTypesInternal());
+            final MeteredState meteredState;
+            if (nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
+                meteredState = MeteredState.METERED_TEMPORARILY_UNMETERED;
+            } else if (nc.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+                meteredState = MeteredState.METERED_NO;
+            } else {
+                meteredState = MeteredState.METERED_YES;
+            }
+            d.setMeteredState(meteredState);
+            final ValidatedState validatedState;
+            if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
+                validatedState = ValidatedState.VS_PORTAL;
+            } else if (nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
+                validatedState = ValidatedState.VS_PARTIAL;
+            } else if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
+                validatedState = ValidatedState.VS_VALID;
+            } else {
+                validatedState = ValidatedState.VS_INVALID;
+            }
+            d.setValidatedState(validatedState);
+            d.setScorePolicies(nai.getScore().getPoliciesInternal());
+            d.setCapabilities(nc.getCapabilitiesInternal());
+            d.setEnterpriseId(nc.getEnterpriseIdsInternal());
+            builder.addNetworkDescription(d);
+        }
+        return builder.build();
+    }
+
     @Override
     public boolean isNetworkSupported(int networkType) {
         enforceAccessPermission();
@@ -3456,6 +3598,8 @@
         if (mDeps.isAtLeastT()) {
             mBpfNetMaps.setPullAtomCallback(mContext);
         }
+        ConnectivitySampleMetricsHelper.start(mContext, mHandler,
+                CONNECTIVITY_STATE_SAMPLE, this::sampleConnectivityStateToStatsEvent);
         // Wait PermissionMonitor to finish the permission update. Then MultipathPolicyTracker won't
         // have permission problem. While CV#block() is unbounded in time and can in principle block
         // forever, this replaces a synchronous call to PermissionMonitor#startMonitoring, which
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 87ae0c9..648f3bf 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -124,7 +124,7 @@
             new Class[]{FullScore.class, NetworkScore.class}, new String[]{"POLICY_"});
 
     @VisibleForTesting
-    static @NonNull String policyNameOf(final int policy) {
+    public static @NonNull String policyNameOf(final int policy) {
         final String name = sMessageNames.get(policy);
         if (name == null) {
             // Don't throw here because name might be null due to proguard stripping out the
@@ -304,6 +304,18 @@
     }
 
     /**
+     * Gets the policies as an long. Internal callers only.
+     *
+     * DO NOT USE if not immediately collapsing back into a scalar. Instead, use
+     * {@link #hasPolicy}.
+     * @return the internal, version-dependent int representing the policies.
+     * @hide
+     */
+    public long getPoliciesInternal() {
+        return mPolicies;
+    }
+
+    /**
      * @return whether this score has a particular policy.
      */
     @VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 845c04c..bdd841f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1105,6 +1105,11 @@
      *         already present.
      */
     public boolean addRequest(NetworkRequest networkRequest) {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on ConnectivityService thread: "
+                            + Thread.currentThread().getName());
+        }
         NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
         if (existing == networkRequest) return false;
         if (existing != null) {
@@ -1123,6 +1128,11 @@
      * Remove the specified request from this network.
      */
     public void removeRequest(int requestId) {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on ConnectivityService thread: "
+                            + Thread.currentThread().getName());
+        }
         NetworkRequest existing = mNetworkRequests.get(requestId);
         if (existing == null) return;
         updateRequestCounts(REMOVE, existing);
@@ -1144,6 +1154,11 @@
      * network.
      */
     public NetworkRequest requestAt(int index) {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on ConnectivityService thread: "
+                            + Thread.currentThread().getName());
+        }
         return mNetworkRequests.valueAt(index);
     }
 
@@ -1174,6 +1189,11 @@
      * Returns the number of requests of any type currently satisfied by this network.
      */
     public int numNetworkRequests() {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on ConnectivityService thread: "
+                            + Thread.currentThread().getName());
+        }
         return mNetworkRequests.size();
     }
 
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
new file mode 100644
index 0000000..ee79ef2
--- /dev/null
+++ b/staticlibs/Android.bp
@@ -0,0 +1,434 @@
+// Copyright (C) 2019 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.
+
+// 1. The "net-utils-framework-common" library is also compiled into the framework and placed on the
+//    boot classpath. It uses jarjar rules so that anything outside the framework can use this
+//    library directly.
+// 2. The "net-utils-services-common" library is for use by modules and frameworks/base/services.
+//    It does not need to be jarjared because it is not placed on the bootclasspath.
+// 3. The "net-utils-telephony-common-srcs" filegroup is for use specifically by telephony, which
+//    places many of its classes, even non-API service classes, on the boot classpath. Any file that
+//    is added to this filegroup *must* have a corresponding jarjar rule in the telephony jarjar
+//    rules file. Otherwise, it will end up on the boot classpath and other modules will not be able
+//    to provide their own copy.
+
+// Note: all filegroups here must have the right path attribute because otherwise, if they are
+// included in the bootclasspath, they could incorrectly be included in the SDK documentation even
+// though they are not in the current.txt files.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+  name: "net-utils-device-common",
+  srcs: [
+      "device/com/android/net/module/util/arp/ArpPacket.java",
+      "device/com/android/net/module/util/DeviceConfigUtils.java",
+      "device/com/android/net/module/util/DomainUtils.java",
+      "device/com/android/net/module/util/FdEventsReader.java",
+      "device/com/android/net/module/util/NetworkMonitorUtils.java",
+      "device/com/android/net/module/util/PacketReader.java",
+      "device/com/android/net/module/util/SharedLog.java",
+      "device/com/android/net/module/util/SocketUtils.java",
+      "device/com/android/net/module/util/FeatureVersions.java",
+      // This library is used by system modules, for which the system health impact of Kotlin
+      // has not yet been evaluated. Annotations may need jarjar'ing.
+      // "src_devicecommon/**/*.kt",
+  ],
+  sdk_version: "module_current",
+  min_sdk_version: "29",
+  target_sdk_version: "30",
+  apex_available: [
+      "//apex_available:anyapex",
+      "//apex_available:platform",
+  ],
+  visibility: [
+        "//frameworks/base/packages/Tethering",
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/Connectivity/framework:__subpackages__",
+        "//frameworks/opt/net/ike",
+        "//frameworks/opt/net/wifi/service",
+        "//packages/modules/Wifi/service",
+        "//frameworks/opt/net/telephony",
+        "//packages/modules/NetworkStack:__subpackages__",
+        "//packages/modules/CaptivePortalLogin",
+  ],
+  static_libs: [
+      "net-utils-framework-common",
+  ],
+  libs: [
+      "androidx.annotation_annotation",
+      "framework-annotations-lib",
+      "framework-configinfrastructure",
+      "framework-connectivity.stubs.module_lib",
+  ],
+  lint: { strict_updatability_linting: true },
+}
+
+java_defaults {
+    name: "lib_mockito_extended",
+    static_libs: [
+        "mockito-target-extended-minus-junit4"
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+}
+
+java_library {
+    name: "net-utils-dnspacket-common",
+    srcs: [
+    "framework/**/DnsPacket.java",
+    "framework/**/DnsPacketUtils.java",
+    ],
+    sdk_version: "module_current",
+    visibility: [
+        "//packages/services/Iwlan:__subpackages__",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "framework-connectivity.stubs.module_lib",
+    ],
+}
+
+filegroup {
+    name: "net-utils-framework-common-srcs",
+    srcs: ["framework/**/*.java"],
+    path: "framework",
+    visibility: [
+        "//frameworks/base",
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+}
+
+java_library {
+    name: "net-utils-device-common-bpf",
+    srcs: [
+        "device/com/android/net/module/util/BpfBitmap.java",
+        "device/com/android/net/module/util/BpfDump.java",
+        "device/com/android/net/module/util/BpfMap.java",
+        "device/com/android/net/module/util/BpfUtils.java",
+        "device/com/android/net/module/util/IBpfMap.java",
+        "device/com/android/net/module/util/JniUtil.java",
+        "device/com/android/net/module/util/Struct.java",
+        "device/com/android/net/module/util/TcUtils.java",
+        "framework/com/android/net/module/util/HexDump.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/NetworkStack:__subpackages__",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-connectivity.stubs.module_lib",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "net-utils-device-common-struct",
+    srcs: [
+        "device/com/android/net/module/util/Ipv6Utils.java",
+        "device/com/android/net/module/util/PacketBuilder.java",
+        "device/com/android/net/module/util/Struct.java",
+        "device/com/android/net/module/util/structs/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/NetworkStack:__subpackages__",
+    ],
+    static_libs: [
+        "net-utils-framework-common",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-connectivity.stubs.module_lib",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "net-utils-device-common-netlink",
+    srcs: [
+        "device/com/android/net/module/util/netlink/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/NetworkStack:__subpackages__",
+    ],
+    static_libs: [
+        "net-utils-device-common-struct",
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-connectivity.stubs.module_lib",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    // TODO : this target should probably be folded into net-utils-device-common
+    name: "net-utils-device-common-ip",
+    srcs: [
+        "device/com/android/net/module/util/ip/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/NetworkStack:__subpackages__",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "framework-connectivity",
+    ],
+    static_libs: [
+        "net-utils-device-common",
+        "net-utils-device-common-netlink",
+        "net-utils-framework-common",
+        "netd-client",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "net-utils-framework-common",
+    srcs: [
+        ":net-utils-framework-common-srcs",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-annotations-lib",
+        "framework-connectivity.stubs.module_lib",
+        "framework-connectivity-t.stubs.module_lib",
+        "framework-location.stubs.module_lib",
+    ],
+    jarjar_rules: "jarjar-rules-shared.txt",
+    visibility: [
+        "//cts/tests/tests/net",
+        "//cts/tests/tests/wifi",
+        "//packages/modules/Connectivity/tests/cts/net",
+        "//frameworks/base/packages/Tethering",
+        "//packages/modules/Connectivity/Tethering",
+        "//frameworks/base/tests:__subpackages__",
+        "//frameworks/opt/net/ike",
+        "//frameworks/opt/telephony",
+        "//frameworks/base/wifi:__subpackages__",
+        "//frameworks/base/packages/Connectivity:__subpackages__",
+        "//packages/modules/Connectivity:__subpackages__",
+        "//packages/modules/NetworkStack:__subpackages__",
+        "//packages/modules/CaptivePortalLogin",
+        "//packages/modules/Wifi/framework/tests:__subpackages__",
+        "//packages/apps/Settings",
+    ],
+    lint: { strict_updatability_linting: true },
+    errorprone: {
+        enabled: true,
+        // Error-prone checking only warns of problems when building. To make the build fail with
+        // these errors, list the specific error-prone problems below.
+        javacflags: [
+            "-Xep:NullablePrimitive:ERROR",
+        ],
+    },
+}
+
+java_library {
+    name: "net-utils-services-common",
+    srcs: [
+        "device/android/net/NetworkFactory.java",
+        "device/android/net/NetworkFactoryImpl.java",
+        "device/android/net/NetworkFactoryLegacyImpl.java",
+        "device/android/net/NetworkFactoryShim.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    static_libs: [
+        "modules-utils-build_system",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "framework-connectivity",
+    ],
+    // TODO: remove "apex_available:platform".
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.btservices",
+        "com.android.tethering",
+        "com.android.wifi",
+    ],
+    visibility: [
+        // TODO: remove after NetworkStatsService moves to the module.
+        "//frameworks/base/services/net",
+        "//packages/modules/Connectivity/service",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Bluetooth/android/app",
+        "//packages/modules/Wifi/service:__subpackages__",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "net-utils-device-common-async",
+    srcs: [
+        "device/com/android/net/module/util/async/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+    libs: [
+        "framework-annotations-lib",
+    ],
+    static_libs: [
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "net-utils-device-common-wear",
+    srcs: [
+        "device/com/android/net/module/util/wear/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+    libs: [
+        "framework-annotations-lib",
+    ],
+    static_libs: [
+        "net-utils-device-common-async",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+// Limited set of utilities for use by service-connectivity-mdns-standalone-build-test, to make sure
+// the mDNS code can build with only system APIs.
+// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that
+// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build
+// rule verifies that the mDNS code can be built into apps, if code transformations are applied to
+// the annotations.
+// When using "system_current", framework annotations are not available; they would appear as
+// package-private as they are marked as such in the system_current stubs. So build against
+// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7.
+java_library {
+    name: "net-utils-device-common-mdns-standalone-build-test",
+    // Build against core_platform and add the stub libraries manually in "libs", as annotations
+    // are already included in android_system_stubs_current but package-private, so
+    // "framework-annotations-lib" needs to be manually included before
+    // "android_system_stubs_current" (b/272392042)
+    sdk_version: "core_platform",
+    srcs: [
+        "device/com/android/net/module/util/FdEventsReader.java",
+        "device/com/android/net/module/util/SharedLog.java",
+        "framework/com/android/net/module/util/ByteUtils.java",
+        "framework/com/android/net/module/util/CollectionUtils.java",
+        "framework/com/android/net/module/util/HexDump.java",
+        "framework/com/android/net/module/util/LinkPropertiesUtils.java",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "android_system_stubs_current",
+        "androidx.annotation_annotation",
+    ],
+    visibility: ["//packages/modules/Connectivity/service-t"],
+}
+
+// Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
+// included either (some annotations would be duplicated on the bootclasspath).
+filegroup {
+    name: "net-utils-telephony-common-srcs",
+    srcs: [
+        // Any class here *must* have a corresponding jarjar rule in the telephony build rules.
+        "device/android/net/NetworkFactory.java",
+        "device/android/net/NetworkFactoryImpl.java",
+        "device/android/net/NetworkFactoryLegacyImpl.java",
+        "device/android/net/NetworkFactoryShim.java",
+    ],
+    path: "device",
+    visibility: [
+        "//frameworks/opt/telephony",
+    ],
+}
+
+// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
+// rules on the wifi side.
+// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
+filegroup {
+    name: "net-utils-framework-wifi-common-srcs",
+    srcs: [
+        "framework/com/android/net/module/util/DnsSdTxtRecord.java",
+        "framework/com/android/net/module/util/Inet4AddressUtils.java",
+        "framework/com/android/net/module/util/InetAddressUtils.java",
+        "framework/com/android/net/module/util/MacAddressUtils.java",
+        "framework/com/android/net/module/util/NetUtils.java",
+    ],
+    path: "framework",
+    visibility: [
+        "//frameworks/base",
+    ],
+}
+
+// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
+// rules on the wifi side.
+// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
+filegroup {
+    name: "net-utils-wifi-service-common-srcs",
+    srcs: [
+       "device/android/net/NetworkFactory.java",
+       "device/android/net/NetworkFactoryImpl.java",
+       "device/android/net/NetworkFactoryLegacyImpl.java",
+       "device/android/net/NetworkFactoryShim.java",
+    ],
+    visibility: [
+        "//frameworks/opt/net/wifi/service",
+        "//packages/modules/Wifi/service",
+    ],
+}
diff --git a/staticlibs/TEST_MAPPING b/staticlibs/TEST_MAPPING
new file mode 100644
index 0000000..b1cb6b5
--- /dev/null
+++ b/staticlibs/TEST_MAPPING
@@ -0,0 +1,28 @@
+{
+  "presubmit": [
+    {
+      "name": "netdutils_test"
+    }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/core/java/android/net"
+    },
+    // Below tests already run the library tests as part of their coverage tests
+    {
+      "path": "packages/modules/NetworkStack"
+    },
+    {
+      "path": "packages/modules/CaptivePortalLogin"
+    },
+    {
+      "path": "frameworks/base/packages/Tethering"
+    },
+    {
+      "path": "packages/modules/Wifi/framework"
+    },
+    {
+      "path": "vendor/xts/gts-tests/hostsidetests/networkstack"
+    }
+  ]
+}
diff --git a/staticlibs/client-libs/Android.bp b/staticlibs/client-libs/Android.bp
new file mode 100644
index 0000000..c560045
--- /dev/null
+++ b/staticlibs/client-libs/Android.bp
@@ -0,0 +1,26 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "netd-client",
+    srcs: ["netd/**/*"],
+    sdk_version: "system_current",
+    min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+        "com.android.wifi"
+    ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//frameworks/base/services:__subpackages__",
+        "//frameworks/base/packages:__subpackages__",
+        "//packages/modules/Wifi/service:__subpackages__"
+    ],
+    libs: ["androidx.annotation_annotation"],
+    static_libs: [
+        "netd_aidl_interface-lateststable-java",
+        "netd_event_listener_interface-lateststable-java"
+    ]
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java
new file mode 100644
index 0000000..4180732
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdEventListener.java
@@ -0,0 +1,61 @@
+/*
+ * 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.net.module.util;
+
+import android.net.metrics.INetdEventListener;
+
+/**
+ * Base {@link INetdEventListener} that provides no-op implementations which can
+ * be overridden.
+ */
+public class BaseNetdEventListener extends INetdEventListener.Stub {
+
+    @Override
+    public void onDnsEvent(int netId, int eventType, int returnCode,
+            int latencyMs, String hostname, String[] ipAddresses,
+            int ipAddressesCount, int uid) { }
+
+    @Override
+    public void onPrivateDnsValidationEvent(int netId, String ipAddress,
+            String hostname, boolean validated) { }
+
+    @Override
+    public void onConnectEvent(int netId, int error, int latencyMs,
+            String ipAddr, int port, int uid) { }
+
+    @Override
+    public void onWakeupEvent(String prefix, int uid, int ethertype,
+            int ipNextHeader, byte[] dstHw, String srcIp, String dstIp,
+            int srcPort, int dstPort, long timestampNs) { }
+
+    @Override
+    public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets,
+            int[] lostPackets, int[] rttUs, int[] sentAckDiffMs) { }
+
+    @Override
+    public void onNat64PrefixEvent(int netId, boolean added,
+            String prefixString, int prefixLength) { }
+
+    @Override
+    public int getInterfaceVersion() {
+        return INetdEventListener.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return INetdEventListener.HASH;
+    }
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java
new file mode 100644
index 0000000..526dd8b
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/BaseNetdUnsolicitedEventListener.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import android.net.INetdUnsolicitedEventListener;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be
+ * overridden.
+ */
+public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs,
+            int uid) { }
+
+    @Override
+    public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS,
+            @NonNull String[] servers) { }
+
+    @Override
+    public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags,
+            int scope) { }
+
+    @Override
+    public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags,
+            int scope) { }
+
+    @Override
+    public void onInterfaceAdded(@NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceRemoved(@NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceChanged(@NonNull String ifName, boolean up) { }
+
+    @Override
+    public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { }
+
+    @Override
+    public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway,
+            @NonNull String ifName) { }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, @NonNull String hex) { }
+
+    @Override
+    public int getInterfaceVersion() {
+        return INetdUnsolicitedEventListener.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return INetdUnsolicitedEventListener.HASH;
+    }
+}
diff --git a/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
new file mode 100644
index 0000000..98fda56
--- /dev/null
+++ b/staticlibs/client-libs/netd/com/android/net/module/util/NetdUtils.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.net.INetd.IF_STATE_DOWN;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.RouteInfo.RTN_THROW;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.system.OsConstants.EBUSY;
+
+import android.annotation.SuppressLint;
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+import android.net.TetherConfigParcel;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collection of utilities for netd.
+ */
+public class NetdUtils {
+    private static final String TAG = NetdUtils.class.getSimpleName();
+
+    /** Used to modify the specified route. */
+    public enum ModifyOperation {
+        ADD,
+        REMOVE,
+    }
+
+    /**
+     * Get InterfaceConfigurationParcel from netd.
+     */
+    public static InterfaceConfigurationParcel getInterfaceConfigParcel(@NonNull INetd netd,
+            @NonNull String iface) {
+        try {
+            return netd.interfaceGetCfg(iface);
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private static void validateFlag(String flag) {
+        if (flag.indexOf(' ') >= 0) {
+            throw new IllegalArgumentException("flag contains space: " + flag);
+        }
+    }
+
+    /**
+     * Check whether the InterfaceConfigurationParcel contains the target flag or not.
+     *
+     * @param config The InterfaceConfigurationParcel instance.
+     * @param flag Target flag string to be checked.
+     */
+    public static boolean hasFlag(@NonNull final InterfaceConfigurationParcel config,
+            @NonNull final String flag) {
+        validateFlag(flag);
+        final Set<String> flagList = new HashSet<String>(Arrays.asList(config.flags));
+        return flagList.contains(flag);
+    }
+
+    @VisibleForTesting
+    protected static String[] removeAndAddFlags(@NonNull String[] flags, @NonNull String remove,
+            @NonNull String add) {
+        final ArrayList<String> result = new ArrayList<>();
+        try {
+            // Validate the add flag first, so that the for-loop can be ignore once the format of
+            // add flag is invalid.
+            validateFlag(add);
+            for (String flag : flags) {
+                // Simply ignore both of remove and add flags first, then add the add flag after
+                // exiting the loop to prevent adding the duplicate flag.
+                if (remove.equals(flag) || add.equals(flag)) continue;
+                result.add(flag);
+            }
+            result.add(add);
+            return result.toArray(new String[result.size()]);
+        } catch (IllegalArgumentException iae) {
+            throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae);
+        }
+    }
+
+    /**
+     * Set interface configuration to netd by passing InterfaceConfigurationParcel.
+     */
+    public static void setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel) {
+        try {
+            netd.interfaceSetCfg(configParcel);
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Set the given interface up.
+     */
+    public static void setInterfaceUp(INetd netd, String iface) {
+        final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface);
+        configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_DOWN /* remove */,
+                IF_STATE_UP /* add */);
+        setInterfaceConfig(netd, configParcel);
+    }
+
+    /**
+     * Set the given interface down.
+     */
+    public static void setInterfaceDown(INetd netd, String iface) {
+        final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface);
+        configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_UP /* remove */,
+                IF_STATE_DOWN /* add */);
+        setInterfaceConfig(netd, configParcel);
+    }
+
+    /** Start tethering. */
+    public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
+            final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
+        final TetherConfigParcel config = new TetherConfigParcel();
+        config.usingLegacyDnsProxy = usingLegacyDnsProxy;
+        config.dhcpRanges = dhcpRange;
+        netd.tetherStartWithConfiguration(config);
+    }
+
+    /** Setup interface for tethering. */
+    public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
+            throws RemoteException, ServiceSpecificException {
+        tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+    }
+
+    /** Setup interface with configurable retries for tethering. */
+    public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
+            int maxAttempts, int pollingIntervalMs)
+            throws RemoteException, ServiceSpecificException {
+        netd.tetherInterfaceAdd(iface);
+        networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
+        List<RouteInfo> routes = new ArrayList<>();
+        routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
+        addRoutesToLocalNetwork(netd, iface, routes);
+    }
+
+    /**
+     * Retry Netd#networkAddInterface for EBUSY error code.
+     * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode.
+     * There can be a race where puts the interface into the local network but interface is still
+     * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
+     * See b/158269544 for detail.
+     */
+    private static void networkAddInterface(final INetd netd, final String iface,
+            int maxAttempts, int pollingIntervalMs)
+            throws ServiceSpecificException, RemoteException {
+        for (int i = 1; i <= maxAttempts; i++) {
+            try {
+                netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+                return;
+            } catch (ServiceSpecificException e) {
+                if (e.errorCode == EBUSY && i < maxAttempts) {
+                    SystemClock.sleep(pollingIntervalMs);
+                    continue;
+                }
+
+                Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e);
+                throw e;
+            }
+        }
+    }
+
+    /** Reset interface for tethering. */
+    public static void untetherInterface(final INetd netd, String iface)
+            throws RemoteException, ServiceSpecificException {
+        try {
+            netd.tetherInterfaceRemove(iface);
+        } finally {
+            netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface);
+        }
+    }
+
+    /** Add |routes| to local network. */
+    public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+            final List<RouteInfo> routes) {
+
+        for (RouteInfo route : routes) {
+            if (!route.isDefaultRoute()) {
+                modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+            }
+        }
+
+        // IPv6 link local should be activated always.
+        modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+                new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
+    }
+
+    /** Remove routes from local network. */
+    public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+        int failures = 0;
+
+        for (RouteInfo route : routes) {
+            try {
+                modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+            } catch (IllegalStateException e) {
+                failures++;
+            }
+        }
+
+        return failures;
+    }
+
+    @SuppressLint("NewApi")
+    private static String findNextHop(final RouteInfo route) {
+        final String nextHop;
+        switch (route.getType()) {
+            case RTN_UNICAST:
+                if (route.hasGateway()) {
+                    nextHop = route.getGateway().getHostAddress();
+                } else {
+                    nextHop = INetd.NEXTHOP_NONE;
+                }
+                break;
+            case RTN_UNREACHABLE:
+                nextHop = INetd.NEXTHOP_UNREACHABLE;
+                break;
+            case RTN_THROW:
+                nextHop = INetd.NEXTHOP_THROW;
+                break;
+            default:
+                nextHop = INetd.NEXTHOP_NONE;
+                break;
+        }
+        return nextHop;
+    }
+
+    /** Add or remove |route|. */
+    public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+            final RouteInfo route) {
+        final String ifName = route.getInterface();
+        final String dst = route.getDestination().toString();
+        final String nextHop = findNextHop(route);
+
+        try {
+            switch(op) {
+                case ADD:
+                    netd.networkAddRoute(netId, ifName, dst, nextHop);
+                    break;
+                case REMOVE:
+                    netd.networkRemoveRoute(netId, ifName, dst, nextHop);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported modify operation:" + op);
+            }
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/staticlibs/client-libs/tests/unit/Android.bp b/staticlibs/client-libs/tests/unit/Android.bp
new file mode 100644
index 0000000..220a6c1
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/Android.bp
@@ -0,0 +1,44 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "NetdStaticLibTestsLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    min_sdk_version: "29",
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-extended-minus-junit4",
+        "net-tests-utils-host-device-common",
+        "netd-client",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    visibility: [
+        // Visible for Tethering and NetworkStack integration test and link NetdStaticLibTestsLib
+        // there, so that the tests under client-libs can also be run when running tethering and
+        // NetworkStack MTS.
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+        "//packages/modules/NetworkStack/tests/integration",
+    ]
+}
+
+android_test {
+    name: "NetdStaticLibTests",
+    certificate: "platform",
+    static_libs: [
+        "NetdStaticLibTestsLib",
+    ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/staticlibs/client-libs/tests/unit/AndroidManifest.xml b/staticlibs/client-libs/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..7a07d3d
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.clientlibs.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.clientlibs.tests"
+        android:label="Netd Static Library Tests" />
+</manifest>
diff --git a/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
new file mode 100644
index 0000000..5069672
--- /dev/null
+++ b/staticlibs/client-libs/tests/unit/src/com/android/net/module/util/NetdUtilsTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.net.INetd.LOCAL_NET_ID;
+import static android.system.OsConstants.EBUSY;
+
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetdUtilsTest {
+    @Mock private INetd mNetd;
+
+    private static final String IFACE = "TEST_IFACE";
+    private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24");
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private void setupFlagsForInterfaceConfiguration(String[] flags) throws Exception {
+        final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
+        config.flags = flags;
+        when(mNetd.interfaceGetCfg(eq(IFACE))).thenReturn(config);
+    }
+
+    private void verifyMethodsAndArgumentsOfSetInterface(boolean ifaceUp) throws Exception {
+        final String[] flagsContainDownAndUp = new String[] {"flagA", "down", "flagB", "up"};
+        final String[] flagsForInterfaceDown = new String[] {"flagA", "down", "flagB"};
+        final String[] flagsForInterfaceUp = new String[] {"flagA", "up", "flagB"};
+        final String[] expectedFinalFlags;
+        setupFlagsForInterfaceConfiguration(flagsContainDownAndUp);
+        if (ifaceUp) {
+            // "down" flag will be removed from flagsContainDownAndUp when interface is up. Set
+            // expectedFinalFlags to flagsForInterfaceUp.
+            expectedFinalFlags = flagsForInterfaceUp;
+            NetdUtils.setInterfaceUp(mNetd, IFACE);
+        } else {
+            // "up" flag will be removed from flagsContainDownAndUp when interface is down. Set
+            // expectedFinalFlags to flagsForInterfaceDown.
+            expectedFinalFlags = flagsForInterfaceDown;
+            NetdUtils.setInterfaceDown(mNetd, IFACE);
+        }
+        verify(mNetd).interfaceSetCfg(
+                argThat(config ->
+                        // Check if actual flags are the same as expected flags.
+                        // TODO: Have a function in MiscAsserts to check if two arrays are the same.
+                        CollectionUtils.all(Arrays.asList(expectedFinalFlags),
+                                flag -> Arrays.asList(config.flags).contains(flag))
+                        && CollectionUtils.all(Arrays.asList(config.flags),
+                                flag -> Arrays.asList(expectedFinalFlags).contains(flag))));
+    }
+
+    @Test
+    public void testSetInterfaceUp() throws Exception {
+        verifyMethodsAndArgumentsOfSetInterface(true /* ifaceUp */);
+    }
+
+    @Test
+    public void testSetInterfaceDown() throws Exception {
+        verifyMethodsAndArgumentsOfSetInterface(false /* ifaceUp */);
+    }
+
+    @Test
+    public void testRemoveAndAddFlags() throws Exception {
+        final String[] flags = new String[] {"flagA", "down", "flagB"};
+        // Add an invalid flag and expect to get an IllegalStateException.
+        assertThrows(IllegalStateException.class,
+                () -> NetdUtils.removeAndAddFlags(flags, "down" /* remove */, "u p" /* add */));
+    }
+
+    private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops)
+            throws Exception {
+        // This cannot be an int because local variables referenced from a lambda expression must
+        // be final or effectively final.
+        final Counter myCounter = new Counter();
+        doAnswer((invocation) -> {
+            myCounter.count();
+            if (myCounter.isCounterReached(numLoops)) {
+                if (cause == null) return null;
+
+                throw cause;
+            }
+
+            throw new ServiceSpecificException(EBUSY);
+        }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE);
+    }
+
+    class Counter {
+        private int mValue = 0;
+
+        private void count() {
+            mValue++;
+        }
+
+        private boolean isCounterReached(int target) {
+            return mValue >= target;
+        }
+    }
+
+    @Test
+    public void testTetherInterfaceSuccessful() throws Exception {
+        // Expect #networkAddInterface successful at first tries.
+        verifyTetherInterfaceSucceeds(1);
+
+        // Expect #networkAddInterface successful after 10 tries.
+        verifyTetherInterfaceSucceeds(10);
+    }
+
+    private void runTetherInterfaceWithServiceSpecificException(int expectedTries,
+            int expectedCode) throws Exception {
+        setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
+
+        try {
+            NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+            fail("Expect throw ServiceSpecificException");
+        } catch (ServiceSpecificException e) {
+            assertEquals(e.errorCode, expectedCode);
+        }
+
+        verifyNetworkAddInterfaceFails(expectedTries);
+        reset(mNetd);
+    }
+
+    private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception {
+        setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
+
+        try {
+            NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX, 20, 0);
+            fail("Expect throw RemoteException");
+        } catch (RemoteException e) { }
+
+        verifyNetworkAddInterfaceFails(expectedTries);
+        reset(mNetd);
+    }
+
+    private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception {
+        verify(mNetd).tetherInterfaceAdd(IFACE);
+        verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+        verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+        verifyNoMoreInteractions(mNetd);
+    }
+
+    private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
+        setNetworkAddInterfaceOutcome(null, expectedTries);
+
+        NetdUtils.tetherInterface(mNetd, IFACE, TEST_IPPREFIX);
+        verify(mNetd).tetherInterfaceAdd(IFACE);
+        verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE);
+        verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE), any(), any());
+        verifyNoMoreInteractions(mNetd);
+        reset(mNetd);
+    }
+
+    @Test
+    public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception {
+        // Test throwing ServiceSpecificException with EBUSY failure.
+        runTetherInterfaceWithServiceSpecificException(20, EBUSY);
+
+        // Test throwing ServiceSpecificException with unexpectedError.
+        final int unexpectedError = 999;
+        runTetherInterfaceWithServiceSpecificException(1, unexpectedError);
+
+        // Test throwing ServiceSpecificException with unexpectedError after 7 tries.
+        runTetherInterfaceWithServiceSpecificException(7, unexpectedError);
+
+        // Test throwing RemoteException.
+        runTetherInterfaceWithRemoteException(1);
+
+        // Test throwing RemoteException after 3 tries.
+        runTetherInterfaceWithRemoteException(3);
+    }
+
+    @Test
+    public void testNetdUtilsHasFlag() throws Exception {
+        final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+        setupFlagsForInterfaceConfiguration(flags);
+
+        // Set interface up.
+        NetdUtils.setInterfaceUp(mNetd, IFACE);
+        final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+                ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+        verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+        final InterfaceConfigurationParcel p = arg.getValue();
+        assertTrue(NetdUtils.hasFlag(p, "up"));
+        assertTrue(NetdUtils.hasFlag(p, "running"));
+        assertTrue(NetdUtils.hasFlag(p, "broadcast"));
+        assertTrue(NetdUtils.hasFlag(p, "multicast"));
+        assertFalse(NetdUtils.hasFlag(p, "down"));
+    }
+
+    @Test
+    public void testNetdUtilsHasFlag_flagContainsSpace() throws Exception {
+        final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+        setupFlagsForInterfaceConfiguration(flags);
+
+        // Set interface up.
+        NetdUtils.setInterfaceUp(mNetd, IFACE);
+        final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+                ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+        verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+        final InterfaceConfigurationParcel p = arg.getValue();
+        assertThrows(IllegalArgumentException.class, () -> NetdUtils.hasFlag(p, "up "));
+    }
+
+    @Test
+    public void testNetdUtilsHasFlag_UppercaseString() throws Exception {
+        final String[] flags = new String[] {"up", "broadcast", "running", "multicast"};
+        setupFlagsForInterfaceConfiguration(flags);
+
+        // Set interface up.
+        NetdUtils.setInterfaceUp(mNetd, IFACE);
+        final ArgumentCaptor<InterfaceConfigurationParcel> arg =
+                ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+        verify(mNetd, times(1)).interfaceSetCfg(arg.capture());
+
+        final InterfaceConfigurationParcel p = arg.getValue();
+        assertFalse(NetdUtils.hasFlag(p, "UP"));
+        assertFalse(NetdUtils.hasFlag(p, "BROADCAST"));
+        assertFalse(NetdUtils.hasFlag(p, "RUNNING"));
+        assertFalse(NetdUtils.hasFlag(p, "MULTICAST"));
+    }
+}
diff --git a/staticlibs/device/android/net/NetworkFactory.java b/staticlibs/device/android/net/NetworkFactory.java
new file mode 100644
index 0000000..87f6dee
--- /dev/null
+++ b/staticlibs/device/android/net/NetworkFactory.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests.  NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function.  All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden.  If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ *
+ * This class is mostly a shim which delegates to one of two implementations depending
+ * on the SDK level of the device it's running on.
+ *
+ * @hide
+ **/
+public class NetworkFactory {
+    static final boolean DBG = true;
+    static final boolean VDBG = false;
+
+    final NetworkFactoryShim mImpl;
+
+    private final String LOG_TAG;
+
+    // Ideally the filter argument would be non-null, but null has historically meant to see
+    // no requests and telephony passes null.
+    public NetworkFactory(Looper looper, Context context, String logTag,
+            @Nullable final NetworkCapabilities filter) {
+        LOG_TAG = logTag;
+        if (isAtLeastS()) {
+            mImpl = new NetworkFactoryImpl(this, looper, context, filter);
+        } else {
+            mImpl = new NetworkFactoryLegacyImpl(this, looper, context, filter);
+        }
+    }
+
+    // TODO : these two constants and the method are only used by telephony tests. Replace it in
+    // the tests and remove them and the associated code.
+    public static final int CMD_REQUEST_NETWORK = 1;
+    public static final int CMD_CANCEL_REQUEST = 2;
+    /** Like Handler#obtainMessage */
+    @VisibleForTesting
+    public Message obtainMessage(final int what, final int arg1, final int arg2,
+            final @Nullable Object obj) {
+        return mImpl.obtainMessage(what, arg1, arg2, obj);
+    }
+
+    // Called by BluetoothNetworkFactory
+    public final Looper getLooper() {
+        return mImpl.getLooper();
+    }
+
+    // Refcount for simple mode requests
+    private int mRefCount = 0;
+
+    /* Registers this NetworkFactory with the system. May only be called once per factory. */
+    public void register() {
+        mImpl.register(LOG_TAG);
+    }
+
+    /**
+     * Registers this NetworkFactory with the system ignoring the score filter. This will let
+     * the factory always see all network requests matching its capabilities filter.
+     * May only be called once per factory.
+     */
+    public void registerIgnoringScore() {
+        mImpl.registerIgnoringScore(LOG_TAG);
+    }
+
+    /** Unregisters this NetworkFactory. After this call, the object can no longer be used. */
+    public void terminate() {
+        mImpl.terminate();
+    }
+
+    protected final void reevaluateAllRequests() {
+        mImpl.reevaluateAllRequests();
+    }
+
+    /**
+     * Overridable function to provide complex filtering.
+     * Called for every request every time a new NetworkRequest is seen
+     * and whenever the filterScore or filterNetworkCapabilities change.
+     *
+     * acceptRequest can be overridden to provide complex filter behavior
+     * for the incoming requests
+     *
+     * For output, this class will call {@link #needNetworkFor} and
+     * {@link #releaseNetworkFor} for every request that passes the filters.
+     * If you don't need to see every request, you can leave the base
+     * implementations of those two functions and instead override
+     * {@link #startNetwork} and {@link #stopNetwork}.
+     *
+     * If you want to see every score fluctuation on every request, set
+     * your score filter to a very high number and watch {@link #needNetworkFor}.
+     *
+     * @return {@code true} to accept the request.
+     */
+    public boolean acceptRequest(@NonNull final NetworkRequest request) {
+        return true;
+    }
+
+    /**
+     * Can be called by a factory to release a request as unfulfillable: the request will be
+     * removed, and the caller will get a
+     * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
+     * returns.
+     *
+     * Note: this should only be called by factory which KNOWS that it is the ONLY factory which
+     * is able to fulfill this request!
+     */
+    protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
+        mImpl.releaseRequestAsUnfulfillableByAnyFactory(r);
+    }
+
+    // override to do simple mode (request independent)
+    protected void startNetwork() { }
+    protected void stopNetwork() { }
+
+    // override to do fancier stuff
+    protected void needNetworkFor(@NonNull final NetworkRequest networkRequest) {
+        if (++mRefCount == 1) startNetwork();
+    }
+
+    protected void releaseNetworkFor(@NonNull final NetworkRequest networkRequest) {
+        if (--mRefCount == 0) stopNetwork();
+    }
+
+    /**
+     * @deprecated this method was never part of the API (system or public) and is only added
+     *   for migration of existing clients.
+     */
+    @Deprecated
+    public void setScoreFilter(final int score) {
+        mImpl.setScoreFilter(score);
+    }
+
+    /**
+     * Set a score filter for this factory.
+     *
+     * This should include the transports the factory knows its networks will have, and
+     * an optimistic view of the attributes it may have. This does not commit the factory
+     * to being able to bring up such a network ; it only lets it avoid hearing about
+     * requests that it has no chance of fulfilling.
+     *
+     * @param score the filter
+     */
+    public void setScoreFilter(@NonNull final NetworkScore score) {
+        mImpl.setScoreFilter(score);
+    }
+
+    public void setCapabilityFilter(NetworkCapabilities netCap) {
+        mImpl.setCapabilityFilter(netCap);
+    }
+
+    @VisibleForTesting
+    protected int getRequestCount() {
+        return mImpl.getRequestCount();
+    }
+
+    public int getSerialNumber() {
+        return mImpl.getSerialNumber();
+    }
+
+    public NetworkProvider getProvider() {
+        return mImpl.getProvider();
+    }
+
+    protected void log(String s) {
+        Log.d(LOG_TAG, s);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mImpl.dump(fd, writer, args);
+    }
+
+    @Override
+    public String toString() {
+        return "{" + LOG_TAG + " " + mImpl.toString() + "}";
+    }
+}
diff --git a/staticlibs/device/android/net/NetworkFactoryImpl.java b/staticlibs/device/android/net/NetworkFactoryImpl.java
new file mode 100644
index 0000000..9c1190c
--- /dev/null
+++ b/staticlibs/device/android/net/NetworkFactoryImpl.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.os.Looper;
+import android.os.Message;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests.  NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function.  All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden.  If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+// TODO(b/187083878): factor out common code between this and NetworkFactoryLegacyImpl
+class NetworkFactoryImpl extends NetworkFactoryLegacyImpl {
+    private static final boolean DBG = NetworkFactory.DBG;
+    private static final boolean VDBG = NetworkFactory.VDBG;
+
+    // A score that will win against everything, so that score filtering will let all requests
+    // through
+    // TODO : remove this and replace with an API to listen to all requests.
+    @NonNull
+    private static final NetworkScore INVINCIBLE_SCORE =
+            new NetworkScore.Builder().setLegacyInt(1000).build();
+
+    // TODO(b/187082970): Replace CMD_* with Handler.post(() -> { ... }) since all the CMDs do is to
+    //  run the tasks asynchronously on the Handler thread.
+
+    /**
+     * Pass a network request to the bearer.  If the bearer believes it can
+     * satisfy the request it should connect to the network and create a
+     * NetworkAgent.  Once the NetworkAgent is fully functional it will
+     * register itself with ConnectivityService using registerNetworkAgent.
+     * If the bearer cannot immediately satisfy the request (no network,
+     * user disabled the radio, lower-scored network) it should remember
+     * any NetworkRequests it may be able to satisfy in the future.  It may
+     * disregard any that it will never be able to service, for example
+     * those requiring a different bearer.
+     * msg.obj = NetworkRequest
+     */
+    // TODO : this and CANCEL_REQUEST are only used by telephony tests. Replace it in the tests
+    // and remove them and the associated code.
+    private static final int CMD_REQUEST_NETWORK = NetworkFactory.CMD_REQUEST_NETWORK;
+
+    /**
+     * Cancel a network request
+     * msg.obj = NetworkRequest
+     */
+    private static final int CMD_CANCEL_REQUEST = NetworkFactory.CMD_CANCEL_REQUEST;
+
+    /**
+     * Internally used to set our best-guess score.
+     * msg.obj = new score
+     */
+    private static final int CMD_SET_SCORE = 3;
+
+    /**
+     * Internally used to set our current filter for coarse bandwidth changes with
+     * technology changes.
+     * msg.obj = new filter
+     */
+    private static final int CMD_SET_FILTER = 4;
+
+    /**
+     * Internally used to send the network offer associated with this factory.
+     * No arguments, will read from members
+     */
+    private static final int CMD_OFFER_NETWORK = 5;
+
+    /**
+     * Internally used to send the request to listen to all requests.
+     * No arguments, will read from members
+     */
+    private static final int CMD_LISTEN_TO_ALL_REQUESTS = 6;
+
+    private final Map<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
+            new LinkedHashMap<>();
+
+    @NonNull private NetworkScore mScore = new NetworkScore.Builder().setLegacyInt(0).build();
+
+    private final NetworkOfferCallback mRequestCallback = new NetworkOfferCallback() {
+        @Override
+        public void onNetworkNeeded(@NonNull final NetworkRequest request) {
+            handleAddRequest(request);
+        }
+
+        @Override
+        public void onNetworkUnneeded(@NonNull final NetworkRequest request) {
+            handleRemoveRequest(request);
+        }
+    };
+    @NonNull private final Executor mExecutor = command -> post(command);
+
+
+    // Ideally the filter argument would be non-null, but null has historically meant to see
+    // no requests and telephony passes null.
+    NetworkFactoryImpl(NetworkFactory parent, Looper looper, Context context,
+            @Nullable final NetworkCapabilities filter) {
+        super(parent, looper, context,
+                null != filter ? filter :
+                        NetworkCapabilities.Builder.withoutDefaultCapabilities().build());
+    }
+
+    /* Registers this NetworkFactory with the system. May only be called once per factory. */
+    @Override public void register(final String logTag) {
+        register(logTag, false);
+    }
+
+    /**
+     * Registers this NetworkFactory with the system ignoring the score filter. This will let
+     * the factory always see all network requests matching its capabilities filter.
+     * May only be called once per factory.
+     */
+    @Override public void registerIgnoringScore(final String logTag) {
+        register(logTag, true);
+    }
+
+    private void register(final String logTag, final boolean listenToAllRequests) {
+        if (mProvider != null) {
+            throw new IllegalStateException("A NetworkFactory must only be registered once");
+        }
+        if (DBG) mParent.log("Registering NetworkFactory");
+
+        mProvider = new NetworkProvider(mContext, NetworkFactoryImpl.this.getLooper(), logTag) {
+            @Override
+            public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+                    int servingProviderId) {
+                handleAddRequest(request);
+            }
+
+            @Override
+            public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+                handleRemoveRequest(request);
+            }
+        };
+
+        ((ConnectivityManager) mContext.getSystemService(
+                Context.CONNECTIVITY_SERVICE)).registerNetworkProvider(mProvider);
+
+        // The mScore and mCapabilityFilter members can only be accessed on the handler thread.
+        // TODO : offer a separate API to listen to all requests instead
+        if (listenToAllRequests) {
+            sendMessage(obtainMessage(CMD_LISTEN_TO_ALL_REQUESTS));
+        } else {
+            sendMessage(obtainMessage(CMD_OFFER_NETWORK));
+        }
+    }
+
+    private void handleOfferNetwork(@NonNull final NetworkScore score) {
+        mProvider.registerNetworkOffer(score, mCapabilityFilter, mExecutor, mRequestCallback);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case CMD_REQUEST_NETWORK: {
+                handleAddRequest((NetworkRequest) msg.obj);
+                break;
+            }
+            case CMD_CANCEL_REQUEST: {
+                handleRemoveRequest((NetworkRequest) msg.obj);
+                break;
+            }
+            case CMD_SET_SCORE: {
+                handleSetScore((NetworkScore) msg.obj);
+                break;
+            }
+            case CMD_SET_FILTER: {
+                handleSetFilter((NetworkCapabilities) msg.obj);
+                break;
+            }
+            case CMD_OFFER_NETWORK: {
+                handleOfferNetwork(mScore);
+                break;
+            }
+            case CMD_LISTEN_TO_ALL_REQUESTS: {
+                handleOfferNetwork(INVINCIBLE_SCORE);
+                break;
+            }
+        }
+    }
+
+    private static class NetworkRequestInfo {
+        @NonNull public final NetworkRequest request;
+        public boolean requested; // do we have a request outstanding, limited by score
+
+        NetworkRequestInfo(@NonNull final NetworkRequest request) {
+            this.request = request;
+            this.requested = false;
+        }
+
+        @Override
+        public String toString() {
+            return "{" + request + ", requested=" + requested + "}";
+        }
+    }
+
+    /**
+     * Add a NetworkRequest that the bearer may want to attempt to satisfy.
+     * @see #CMD_REQUEST_NETWORK
+     *
+     * @param request the request to handle.
+     */
+    private void handleAddRequest(@NonNull final NetworkRequest request) {
+        NetworkRequestInfo n = mNetworkRequests.get(request);
+        if (n == null) {
+            if (DBG) mParent.log("got request " + request);
+            n = new NetworkRequestInfo(request);
+            mNetworkRequests.put(n.request, n);
+        } else {
+            if (VDBG) mParent.log("handle existing request " + request);
+        }
+        if (VDBG) mParent.log("  my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+        if (mParent.acceptRequest(request)) {
+            n.requested = true;
+            mParent.needNetworkFor(request);
+        }
+    }
+
+    private void handleRemoveRequest(NetworkRequest request) {
+        NetworkRequestInfo n = mNetworkRequests.get(request);
+        if (n != null) {
+            mNetworkRequests.remove(request);
+            if (n.requested) mParent.releaseNetworkFor(n.request);
+        }
+    }
+
+    private void handleSetScore(@NonNull final NetworkScore score) {
+        if (mScore.equals(score)) return;
+        mScore = score;
+        mParent.reevaluateAllRequests();
+    }
+
+    private void handleSetFilter(@NonNull final NetworkCapabilities netCap) {
+        if (netCap.equals(mCapabilityFilter)) return;
+        mCapabilityFilter = netCap;
+        mParent.reevaluateAllRequests();
+    }
+
+    @Override public final void reevaluateAllRequests() {
+        if (mProvider == null) return;
+        mProvider.registerNetworkOffer(mScore, mCapabilityFilter, mExecutor, mRequestCallback);
+    }
+
+    /**
+     * @deprecated this method was never part of the API (system or public) and is only added
+     *   for migration of existing clients.
+     */
+    @Deprecated
+    public void setScoreFilter(final int score) {
+        setScoreFilter(new NetworkScore.Builder().setLegacyInt(score).build());
+    }
+
+    /**
+     * Set a score filter for this factory.
+     *
+     * This should include the transports the factory knows its networks will have, and
+     * an optimistic view of the attributes it may have. This does not commit the factory
+     * to being able to bring up such a network ; it only lets it avoid hearing about
+     * requests that it has no chance of fulfilling.
+     *
+     * @param score the filter
+     */
+    @Override public void setScoreFilter(@NonNull final NetworkScore score) {
+        sendMessage(obtainMessage(CMD_SET_SCORE, score));
+    }
+
+    @Override public void setCapabilityFilter(NetworkCapabilities netCap) {
+        sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+    }
+
+    @Override public int getRequestCount() {
+        return mNetworkRequests.size();
+    }
+
+    @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println(toString());
+        for (NetworkRequestInfo n : mNetworkRequests.values()) {
+            writer.println("  " + n);
+        }
+    }
+
+    @Override public String toString() {
+        return "providerId=" + (mProvider != null ? mProvider.getProviderId() : "null")
+                + ", ScoreFilter=" + mScore + ", Filter=" + mCapabilityFilter
+                + ", requests=" + mNetworkRequests.size();
+    }
+}
diff --git a/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java b/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java
new file mode 100644
index 0000000..6cba625
--- /dev/null
+++ b/staticlibs/device/android/net/NetworkFactoryLegacyImpl.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests.  NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function.  All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden.  If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+// TODO(b/187083878): factor out common code between this and NetworkFactoryImpl
+class NetworkFactoryLegacyImpl extends Handler
+        implements NetworkFactoryShim {
+    private static final boolean DBG = NetworkFactory.DBG;
+    private static final boolean VDBG = NetworkFactory.VDBG;
+
+    // TODO(b/187082970): Replace CMD_* with Handler.post(() -> { ... }) since all the CMDs do is to
+    //  run the tasks asynchronously on the Handler thread.
+
+    /**
+     * Pass a network request to the bearer.  If the bearer believes it can
+     * satisfy the request it should connect to the network and create a
+     * NetworkAgent.  Once the NetworkAgent is fully functional it will
+     * register itself with ConnectivityService using registerNetworkAgent.
+     * If the bearer cannot immediately satisfy the request (no network,
+     * user disabled the radio, lower-scored network) it should remember
+     * any NetworkRequests it may be able to satisfy in the future.  It may
+     * disregard any that it will never be able to service, for example
+     * those requiring a different bearer.
+     * msg.obj = NetworkRequest
+     * msg.arg1 = score - the score of the network currently satisfying this
+     *            request.  If this bearer knows in advance it cannot
+     *            exceed this score it should not try to connect, holding the request
+     *            for the future.
+     *            Note that subsequent events may give a different (lower
+     *            or higher) score for this request, transmitted to each
+     *            NetworkFactory through additional CMD_REQUEST_NETWORK msgs
+     *            with the same NetworkRequest but an updated score.
+     *            Also, network conditions may change for this bearer
+     *            allowing for a better score in the future.
+     * msg.arg2 = the ID of the NetworkProvider currently responsible for the
+     *            NetworkAgent handling this request, or NetworkProvider.ID_NONE if none.
+     */
+    public static final int CMD_REQUEST_NETWORK = 1;
+
+    /**
+     * Cancel a network request
+     * msg.obj = NetworkRequest
+     */
+    public static final int CMD_CANCEL_REQUEST = 2;
+
+    /**
+     * Internally used to set our best-guess score.
+     * msg.arg1 = new score
+     */
+    private static final int CMD_SET_SCORE = 3;
+
+    /**
+     * Internally used to set our current filter for coarse bandwidth changes with
+     * technology changes.
+     * msg.obj = new filter
+     */
+    private static final int CMD_SET_FILTER = 4;
+
+    final Context mContext;
+    final NetworkFactory mParent;
+
+    private final Map<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
+            new LinkedHashMap<>();
+
+    private int mScore;
+    NetworkCapabilities mCapabilityFilter;
+
+    NetworkProvider mProvider = null;
+
+    NetworkFactoryLegacyImpl(NetworkFactory parent, Looper looper, Context context,
+            NetworkCapabilities filter) {
+        super(looper);
+        mParent = parent;
+        mContext = context;
+        mCapabilityFilter = filter;
+    }
+
+    /* Registers this NetworkFactory with the system. May only be called once per factory. */
+    @Override public void register(final String logTag) {
+        if (mProvider != null) {
+            throw new IllegalStateException("A NetworkFactory must only be registered once");
+        }
+        if (DBG) mParent.log("Registering NetworkFactory");
+
+        mProvider = new NetworkProvider(mContext, NetworkFactoryLegacyImpl.this.getLooper(),
+                logTag) {
+            @Override
+            public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+                    int servingProviderId) {
+                handleAddRequest(request, score, servingProviderId);
+            }
+
+            @Override
+            public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+                handleRemoveRequest(request);
+            }
+        };
+
+        ((ConnectivityManager) mContext.getSystemService(
+            Context.CONNECTIVITY_SERVICE)).registerNetworkProvider(mProvider);
+    }
+
+    /** Unregisters this NetworkFactory. After this call, the object can no longer be used. */
+    @Override public void terminate() {
+        if (mProvider == null) {
+            throw new IllegalStateException("This NetworkFactory was never registered");
+        }
+        if (DBG) mParent.log("Unregistering NetworkFactory");
+
+        ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+            .unregisterNetworkProvider(mProvider);
+
+        // Remove all pending messages, since this object cannot be reused. Any message currently
+        // being processed will continue to run.
+        removeCallbacksAndMessages(null);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case CMD_REQUEST_NETWORK: {
+                handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case CMD_CANCEL_REQUEST: {
+                handleRemoveRequest((NetworkRequest) msg.obj);
+                break;
+            }
+            case CMD_SET_SCORE: {
+                handleSetScore(msg.arg1);
+                break;
+            }
+            case CMD_SET_FILTER: {
+                handleSetFilter((NetworkCapabilities) msg.obj);
+                break;
+            }
+        }
+    }
+
+    private static class NetworkRequestInfo {
+        public final NetworkRequest request;
+        public int score;
+        public boolean requested; // do we have a request outstanding, limited by score
+        public int providerId;
+
+        NetworkRequestInfo(NetworkRequest request, int score, int providerId) {
+            this.request = request;
+            this.score = score;
+            this.requested = false;
+            this.providerId = providerId;
+        }
+
+        @Override
+        public String toString() {
+            return "{" + request + ", score=" + score + ", requested=" + requested + "}";
+        }
+    }
+
+    /**
+     * Add a NetworkRequest that the bearer may want to attempt to satisfy.
+     * @see #CMD_REQUEST_NETWORK
+     *
+     * @param request the request to handle.
+     * @param score the score of the NetworkAgent currently satisfying this request.
+     * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent
+     *        currently satisfying this request.
+     */
+    @VisibleForTesting
+    protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) {
+        NetworkRequestInfo n = mNetworkRequests.get(request);
+        if (n == null) {
+            if (DBG) {
+                mParent.log("got request " + request + " with score " + score
+                        + " and providerId " + servingProviderId);
+            }
+            n = new NetworkRequestInfo(request, score, servingProviderId);
+            mNetworkRequests.put(n.request, n);
+        } else {
+            if (VDBG) {
+                mParent.log("new score " + score + " for existing request " + request
+                        + " and providerId " + servingProviderId);
+            }
+            n.score = score;
+            n.providerId = servingProviderId;
+        }
+        if (VDBG) mParent.log("  my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+        evalRequest(n);
+    }
+
+    private void handleRemoveRequest(NetworkRequest request) {
+        NetworkRequestInfo n = mNetworkRequests.get(request);
+        if (n != null) {
+            mNetworkRequests.remove(request);
+            if (n.requested) mParent.releaseNetworkFor(n.request);
+        }
+    }
+
+    private void handleSetScore(int score) {
+        mScore = score;
+        evalRequests();
+    }
+
+    private void handleSetFilter(NetworkCapabilities netCap) {
+        mCapabilityFilter = netCap;
+        evalRequests();
+    }
+
+    /**
+     * Overridable function to provide complex filtering.
+     * Called for every request every time a new NetworkRequest is seen
+     * and whenever the filterScore or filterNetworkCapabilities change.
+     *
+     * acceptRequest can be overridden to provide complex filter behavior
+     * for the incoming requests
+     *
+     * For output, this class will call {@link NetworkFactory#needNetworkFor} and
+     * {@link NetworkFactory#releaseNetworkFor} for every request that passes the filters.
+     * If you don't need to see every request, you can leave the base
+     * implementations of those two functions and instead override
+     * {@link NetworkFactory#startNetwork} and {@link NetworkFactory#stopNetwork}.
+     *
+     * If you want to see every score fluctuation on every request, set
+     * your score filter to a very high number and watch {@link NetworkFactory#needNetworkFor}.
+     *
+     * @return {@code true} to accept the request.
+     */
+    public boolean acceptRequest(NetworkRequest request) {
+        return mParent.acceptRequest(request);
+    }
+
+    private void evalRequest(NetworkRequestInfo n) {
+        if (VDBG) {
+            mParent.log("evalRequest");
+            mParent.log(" n.requests = " + n.requested);
+            mParent.log(" n.score = " + n.score);
+            mParent.log(" mScore = " + mScore);
+            mParent.log(" request.providerId = " + n.providerId);
+            mParent.log(" mProvider.id = " + mProvider.getProviderId());
+        }
+        if (shouldNeedNetworkFor(n)) {
+            if (VDBG) mParent.log("  needNetworkFor");
+            mParent.needNetworkFor(n.request);
+            n.requested = true;
+        } else if (shouldReleaseNetworkFor(n)) {
+            if (VDBG) mParent.log("  releaseNetworkFor");
+            mParent.releaseNetworkFor(n.request);
+            n.requested = false;
+        } else {
+            if (VDBG) mParent.log("  done");
+        }
+    }
+
+    private boolean shouldNeedNetworkFor(NetworkRequestInfo n) {
+        // If this request is already tracked, it doesn't qualify for need
+        return !n.requested
+            // If the score of this request is higher or equal to that of this factory and some
+            // other factory is responsible for it, then this factory should not track the request
+            // because it has no hope of satisfying it.
+            && (n.score < mScore || n.providerId == mProvider.getProviderId())
+            // If this factory can't satisfy the capability needs of this request, then it
+            // should not be tracked.
+            && n.request.canBeSatisfiedBy(mCapabilityFilter)
+            // Finally if the concrete implementation of the factory rejects the request, then
+            // don't track it.
+            && acceptRequest(n.request);
+    }
+
+    private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) {
+        // Don't release a request that's not tracked.
+        return n.requested
+            // The request should be released if it can't be satisfied by this factory. That
+            // means either of the following conditions are met :
+            // - Its score is too high to be satisfied by this factory and it's not already
+            //   assigned to the factory
+            // - This factory can't satisfy the capability needs of the request
+            // - The concrete implementation of the factory rejects the request
+            && ((n.score > mScore && n.providerId != mProvider.getProviderId())
+                    || !n.request.canBeSatisfiedBy(mCapabilityFilter)
+                    || !acceptRequest(n.request));
+    }
+
+    private void evalRequests() {
+        for (NetworkRequestInfo n : mNetworkRequests.values()) {
+            evalRequest(n);
+        }
+    }
+
+    /**
+     * Post a command, on this NetworkFactory Handler, to re-evaluate all
+     * outstanding requests. Can be called from a factory implementation.
+     */
+    @Override public void reevaluateAllRequests() {
+        post(this::evalRequests);
+    }
+
+    /**
+     * Can be called by a factory to release a request as unfulfillable: the request will be
+     * removed, and the caller will get a
+     * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
+     * returns.
+     *
+     * Note: this should only be called by factory which KNOWS that it is the ONLY factory which
+     * is able to fulfill this request!
+     */
+    @Override public void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
+        post(() -> {
+            if (DBG) mParent.log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
+            final NetworkProvider provider = mProvider;
+            if (provider == null) {
+                mParent.log("Ignoring attempt to release unregistered request as unfulfillable");
+                return;
+            }
+            provider.declareNetworkRequestUnfulfillable(r);
+        });
+    }
+
+    @Override public void setScoreFilter(int score) {
+        sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
+    }
+
+    @Override public void setScoreFilter(@NonNull final NetworkScore score) {
+        setScoreFilter(score.getLegacyInt());
+    }
+
+    @Override public void setCapabilityFilter(NetworkCapabilities netCap) {
+        sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+    }
+
+    @Override public int getRequestCount() {
+        return mNetworkRequests.size();
+    }
+
+    /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
+    @Override public int getSerialNumber() {
+        return mProvider.getProviderId();
+    }
+
+    @Override public NetworkProvider getProvider() {
+        return mProvider;
+    }
+
+    @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println(toString());
+        for (NetworkRequestInfo n : mNetworkRequests.values()) {
+            writer.println("  " + n);
+        }
+    }
+
+    @Override public String toString() {
+        return "providerId=" + (mProvider != null ? mProvider.getProviderId() : "null")
+                + ", ScoreFilter=" + mScore + ", Filter=" + mCapabilityFilter
+                + ", requests=" + mNetworkRequests.size();
+    }
+}
diff --git a/staticlibs/device/android/net/NetworkFactoryShim.java b/staticlibs/device/android/net/NetworkFactoryShim.java
new file mode 100644
index 0000000..dfbb5ec
--- /dev/null
+++ b/staticlibs/device/android/net/NetworkFactoryShim.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Extract an interface for multiple implementation of {@link NetworkFactory}, depending on the SDK
+ * version.
+ *
+ * Known implementations:
+ * - {@link NetworkFactoryImpl}: For Android S+
+ * - {@link NetworkFactoryLegacyImpl}: For Android R-
+ *
+ * @hide
+ */
+interface NetworkFactoryShim {
+    void register(String logTag);
+
+    default void registerIgnoringScore(String logTag) {
+        throw new UnsupportedOperationException();
+    }
+
+    void terminate();
+
+    void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r);
+
+    void reevaluateAllRequests();
+
+    void setScoreFilter(int score);
+
+    void setScoreFilter(@NonNull NetworkScore score);
+
+    void setCapabilityFilter(NetworkCapabilities netCap);
+
+    int getRequestCount();
+
+    int getSerialNumber();
+
+    NetworkProvider getProvider();
+
+    void dump(FileDescriptor fd, PrintWriter writer, String[] args);
+
+    // All impls inherit Handler
+    @VisibleForTesting
+    Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj);
+
+    Looper getLooper();
+}
diff --git a/staticlibs/device/com/android/net/module/util/BpfBitmap.java b/staticlibs/device/com/android/net/module/util/BpfBitmap.java
new file mode 100644
index 0000000..d2a5b65
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/BpfBitmap.java
@@ -0,0 +1,126 @@
+/*
+ * 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.net.module.util;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+
+ /**
+ *
+ * Generic bitmap class for use with BPF programs. Corresponds to a BpfMap
+ * array type with key->int and value->uint64_t defined in the bpf program.
+ *
+ */
+public class BpfBitmap {
+    private BpfMap<Struct.S32, Struct.S64> mBpfMap;
+
+    /**
+     * Create a BpfBitmap map wrapper with "path" of filesystem.
+     *
+     * @param path The path of the BPF map.
+     */
+    public BpfBitmap(@NonNull String path) throws ErrnoException {
+        mBpfMap = new BpfMap<Struct.S32, Struct.S64>(path, BpfMap.BPF_F_RDWR,
+                Struct.S32.class, Struct.S64.class);
+    }
+
+    /**
+     * Retrieves the value from BpfMap for the given key.
+     *
+     * @param key The key in the map corresponding to the value to return.
+     */
+    private long getBpfMapValue(Struct.S32 key) throws ErrnoException  {
+        Struct.S64 curVal = mBpfMap.getValue(key);
+        if (curVal != null) {
+            return curVal.val;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Retrieves the bit for the given index in the bitmap.
+     *
+     * @param index Position in bitmap.
+     */
+    public boolean get(int index) throws ErrnoException  {
+        if (index < 0) return false;
+
+        Struct.S32 key = new Struct.S32(index >> 6);
+        return ((getBpfMapValue(key) >>> (index & 63)) & 1L) != 0;
+    }
+
+    /**
+     * Set the specified index in the bitmap.
+     *
+     * @param index Position to set in bitmap.
+     */
+    public void set(int index) throws ErrnoException {
+        set(index, true);
+    }
+
+    /**
+     * Unset the specified index in the bitmap.
+     *
+     * @param index Position to unset in bitmap.
+     */
+    public void unset(int index) throws ErrnoException {
+        set(index, false);
+    }
+
+    /**
+     * Change the specified index in the bitmap to set value.
+     *
+     * @param index Position to unset in bitmap.
+     * @param set Boolean indicating to set or unset index.
+     */
+    public void set(int index, boolean set) throws ErrnoException {
+        if (index < 0) throw new IllegalArgumentException("Index out of bounds.");
+
+        Struct.S32 key = new Struct.S32(index >> 6);
+        long mask = (1L << (index & 63));
+        long val = getBpfMapValue(key);
+        if (set) val |= mask; else val &= ~mask;
+        mBpfMap.updateEntry(key, new Struct.S64(val));
+    }
+
+    /**
+     * Clears the map. The map may already be empty.
+     *
+     * @throws ErrnoException if updating entry to 0 fails.
+     */
+    public void clear() throws ErrnoException {
+        mBpfMap.forEach((key, value) -> {
+            mBpfMap.updateEntry(key, new Struct.S64(0));
+        });
+    }
+
+    /**
+     * Checks if all bitmap values are 0.
+     */
+    public boolean isEmpty() throws ErrnoException {
+        Struct.S32 key = mBpfMap.getFirstKey();
+        while (key != null) {
+            if (getBpfMapValue(key) != 0) {
+                return false;
+            }
+            key = mBpfMap.getNextKey(key);
+        }
+        return true;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
new file mode 100644
index 0000000..7549e71
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -0,0 +1,136 @@
+/*
+ * 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.net.module.util;
+
+import static android.system.OsConstants.R_OK;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Base64;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.function.BiFunction;
+
+/**
+ * The classes and the methods for BPF dump utilization.
+ */
+public class BpfDump {
+    // Using "," as a separator between base64 encoded key and value is safe because base64
+    // characters are [0-9a-zA-Z/=+].
+    private static final String BASE64_DELIMITER = ",";
+
+    /**
+     * Encode BPF key and value into a base64 format string which uses the delimiter ',':
+     * <base64 encoded key>,<base64 encoded value>
+     */
+    public static <K extends Struct, V extends Struct> String toBase64EncodedString(
+            @NonNull final K key, @NonNull final V value) {
+        final byte[] keyBytes = key.writeToBytes();
+        final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
+                .replace("\n", "");
+        final byte[] valueBytes = value.writeToBytes();
+        final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
+                .replace("\n", "");
+
+        return keyBase64Str + BASE64_DELIMITER + valueBase64Str;
+    }
+
+    /**
+     * Decode Struct from a base64 format string
+     */
+    private static <T extends Struct> T parseStruct(
+            Class<T> structClass, @NonNull String base64Str) {
+        final byte[] bytes = Base64.decode(base64Str, Base64.DEFAULT);
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        return Struct.parse(structClass, byteBuffer);
+    }
+
+    /**
+     * Decode BPF key and value from a base64 format string which uses the delimiter ',':
+     * <base64 encoded key>,<base64 encoded value>
+     */
+    public static <K extends Struct, V extends Struct> Pair<K, V> fromBase64EncodedString(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String base64Str) {
+        String[] keyValueStrs = base64Str.split(BASE64_DELIMITER);
+        if (keyValueStrs.length != 2 /* key + value */) {
+            throw new IllegalArgumentException("Invalid base64Str (" + base64Str + "), base64Str"
+                    + " must contain exactly one delimiter '" + BASE64_DELIMITER + "'");
+        }
+        final K k = parseStruct(keyClass, keyValueStrs[0]);
+        final V v = parseStruct(valueClass, keyValueStrs[1]);
+        return new Pair<>(k, v);
+    }
+
+    /**
+     * Dump the BpfMap name and entries
+     */
+    public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+            PrintWriter pw, String mapName, BiFunction<K, V, String> entryToString) {
+        dumpMap(map, pw, mapName, "" /* header */, entryToString);
+    }
+
+    /**
+     * Dump the BpfMap name, header, and entries
+     */
+    public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
+            PrintWriter pw, String mapName, String header, BiFunction<K, V, String> entryToString) {
+        pw.println(mapName + ":");
+        if (!header.isEmpty()) {
+            pw.println("  " + header);
+        }
+        try {
+            map.forEach((key, value) -> {
+                // Value could be null if there is a concurrent entry deletion.
+                // http://b/220084230.
+                if (value != null) {
+                    pw.println("  " + entryToString.apply(key, value));
+                } else {
+                    pw.println("Entry is deleted while dumping, iterating from first entry");
+                }
+            });
+        } catch (ErrnoException e) {
+            pw.println("Map dump end with error: " + Os.strerror(e.errno));
+        }
+    }
+
+    /**
+     * Dump the BpfMap status
+     */
+    public static <K extends Struct, V extends Struct> void dumpMapStatus(IBpfMap<K, V> map,
+            PrintWriter pw, String mapName, String path) {
+        if (map != null) {
+            pw.println(mapName + ": OK");
+            return;
+        }
+        try {
+            Os.access(path, R_OK);
+            pw.println(mapName + ": NULL(map is pinned to " + path + ")");
+        } catch (ErrnoException e) {
+            pw.println(mapName + ": NULL(map is not pinned to " + path + ": "
+                    + Os.strerror(e.errno) + ")");
+        }
+    }
+
+    // TODO: add a helper to dump bpf map content with the map name, the header line
+    // (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
+    // knows how to dump each line, and the PrintWriter.
+}
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
new file mode 100644
index 0000000..d45cace
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.system.OsConstants.EEXIST;
+import static android.system.OsConstants.ENOENT;
+
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
+ * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
+ * passing syscalls with map file descriptor.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
+    static {
+        System.loadLibrary(JniUtil.getJniLibraryName(BpfMap.class.getPackage()));
+    }
+
+    // Following definitions from kernel include/uapi/linux/bpf.h
+    public static final int BPF_F_RDWR = 0;
+    public static final int BPF_F_RDONLY = 1 << 3;
+    public static final int BPF_F_WRONLY = 1 << 4;
+
+    public static final int BPF_MAP_TYPE_HASH = 1;
+
+    private static final int BPF_F_NO_PREALLOC = 1;
+
+    private static final int BPF_ANY = 0;
+    private static final int BPF_NOEXIST = 1;
+    private static final int BPF_EXIST = 2;
+
+    private final ParcelFileDescriptor mMapFd;
+    private final Class<K> mKeyClass;
+    private final Class<V> mValueClass;
+    private final int mKeySize;
+    private final int mValueSize;
+
+    private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
+            new ConcurrentHashMap<>();
+
+    private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode,
+                                                       int keySize, int valueSize)
+            throws ErrnoException, NullPointerException {
+        // Supports up to 1023 byte key and 65535 byte values
+        // Creating a BpfMap with larger keys/values seems like a bad idea any way...
+        keySize &= 1023; // 10-bits
+        valueSize &= 65535; // 16-bits
+        var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize);
+        // unlocked fetch is safe: map is concurrent read capable, and only inserted into
+        ParcelFileDescriptor fd = sFdCache.get(key);
+        if (fd != null) return fd;
+        // ok, no cached fd present, need to grab a lock
+        synchronized (BpfMap.class) {
+            // need to redo the check
+            fd = sFdCache.get(key);
+            if (fd != null) return fd;
+            // okay, we really haven't opened this before...
+            fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize));
+            sFdCache.put(key, fd);
+            return fd;
+        }
+    }
+
+    /**
+     * Create a BpfMap map wrapper with "path" of filesystem.
+     *
+     * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY.
+     * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
+     * @throws NullPointerException if {@code path} is null.
+     */
+    public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
+            final Class<V> value) throws ErrnoException, NullPointerException {
+        mKeyClass = key;
+        mValueClass = value;
+        mKeySize = Struct.getSize(key);
+        mValueSize = Struct.getSize(value);
+        mMapFd = cachedBpfFdGet(path, flag, mKeySize, mValueSize);
+    }
+
+    /**
+     * Update an existing or create a new key -> value entry in an eBbpf map.
+     * (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
+     */
+    @Override
+    public void updateEntry(K key, V value) throws ErrnoException {
+        nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), BPF_ANY);
+    }
+
+    /**
+     * If the key does not exist in the map, insert key -> value entry into eBpf map.
+     * Otherwise IllegalStateException will be thrown.
+     */
+    @Override
+    public void insertEntry(K key, V value)
+            throws ErrnoException, IllegalStateException {
+        try {
+            nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+                    BPF_NOEXIST);
+        } catch (ErrnoException e) {
+            if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
+
+            throw e;
+        }
+    }
+
+    /**
+     * If the key already exists in the map, replace its value. Otherwise NoSuchElementException
+     * will be thrown.
+     */
+    @Override
+    public void replaceEntry(K key, V value)
+            throws ErrnoException, NoSuchElementException {
+        try {
+            nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+                    BPF_EXIST);
+        } catch (ErrnoException e) {
+            if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
+
+            throw e;
+        }
+    }
+
+    /**
+     * Update an existing or create a new key -> value entry in an eBbpf map.
+     * Returns true if inserted, false if replaced.
+     * (use updateEntry() if you don't care whether insert or replace happened)
+     * Note: see inline comment below if running concurrently with delete operations.
+     */
+    @Override
+    public boolean insertOrReplaceEntry(K key, V value)
+            throws ErrnoException {
+        try {
+            nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+                    BPF_NOEXIST);
+            return true;   /* insert succeeded */
+        } catch (ErrnoException e) {
+            if (e.errno != EEXIST) throw e;
+        }
+        try {
+            nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+                    BPF_EXIST);
+            return false;   /* replace succeeded */
+        } catch (ErrnoException e) {
+            if (e.errno != ENOENT) throw e;
+        }
+        /* If we reach here somebody deleted after our insert attempt and before our replace:
+         * this implies a race happened.  The kernel bpf delete interface only takes a key,
+         * and not the value, so we can safely pretend the replace actually succeeded and
+         * was immediately followed by the other thread's delete, since the delete cannot
+         * observe the potential change to the value.
+         */
+        return false;   /* pretend replace succeeded */
+    }
+
+    /** Remove existing key from eBpf map. Return false if map was not modified. */
+    @Override
+    public boolean deleteEntry(K key) throws ErrnoException {
+        return nativeDeleteMapEntry(mMapFd.getFd(), key.writeToBytes());
+    }
+
+    /** Returns {@code true} if this map contains no elements. */
+    @Override
+    public boolean isEmpty() throws ErrnoException {
+        return getFirstKey() == null;
+    }
+
+    private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
+        byte[] rawKey = new byte[mKeySize];
+
+        if (!nativeGetNextMapKey(mMapFd.getFd(),
+                                 key == null ? null : key.writeToBytes(),
+                                 rawKey)) return null;
+
+        final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
+        buffer.order(ByteOrder.nativeOrder());
+        return Struct.parse(mKeyClass, buffer);
+    }
+
+    /**
+     * Get the next key of the passed-in key. If the passed-in key is not found, return the first
+     * key. If the passed-in key is the last one, return null.
+     *
+     * TODO: consider allowing null passed-in key.
+     */
+    @Override
+    public K getNextKey(@NonNull K key) throws ErrnoException {
+        Objects.requireNonNull(key);
+        return getNextKeyInternal(key);
+    }
+
+    /** Get the first key of eBpf map. */
+    @Override
+    public K getFirstKey() throws ErrnoException {
+        return getNextKeyInternal(null);
+    }
+
+    /** Check whether a key exists in the map. */
+    @Override
+    public boolean containsKey(@NonNull K key) throws ErrnoException {
+        Objects.requireNonNull(key);
+
+        byte[] rawValue = new byte[mValueSize];
+        return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue);
+    }
+
+    /** Retrieve a value from the map. Return null if there is no such key. */
+    @Override
+    public V getValue(@NonNull K key) throws ErrnoException {
+        Objects.requireNonNull(key);
+
+        byte[] rawValue = new byte[mValueSize];
+        if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null;
+
+        final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
+        buffer.order(ByteOrder.nativeOrder());
+        return Struct.parse(mValueClass, buffer);
+    }
+
+    /**
+     * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
+     * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
+     * other structural modifications to the map, such as adding entries or deleting other entries.
+     * Otherwise, iteration will result in undefined behaviour.
+     */
+    @Override
+    public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException {
+        @Nullable K nextKey = getFirstKey();
+
+        while (nextKey != null) {
+            @NonNull final K curKey = nextKey;
+            @NonNull final V value = getValue(curKey);
+
+            nextKey = getNextKey(curKey);
+            action.accept(curKey, value);
+        }
+    }
+
+    /* Empty implementation to implement AutoCloseable, so we can use BpfMaps
+     * with try with resources, but due to persistent FD cache, there is no actual
+     * need to close anything.  File descriptors will actually be closed when we
+     * unlock the BpfMap class and destroy the ParcelFileDescriptor objects.
+     */
+    @Override
+    public void close() throws IOException {
+    }
+
+    /**
+     * Clears the map. The map may already be empty.
+     *
+     * @throws ErrnoException if the map is already closed, if an error occurred during iteration,
+     *                        or if a non-ENOENT error occurred when deleting a key.
+     */
+    @Override
+    public void clear() throws ErrnoException {
+        K key = getFirstKey();
+        while (key != null) {
+            deleteEntry(key);  // ignores ENOENT.
+            key = getFirstKey();
+        }
+    }
+
+    private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize)
+            throws ErrnoException, NullPointerException;
+
+    // Note: the following methods appear to not require the object by virtue of taking the
+    // fd as an int argument, but the hidden reference to this is actually what prevents
+    // the object from being garbage collected (and thus potentially maps closed) prior
+    // to the native code actually running (with a possibly already closed fd).
+
+    private native void nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags)
+            throws ErrnoException;
+
+    private native boolean nativeDeleteMapEntry(int fd, byte[] key) throws ErrnoException;
+
+    // If key is found, the operation returns true and the nextKey would reference to the next
+    // element.  If key is not found, the operation returns true and the nextKey would reference to
+    // the first element.  If key is the last element, false is returned.
+    private native boolean nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey)
+            throws ErrnoException;
+
+    private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
+            throws ErrnoException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
new file mode 100644
index 0000000..94af11b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.net.module.util;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+/**
+ * The classes and the methods for BPF utilization.
+ *
+ * {@hide}
+ */
+public class BpfUtils {
+    static {
+        System.loadLibrary(JniUtil.getJniLibraryName(BpfUtils.class.getPackage()));
+    }
+
+    // Defined in include/uapi/linux/bpf.h. Only adding the CGROUPS currently being used for now.
+    public static final int BPF_CGROUP_INET_INGRESS = 0;
+    public static final int BPF_CGROUP_INET_EGRESS = 1;
+    public static final int BPF_CGROUP_INET4_BIND = 8;
+    public static final int BPF_CGROUP_INET6_BIND = 9;
+
+
+    /**
+     * Attach BPF program to CGROUP
+     */
+    public static void attachProgram(int type, @NonNull String programPath,
+            @NonNull String cgroupPath, int flags) throws IOException {
+        native_attachProgramToCgroup(type, programPath, cgroupPath, flags);
+    }
+
+    /**
+     * Detach BPF program from CGROUP
+     */
+    public static void detachProgram(int type, @NonNull String cgroupPath)
+            throws IOException {
+        native_detachProgramFromCgroup(type, cgroupPath);
+    }
+
+    /**
+     * Detach single BPF program from CGROUP
+     */
+    public static void detachSingleProgram(int type, @NonNull String programPath,
+            @NonNull String cgroupPath) throws IOException {
+        native_detachSingleProgramFromCgroup(type, programPath, cgroupPath);
+    }
+
+    private static native boolean native_attachProgramToCgroup(int type, String programPath,
+            String cgroupPath, int flags) throws IOException;
+    private static native boolean native_detachProgramFromCgroup(int type, String cgroupPath)
+            throws IOException;
+    private static native boolean native_detachSingleProgramFromCgroup(int type,
+            String programPath, String cgroupPath) throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
new file mode 100644
index 0000000..e646f37
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.MODULE_MASK;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.VERSION_MASK;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.annotation.BoolRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities for modules to query {@link DeviceConfig} and flags.
+ */
+public final class DeviceConfigUtils {
+    private DeviceConfigUtils() {}
+
+    private static final String TAG = DeviceConfigUtils.class.getSimpleName();
+    /**
+     * DO NOT MODIFY: this may be used by multiple modules that will not see the updated value
+     * until they are recompiled, so modifying this constant means that different modules may
+     * be referencing a different tethering module variant, or having a stale reference.
+     */
+    public static final String TETHERING_MODULE_NAME = "com.android.tethering";
+
+    @VisibleForTesting
+    public static final String RESOURCES_APK_INTENT =
+            "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
+    private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/";
+
+    @VisibleForTesting
+    public static final long DEFAULT_PACKAGE_VERSION = 1000;
+
+    @VisibleForTesting
+    public static void resetPackageVersionCacheForTest() {
+        sPackageVersion = -1;
+        sModuleVersion = -1;
+        sNetworkStackModuleVersion = -1;
+    }
+
+    private static volatile long sPackageVersion = -1;
+    private static long getPackageVersion(@NonNull final Context context) {
+        // sPackageVersion may be set by another thread just after this check, but querying the
+        // package version several times on rare occasions is fine.
+        if (sPackageVersion >= 0) {
+            return sPackageVersion;
+        }
+        try {
+            final long version = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0).getLongVersionCode();
+            sPackageVersion = version;
+            return version;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Failed to get package info: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+    }
+
+    /**
+     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @param defaultValue The value to return if the property does not exist or has no valid value.
+     * @return the corresponding value, or defaultValue if none exists.
+     */
+    @Nullable
+    public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name,
+            @Nullable String defaultValue) {
+        String value = DeviceConfig.getProperty(namespace, name);
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @param defaultValue The value to return if the property does not exist or its value is null.
+     * @return the corresponding value, or defaultValue if none exists.
+     */
+    public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
+            int defaultValue) {
+        String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */);
+        try {
+            return (value != null) ? Integer.parseInt(value) : defaultValue;
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
+     *
+     * Flags like timeouts should use this method and set an appropriate min/max range: if invalid
+     * values like "0" or "1" are pushed to devices, everything would timeout. The min/max range
+     * protects against this kind of breakage.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @param minimumValue The minimum value of a property.
+     * @param maximumValue The maximum value of a property.
+     * @param defaultValue The value to return if the property does not exist or its value is null.
+     * @return the corresponding value, or defaultValue if none exists or the fetched value is
+     *         not in the provided range.
+     */
+    public static int getDeviceConfigPropertyInt(@NonNull String namespace, @NonNull String name,
+            int minimumValue, int maximumValue, int defaultValue) {
+        int value = getDeviceConfigPropertyInt(namespace, name, defaultValue);
+        if (value < minimumValue || value > maximumValue) return defaultValue;
+        return value;
+    }
+
+    /**
+     * Look up the value of a property for a particular namespace from {@link DeviceConfig}.
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @param defaultValue The value to return if the property does not exist or its value is null.
+     * @return the corresponding value, or defaultValue if none exists.
+     */
+    public static boolean getDeviceConfigPropertyBoolean(@NonNull String namespace,
+            @NonNull String name, boolean defaultValue) {
+        String value = getDeviceConfigProperty(namespace, name, null /* defaultValue */);
+        return (value != null) ? Boolean.parseBoolean(value) : defaultValue;
+    }
+
+    /**
+     * Check whether or not one specific experimental feature for a particular namespace from
+     * {@link DeviceConfig} is enabled by comparing module package version
+     * with current version of property. If this property version is valid, the corresponding
+     * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
+     * @param context The global context information about an app environment.
+     * @param name The name of the property to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isNetworkStackFeatureEnabled(@NonNull Context context,
+            @NonNull String name) {
+        return isNetworkStackFeatureEnabled(context, name, false /* defaultEnabled */);
+    }
+
+    /**
+     * Check whether or not one specific experimental feature for a particular namespace from
+     * {@link DeviceConfig} is enabled by comparing module package version
+     * with current version of property. If this property version is valid, the corresponding
+     * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
+     * @param context The global context information about an app environment.
+     * @param name The name of the property to look up.
+     * @param defaultEnabled The value to return if the property does not exist or its value is
+     *                       null.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isNetworkStackFeatureEnabled(@NonNull Context context,
+            @NonNull String name, boolean defaultEnabled) {
+        final long packageVersion = getPackageVersion(context);
+        return isFeatureEnabled(context, packageVersion, NAMESPACE_CONNECTIVITY, name,
+                defaultEnabled);
+    }
+
+    /**
+     * Check whether or not one specific experimental feature for a particular namespace from
+     * {@link DeviceConfig} is enabled by comparing module package version
+     * with current version of property. If this property version is valid, the corresponding
+     * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
+     *
+     * If the feature is disabled by default and enabled by flag push, this method should be used.
+     * If the feature is enabled by default and disabled by flag push (kill switch),
+     * {@link #isTetheringFeatureNotChickenedOut(String)} should be used.
+     *
+     * @param context The global context information about an app environment.
+     * @param name The name of the property to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isTetheringFeatureEnabled(@NonNull Context context,
+            @NonNull String name) {
+        final long packageVersion = getTetheringModuleVersion(context);
+        return isFeatureEnabled(context, packageVersion, NAMESPACE_TETHERING, name,
+                false /* defaultEnabled */);
+    }
+
+    private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion,
+            @NonNull String namespace, String name, boolean defaultEnabled) {
+        final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
+                0 /* default value */);
+        return (propertyVersion == 0 && defaultEnabled)
+                || (propertyVersion != 0 && packageVersion >= (long) propertyVersion);
+    }
+
+    // Guess the tethering module name based on the package prefix of the connectivity resources
+    // Take the resource package name, cut it before "connectivity" and append "tethering".
+    // Then resolve that package version number with packageManager.
+    // If that fails retry by appending "go.tethering" instead
+    private static long resolveTetheringModuleVersion(@NonNull Context context)
+            throws PackageManager.NameNotFoundException {
+        final String pkgPrefix = resolvePkgPrefix(context);
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            return packageManager.getPackageInfo(pkgPrefix + "tethering",
+                    PackageManager.MATCH_APEX).getLongVersionCode();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(TAG, "Device is using go modules");
+            // fall through
+        }
+
+        return packageManager.getPackageInfo(pkgPrefix + "go.tethering",
+                PackageManager.MATCH_APEX).getLongVersionCode();
+    }
+
+    private static String resolvePkgPrefix(Context context) {
+        final String connResourcesPackage = getConnectivityResourcesPackageName(context);
+        final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
+        if (pkgPrefixLen < 0) {
+            throw new IllegalStateException(
+                    "Invalid connectivity resources package: " + connResourcesPackage);
+        }
+
+        return connResourcesPackage.substring(0, pkgPrefixLen);
+    }
+
+    private static volatile long sModuleVersion = -1;
+    private static long getTetheringModuleVersion(@NonNull Context context) {
+        if (sModuleVersion >= 0) return sModuleVersion;
+
+        try {
+            sModuleVersion = resolveTetheringModuleVersion(context);
+        } catch (PackageManager.NameNotFoundException e) {
+            // It's expected to fail tethering module version resolution on the devices with
+            // flattened apex
+            Log.e(TAG, "Failed to resolve tethering module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+        return sModuleVersion;
+    }
+
+    private static volatile long sNetworkStackModuleVersion = -1;
+
+    /**
+     * Get networkstack module version.
+     */
+    @VisibleForTesting
+    static long getNetworkStackModuleVersion(@NonNull Context context) {
+        if (sNetworkStackModuleVersion >= 0) return sNetworkStackModuleVersion;
+
+        try {
+            sNetworkStackModuleVersion = resolveNetworkStackModuleVersion(context);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG, "Failed to resolve networkstack module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+        return sNetworkStackModuleVersion;
+    }
+
+    private static long resolveNetworkStackModuleVersion(@NonNull Context context)
+            throws PackageManager.NameNotFoundException {
+        // TODO(b/293975546): Strictly speaking this is the prefix for connectivity and not
+        //  network stack. In practice, it's the same. Read the prefix from network stack instead.
+        final String pkgPrefix = resolvePkgPrefix(context);
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            return packageManager.getPackageInfo(pkgPrefix + "networkstack",
+                    PackageManager.MATCH_SYSTEM_ONLY).getLongVersionCode();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(TAG, "Device is using go or non-mainline modules");
+            // fall through
+        }
+
+        return packageManager.getPackageInfo(pkgPrefix + "go.networkstack",
+                PackageManager.MATCH_ALL).getLongVersionCode();
+    }
+
+    /**
+     * Check whether one specific feature is supported from the feature Id. The feature Id is
+     * composed by a module package Id and version Id from {@link FeatureVersions}.
+     *
+     * This is useful when a feature required minimal module version supported and cannot function
+     * well with a standalone newer module.
+     * @param context The global context information about an app environment.
+     * @param featureId The feature id that contains required module id and minimal module version
+     * @return true if this feature is supported, or false if not supported.
+     **/
+    public static boolean isFeatureSupported(@NonNull Context context, long featureId) {
+        final long moduleVersion;
+        final long moduleId = featureId & MODULE_MASK;
+        if (moduleId == CONNECTIVITY_MODULE_ID) {
+            moduleVersion = getTetheringModuleVersion(context);
+        } else if (moduleId == NETWORK_STACK_MODULE_ID) {
+            moduleVersion = getNetworkStackModuleVersion(context);
+        } else {
+            throw new IllegalArgumentException("Unknown module " + moduleId);
+        }
+        // Support by default if no module version is available.
+        return moduleVersion == DEFAULT_PACKAGE_VERSION
+                || moduleVersion >= (featureId & VERSION_MASK);
+    }
+
+    /**
+     * Check whether one specific experimental feature in specific namespace from
+     * {@link DeviceConfig} is not disabled. Feature can be disabled by setting a non-zero
+     * value in the property. If the feature is enabled by default and disabled by flag push
+     * (kill switch), this method should be used. If the feature is disabled by default and
+     * enabled by flag push, {@link #isFeatureEnabled} should be used.
+     *
+     * @param namespace The namespace containing the property to look up.
+     * @param name The name of the property to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    private static boolean isFeatureNotChickenedOut(String namespace, String name) {
+        final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
+                0 /* default value */);
+        return propertyVersion == 0;
+    }
+
+    /**
+     * Check whether one specific experimental feature in Tethering module from {@link DeviceConfig}
+     * is not disabled.
+     *
+     * @param name The name of the property in tethering module to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isTetheringFeatureNotChickenedOut(String name) {
+        return isFeatureNotChickenedOut(NAMESPACE_TETHERING, name);
+    }
+
+    /**
+     * Check whether one specific experimental feature in NetworkStack module from
+     * {@link DeviceConfig} is not disabled.
+     *
+     * @param name The name of the property in NetworkStack module to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isNetworkStackFeatureNotChickenedOut(String name) {
+        return isFeatureNotChickenedOut(NAMESPACE_CONNECTIVITY, name);
+    }
+
+    /**
+     * Gets boolean config from resources.
+     */
+    public static boolean getResBooleanConfig(@NonNull final Context context,
+            @BoolRes int configResource, final boolean defaultValue) {
+        final Resources res = context.getResources();
+        try {
+            return res.getBoolean(configResource);
+        } catch (Resources.NotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Gets int config from resources.
+     */
+    public static int getResIntegerConfig(@NonNull final Context context,
+            @BoolRes int configResource, final int defaultValue) {
+        final Resources res = context.getResources();
+        try {
+            return res.getInteger(configResource);
+        } catch (Resources.NotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get the package name of the ServiceConnectivityResources package, used to provide resources
+     * for service-connectivity.
+     */
+    @NonNull
+    public static String getConnectivityResourcesPackageName(@NonNull Context context) {
+        final List<ResolveInfo> pkgs = new ArrayList<>(context.getPackageManager()
+                .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY));
+        pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(
+                CONNECTIVITY_RES_PKG_DIR));
+        if (pkgs.size() > 1) {
+            Log.wtf(TAG, "More than one connectivity resources package found: " + pkgs);
+        }
+        if (pkgs.isEmpty()) {
+            throw new IllegalStateException("No connectivity resource package found");
+        }
+
+        return pkgs.get(0).activityInfo.applicationInfo.packageName;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/DomainUtils.java b/staticlibs/device/com/android/net/module/util/DomainUtils.java
new file mode 100644
index 0000000..b327fd0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/DomainUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Utilities for encoding/decoding the domain name or domain search list.
+ *
+ * @hide
+ */
+public final class DomainUtils {
+    private static final String TAG = "DomainUtils";
+    private static final int MAX_OPTION_LEN = 255;
+
+    @NonNull
+    private static String getSubstring(@NonNull final String string, @NonNull final String[] labels,
+            int index) {
+        int beginIndex = 0;
+        for (int i = 0; i < index; i++) {
+            beginIndex += labels[i].length() + 1; // include the dot
+        }
+        return string.substring(beginIndex);
+    }
+
+    /**
+     * Encode the given single domain name to byte array, comply with RFC1035 section-3.1.
+     *
+     * @return null if the given domain string is invalid, otherwise, return a byte array
+     *         wrapping the encoded domain, not including any padded octets, caller should
+     *         pad zero octets at the end if needed.
+     */
+    @Nullable
+    public static byte[] encode(@NonNull final String domain) {
+        if (!DnsRecordParser.isHostName(domain)) return null;
+        return encode(new String[]{ domain }, false /* compression */);
+    }
+
+    /**
+     * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1
+     * and section 4.1.4 (message compression) if enabled.
+     *
+     * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte
+     *         array wrapping the encoded domains, not including any padded octets, caller
+     *         should pad zero octets at the end if needed. The byte array may be empty if
+     *         the given domain strings are invalid.
+     */
+    @Nullable
+    public static byte[] encode(@NonNull final String[] domains, boolean compression) {
+        try {
+            final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN);
+            final ArrayMap<String, Integer> offsetMap = new ArrayMap<>();
+            for (int i = 0; i < domains.length; i++) {
+                if (!DnsRecordParser.isHostName(domains[i])) {
+                    Log.e(TAG, "Skip invalid domain name " + domains[i]);
+                    continue;
+                }
+                final String[] labels = domains[i].split("\\.");
+                for (int j = 0; j < labels.length; j++) {
+                    if (compression) {
+                        final String suffix = getSubstring(domains[i], labels, j);
+                        if (offsetMap.containsKey(suffix)) {
+                            int offsetOfSuffix = offsetMap.get(suffix);
+                            offsetOfSuffix |= 0xC000;
+                            buffer.putShort((short) offsetOfSuffix);
+                            break; // unnecessary to put the compressed string into map
+                        } else {
+                            offsetMap.put(suffix, buffer.position());
+                        }
+                    }
+                    // encode the domain name string without compression when:
+                    // - compression feature isn't enabled,
+                    // - suffix does not match any string in the map.
+                    final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8);
+                    buffer.put((byte) labelBytes.length);
+                    buffer.put(labelBytes);
+                    if (j == labels.length - 1) {
+                        // Pad terminate label at the end of last label.
+                        buffer.put((byte) 0);
+                    }
+                }
+            }
+            buffer.flip();
+            final byte[] out = new byte[buffer.limit()];
+            buffer.get(out);
+            return out;
+        } catch (BufferOverflowException e) {
+            Log.e(TAG, "Fail to encode domain name and stop encoding", e);
+            return null;
+        }
+    }
+
+    /**
+     * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and
+     * section 4.1.4(message compression).
+     *
+     * @return domain name(s) string array with space separated, or empty string if decode fails.
+     */
+    @NonNull
+    public static ArrayList<String> decode(@NonNull final ByteBuffer buffer, boolean compression) {
+        final ArrayList<String> domainList = new ArrayList<>();
+        while (buffer.remaining() > 0) {
+            try {
+                // TODO: replace the recursion with loop in parseName and don't need to pass in the
+                // maxLabelCount parameter to prevent recursion from overflowing stack.
+                final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */,
+                        15 /* maxLabelCount */, compression);
+                if (!DnsRecordParser.isHostName(domain)) continue;
+                domainList.add(domain);
+            } catch (BufferUnderflowException | DnsPacket.ParseException e) {
+                Log.e(TAG, "Fail to parse domain name and stop parsing", e);
+                break;
+            }
+        }
+        return domainList;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/FdEventsReader.java b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
new file mode 100644
index 0000000..f88883b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 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.net.module.util;
+
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.util.SocketUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+
+/**
+ * This class encapsulates the mechanics of registering a file descriptor
+ * with a thread's Looper and handling read events (and errors).
+ *
+ * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override
+ * onStop() and onStart().
+ *
+ * Subclasses can expect a call life-cycle like the following:
+ *
+ *     [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all
+ *         goes well. Implementations may override onStart() for additional initialization.
+ *
+ *     [2] yield, waiting for read event or error notification:
+ *
+ *             [a] readPacket() && handlePacket()
+ *
+ *             [b] if (no error):
+ *                     goto 2
+ *                 else:
+ *                     goto 3
+ *
+ *     [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never
+ *         started). Implementations may override onStop() for additional cleanup.
+ *
+ * The packet receive buffer is recycled on every read call, so subclasses
+ * should make any copies they would like inside their handlePacket()
+ * implementation.
+ *
+ * All public methods MUST only be called from the same thread with which
+ * the Handler constructor argument is associated.
+ *
+ * @param <BufferType> the type of the buffer used to read data.
+ */
+public abstract class FdEventsReader<BufferType> {
+    private static final String TAG = FdEventsReader.class.getSimpleName();
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private static final int UNREGISTER_THIS_FD = 0;
+
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final MessageQueue mQueue;
+    @NonNull
+    private final BufferType mBuffer;
+    @Nullable
+    private FileDescriptor mFd;
+    private long mPacketsReceived;
+
+    protected static void closeFd(FileDescriptor fd) {
+        try {
+            SocketUtils.closeSocket(fd);
+        } catch (IOException ignored) {
+        }
+    }
+
+    protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
+        mHandler = h;
+        mQueue = mHandler.getLooper().getQueue();
+        mBuffer = buffer;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    protected MessageQueue getMessageQueue() {
+        return mQueue;
+    }
+
+    /** Start this FdEventsReader. */
+    public boolean start() {
+        if (!onCorrectThread()) {
+            throw new IllegalStateException("start() called from off-thread");
+        }
+
+        return createAndRegisterFd();
+    }
+
+    /** Stop this FdEventsReader and destroy the file descriptor. */
+    public void stop() {
+        if (!onCorrectThread()) {
+            throw new IllegalStateException("stop() called from off-thread");
+        }
+
+        unregisterAndDestroyFd();
+    }
+
+    @NonNull
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    protected abstract int recvBufSize(@NonNull BufferType buffer);
+
+    /** Returns the size of the receive buffer. */
+    public int recvBufSize() {
+        return recvBufSize(mBuffer);
+    }
+
+    /**
+     * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
+     *
+     * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
+     */
+    public final long numPacketsReceived() {
+        return mPacketsReceived;
+    }
+
+    /**
+     * Subclasses MUST create the listening socket here, including setting all desired socket
+     * options, interface or address/port binding, etc. The socket MUST be created nonblocking.
+     */
+    @Nullable
+    protected abstract FileDescriptor createFd();
+
+    /**
+     * Implementations MUST return the bytes read or throw an Exception.
+     *
+     * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or
+     * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer
+     * contents and respectively wait for further input or retry the read immediately. For all other
+     * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this
+     * method.
+     */
+    protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
+            throws Exception;
+
+    /**
+     * Called by the main loop for every packet.  Any desired copies of
+     * |recvbuf| should be made in here, as the underlying byte array is
+     * reused across all reads.
+     */
+    protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
+
+    /**
+     * Called by the subclasses of FdEventsReader, decide whether it should stop reading packet or
+     * just ignore the specific error other than EAGAIN or EINTR.
+     *
+     * @return {@code true} if this FdEventsReader should stop reading from the socket.
+     *         {@code false} if it should continue.
+     */
+    protected boolean handleReadError(@NonNull ErrnoException e) {
+        logError("readPacket error: ", e);
+        return true; // by default, stop reading on any error.
+    }
+
+    /**
+     * Called by the subclasses of FdEventsReader, decide whether it should stop reading from the
+     * socket or process the packet and continue to read upon receiving a zero-length packet.
+     *
+     * @return {@code true} if this FdEventsReader should process the zero-length packet.
+     *         {@code false} if it should stop reading from the socket.
+     */
+    protected boolean shouldProcessZeroLengthPacket() {
+        return false; // by default, stop reading upon receiving zero-length packet.
+    }
+
+    /**
+     * Called by the main loop to log errors.  In some cases |e| may be null.
+     */
+    protected void logError(@NonNull String msg, @Nullable Exception e) {}
+
+    /**
+     * Called by start(), if successful, just prior to returning.
+     */
+    protected void onStart() {}
+
+    /**
+     * Called by stop() just prior to returning.
+     */
+    protected void onStop() {}
+
+    private boolean createAndRegisterFd() {
+        if (mFd != null) return true;
+
+        try {
+            mFd = createFd();
+        } catch (Exception e) {
+            logError("Failed to create socket: ", e);
+            closeFd(mFd);
+            mFd = null;
+        }
+
+        if (mFd == null) return false;
+
+        getMessageQueue().addOnFileDescriptorEventListener(
+                mFd,
+                FD_EVENTS,
+                (fd, events) -> {
+                    // Always call handleInput() so read/recvfrom are given
+                    // a proper chance to encounter a meaningful errno and
+                    // perhaps log a useful error message.
+                    if (!isRunning() || !handleInput()) {
+                        unregisterAndDestroyFd();
+                        return UNREGISTER_THIS_FD;
+                    }
+                    return FD_EVENTS;
+                });
+        onStart();
+        return true;
+    }
+
+    protected boolean isRunning() {
+        return (mFd != null) && mFd.valid();
+    }
+
+    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
+    private boolean handleInput() {
+        while (isRunning()) {
+            final int bytesRead;
+
+            try {
+                bytesRead = readPacket(mFd, mBuffer);
+                if (bytesRead == 0 && !shouldProcessZeroLengthPacket()) {
+                    if (isRunning()) logError("Socket closed, exiting", null);
+                    break;
+                }
+                mPacketsReceived++;
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.EAGAIN) {
+                    // We've read everything there is to read this time around.
+                    return true;
+                } else if (e.errno == OsConstants.EINTR) {
+                    continue;
+                } else {
+                    if (!isRunning()) break;
+                    final boolean shouldStop = handleReadError(e);
+                    if (shouldStop) break;
+                    continue;
+                }
+            } catch (Exception e) {
+                if (isRunning()) logError("readPacket error: ", e);
+                break;
+            }
+
+            try {
+                handlePacket(mBuffer, bytesRead);
+            } catch (Exception e) {
+                logError("handlePacket error: ", e);
+                Log.wtf(TAG, "Error handling packet", e);
+            }
+        }
+
+        return false;
+    }
+
+    private void unregisterAndDestroyFd() {
+        if (mFd == null) return;
+
+        getMessageQueue().removeOnFileDescriptorEventListener(mFd);
+        closeFd(mFd);
+        mFd = null;
+        onStop();
+    }
+
+    private boolean onCorrectThread() {
+        return (mHandler.getLooper() == Looper.myLooper());
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
new file mode 100644
index 0000000..149756c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+/**
+ * Class to centralize feature version control that requires a specific module or a specific
+ * module version.
+ * @hide
+ */
+public class FeatureVersions {
+    /**
+     * This constant is used to do bitwise shift operation to create module ids.
+     * The module version is composed with 9 digits which is placed in the lower 36 bits.
+     */
+    private static final int MODULE_SHIFT = 36;
+    /**
+     * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module id.
+     */
+    public static final long MODULE_MASK = 0xFF0_0000_0000L;
+    /**
+     * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module version.
+     */
+    public static final long VERSION_MASK = 0x00F_FFFF_FFFFL;
+    public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT;
+    public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT;
+    // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
+    // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
+    // M-2023-Sept on July 3rd, 2023.
+    public static final long FEATURE_CLAT_ADDRESS_TRANSLATE =
+            NETWORK_STACK_MODULE_ID + 34_09_00_000L;
+}
diff --git a/staticlibs/device/com/android/net/module/util/IBpfMap.java b/staticlibs/device/com/android/net/module/util/IBpfMap.java
new file mode 100644
index 0000000..83ff875
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/IBpfMap.java
@@ -0,0 +1,82 @@
+/*
+ * 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.net.module.util;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.util.NoSuchElementException;
+
+/**
+ * The interface of BpfMap. This could be used to inject for testing.
+ * So the testing code won't load the JNI and update the entries to kernel.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+public interface IBpfMap<K extends Struct, V extends Struct> extends AutoCloseable {
+    /** Update an existing or create a new key -> value entry in an eBbpf map. */
+    void updateEntry(K key, V value) throws ErrnoException;
+
+    /** If the key does not exist in the map, insert key -> value entry into eBpf map. */
+    void insertEntry(K key, V value) throws ErrnoException, IllegalStateException;
+
+    /** If the key already exists in the map, replace its value. */
+    void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException;
+
+    /**
+     * Update an existing or create a new key -> value entry in an eBbpf map. Returns true if
+     * inserted, false if replaced. (use updateEntry() if you don't care whether insert or replace
+     * happened).
+     */
+    boolean insertOrReplaceEntry(K key, V value) throws ErrnoException;
+
+    /** Remove existing key from eBpf map. Return true if something was deleted. */
+    boolean deleteEntry(K key) throws ErrnoException;
+
+    /** Returns {@code true} if this map contains no elements. */
+    boolean isEmpty() throws ErrnoException;
+
+    /** Get the key after the passed-in key. */
+    K getNextKey(@NonNull K key) throws ErrnoException;
+
+    /** Get the first key of the eBpf map. */
+    K getFirstKey() throws ErrnoException;
+
+    /** Check whether a key exists in the map. */
+    boolean containsKey(@NonNull K key) throws ErrnoException;
+
+    /** Retrieve a value from the map. */
+    V getValue(@NonNull K key) throws ErrnoException;
+
+    public interface ThrowingBiConsumer<T,U> {
+        void accept(T t, U u) throws ErrnoException;
+    }
+
+    /**
+     * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
+     */
+    void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException;
+
+    /** Clears the map. */
+    void clear() throws ErrnoException;
+
+    /** Close for AutoCloseable. */
+    @Override
+    void close() throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/Ipv6Utils.java b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
new file mode 100644
index 0000000..d538221
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.IpUtils.icmpv6Checksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.NaHeader;
+import com.android.net.module.util.structs.NsHeader;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.RsHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Utilities for IPv6 packets.
+ */
+public class Ipv6Utils {
+    /**
+     * Build a generic ICMPv6 packet without Ethernet header.
+     */
+    public static ByteBuffer buildIcmpv6Packet(final Inet6Address srcIp, final Inet6Address dstIp,
+            short type, short code, final ByteBuffer... options) {
+        final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+        final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+        int payloadLen = 0;
+        for (ByteBuffer option: options) {
+            payloadLen += option.limit();
+        }
+        final ByteBuffer packet = ByteBuffer.allocate(ipv6HeaderLen + icmpv6HeaderLen + payloadLen);
+        final Ipv6Header ipv6Header =
+                new Ipv6Header((int) 0x60000000 /* version, traffic class, flowlabel */,
+                icmpv6HeaderLen + payloadLen /* payload length */,
+                (byte) IPPROTO_ICMPV6 /* next header */, (byte) 0xff /* hop limit */, srcIp, dstIp);
+        final Icmpv6Header icmpv6Header = new Icmpv6Header(type, code, (short) 0 /* checksum */);
+
+        ipv6Header.writeToByteBuffer(packet);
+        icmpv6Header.writeToByteBuffer(packet);
+        for (ByteBuffer option : options) {
+            packet.put(option);
+            // in case option might be reused by caller, restore the position and
+            // limit of bytebuffer.
+            option.clear();
+        }
+        packet.flip();
+
+        // Populate the ICMPv6 checksum field.
+        packet.putShort(ipv6HeaderLen + 2, icmpv6Checksum(packet, 0 /* ipOffset */,
+                ipv6HeaderLen /* transportOffset */,
+                (short) (icmpv6HeaderLen + payloadLen) /* transportLen */));
+        return packet;
+
+    }
+
+    /**
+     * Build a generic ICMPv6 packet(e.g., packet used in the neighbor discovery protocol).
+     */
+    public static ByteBuffer buildIcmpv6Packet(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, short type, short code,
+            final ByteBuffer... options) {
+        final ByteBuffer payload = buildIcmpv6Packet(srcIp, dstIp, type, code, options);
+
+        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + payload.limit());
+        final EthernetHeader ethHeader =
+                new EthernetHeader(dstMac, srcMac, (short) ETHER_TYPE_IPV6);
+
+        ethHeader.writeToByteBuffer(packet);
+        packet.put(payload);
+        packet.flip();
+
+        return packet;
+    }
+
+    /**
+     * Build the ICMPv6 packet payload including payload header and specific options.
+     */
+    private static ByteBuffer[] buildIcmpv6Payload(final ByteBuffer payloadHeader,
+            final ByteBuffer... options) {
+        final ByteBuffer[] payload = new ByteBuffer[options.length + 1];
+        payload[0] = payloadHeader;
+        System.arraycopy(options, 0, payload, 1, options.length);
+        return payload;
+    }
+
+    /**
+     * Build an ICMPv6 Router Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRaPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final byte flags,
+            final int lifetime, final long reachableTime, final long retransTimer,
+            final ByteBuffer... options) {
+        final RaHeader raHeader = new RaHeader((byte) 0 /* hopLimit, unspecified */,
+                flags, lifetime, reachableTime, retransTimer);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(raHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_ROUTER_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Neighbor Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer buildNaPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final int flags,
+            final Inet6Address target, final ByteBuffer... options) {
+        final NaHeader naHeader = new NaHeader(flags, target);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(naHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Neighbor Solicitation packet from the required specified parameters.
+     */
+    public static ByteBuffer buildNsPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp,
+            final Inet6Address target, final ByteBuffer... options) {
+        final NsHeader nsHeader = new NsHeader(target);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(nsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_NEIGHBOR_SOLICITATION /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Router Solicitation packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRsPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final ByteBuffer... options) {
+        final RsHeader rsHeader = new RsHeader((int) 0 /* reserved */);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(rsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_ROUTER_SOLICITATION /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Echo Request packet from the required specified parameters.
+     */
+    public static ByteBuffer buildEchoRequestPacket(final MacAddress srcMac,
+            final MacAddress dstMac, final Inet6Address srcIp, final Inet6Address dstIp) {
+        final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero.
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_ECHO_REQUEST_TYPE /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Echo Reply packet without ethernet header.
+     */
+    public static ByteBuffer buildEchoReplyPacket(final Inet6Address srcIp,
+            final Inet6Address dstIp) {
+        final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero.
+        return buildIcmpv6Packet(srcIp, dstIp, (byte) ICMPV6_ECHO_REPLY_TYPE /* type */,
+                (byte) 0 /* code */, payload);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/JniUtil.java b/staticlibs/device/com/android/net/module/util/JniUtil.java
new file mode 100644
index 0000000..5210a3e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/JniUtil.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+/**
+ * Utilities for modules to use jni.
+ */
+public final class JniUtil {
+    /**
+     * The method to find jni library accroding to the giving package name.
+     *
+     * The jni library name would be packageName + _jni.so. E.g.
+     * com_android_networkstack_tethering_util_jni for tethering,
+     * com_android_connectivity_util_jni for connectivity.
+     */
+    public static String getJniLibraryName(final Package pkg) {
+        final String libPrefix = pkg.getName().replaceAll("\\.", "_");
+
+        return libPrefix + "_jni";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java b/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java
new file mode 100644
index 0000000..5a4412f
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/NetworkMonitorUtils.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import android.os.Build;
+
+/** @hide */
+public class NetworkMonitorUtils {
+    // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+    // NetworkStack shims, but at the same time cannot use non-system APIs.
+    // TRANSPORT_TEST is test API as of R (so it is enforced to always be 7 and can't be changed),
+    // and it is being added as a system API in S.
+    // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31.
+    private static final int TRANSPORT_TEST = 7;
+
+    // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+    // NetworkStack shims, but at the same time cannot use non-system APIs.
+    // NET_CAPABILITY_NOT_VCN_MANAGED is system API as of S (so it is enforced to always be 28 and
+    // can't be changed).
+    // TODO: use NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED once NetworkStack builds against
+    //       API 31.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
+    // Network conditions broadcast constants
+    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+    public static final String EXTRA_CELL_ID = "extra_cellid";
+    public static final String EXTRA_SSID = "extra_ssid";
+    public static final String EXTRA_BSSID = "extra_bssid";
+    /** real time since boot */
+    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+    public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+            "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+    /**
+     * Return whether validation is required for private DNS in strict mode.
+     * @param nc Network capabilities of the network to test.
+     */
+    public static boolean isPrivateDnsValidationRequired(@NonNull final NetworkCapabilities nc) {
+        final boolean isVcnManaged = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+                && !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final boolean isOemPaid = nc.hasCapability(NET_CAPABILITY_OEM_PAID)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+        final boolean isDefaultCapable = nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+
+        // TODO: Consider requiring validation for DUN networks.
+        if (nc.hasCapability(NET_CAPABILITY_INTERNET)
+                && (isVcnManaged || isOemPaid || isDefaultCapable)) {
+            return true;
+        }
+
+        // Test networks that also have one of the major transport types are attempting to replicate
+        // that transport on a test interface (for example, test ethernet networks with
+        // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests.
+        // See also comments on EthernetManager#setIncludeTestInterfaces and on TestNetworkManager.
+        if (nc.hasTransport(TRANSPORT_TEST) && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) && (
+                nc.hasTransport(TRANSPORT_WIFI)
+                || nc.hasTransport(TRANSPORT_CELLULAR)
+                || nc.hasTransport(TRANSPORT_BLUETOOTH)
+                || nc.hasTransport(TRANSPORT_ETHERNET))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Return whether validation is required for a network.
+     * @param isVpnValidationRequired Whether network validation should be performed for VPN
+     *                                networks.
+     * @param nc Network capabilities of the network to test.
+     */
+    public static boolean isValidationRequired(boolean isDunValidationRequired,
+            boolean isVpnValidationRequired,
+            @NonNull final NetworkCapabilities nc) {
+        if (isDunValidationRequired && nc.hasCapability(NET_CAPABILITY_DUN)) {
+            return true;
+        }
+        if (!nc.hasCapability(NET_CAPABILITY_NOT_VPN)) {
+            return isVpnValidationRequired;
+        }
+        return isPrivateDnsValidationRequired(nc);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
new file mode 100644
index 0000000..33e5bfa
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.IpUtils.ipChecksum;
+import static com.android.net.module.util.IpUtils.tcpChecksum;
+import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * The class is used to build a packet.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                Layer 2 header (EthernetHeader)                | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Layer 3 header (Ipv4Header, Ipv6Header)             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Layer 4 header (TcpHeader, UdpHeader)               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Payload                             | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Below is a sample code to build a packet.
+ *
+ * // Initialize builder
+ * final ByteBuffer buf = ByteBuffer.allocate(...);
+ * final PacketBuilder pb = new PacketBuilder(buf);
+ * // Write headers
+ * pb.writeL2Header(...);
+ * pb.writeIpHeader(...);
+ * pb.writeTcpHeader(...);
+ * // Write payload
+ * buf.putInt(...);
+ * buf.putShort(...);
+ * buf.putByte(...);
+ * // Finalize and use the packet
+ * pb.finalizePacket();
+ * sendPacket(buf);
+ */
+public class PacketBuilder {
+    private static final int INVALID_OFFSET = -1;
+
+    private final ByteBuffer mBuffer;
+
+    private int mIpv4HeaderOffset = INVALID_OFFSET;
+    private int mIpv6HeaderOffset = INVALID_OFFSET;
+    private int mTcpHeaderOffset = INVALID_OFFSET;
+    private int mUdpHeaderOffset = INVALID_OFFSET;
+
+    public PacketBuilder(@NonNull ByteBuffer buffer) {
+        mBuffer = buffer;
+    }
+
+    /**
+     * Write an ethernet header.
+     *
+     * @param srcMac source MAC address
+     * @param dstMac destination MAC address
+     * @param etherType ether type
+     */
+    public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
+            IOException {
+        final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType);
+        try {
+            ethHeader.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write an IPv4 header.
+     * The IP header length and checksum are calculated and written back in #finalizePacket.
+     *
+     * @param tos type of service
+     * @param id the identification
+     * @param flagsAndFragmentOffset flags and fragment offset
+     * @param ttl time to live
+     * @param protocol protocol
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     */
+    public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl,
+            byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)
+            throws IOException {
+        mIpv4HeaderOffset = mBuffer.position();
+        final Ipv4Header ipv4Header = new Ipv4Header(tos,
+                (short) 0 /* totalLength, calculate in #finalizePacket */, id,
+                flagsAndFragmentOffset, ttl, protocol,
+                (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp);
+
+        try {
+            ipv4Header.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write an IPv6 header.
+     * The IP header length is calculated and written back in #finalizePacket.
+     *
+     * @param vtf version, traffic class and flow label
+     * @param nextHeader the transport layer protocol
+     * @param hopLimit hop limit
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     */
+    public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit,
+            @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)
+            throws IOException {
+        mIpv6HeaderOffset = mBuffer.position();
+        final Ipv6Header ipv6Header = new Ipv6Header(vtf,
+                (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader,
+                hopLimit, srcIp, dstIp);
+
+        try {
+            ipv6Header.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write a TCP header.
+     * The TCP header checksum is calculated and written back in #finalizePacket.
+     *
+     * @param srcPort source port
+     * @param dstPort destination port
+     * @param seq sequence number
+     * @param ack acknowledgement number
+     * @param tcpFlags tcp flags
+     * @param window window size
+     * @param urgentPointer urgent pointer
+     */
+    public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack,
+            byte tcpFlags, short window, short urgentPointer) throws IOException {
+        mTcpHeaderOffset = mBuffer.position();
+        final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack,
+                (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits,
+                dataOffset is always 5(*4bytes) because options not supported */, window,
+                (short) 0 /* checksum, calculate in #finalizePacket */,
+                urgentPointer);
+
+        try {
+            tcpHeader.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write a UDP header.
+     * The UDP header length and checksum are calculated and written back in #finalizePacket.
+     *
+     * @param srcPort source port
+     * @param dstPort destination port
+     */
+    public void writeUdpHeader(short srcPort, short dstPort) throws IOException {
+        mUdpHeaderOffset = mBuffer.position();
+        final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort,
+                (short) 0 /* length, calculate in #finalizePacket */,
+                (short) 0 /* checksum, calculate in #finalizePacket */);
+
+        try {
+            udpHeader.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Finalize the packet.
+     *
+     * Call after writing L4 header (no payload) or payload to the buffer used by the builder.
+     * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+     * after finalization.
+     */
+    @NonNull
+    public ByteBuffer finalizePacket() throws IOException {
+        // [1] Finalize IPv4 or IPv6 header.
+        int ipHeaderOffset = INVALID_OFFSET;
+        if (mIpv4HeaderOffset != INVALID_OFFSET) {
+            ipHeaderOffset = mIpv4HeaderOffset;
+
+            // Populate the IPv4 totalLength field.
+            mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
+                    (short) (mBuffer.position() - mIpv4HeaderOffset));
+
+            // Populate the IPv4 header checksum field.
+            mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+                    ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
+        } else if (mIpv6HeaderOffset != INVALID_OFFSET) {
+            ipHeaderOffset = mIpv6HeaderOffset;
+
+            // Populate the IPv6 payloadLength field.
+            // The payload length doesn't include IPv6 header length. See rfc8200 section 3.
+            mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+                    (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN));
+        } else {
+            throw new IOException("Packet is missing neither IPv4 nor IPv6 header");
+        }
+
+        // [2] Finalize TCP or UDP header.
+        if (mTcpHeaderOffset != INVALID_OFFSET) {
+            // Populate the TCP header checksum field.
+            mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
+                    ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
+                    mBuffer.position() - mTcpHeaderOffset /* transportLen */));
+        } else if (mUdpHeaderOffset != INVALID_OFFSET) {
+            // Populate the UDP header length field.
+            mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
+                    (short) (mBuffer.position() - mUdpHeaderOffset));
+
+            // Populate the UDP header checksum field.
+            mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
+                    ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
+        } else {
+            throw new IOException("Packet is missing neither TCP nor UDP header");
+        }
+
+        mBuffer.flip();
+        return mBuffer;
+    }
+
+    /**
+     * Allocate bytebuffer for building the packet.
+     *
+     * @param hasEther has ethernet header. Set this flag to indicate that the packet has an
+     *        ethernet header.
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+     *        currently supported.
+     * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+     *        currently supported.
+     * @param payloadLen length of the payload.
+     */
+    @NonNull
+    public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
+        if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+            throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        if (payloadLen < 0) {
+            throw new IllegalArgumentException("Invalid payload length " + payloadLen);
+        }
+
+        int packetLen = 0;
+        if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
+        packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class)
+                : Struct.getSize(Ipv6Header.class);
+        packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
+                : Struct.getSize(UdpHeader.class);
+        packetLen += payloadLen;
+
+        return ByteBuffer.allocate(packetLen);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/PacketReader.java b/staticlibs/device/com/android/net/module/util/PacketReader.java
new file mode 100644
index 0000000..66c4788
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/PacketReader.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.net.module.util;
+
+import static java.lang.Math.max;
+
+import android.os.Handler;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Specialization of {@link FdEventsReader} that reads packets into a byte array.
+ *
+ * TODO: rename this class to something more correctly descriptive (something
+ * like [or less horrible than] FdReadEventsHandler?).
+ */
+public abstract class PacketReader extends FdEventsReader<byte[]> {
+
+    public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
+
+    protected PacketReader(Handler h) {
+        this(h, DEFAULT_RECV_BUF_SIZE);
+    }
+
+    protected PacketReader(Handler h, int recvBufSize) {
+        super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
+    }
+
+    @Override
+    protected final int recvBufSize(byte[] buffer) {
+        return buffer.length;
+    }
+
+    /**
+     * Subclasses MAY override this to change the default read() implementation
+     * in favour of, say, recvfrom().
+     *
+     * Implementations MUST return the bytes read or throw an Exception.
+     */
+    @Override
+    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
+        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/SharedLog.java b/staticlibs/device/com/android/net/module/util/SharedLog.java
new file mode 100644
index 0000000..6b12c80
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SharedLog.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.StringJoiner;
+
+
+/**
+ * Class to centralize logging functionality for tethering.
+ *
+ * All access to class methods other than dump() must be on the same thread.
+ *
+ * @hide
+ */
+public class SharedLog {
+    private static final int DEFAULT_MAX_RECORDS = 500;
+    private static final String COMPONENT_DELIMITER = ".";
+
+    private enum Category {
+        NONE,
+        ERROR,
+        MARK,
+        WARN,
+        VERBOSE,
+        TERRIBLE,
+    }
+
+    private final LocalLog mLocalLog;
+    // The tag to use for output to the system log. This is not output to the
+    // LocalLog because that would be redundant.
+    private final String mTag;
+    // The component (or subcomponent) of a system that is sharing this log.
+    // This can grow in depth if components call forSubComponent() to obtain
+    // their SharedLog instance. The tag is not included in the component for
+    // brevity.
+    private final String mComponent;
+
+    public SharedLog(String tag) {
+        this(DEFAULT_MAX_RECORDS, tag);
+    }
+
+    public SharedLog(int maxRecords, String tag) {
+        this(new LocalLog(maxRecords), tag, tag);
+    }
+
+    private SharedLog(LocalLog localLog, String tag, String component) {
+        mLocalLog = localLog;
+        mTag = tag;
+        mComponent = component;
+    }
+
+    public String getTag() {
+        return mTag;
+    }
+
+    /**
+     * Create a SharedLog based on this log with an additional component prefix on each logged line.
+     */
+    public SharedLog forSubComponent(String component) {
+        if (!isRootLogInstance()) {
+            component = mComponent + COMPONENT_DELIMITER + component;
+        }
+        return new SharedLog(mLocalLog, mTag, component);
+    }
+
+    /**
+     * Dump the contents of this log.
+     *
+     * <p>This method may be called on any thread.
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mLocalLog.dump(writer);
+    }
+
+    /**
+     * Reverse dump the contents of this log.
+     *
+     * <p>This method may be called on any thread.
+     */
+    public void reverseDump(PrintWriter writer) {
+        mLocalLog.reverseDump(writer);
+    }
+
+    //////
+    // Methods that both log an entry and emit it to the system log.
+    //////
+
+    /**
+     * Log an error due to an exception. This does not include the exception stacktrace.
+     *
+     * <p>The log entry will be also added to the system log.
+     * @see #e(String, Throwable)
+     */
+    public void e(Exception e) {
+        Log.e(mTag, record(Category.ERROR, e.toString()));
+    }
+
+    /**
+     * Log an error message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void e(String msg) {
+        Log.e(mTag, record(Category.ERROR, msg));
+    }
+
+    /**
+     * Log an error due to an exception, with the exception stacktrace if provided.
+     *
+     * <p>The error and exception message appear in the shared log, but the stacktrace is only
+     * logged in general log output (logcat). The log entry will be also added to the system log.
+     */
+    public void e(@NonNull String msg, @Nullable Throwable exception) {
+        if (exception == null) {
+            e(msg);
+            return;
+        }
+        Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
+    }
+
+    /**
+     * Log an informational message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void i(String msg) {
+        Log.i(mTag, record(Category.NONE, msg));
+    }
+
+    /**
+     * Log a warning message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void w(String msg) {
+        Log.w(mTag, record(Category.WARN, msg));
+    }
+
+    /**
+     * Log a verbose message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void v(String msg) {
+        Log.v(mTag, record(Category.VERBOSE, msg));
+    }
+
+    /**
+     * Log a terrible failure message.
+     *
+     * <p>The log entry will be also added to the system log and will trigger system reporting
+     * for terrible failures.
+     */
+    public void wtf(String msg) {
+        Log.wtf(mTag, record(Category.TERRIBLE, msg));
+    }
+
+    /**
+     * Log a terrible failure due to an exception, with the exception stacktrace if provided.
+     *
+     * <p>The error and exception message appear in the shared log, but the stacktrace is only
+     * logged in general log output (logcat). The log entry will be also added to the system log
+     * and will trigger system reporting for terrible failures.
+     */
+    public void wtf(@NonNull String msg, @Nullable Throwable exception) {
+        if (exception == null) {
+            e(msg);
+            return;
+        }
+        Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception);
+    }
+
+
+    //////
+    // Methods that only log an entry (and do NOT emit to the system log).
+    //////
+
+    /**
+     * Log a general message to be only included in the in-memory log.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     */
+    public void log(String msg) {
+        record(Category.NONE, msg);
+    }
+
+    /**
+     * Log a general, formatted message to be only included in the in-memory log.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     * @see String#format(String, Object...)
+     */
+    public void logf(String fmt, Object... args) {
+        log(String.format(fmt, args));
+    }
+
+    /**
+     * Log a message with MARK level.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     */
+    public void mark(String msg) {
+        record(Category.MARK, msg);
+    }
+
+    private String record(Category category, String msg) {
+        final String entry = logLine(category, msg);
+        mLocalLog.append(entry);
+        return entry;
+    }
+
+    private String logLine(Category category, String msg) {
+        final StringJoiner sj = new StringJoiner(" ");
+        if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
+        if (category != Category.NONE) sj.add(category.toString());
+        return sj.add(msg).toString();
+    }
+
+    // Check whether this SharedLog instance is nominally the top level in
+    // a potential hierarchy of shared logs (the root of a tree),
+    // or is a subcomponent within the hierarchy.
+    private boolean isRootLogInstance() {
+        return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
+    }
+
+    private static final class LocalLog {
+        private final Deque<String> mLog;
+        private final int mMaxLines;
+
+        LocalLog(int maxLines) {
+            mMaxLines = Math.max(0, maxLines);
+            mLog = new ArrayDeque<>(mMaxLines);
+        }
+
+        synchronized void append(String logLine) {
+            if (mMaxLines <= 0) return;
+            while (mLog.size() >= mMaxLines) {
+                mLog.remove();
+            }
+            mLog.add(LocalDateTime.now() + " - " + logLine);
+        }
+
+        /**
+         * Dumps the content of local log to print writer with each log entry
+         *
+         * @param pw printer writer to write into
+         */
+        synchronized void dump(PrintWriter pw) {
+            for (final String s : mLog) {
+                pw.println(s);
+            }
+        }
+
+        synchronized void reverseDump(PrintWriter pw) {
+            final Iterator<String> itr = mLog.descendingIterator();
+            while (itr.hasNext()) {
+                pw.println(itr.next());
+            }
+        }
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
new file mode 100644
index 0000000..9878ea5
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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.net.module.util;
+
+import static android.net.util.SocketUtils.closeSocket;
+
+import android.annotation.NonNull;
+import android.system.NetlinkSocketAddress;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.SocketAddress;
+
+/**
+ * Collection of utilities to interact with raw sockets.
+ *
+ * This class also provides utilities to interact with {@link android.net.util.SocketUtils}
+ * because it is in the API surface could not be simply move the frameworks/libs/net/
+ * to share with module.
+ *
+ * TODO: deprecate android.net.util.SocketUtils and replace with this class.
+ */
+public class SocketUtils {
+
+    /**
+     * Make a socket address to communicate with netlink.
+     */
+    @NonNull
+    public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
+        return new NetlinkSocketAddress(portId, groupsMask);
+    }
+
+    /**
+     * Close a socket, ignoring any exception while closing.
+     */
+    public static void closeSocketQuietly(FileDescriptor fd) {
+        try {
+            closeSocket(fd);
+        } catch (IOException ignored) {
+        }
+    }
+
+    private SocketUtils() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
new file mode 100644
index 0000000..b638a46
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -0,0 +1,774 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Define a generic class that helps to parse the structured message.
+ *
+ * Example usage:
+ *
+ *    // C-style NduserOption message header definition in the kernel:
+ *    struct nduseroptmsg {
+ *        unsigned char nduseropt_family;
+ *        unsigned char nduseropt_pad1;
+ *        unsigned short nduseropt_opts_len;
+ *        int nduseropt_ifindex;
+ *        __u8 nduseropt_icmp_type;
+ *        __u8 nduseropt_icmp_code;
+ *        unsigned short nduseropt_pad2;
+ *        unsigned int nduseropt_pad3;
+ *    }
+ *
+ *    - Declare a subclass with explicit constructor or not which extends from this class to parse
+ *      NduserOption header from raw bytes array.
+ *
+ *    - Option w/ explicit constructor:
+ *      static class NduserOptHeaderMessage extends Struct {
+ *          @Field(order = 0, type = Type.U8, padding = 1)
+ *          final short family;
+ *          @Field(order = 1, type = Type.U16)
+ *          final int len;
+ *          @Field(order = 2, type = Type.S32)
+ *          final int ifindex;
+ *          @Field(order = 3, type = Type.U8)
+ *          final short type;
+ *          @Field(order = 4, type = Type.U8, padding = 6)
+ *          final short code;
+ *
+ *          NduserOptHeaderMessage(final short family, final int len, final int ifindex,
+ *                  final short type, final short code) {
+ *              this.family = family;
+ *              this.len = len;
+ *              this.ifindex = ifindex;
+ *              this.type = type;
+ *              this.code = code;
+ *          }
+ *      }
+ *
+ *      - Option w/o explicit constructor:
+ *        static class NduserOptHeaderMessage extends Struct {
+ *            @Field(order = 0, type = Type.U8, padding = 1)
+ *            short family;
+ *            @Field(order = 1, type = Type.U16)
+ *            int len;
+ *            @Field(order = 2, type = Type.S32)
+ *            int ifindex;
+ *            @Field(order = 3, type = Type.U8)
+ *            short type;
+ *            @Field(order = 4, type = Type.U8, padding = 6)
+ *            short code;
+ *        }
+ *
+ *    - Parse the target message and refer the members.
+ *      final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
+ *      buf.order(ByteOrder.nativeOrder());
+ *      final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
+ *      assertEquals(10, nduserHdrMsg.family);
+ */
+public class Struct {
+    public enum Type {
+        U8,          // unsigned byte,  size = 1 byte
+        U16,         // unsigned short, size = 2 bytes
+        U32,         // unsigned int,   size = 4 bytes
+        U63,         // unsigned long(MSB: 0), size = 8 bytes
+        U64,         // unsigned long,  size = 8 bytes
+        S8,          // signed byte,    size = 1 byte
+        S16,         // signed short,   size = 2 bytes
+        S32,         // signed int,     size = 4 bytes
+        S64,         // signed long,    size = 8 bytes
+        UBE16,       // unsigned short in network order, size = 2 bytes
+        UBE32,       // unsigned int in network order,   size = 4 bytes
+        UBE63,       // unsigned long(MSB: 0) in network order, size = 8 bytes
+        UBE64,       // unsigned long in network order,  size = 8 bytes
+        ByteArray,   // byte array with predefined length
+        EUI48,       // IEEE Extended Unique Identifier, a 48-bits long MAC address in network order
+        Ipv4Address, // IPv4 address in network order
+        Ipv6Address, // IPv6 address in network order
+    }
+
+    /**
+     * Indicate that the field marked with this annotation will automatically be managed by this
+     * class (e.g., will be parsed by #parse).
+     *
+     * order:     The placeholder associated with each field, consecutive order starting from zero.
+     * type:      The primitive data type listed in above Type enumeration.
+     * padding:   Padding bytes appear after the field for alignment.
+     * arraysize: The length of byte array.
+     *
+     * Annotation associated with field MUST have order and type properties at least, padding
+     * and arraysize properties depend on the specific usage, if these properties are absent,
+     * then default value 0 will be applied.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.FIELD)
+    public @interface Field {
+        int order();
+        Type type();
+        int padding() default 0;
+        int arraysize() default 0;
+    }
+
+    private static class FieldInfo {
+        @NonNull
+        public final Field annotation;
+        @NonNull
+        public final java.lang.reflect.Field field;
+
+        FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
+            this.annotation = annotation;
+            this.field = field;
+        }
+    }
+    private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
+
+    private static void checkAnnotationType(final Field annotation, final Class fieldType) {
+        switch (annotation.type()) {
+            case U8:
+            case S16:
+                if (fieldType == Short.TYPE) return;
+                break;
+            case U16:
+            case S32:
+            case UBE16:
+                if (fieldType == Integer.TYPE) return;
+                break;
+            case U32:
+            case U63:
+            case S64:
+            case UBE32:
+            case UBE63:
+                if (fieldType == Long.TYPE) return;
+                break;
+            case U64:
+            case UBE64:
+                if (fieldType == BigInteger.class) return;
+                break;
+            case S8:
+                if (fieldType == Byte.TYPE) return;
+                break;
+            case ByteArray:
+                if (fieldType != byte[].class) break;
+                if (annotation.arraysize() <= 0) {
+                    throw new IllegalArgumentException("Invalid ByteArray size: "
+                            + annotation.arraysize());
+                }
+                return;
+            case EUI48:
+                if (fieldType == MacAddress.class) return;
+                break;
+            case Ipv4Address:
+                if (fieldType == Inet4Address.class) return;
+                break;
+            case Ipv6Address:
+                if (fieldType == Inet6Address.class) return;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type" + annotation.type());
+        }
+        throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
+                + " for annotation type: " + annotation.type());
+    }
+
+    private static int getFieldLength(final Field annotation) {
+        int length = 0;
+        switch (annotation.type()) {
+            case U8:
+            case S8:
+                length = 1;
+                break;
+            case U16:
+            case S16:
+            case UBE16:
+                length = 2;
+                break;
+            case U32:
+            case S32:
+            case UBE32:
+                length = 4;
+                break;
+            case U63:
+            case U64:
+            case S64:
+            case UBE63:
+            case UBE64:
+                length = 8;
+                break;
+            case ByteArray:
+                length = annotation.arraysize();
+                break;
+            case EUI48:
+                length = 6;
+                break;
+            case Ipv4Address:
+                length = 4;
+                break;
+            case Ipv6Address:
+                length = 16;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type" + annotation.type());
+        }
+        return length + annotation.padding();
+    }
+
+    private static boolean isStructSubclass(final Class clazz) {
+        return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
+    }
+
+    private static int getAnnotationFieldCount(final Class clazz) {
+        int count = 0;
+        for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
+            if (field.isAnnotationPresent(Field.class)) count++;
+        }
+        return count;
+    }
+
+    private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) {
+        for (FieldInfo fi : fields) {
+            if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
+        }
+        return true;
+    }
+
+    private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
+        return !allFieldsFinal(fields, true /* immutable */)
+                && !allFieldsFinal(fields, false /* mutable */);
+    }
+
+    private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
+        final Class[] paramTypes = cons.getParameterTypes();
+        if (paramTypes.length != fields.length) return false;
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (!paramTypes[i].equals(fields[i].field.getType())) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Read U64/UBE64 type data from ByteBuffer and output a BigInteger instance.
+     *
+     * @param buf The byte buffer to read.
+     * @param type The annotation type.
+     *
+     * The magnitude argument of BigInteger constructor is a byte array in big-endian order.
+     * If BigInteger data is read from the byte buffer in little-endian, reverse the order of
+     * the bytes is required; if BigInteger data is read from the byte buffer in big-endian,
+     * then just keep it as-is.
+     */
+    private static BigInteger readBigInteger(final ByteBuffer buf, final Type type) {
+        final byte[] input = new byte[8];
+        boolean reverseBytes = (type == Type.U64 && buf.order() == ByteOrder.LITTLE_ENDIAN);
+        for (int i = 0; i < 8; i++) {
+            input[reverseBytes ? input.length - 1 - i : i] = buf.get();
+        }
+        return new BigInteger(1, input);
+    }
+
+    /**
+     * Get the last 8 bytes of a byte array. If there are less than 8 bytes,
+     * the first bytes are replaced with zeroes.
+     */
+    private static byte[] getLast8Bytes(final byte[] input) {
+        final byte[] tmp = new byte[8];
+        System.arraycopy(
+                input,
+                Math.max(0, input.length - 8), // srcPos: read at most last 8 bytes
+                tmp,
+                Math.max(0, 8 - input.length), // dstPos: pad output with that many zeroes
+                Math.min(8, input.length));    // length
+        return tmp;
+    }
+
+    /**
+     * Convert U64/UBE64 type data interpreted by BigInteger class to bytes array, output are
+     * always 8 bytes.
+     *
+     * @param bigInteger The number to convert.
+     * @param order Indicate ByteBuffer is read as little-endian or big-endian.
+     * @param type The annotation U64 type.
+     *
+     * BigInteger#toByteArray returns a byte array containing the 2's complement representation
+     * of this BigInteger, in big-endian. If annotation type is U64 and ByteBuffer is read as
+     * little-endian, then reversing the order of the bytes is required.
+     */
+    private static byte[] bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order,
+            final Type type) {
+        final byte[] bigIntegerBytes = bigInteger.toByteArray();
+        final byte[] output = getLast8Bytes(bigIntegerBytes);
+
+        if (type == Type.U64 && order == ByteOrder.LITTLE_ENDIAN) {
+            for (int i = 0; i < 4; i++) {
+                byte tmp = output[i];
+                output[i] = output[7 - i];
+                output[7 - i] = tmp;
+            }
+        }
+        return output;
+    }
+
+    private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
+            throws BufferUnderflowException {
+        final Object value;
+        checkAnnotationType(fieldInfo.annotation, fieldInfo.field.getType());
+        switch (fieldInfo.annotation.type()) {
+            case U8:
+                value = (short) (buf.get() & 0xFF);
+                break;
+            case U16:
+                value = (int) (buf.getShort() & 0xFFFF);
+                break;
+            case U32:
+                value = (long) (buf.getInt() & 0xFFFFFFFFL);
+                break;
+            case U64:
+                value = readBigInteger(buf, Type.U64);
+                break;
+            case S8:
+                value = buf.get();
+                break;
+            case S16:
+                value = buf.getShort();
+                break;
+            case S32:
+                value = buf.getInt();
+                break;
+            case U63:
+            case S64:
+                value = buf.getLong();
+                break;
+            case UBE16:
+                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                    value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
+                } else {
+                    value = (int) (buf.getShort() & 0xFFFF);
+                }
+                break;
+            case UBE32:
+                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                    value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
+                } else {
+                    value = (long) (buf.getInt() & 0xFFFFFFFFL);
+                }
+                break;
+            case UBE63:
+                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                    value = Long.reverseBytes(buf.getLong());
+                } else {
+                    value = buf.getLong();
+                }
+                break;
+            case UBE64:
+                value = readBigInteger(buf, Type.UBE64);
+                break;
+            case ByteArray:
+                final byte[] array = new byte[fieldInfo.annotation.arraysize()];
+                buf.get(array);
+                value = array;
+                break;
+            case EUI48:
+                final byte[] macAddress = new byte[6];
+                buf.get(macAddress);
+                value = MacAddress.fromBytes(macAddress);
+                break;
+            case Ipv4Address:
+            case Ipv6Address:
+                final boolean isIpv6 = (fieldInfo.annotation.type() == Type.Ipv6Address);
+                final byte[] address = new byte[isIpv6 ? 16 : 4];
+                buf.get(address);
+                try {
+                    value = InetAddress.getByAddress(address);
+                } catch (UnknownHostException e) {
+                    throw new IllegalArgumentException("illegal length of IP address", e);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
+        }
+
+        // Skip the padding data for alignment if any.
+        if (fieldInfo.annotation.padding() > 0) {
+            buf.position(buf.position() + fieldInfo.annotation.padding());
+        }
+        return value;
+    }
+
+    @Nullable
+    private Object getFieldValue(@NonNull java.lang.reflect.Field field) {
+        try {
+            return field.get(this);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException("Cannot access field: " + field, e);
+        }
+    }
+
+    private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
+            final Object value) throws BufferUnderflowException {
+        switch (fieldInfo.annotation.type()) {
+            case U8:
+                output.put((byte) (((short) value) & 0xFF));
+                break;
+            case U16:
+                output.putShort((short) (((int) value) & 0xFFFF));
+                break;
+            case U32:
+                output.putInt((int) (((long) value) & 0xFFFFFFFFL));
+                break;
+            case U63:
+                output.putLong((long) value);
+                break;
+            case U64:
+                output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.U64));
+                break;
+            case S8:
+                output.put((byte) value);
+                break;
+            case S16:
+                output.putShort((short) value);
+                break;
+            case S32:
+                output.putInt((int) value);
+                break;
+            case S64:
+                output.putLong((long) value);
+                break;
+            case UBE16:
+                if (output.order() == ByteOrder.LITTLE_ENDIAN) {
+                    output.putShort(Short.reverseBytes((short) (((int) value) & 0xFFFF)));
+                } else {
+                    output.putShort((short) (((int) value) & 0xFFFF));
+                }
+                break;
+            case UBE32:
+                if (output.order() == ByteOrder.LITTLE_ENDIAN) {
+                    output.putInt(Integer.reverseBytes(
+                            (int) (((long) value) & 0xFFFFFFFFL)));
+                } else {
+                    output.putInt((int) (((long) value) & 0xFFFFFFFFL));
+                }
+                break;
+            case UBE63:
+                if (output.order() == ByteOrder.LITTLE_ENDIAN) {
+                    output.putLong(Long.reverseBytes((long) value));
+                } else {
+                    output.putLong((long) value);
+                }
+                break;
+            case UBE64:
+                output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
+                break;
+            case ByteArray:
+                checkByteArraySize((byte[]) value, fieldInfo);
+                output.put((byte[]) value);
+                break;
+            case EUI48:
+                final byte[] macAddress = ((MacAddress) value).toByteArray();
+                output.put(macAddress);
+                break;
+            case Ipv4Address:
+            case Ipv6Address:
+                final byte[] address = ((InetAddress) value).getAddress();
+                output.put(address);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
+        }
+
+        // padding zero after field value for alignment.
+        for (int i = 0; i < fieldInfo.annotation.padding(); i++) output.put((byte) 0);
+    }
+
+    private static FieldInfo[] getClassFieldInfo(final Class clazz) {
+        if (!isStructSubclass(clazz)) {
+            throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
+                    + Struct.class.getName() + ", its superclass is "
+                    + clazz.getSuperclass().getName());
+        }
+
+        final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
+        if (cachedAnnotationFields != null) {
+            return cachedAnnotationFields;
+        }
+
+        // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
+        // of field appeared in the class, that is a problem when parsing raw data read from
+        // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
+        final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
+        for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
+            if (Modifier.isStatic(field.getModifiers())) continue;
+
+            final Field annotation = field.getAnnotation(Field.class);
+            if (annotation == null) {
+                throw new IllegalArgumentException("Field " + field.getName()
+                        + " is missing the " + Field.class.getSimpleName()
+                        + " annotation");
+            }
+            if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
+                throw new IllegalArgumentException("Annotation order: " + annotation.order()
+                        + " is negative or non-consecutive");
+            }
+            if (annotationFields[annotation.order()] != null) {
+                throw new IllegalArgumentException("Duplicated annotation order: "
+                        + annotation.order());
+            }
+            annotationFields[annotation.order()] = new FieldInfo(annotation, field);
+        }
+        sFieldCache.putIfAbsent(clazz, annotationFields);
+        return annotationFields;
+    }
+
+    /**
+     * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
+     * the type-variable object which is subclass of Struct class.
+     *
+     * TODO:
+     * 1. Support subclass inheritance.
+     * 2. Introduce annotation processor to enforce the subclass naming schema.
+     */
+    public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
+        try {
+            final FieldInfo[] foundFields = getClassFieldInfo(clazz);
+            if (hasBothMutableAndImmutableFields(foundFields)) {
+                throw new IllegalArgumentException("Class has both final and non-final fields");
+            }
+
+            Constructor<?> constructor = null;
+            Constructor<?> defaultConstructor = null;
+            final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
+            for (Constructor cons : constructors) {
+                if (matchConstructor(cons, foundFields)) constructor = cons;
+                if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
+            }
+
+            if (constructor == null && defaultConstructor == null) {
+                throw new IllegalArgumentException("Fail to find available constructor");
+            }
+            if (constructor != null) {
+                final Object[] args = new Object[foundFields.length];
+                for (int i = 0; i < args.length; i++) {
+                    args[i] = getFieldValue(buf, foundFields[i]);
+                }
+                return (T) constructor.newInstance(args);
+            }
+
+            final Object instance = defaultConstructor.newInstance();
+            for (FieldInfo fi : foundFields) {
+                fi.field.set(instance, getFieldValue(buf, fi));
+            }
+            return (T) instance;
+        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+            throw new IllegalArgumentException("Fail to create a instance from constructor", e);
+        } catch (BufferUnderflowException e) {
+            throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
+        }
+    }
+
+    private static int getSizeInternal(final FieldInfo[] fieldInfos) {
+        int size = 0;
+        for (FieldInfo fi : fieldInfos) {
+            size += getFieldLength(fi.annotation);
+        }
+        return size;
+    }
+
+    // Check whether the actual size of byte array matches the array size declared in
+    // annotation. For other annotation types, the actual length of field could be always
+    // deduced from annotation correctly.
+    private static void checkByteArraySize(@Nullable final byte[] array,
+            @NonNull final FieldInfo fieldInfo) {
+        Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName());
+        int annotationArraySize = fieldInfo.annotation.arraysize();
+        if (array.length == annotationArraySize) return;
+        throw new IllegalStateException("byte array actual length: "
+                + array.length + " doesn't match the declared array size: " + annotationArraySize);
+    }
+
+    private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
+        for (FieldInfo fi : fieldInfos) {
+            final Object value = getFieldValue(fi.field);
+            try {
+                putFieldValue(output, fi, value);
+            } catch (BufferUnderflowException e) {
+                throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
+            }
+        }
+    }
+
+    /**
+     * Get the size of Struct subclass object.
+     */
+    public static <T extends Struct> int getSize(final Class<T> clazz) {
+        final FieldInfo[] fieldInfos = getClassFieldInfo(clazz);
+        return getSizeInternal(fieldInfos);
+    }
+
+    /**
+     * Convert the parsed Struct subclass object to ByteBuffer.
+     *
+     * @param output ByteBuffer passed-in from the caller.
+     */
+    public final void writeToByteBuffer(final ByteBuffer output) {
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        writeToByteBufferInternal(output, fieldInfos);
+    }
+
+    /**
+     * Convert the parsed Struct subclass object to byte array.
+     *
+     * @param order indicate ByteBuffer is outputted as little-endian or big-endian.
+     */
+    public final byte[] writeToBytes(final ByteOrder order) {
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        final byte[] output = new byte[getSizeInternal(fieldInfos)];
+        final ByteBuffer buffer = ByteBuffer.wrap(output);
+        buffer.order(order);
+        writeToByteBufferInternal(buffer, fieldInfos);
+        return output;
+    }
+
+    /** Convert the parsed Struct subclass object to byte array with native order. */
+    public final byte[] writeToBytes() {
+        return writeToBytes(ByteOrder.nativeOrder());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || this.getClass() != obj.getClass()) return false;
+
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        for (int i = 0; i < fieldInfos.length; i++) {
+            try {
+                final Object value = fieldInfos[i].field.get(this);
+                final Object otherValue = fieldInfos[i].field.get(obj);
+
+                // Use Objects#deepEquals because the equals method on arrays does not check the
+                // contents of the array. The only difference between Objects#deepEquals and
+                // Objects#equals is that the former will call Arrays#deepEquals when comparing
+                // arrays. In turn, the only difference between Arrays#deepEquals is that it
+                // supports nested arrays. Struct does not currently support these, and if it did,
+                // Objects#deepEquals might be more correct.
+                if (!Objects.deepEquals(value, otherValue)) return false;
+            } catch (IllegalAccessException e) {
+                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        final Object[] values = new Object[fieldInfos.length];
+        for (int i = 0; i < fieldInfos.length; i++) {
+            final Object value = getFieldValue(fieldInfos[i].field);
+            // For byte array field, put the hash code generated based on the array content into
+            // the Object array instead of the reference to byte array, which might change and cause
+            // to get a different hash code even with the exact same elements.
+            if (fieldInfos[i].field.getType() == byte[].class) {
+                values[i] = Arrays.hashCode((byte[]) value);
+            } else {
+                values[i] = value;
+            }
+        }
+        return Objects.hash(values);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        for (int i = 0; i < fieldInfos.length; i++) {
+            sb.append(fieldInfos[i].field.getName()).append(": ");
+            final Object value = getFieldValue(fieldInfos[i].field);
+            if (value == null) {
+                sb.append("null");
+            } else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
+                sb.append("0x").append(HexDump.toHexString((byte[]) value));
+            } else if (fieldInfos[i].annotation.type() == Type.Ipv4Address
+                    || fieldInfos[i].annotation.type() == Type.Ipv6Address) {
+                sb.append(((InetAddress) value).getHostAddress());
+            } else {
+                sb.append(value.toString());
+            }
+            if (i != fieldInfos.length - 1) sb.append(", ");
+        }
+        return sb.toString();
+    }
+
+    /** A simple Struct which only contains a u8 field. */
+    public static class U8 extends Struct {
+        @Struct.Field(order = 0, type = Struct.Type.U8)
+        public final short val;
+
+        public U8(final short val) {
+            this.val = val;
+        }
+    }
+
+    /** A simple Struct which only contains an s32 field. */
+    public static class S32 extends Struct {
+        @Struct.Field(order = 0, type = Struct.Type.S32)
+        public final int val;
+
+        public S32(final int val) {
+            this.val = val;
+        }
+    }
+
+    /** A simple Struct which only contains a u32 field. */
+    public static class U32 extends Struct {
+        @Struct.Field(order = 0, type = Struct.Type.U32)
+        public final long val;
+
+        public U32(final long val) {
+            this.val = val;
+        }
+    }
+
+    /** A simple Struct which only contains an s64 field. */
+    public static class S64 extends Struct {
+        @Struct.Field(order = 0, type = Struct.Type.S64)
+        public final long val;
+
+        public S64(final long val) {
+            this.val = val;
+        }
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
new file mode 100644
index 0000000..9d2fb7f
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.net.module.util;
+
+import java.io.IOException;
+
+/**
+ * Contains mostly tc-related functionality.
+ */
+public class TcUtils {
+    static {
+        System.loadLibrary(JniUtil.getJniLibraryName(TcUtils.class.getPackage()));
+    }
+
+    /**
+     * Checks if the network interface uses an ethernet L2 header.
+     *
+     * @param iface the network interface.
+     * @return true if the interface uses an ethernet L2 header.
+     * @throws IOException
+     */
+    public static native boolean isEthernet(String iface) throws IOException;
+
+    /**
+     * Attach a tc bpf filter.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc filter add dev .. in/egress prio .. protocol ipv6/ip bpf object-pinned
+     * /sys/fs/bpf/... direct-action
+     *
+     * @param ifIndex the network interface index.
+     * @param ingress ingress or egress qdisc.
+     * @param prio
+     * @param proto
+     * @param bpfProgPath
+     * @throws IOException
+     */
+    public static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio,
+            short proto, String bpfProgPath) throws IOException;
+
+    /**
+     * Attach a tc police action.
+     *
+     * Attaches a matchall filter to the clsact qdisc with a tc police and tc bpf action attached.
+     * This causes the ingress rate to be limited and exceeding packets to be forwarded to a bpf
+     * program (specified in bpfProgPah) that accounts for the packets before dropping them.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc filter add dev .. ingress prio .. protocol .. matchall \
+     *     action police rate .. burst .. conform-exceed pipe/continue \
+     *     action bpf object-pinned .. \
+     *     drop
+     *
+     * @param ifIndex the network interface index.
+     * @param prio the filter preference.
+     * @param proto protocol.
+     * @param rateInBytesPerSec rate limit in bytes/s.
+     * @param bpfProgPath bpg program that accounts for rate exceeding packets before they are
+     *                    dropped.
+     * @throws IOException
+     */
+    public static native void tcFilterAddDevIngressPolice(int ifIndex, short prio, short proto,
+            int rateInBytesPerSec, String bpfProgPath) throws IOException;
+
+    /**
+     * Delete a tc filter.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc filter del dev .. in/egress prio .. protocol ..
+     *
+     * @param ifIndex the network interface index.
+     * @param ingress ingress or egress qdisc.
+     * @param prio the filter preference.
+     * @param proto protocol.
+     * @throws IOException
+     */
+    public static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio,
+            short proto) throws IOException;
+
+    /**
+     * Add a clsact qdisc.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc qdisc add dev .. clsact
+     *
+     * @param ifIndex the network interface index.
+     * @throws IOException
+     */
+    public static native void tcQdiscAddDevClsact(int ifIndex) throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
new file mode 100644
index 0000000..dab9694
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.arp;
+
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+
+import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
+
+import android.net.MacAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse packets for the
+ * ARP protocol.
+ *
+ * @hide
+ */
+public class ArpPacket {
+    private static final String TAG = "ArpPacket";
+
+    public final short opCode;
+    public final Inet4Address senderIp;
+    public final Inet4Address targetIp;
+    public final MacAddress senderHwAddress;
+    public final MacAddress targetHwAddress;
+
+    ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp,
+            MacAddress targetHwAddress, Inet4Address targetIp) {
+        this.opCode = opCode;
+        this.senderHwAddress = senderHwAddress;
+        this.senderIp = senderIp;
+        this.targetHwAddress = targetHwAddress;
+        this.targetIp = targetIp;
+    }
+
+    /**
+     * Build an ARP packet from the required specified parameters.
+     */
+    @VisibleForTesting
+    public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac,
+            final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp,
+            final short opCode) {
+        final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN);
+
+        // Ether header
+        buf.put(dstMac);
+        buf.put(srcMac);
+        buf.putShort((short) ETH_P_ARP);
+
+        // ARP header
+        buf.putShort((short) ARP_HWTYPE_ETHER);  // hrd
+        buf.putShort((short) ETH_P_IP);          // pro
+        buf.put((byte) ETHER_ADDR_LEN);          // hln
+        buf.put((byte) IPV4_ADDR_LEN);           // pln
+        buf.putShort(opCode);                    // op
+        buf.put(srcMac);                         // sha
+        buf.put(senderIp);                       // spa
+        buf.put(targetHwAddress);                // tha
+        buf.put(targetIp);                       // tpa
+        buf.flip();
+        return buf;
+    }
+
+    /**
+     * Parse an ARP packet from a ByteBuffer object.
+     */
+    @VisibleForTesting
+    public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length)
+            throws ParseException {
+        try {
+            if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) {
+                throw new ParseException("Invalid packet length: " + length);
+            }
+
+            final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length);
+            byte[] l2dst = new byte[ETHER_ADDR_LEN];
+            byte[] l2src = new byte[ETHER_ADDR_LEN];
+            buffer.get(l2dst);
+            buffer.get(l2src);
+
+            final short etherType = buffer.getShort();
+            if (etherType != ETH_P_ARP) {
+                throw new ParseException("Incorrect Ether Type: " + etherType);
+            }
+
+            final short hwType = buffer.getShort();
+            if (hwType != ARP_HWTYPE_ETHER) {
+                throw new ParseException("Incorrect HW Type: " + hwType);
+            }
+
+            final short protoType = buffer.getShort();
+            if (protoType != ETH_P_IP) {
+                throw new ParseException("Incorrect Protocol Type: " + protoType);
+            }
+
+            final byte hwAddrLength = buffer.get();
+            if (hwAddrLength != ETHER_ADDR_LEN) {
+                throw new ParseException("Incorrect HW address length: " + hwAddrLength);
+            }
+
+            final byte ipAddrLength = buffer.get();
+            if (ipAddrLength != IPV4_ADDR_LEN) {
+                throw new ParseException("Incorrect Protocol address length: " + ipAddrLength);
+            }
+
+            final short opCode = buffer.getShort();
+            if (opCode != ARP_REQUEST && opCode != ARP_REPLY) {
+                throw new ParseException("Incorrect opCode: " + opCode);
+            }
+
+            byte[] senderHwAddress = new byte[ETHER_ADDR_LEN];
+            byte[] senderIp = new byte[IPV4_ADDR_LEN];
+            buffer.get(senderHwAddress);
+            buffer.get(senderIp);
+
+            byte[] targetHwAddress = new byte[ETHER_ADDR_LEN];
+            byte[] targetIp = new byte[IPV4_ADDR_LEN];
+            buffer.get(targetHwAddress);
+            buffer.get(targetIp);
+
+            return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress),
+                    (Inet4Address) InetAddress.getByAddress(senderIp),
+                    MacAddress.fromBytes(targetHwAddress),
+                    (Inet4Address) InetAddress.getByAddress(targetIp));
+        } catch (IndexOutOfBoundsException e) {
+            throw new ParseException("Invalid index when wrapping a byte array into a buffer");
+        } catch (BufferUnderflowException e) {
+            throw new ParseException("Invalid buffer position");
+        } catch (IllegalArgumentException e) {
+            throw new ParseException("Invalid MAC address representation");
+        } catch (UnknownHostException e) {
+            throw new ParseException("Invalid IP address of Host");
+        }
+    }
+
+    /**
+     * Thrown when parsing ARP packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/Assertions.java b/staticlibs/device/com/android/net/module/util/async/Assertions.java
new file mode 100644
index 0000000..ce701d0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/Assertions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.net.module.util.async;
+
+import android.os.Build;
+
+/**
+ * Implements basic assert functions for runtime error-checking.
+ *
+ * @hide
+ */
+public final class Assertions {
+    public static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
+
+    public static void throwsIfOutOfBounds(int totalLength, int pos, int len) {
+        if (!IS_USER_BUILD && ((totalLength | pos | len) < 0 || pos > totalLength - len)) {
+            throw new ArrayIndexOutOfBoundsException(
+                "length=" + totalLength + "; regionStart=" + pos + "; regionLength=" + len);
+        }
+    }
+
+    public static void throwsIfOutOfBounds(byte[] buffer, int pos, int len) {
+        throwsIfOutOfBounds(buffer != null ? buffer.length : 0, pos, len);
+    }
+
+    private Assertions() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/AsyncFile.java b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java
new file mode 100644
index 0000000..2a3231b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java
@@ -0,0 +1,78 @@
+/*
+ * 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.net.module.util.async;
+
+import java.io.IOException;
+
+/**
+ * Represents an EventManager-managed file with Async IO semantics.
+ *
+ * Implements level-based Asyn IO semantics. This means that:
+ *   - onReadReady() callback would keep happening as long as there's any remaining
+ *     data to read, or the user calls enableReadEvents(false)
+ *   - onWriteReady() callback would keep happening as long as there's remaining space
+ *     to write to, or the user calls enableWriteEvents(false)
+ *
+ * All operations except close() must be called on the EventManager thread.
+ *
+ * @hide
+ */
+public interface AsyncFile {
+    /**
+     * Receives notifications when file readability or writeability changes.
+     * @hide
+     */
+    public interface Listener {
+        /** Invoked after the underlying file has been closed. */
+        void onClosed(AsyncFile file);
+
+        /** Invoked while the file has readable data and read notifications are enabled. */
+        void onReadReady(AsyncFile file);
+
+        /** Invoked while the file has writeable space and write notifications are enabled. */
+        void onWriteReady(AsyncFile file);
+    }
+
+    /** Requests this file to be closed. */
+    void close();
+
+    /** Enables or disables onReadReady() events. */
+    void enableReadEvents(boolean enable);
+
+    /** Enables or disables onWriteReady() events. */
+    void enableWriteEvents(boolean enable);
+
+    /** Returns true if the input stream has reached its end, or has been closed. */
+    boolean reachedEndOfFile();
+
+    /**
+     * Reads available data from the given non-blocking file descriptor.
+     *
+     * Returns zero if there's no data to read at this moment.
+     * Returns -1 if the file has reached its end or the input stream has been closed.
+     * Otherwise returns the number of bytes read.
+     */
+    int read(byte[] buffer, int pos, int len) throws IOException;
+
+    /**
+     * Writes data into the given non-blocking file descriptor.
+     *
+     * Returns zero if there's no buffer space to write to at this moment.
+     * Otherwise returns the number of bytes written.
+     */
+    int write(byte[] buffer, int pos, int len) throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/BufferedFile.java b/staticlibs/device/com/android/net/module/util/async/BufferedFile.java
new file mode 100644
index 0000000..bb5736b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/BufferedFile.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.async;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Buffers inbound and outbound file data within given strict limits.
+ *
+ * Automatically manages all readability and writeability events in EventManager:
+ *   - When read buffer has more space - asks EventManager to notify on more data
+ *   - When write buffer has more space - asks the user to provide more data
+ *   - When underlying file cannot accept more data - registers EventManager callback
+ *
+ * @hide
+ */
+public final class BufferedFile implements AsyncFile.Listener {
+    /**
+     * Receives notifications when new data or output space is available.
+     * @hide
+     */
+    public interface Listener {
+        /** Invoked after the underlying file has been closed. */
+        void onBufferedFileClosed();
+
+        /** Invoked when there's new data in the inbound buffer. */
+        void onBufferedFileInboundData(int readByteCount);
+
+        /** Notifies on data being flushed from output buffer. */
+        void onBufferedFileOutboundSpace();
+
+        /** Notifies on unrecoverable error in file access. */
+        void onBufferedFileIoError(String message);
+    }
+
+    private final Listener mListener;
+    private final EventManager mEventManager;
+    private AsyncFile mFile;
+
+    private final CircularByteBuffer mInboundBuffer;
+    private final AtomicLong mTotalBytesRead = new AtomicLong();
+    private boolean mIsReadingShutdown;
+
+    private final CircularByteBuffer mOutboundBuffer;
+    private final AtomicLong mTotalBytesWritten = new AtomicLong();
+
+    /** Creates BufferedFile based on the given file descriptor. */
+    public static BufferedFile create(
+            EventManager eventManager,
+            FileHandle fileHandle,
+            Listener listener,
+            int inboundBufferSize,
+            int outboundBufferSize) throws IOException {
+        if (fileHandle == null) {
+            throw new NullPointerException();
+        }
+        BufferedFile file = new BufferedFile(
+            eventManager, listener, inboundBufferSize, outboundBufferSize);
+        file.mFile = eventManager.registerFile(fileHandle, file);
+        return file;
+    }
+
+    private BufferedFile(
+            EventManager eventManager,
+            Listener listener,
+            int inboundBufferSize,
+            int outboundBufferSize) {
+        if (eventManager == null || listener == null) {
+            throw new NullPointerException();
+        }
+        mEventManager = eventManager;
+        mListener = listener;
+
+        mInboundBuffer = new CircularByteBuffer(inboundBufferSize);
+        mOutboundBuffer = new CircularByteBuffer(outboundBufferSize);
+    }
+
+    /** Requests this file to be closed. */
+    public void close() {
+        mFile.close();
+    }
+
+    @Override
+    public void onClosed(AsyncFile file) {
+        mListener.onBufferedFileClosed();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // READ PATH
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Returns buffer that is automatically filled with inbound data. */
+    public ReadableByteBuffer getInboundBuffer() {
+        return mInboundBuffer;
+    }
+
+    public int getInboundBufferFreeSizeForTest() {
+        return mInboundBuffer.freeSize();
+    }
+
+    /** Permanently disables reading of this file, and clears all buffered data. */
+    public void shutdownReading() {
+        mIsReadingShutdown = true;
+        mInboundBuffer.clear();
+        mFile.enableReadEvents(false);
+    }
+
+    /** Returns true after shutdownReading() has been called. */
+    public boolean isReadingShutdown() {
+        return mIsReadingShutdown;
+    }
+
+    /** Starts or resumes async read operations on this file. */
+    public void continueReading() {
+        if (!mIsReadingShutdown && mInboundBuffer.freeSize() > 0) {
+            mFile.enableReadEvents(true);
+        }
+    }
+
+    @Override
+    public void onReadReady(AsyncFile file) {
+        if (mIsReadingShutdown) {
+            return;
+        }
+
+        int readByteCount;
+        try {
+            readByteCount = bufferInputData();
+        } catch (IOException e) {
+            mListener.onBufferedFileIoError("IOException while reading: " + e.toString());
+            return;
+        }
+
+        if (readByteCount > 0) {
+            mListener.onBufferedFileInboundData(readByteCount);
+        }
+
+        continueReading();
+    }
+
+    private int bufferInputData() throws IOException {
+        int totalReadCount = 0;
+        while (true) {
+            final int maxReadCount = mInboundBuffer.getDirectWriteSize();
+            if (maxReadCount == 0) {
+                mFile.enableReadEvents(false);
+                break;
+            }
+
+            final int bufferOffset = mInboundBuffer.getDirectWritePos();
+            final byte[] buffer = mInboundBuffer.getDirectWriteBuffer();
+
+            final int readCount = mFile.read(buffer, bufferOffset, maxReadCount);
+            if (readCount <= 0) {
+                break;
+            }
+
+            mInboundBuffer.accountForDirectWrite(readCount);
+            totalReadCount += readCount;
+        }
+
+        mTotalBytesRead.addAndGet(totalReadCount);
+        return totalReadCount;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // WRITE PATH
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Returns the number of bytes currently buffered for output. */
+    public int getOutboundBufferSize() {
+        return mOutboundBuffer.size();
+    }
+
+    /** Returns the number of bytes currently available for buffering for output. */
+    public int getOutboundBufferFreeSize() {
+        return mOutboundBuffer.freeSize();
+    }
+
+    /**
+     * Queues the given data for output.
+     * Throws runtime exception if there is not enough space.
+     */
+    public boolean enqueueOutboundData(byte[] data, int pos, int len) {
+        return enqueueOutboundData(data, pos, len, null, 0, 0);
+    }
+
+    /**
+     * Queues data1, then data2 for output.
+     * Throws runtime exception if there is not enough space.
+     */
+    public boolean enqueueOutboundData(
+            byte[] data1, int pos1, int len1,
+            byte[] buffer2, int pos2, int len2) {
+        Assertions.throwsIfOutOfBounds(data1, pos1, len1);
+        Assertions.throwsIfOutOfBounds(buffer2, pos2, len2);
+
+        final int totalLen = len1 + len2;
+
+        if (totalLen > mOutboundBuffer.freeSize()) {
+            flushOutboundBuffer();
+
+            if (totalLen > mOutboundBuffer.freeSize()) {
+                return false;
+            }
+        }
+
+        mOutboundBuffer.writeBytes(data1, pos1, len1);
+
+        if (buffer2 != null) {
+            mOutboundBuffer.writeBytes(buffer2, pos2, len2);
+        }
+
+        flushOutboundBuffer();
+
+        return true;
+    }
+
+    private void flushOutboundBuffer() {
+        try {
+            while (mOutboundBuffer.getDirectReadSize() > 0) {
+                final int maxReadSize = mOutboundBuffer.getDirectReadSize();
+                final int writeCount = mFile.write(
+                    mOutboundBuffer.getDirectReadBuffer(),
+                    mOutboundBuffer.getDirectReadPos(),
+                    maxReadSize);
+
+                if (writeCount == 0) {
+                    mFile.enableWriteEvents(true);
+                    break;
+                }
+
+                if (writeCount > maxReadSize) {
+                    throw new IllegalArgumentException(
+                        "Write count " + writeCount + " above max " + maxReadSize);
+                }
+
+                mOutboundBuffer.accountForDirectRead(writeCount);
+            }
+        } catch (IOException e) {
+            scheduleOnIoError("IOException while writing: " + e.toString());
+        }
+    }
+
+    private void scheduleOnIoError(String message) {
+        mEventManager.execute(() -> {
+            mListener.onBufferedFileIoError(message);
+        });
+    }
+
+    @Override
+    public void onWriteReady(AsyncFile file) {
+        mFile.enableWriteEvents(false);
+        flushOutboundBuffer();
+        mListener.onBufferedFileOutboundSpace();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("file={");
+        sb.append(mFile);
+        sb.append("}");
+        if (mIsReadingShutdown) {
+            sb.append(", readingShutdown");
+        }
+        sb.append("}, inboundBuffer={");
+        sb.append(mInboundBuffer);
+        sb.append("}, outboundBuffer={");
+        sb.append(mOutboundBuffer);
+        sb.append("}, totalBytesRead=");
+        sb.append(mTotalBytesRead);
+        sb.append(", totalBytesWritten=");
+        sb.append(mTotalBytesWritten);
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
new file mode 100644
index 0000000..92daa08
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
@@ -0,0 +1,210 @@
+/*
+ * 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.net.module.util.async;
+
+import java.util.Arrays;
+
+/**
+ * Implements a circular read-write byte buffer.
+ *
+ * @hide
+ */
+public final class CircularByteBuffer implements ReadableByteBuffer {
+    private final byte[] mBuffer;
+    private final int mCapacity;
+    private int mReadPos;
+    private int mWritePos;
+    private int mSize;
+
+    public CircularByteBuffer(int capacity) {
+        mCapacity = capacity;
+        mBuffer = new byte[mCapacity];
+    }
+
+    @Override
+    public void clear() {
+        mReadPos = 0;
+        mWritePos = 0;
+        mSize = 0;
+        Arrays.fill(mBuffer, (byte) 0);
+    }
+
+    @Override
+    public int capacity() {
+        return mCapacity;
+    }
+
+    @Override
+    public int size() {
+        return mSize;
+    }
+
+    /** Returns the amount of remaining writeable space in this buffer. */
+    public int freeSize() {
+        return mCapacity - mSize;
+    }
+
+    @Override
+    public byte peek(int offset) {
+        if (offset < 0 || offset >= size()) {
+            throw new IllegalArgumentException("Invalid offset=" + offset + ", size=" + size());
+        }
+
+        return mBuffer[(mReadPos + offset) % mCapacity];
+    }
+
+    @Override
+    public void readBytes(byte[] dst, int dstPos, int dstLen) {
+        if (dst != null) {
+            Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+        }
+        if (dstLen > size()) {
+            throw new IllegalArgumentException("Invalid len=" + dstLen + ", size=" + size());
+        }
+
+        while (dstLen > 0) {
+            final int copyLen = getCopyLen(mReadPos, mWritePos, dstLen);
+            if (dst != null) {
+                System.arraycopy(mBuffer, mReadPos, dst, dstPos, copyLen);
+            }
+            dstPos += copyLen;
+            dstLen -= copyLen;
+            mSize -= copyLen;
+            mReadPos = (mReadPos + copyLen) % mCapacity;
+        }
+
+        if (mSize == 0) {
+            // Reset to the beginning for better contiguous access.
+            mReadPos = 0;
+            mWritePos = 0;
+        }
+    }
+
+    @Override
+    public void peekBytes(int offset, byte[] dst, int dstPos, int dstLen) {
+        Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+        if (offset + dstLen > size()) {
+            throw new IllegalArgumentException("Invalid len=" + dstLen
+                    + ", offset=" + offset + ", size=" + size());
+        }
+
+        int tmpReadPos = (mReadPos + offset) % mCapacity;
+        while (dstLen > 0) {
+            final int copyLen = getCopyLen(tmpReadPos, mWritePos, dstLen);
+            System.arraycopy(mBuffer, tmpReadPos, dst, dstPos, copyLen);
+            dstPos += copyLen;
+            dstLen -= copyLen;
+            tmpReadPos = (tmpReadPos + copyLen) % mCapacity;
+        }
+    }
+
+    @Override
+    public int getDirectReadSize() {
+        if (size() == 0) {
+            return 0;
+        }
+        return (mReadPos < mWritePos ? (mWritePos - mReadPos) : (mCapacity - mReadPos));
+    }
+
+    @Override
+    public int getDirectReadPos() {
+        return mReadPos;
+    }
+
+    @Override
+    public byte[] getDirectReadBuffer() {
+        return mBuffer;
+    }
+
+    @Override
+    public void accountForDirectRead(int len) {
+        if (len < 0 || len > size()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + size());
+        }
+
+        mSize -= len;
+        mReadPos = (mReadPos + len) % mCapacity;
+    }
+
+    /** Copies given data to the end of the buffer. */
+    public void writeBytes(byte[] buffer, int pos, int len) {
+        Assertions.throwsIfOutOfBounds(buffer, pos, len);
+        if (len > freeSize()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+        }
+
+        while (len > 0) {
+            final int copyLen = getCopyLen(mWritePos, mReadPos,len);
+            System.arraycopy(buffer, pos, mBuffer, mWritePos, copyLen);
+            pos += copyLen;
+            len -= copyLen;
+            mSize += copyLen;
+            mWritePos = (mWritePos + copyLen) % mCapacity;
+        }
+    }
+
+    private int getCopyLen(int startPos, int endPos, int len) {
+        if (startPos < endPos) {
+            return Math.min(len, endPos - startPos);
+        } else {
+            return Math.min(len, mCapacity - startPos);
+        }
+    }
+
+    /** Returns the amount of contiguous writeable space. */
+    public int getDirectWriteSize() {
+        if (freeSize() == 0) {
+            return 0;  // Return zero in case buffer is full.
+        }
+        return (mWritePos < mReadPos ? (mReadPos - mWritePos) : (mCapacity - mWritePos));
+    }
+
+    /** Returns the position of contiguous writeable space. */
+    public int getDirectWritePos() {
+        return mWritePos;
+    }
+
+    /** Returns the buffer reference for direct write operation. */
+    public byte[] getDirectWriteBuffer() {
+        return mBuffer;
+    }
+
+    /** Must be called after performing a direct write using getDirectWriteBuffer(). */
+    public void accountForDirectWrite(int len) {
+        if (len < 0 || len > freeSize()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+        }
+
+        mSize += len;
+        mWritePos = (mWritePos + len) % mCapacity;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("CircularByteBuffer{c=");
+        sb.append(mCapacity);
+        sb.append(",s=");
+        sb.append(mSize);
+        sb.append(",r=");
+        sb.append(mReadPos);
+        sb.append(",w=");
+        sb.append(mWritePos);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/EventManager.java b/staticlibs/device/com/android/net/module/util/async/EventManager.java
new file mode 100644
index 0000000..4ed4a70
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/EventManager.java
@@ -0,0 +1,75 @@
+/*
+ * 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.net.module.util.async;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages Async IO files and scheduled alarms, and executes all related callbacks
+ * in its own thread.
+ *
+ * All callbacks of AsyncFile, Alarm and EventManager will execute on EventManager's thread.
+ *
+ * Methods of this interface can be called from any thread.
+ *
+ * @hide
+ */
+public interface EventManager extends Executor {
+    /**
+     * Represents a scheduled alarm, allowing caller to attempt to cancel that alarm
+     * before it executes.
+     *
+     * @hide
+     */
+    public interface Alarm {
+        /** @hide */
+        public interface Listener {
+            void onAlarm(Alarm alarm, long elapsedTimeMs);
+            void onAlarmCancelled(Alarm alarm);
+        }
+
+        /**
+         * Attempts to cancel this alarm. Note that this request is inherently
+         * racy if executed close to the alarm's expiration time.
+         */
+        void cancel();
+    }
+
+    /**
+     * Requests EventManager to manage the given file.
+     *
+     * The file descriptors are not cloned, and EventManager takes ownership of all files passed.
+     *
+     * No event callbacks are enabled by this method.
+     */
+    AsyncFile registerFile(FileHandle fileHandle, AsyncFile.Listener listener) throws IOException;
+
+    /**
+     * Schedules Alarm with the given timeout.
+     *
+     * Timeout of zero can be used for immediate execution.
+     */
+    Alarm scheduleAlarm(long timeout, Alarm.Listener callback);
+
+    /** Schedules Runnable for immediate execution. */
+    @Override
+    void execute(Runnable callback);
+
+    /** Throws a runtime exception if the caller is not executing on this EventManager's thread. */
+    void assertInThread();
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/FileHandle.java b/staticlibs/device/com/android/net/module/util/async/FileHandle.java
new file mode 100644
index 0000000..9f7942d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/FileHandle.java
@@ -0,0 +1,74 @@
+/*
+ * 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.net.module.util.async;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Represents an file descriptor or another way to access a file.
+ *
+ * @hide
+ */
+public final class FileHandle {
+    private final ParcelFileDescriptor mFd;
+    private final Closeable mCloseable;
+    private final InputStream mInputStream;
+    private final OutputStream mOutputStream;
+
+    public static FileHandle fromFileDescriptor(ParcelFileDescriptor fd) {
+        if (fd == null) {
+            throw new NullPointerException();
+        }
+        return new FileHandle(fd, null, null, null);
+    }
+
+    public static FileHandle fromBlockingStream(
+            Closeable closeable, InputStream is, OutputStream os) {
+        if (closeable == null || is == null || os == null) {
+            throw new NullPointerException();
+        }
+        return new FileHandle(null, closeable, is, os);
+    }
+
+    private FileHandle(ParcelFileDescriptor fd, Closeable closeable,
+            InputStream is, OutputStream os) {
+        mFd = fd;
+        mCloseable = closeable;
+        mInputStream = is;
+        mOutputStream = os;
+    }
+
+    ParcelFileDescriptor getFileDescriptor() {
+        return mFd;
+    }
+
+    Closeable getCloseable() {
+        return mCloseable;
+    }
+
+    InputStream getInputStream() {
+        return mInputStream;
+    }
+
+    OutputStream getOutputStream() {
+        return mOutputStream;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/OsAccess.java b/staticlibs/device/com/android/net/module/util/async/OsAccess.java
new file mode 100644
index 0000000..df0ded2
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/OsAccess.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.async;
+
+import android.os.ParcelFileDescriptor;
+import android.system.StructPollfd;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Provides access to all relevant OS functions..
+ *
+ * @hide
+ */
+public abstract class OsAccess {
+    /** Closes the given file, suppressing IO exceptions. */
+    public abstract void close(ParcelFileDescriptor fd);
+
+    /** Returns file name for debugging purposes. */
+    public abstract String getFileDebugName(ParcelFileDescriptor fd);
+
+    /** Returns inner FileDescriptor instance. */
+    public abstract FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd);
+
+    /**
+     * Reads available data from the given non-blocking file descriptor.
+     *
+     * Returns zero if there's no data to read at this moment.
+     * Returns -1 if the file has reached its end or the input stream has been closed.
+     * Otherwise returns the number of bytes read.
+     */
+    public abstract int read(FileDescriptor fd, byte[] buffer, int pos, int len)
+            throws IOException;
+
+    /**
+     * Writes data into the given non-blocking file descriptor.
+     *
+     * Returns zero if there's no buffer space to write to at this moment.
+     * Otherwise returns the number of bytes written.
+     */
+    public abstract int write(FileDescriptor fd, byte[] buffer, int pos, int len)
+            throws IOException;
+
+    public abstract long monotonicTimeMillis();
+    public abstract void setNonBlocking(FileDescriptor fd) throws IOException;
+    public abstract ParcelFileDescriptor[] pipe() throws IOException;
+
+    public abstract int poll(StructPollfd[] fds, int timeoutMs) throws IOException;
+    public abstract short getPollInMask();
+    public abstract short getPollOutMask();
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
new file mode 100644
index 0000000..7f82404
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.net.module.util.async;
+
+/**
+ * Allows reading from a buffer of bytes. The data can be read and thus removed,
+ * or peeked at without disturbing the buffer state.
+ *
+ * @hide
+ */
+public interface ReadableByteBuffer {
+    /** Returns the size of the buffered data. */
+    int size();
+
+    /**
+     * Returns the maximum amount of the buffered data.
+     *
+     * The caller may use this method in combination with peekBytes()
+     * to estimate when the buffer needs to be emptied using readData().
+     */
+    int capacity();
+
+    /** Clears all buffered data. */
+    void clear();
+
+    /** Returns a single byte at the given offset. */
+    byte peek(int offset);
+
+    /** Copies an array of bytes from the given offset to "dst". */
+    void peekBytes(int offset, byte[] dst, int dstPos, int dstLen);
+
+    /** Reads and removes an array of bytes from the head of the buffer. */
+    void readBytes(byte[] dst, int dstPos, int dstLen);
+
+    /** Returns the amount of contiguous readable data. */
+    int getDirectReadSize();
+
+    /** Returns the position of contiguous readable data. */
+    int getDirectReadPos();
+
+    /** Returns the buffer reference for direct read operation. */
+    byte[] getDirectReadBuffer();
+
+    /** Must be called after performing a direct read using getDirectReadBuffer(). */
+    void accountForDirectRead(int len);
+}
diff --git a/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java b/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java
new file mode 100644
index 0000000..420a544
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/ip/ConntrackMonitor.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.ip;
+
+import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
+import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.system.OsConstants;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.ConntrackMessage;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
+
+import java.util.Objects;
+
+
+/**
+ * ConntrackMonitor.
+ *
+ * Monitors the netfilter conntrack notifications and presents to callers
+ * ConntrackEvents describing each event.
+ *
+ * @hide
+ */
+public class ConntrackMonitor extends NetlinkMonitor {
+    private static final String TAG = ConntrackMonitor.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
+    public static final int NF_NETLINK_CONNTRACK_NEW = 1;
+    public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+    public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+
+    // The socket receive buffer size in bytes. If too many conntrack messages are sent too
+    // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
+    // if too many connections are disconnected by losing network and so on. Use a large-enough
+    // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
+    private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
+
+    /**
+     * A class for describing parsed netfilter conntrack events.
+     */
+    public static class ConntrackEvent {
+        /**
+         * Conntrack event type.
+         */
+        public final short msgType;
+        /**
+         * Original direction conntrack tuple.
+         */
+        public final ConntrackMessage.Tuple tupleOrig;
+        /**
+         * Reply direction conntrack tuple.
+         */
+        public final ConntrackMessage.Tuple tupleReply;
+        /**
+         * Connection status. A bitmask of ip_conntrack_status enum flags.
+         */
+        public final int status;
+        /**
+         * Conntrack timeout.
+         */
+        public final int timeoutSec;
+
+        public ConntrackEvent(ConntrackMessage msg) {
+            this.msgType = msg.getHeader().nlmsg_type;
+            this.tupleOrig = msg.tupleOrig;
+            this.tupleReply = msg.tupleReply;
+            this.status = msg.status;
+            this.timeoutSec = msg.timeoutSec;
+        }
+
+        @VisibleForTesting
+        public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
+                ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
+            this.msgType = msgType;
+            this.tupleOrig = tupleOrig;
+            this.tupleReply = tupleReply;
+            this.status = status;
+            this.timeoutSec = timeoutSec;
+        }
+
+        @Override
+        @VisibleForTesting
+        public boolean equals(Object o) {
+            if (!(o instanceof ConntrackEvent)) return false;
+            ConntrackEvent that = (ConntrackEvent) o;
+            return this.msgType == that.msgType
+                    && Objects.equals(this.tupleOrig, that.tupleOrig)
+                    && Objects.equals(this.tupleReply, that.tupleReply)
+                    && this.status == that.status
+                    && this.timeoutSec == that.timeoutSec;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
+        }
+
+        @Override
+        public String toString() {
+            return "ConntrackEvent{"
+                    + "msg_type{"
+                    + NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER)
+                    + "}, "
+                    + "tuple_orig{" + tupleOrig + "}, "
+                    + "tuple_reply{" + tupleReply + "}, "
+                    + "status{"
+                    + status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")"
+                    + "}, "
+                    + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
+                    + "}";
+        }
+
+        /**
+         * Check the established NAT session conntrack message.
+         *
+         * @param msg the conntrack message to check.
+         * @return true if an established NAT message, false if not.
+         */
+        public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
+            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
+            if (msg.tupleOrig == null) return false;
+            if (msg.tupleReply == null) return false;
+            if (msg.timeoutSec == 0) return false;
+            if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
+
+            return true;
+        }
+
+        /**
+         * Check the dying NAT session conntrack message.
+         * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
+         *
+         * @param msg the conntrack message to check.
+         * @return true if a dying NAT message, false if not.
+         */
+        public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
+            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
+            if (msg.tupleOrig == null) return false;
+            if (msg.tupleReply == null) return false;
+            if (msg.timeoutSec != 0) return false;
+            if ((msg.status & DYING_MASK) != DYING_MASK) return false;
+
+            return true;
+        }
+    }
+
+    /**
+     * A callback to caller for conntrack event.
+     */
+    public interface ConntrackEventConsumer {
+        /**
+         * Every conntrack event received on the netlink socket is passed in
+         * here.
+         */
+        void accept(@NonNull ConntrackEvent event);
+    }
+
+    private final ConntrackEventConsumer mConsumer;
+
+    public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
+            @NonNull ConntrackEventConsumer cb) {
+        super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
+                | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
+        mConsumer = cb;
+    }
+
+    @Override
+    public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
+        if (!(nlMsg instanceof ConntrackMessage)) {
+            mLog.e("non-conntrack msg: " + nlMsg);
+            return;
+        }
+
+        final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
+        if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
+                || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
+            return;
+        }
+
+        mConsumer.accept(new ConntrackEvent(conntrackMsg));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java b/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java
new file mode 100644
index 0000000..7277fec
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/ip/InterfaceController.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.ip;
+
+import static android.net.INetd.IF_STATE_DOWN;
+import static android.net.INetd.IF_STATE_UP;
+
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.LinkAddress;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.OsConstants;
+
+import com.android.net.module.util.SharedLog;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/**
+ * Encapsulates the multiple IP configuration operations performed on an interface.
+ *
+ * TODO: refactor/eliminate the redundant ways to set and clear addresses.
+ *
+ * @hide
+ */
+public class InterfaceController {
+    private static final boolean DBG = false;
+
+    private final String mIfName;
+    private final INetd mNetd;
+    private final SharedLog mLog;
+
+    public InterfaceController(String ifname, INetd netd, SharedLog log) {
+        mIfName = ifname;
+        mNetd = netd;
+        mLog = log;
+    }
+
+    /**
+     * Set the IPv4 address and also optionally bring the interface up or down.
+     */
+    public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr,
+            final Boolean setIfaceUp) {
+        if (!(ipv4Addr.getAddress() instanceof Inet4Address)) {
+            throw new IllegalArgumentException("Invalid or mismatched Inet4Address");
+        }
+        // Note: currently netd only support INetd#IF_STATE_UP and #IF_STATE_DOWN.
+        // Other flags would be ignored.
+
+        final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+        ifConfig.ifName = mIfName;
+        ifConfig.ipv4Addr = ipv4Addr.getAddress().getHostAddress();
+        ifConfig.prefixLength = ipv4Addr.getPrefixLength();
+        // Netd ignores hwaddr in interfaceSetCfg.
+        ifConfig.hwAddr = "";
+        if (setIfaceUp == null) {
+            // Empty array means no change.
+            ifConfig.flags = new String[0];
+        } else {
+            // Netd ignores any flag that's not IF_STATE_UP or IF_STATE_DOWN in interfaceSetCfg.
+            ifConfig.flags = setIfaceUp.booleanValue()
+                    ? new String[] {IF_STATE_UP} : new String[] {IF_STATE_DOWN};
+        }
+        try {
+            mNetd.interfaceSetCfg(ifConfig);
+        } catch (RemoteException | ServiceSpecificException e) {
+            logError("Setting IPv4 address to %s/%d failed: %s",
+                    ifConfig.ipv4Addr, ifConfig.prefixLength, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set the IPv4 address of the interface.
+     */
+    public boolean setIPv4Address(final LinkAddress address) {
+        return setInterfaceConfiguration(address, null);
+    }
+
+    /**
+     * Clear the IPv4Address of the interface.
+     */
+    public boolean clearIPv4Address() {
+        return setIPv4Address(new LinkAddress("0.0.0.0/0"));
+    }
+
+    private boolean setEnableIPv6(boolean enabled) {
+        try {
+            mNetd.interfaceSetEnableIPv6(mIfName, enabled);
+        } catch (RemoteException | ServiceSpecificException e) {
+            logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Enable IPv6 on the interface.
+     */
+    public boolean enableIPv6() {
+        return setEnableIPv6(true);
+    }
+
+    /**
+     * Disable IPv6 on the interface.
+     */
+    public boolean disableIPv6() {
+        return setEnableIPv6(false);
+    }
+
+    /**
+     * Enable or disable IPv6 privacy extensions on the interface.
+     * @param enabled Whether the extensions should be enabled.
+     */
+    public boolean setIPv6PrivacyExtensions(boolean enabled) {
+        try {
+            mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
+        } catch (RemoteException | ServiceSpecificException e) {
+            logError("error %s IPv6 privacy extensions: %s",
+                    (enabled ? "enabling" : "disabling"), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set IPv6 address generation mode on the interface.
+     *
+     * <p>IPv6 should be disabled before changing the mode.
+     */
+    public boolean setIPv6AddrGenModeIfSupported(int mode) {
+        try {
+            mNetd.setIPv6AddrGenMode(mIfName, mode);
+        } catch (RemoteException e) {
+            logError("Unable to set IPv6 addrgen mode: %s", e);
+            return false;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode != OsConstants.EOPNOTSUPP) {
+                logError("Unable to set IPv6 addrgen mode: %s", e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Add an address to the interface.
+     */
+    public boolean addAddress(LinkAddress addr) {
+        return addAddress(addr.getAddress(), addr.getPrefixLength());
+    }
+
+    /**
+     * Add an address to the interface.
+     */
+    public boolean addAddress(InetAddress ip, int prefixLen) {
+        try {
+            mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
+        } catch (ServiceSpecificException | RemoteException e) {
+            logError("failed to add %s/%d: %s", ip, prefixLen, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Remove an address from the interface.
+     */
+    public boolean removeAddress(InetAddress ip, int prefixLen) {
+        try {
+            mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
+        } catch (ServiceSpecificException | RemoteException e) {
+            logError("failed to remove %s/%d: %s", ip, prefixLen, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Remove all addresses from the interface.
+     */
+    public boolean clearAllAddresses() {
+        try {
+            mNetd.interfaceClearAddrs(mIfName);
+        } catch (Exception e) {
+            logError("Failed to clear addresses: %s", e);
+            return false;
+        }
+        return true;
+    }
+
+    private void logError(String fmt, Object... args) {
+        mLog.e(String.format(fmt, args));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java
new file mode 100644
index 0000000..e88e7f0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 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.net.module.util.ip;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
+import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
+
+import android.annotation.NonNull;
+import android.net.MacAddress;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkUtils;
+import com.android.net.module.util.netlink.RtNetlinkNeighborMessage;
+import com.android.net.module.util.netlink.StructNdMsg;
+
+import java.net.InetAddress;
+import java.util.StringJoiner;
+
+/**
+ * IpNeighborMonitor.
+ *
+ * Monitors the kernel rtnetlink neighbor notifications and presents to callers
+ * NeighborEvents describing each event. Callers can provide a consumer instance
+ * to both filter (e.g. by interface index and IP address) and handle the
+ * generated NeighborEvents.
+ *
+ * @hide
+ */
+public class IpNeighborMonitor extends NetlinkMonitor {
+    private static final String TAG = IpNeighborMonitor.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    /**
+     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+     * for the given IP address on the specified interface index.
+     *
+     * @return 0 if the request was successfully passed to the kernel; otherwise return
+     *         a non-zero error code.
+     */
+    public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
+        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
+        if (DBG) Log.d(TAG, msgSnippet);
+
+        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
+                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
+
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Error " + msgSnippet + ": " + e);
+            return -e.errno;
+        }
+
+        return 0;
+    }
+
+    /**
+     * An event about a neighbor.
+     */
+    public static class NeighborEvent {
+        public final long elapsedMs;
+        public final short msgType;
+        public final int ifindex;
+        @NonNull
+        public final InetAddress ip;
+        public final short nudState;
+        @NonNull
+        public final MacAddress macAddr;
+
+        public NeighborEvent(long elapsedMs, short msgType, int ifindex, @NonNull InetAddress ip,
+                short nudState, @NonNull MacAddress macAddr) {
+            this.elapsedMs = elapsedMs;
+            this.msgType = msgType;
+            this.ifindex = ifindex;
+            this.ip = ip;
+            this.nudState = nudState;
+            this.macAddr = macAddr;
+        }
+
+        boolean isConnected() {
+            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
+        }
+
+        public boolean isValid() {
+            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
+        }
+
+        @Override
+        public String toString() {
+            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
+            return j.add("@" + elapsedMs)
+                    .add(stringForNlMsgType(msgType, NETLINK_ROUTE))
+                    .add("if=" + ifindex)
+                    .add(ip.getHostAddress())
+                    .add(StructNdMsg.stringForNudState(nudState))
+                    .add("[" + macAddr + "]")
+                    .toString();
+        }
+    }
+
+    /**
+     * A client that consumes NeighborEvent instances.
+     * Implement this to listen to neighbor events.
+     */
+    public interface NeighborEventConsumer {
+        // Every neighbor event received on the netlink socket is passed in
+        // here. Subclasses should filter for events of interest.
+        /**
+         * Consume a neighbor event
+         * @param event the event
+         */
+        void accept(NeighborEvent event);
+    }
+
+    private final NeighborEventConsumer mConsumer;
+
+    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
+        super(h, log, TAG, NETLINK_ROUTE, OsConstants.RTMGRP_NEIGH);
+        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
+    }
+
+    @Override
+    public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
+        if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
+            mLog.e("non-rtnetlink neighbor msg: " + nlMsg);
+            return;
+        }
+
+        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) nlMsg;
+        final short msgType = neighMsg.getHeader().nlmsg_type;
+        final StructNdMsg ndMsg = neighMsg.getNdHeader();
+        if (ndMsg == null) {
+            mLog.e("RtNetlinkNeighborMessage without ND message header!");
+            return;
+        }
+
+        final int ifindex = ndMsg.ndm_ifindex;
+        final InetAddress destination = neighMsg.getDestination();
+        final short nudState =
+                (msgType == RTM_DELNEIGH)
+                ? StructNdMsg.NUD_NONE
+                : ndMsg.ndm_state;
+
+        final NeighborEvent event = new NeighborEvent(
+                whenMs, msgType, ifindex, destination, nudState,
+                getMacAddress(neighMsg.getLinkLayerAddress()));
+
+        if (VDBG) {
+            Log.d(TAG, neighMsg.toString());
+        }
+        if (DBG) {
+            Log.d(TAG, event.toString());
+        }
+
+        mConsumer.accept(event);
+    }
+
+    private static MacAddress getMacAddress(byte[] linkLayerAddress) {
+        if (linkLayerAddress != null) {
+            try {
+                return MacAddress.fromBytes(linkLayerAddress);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java
new file mode 100644
index 0000000..f882483
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.ip;
+
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.ENOBUFS;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
+
+import static com.android.net.module.util.SocketUtils.closeSocketQuietly;
+import static com.android.net.module.util.SocketUtils.makeNetlinkSocketAddress;
+import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.net.module.util.PacketReader;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.NetlinkErrorMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkUtils;
+
+import java.io.FileDescriptor;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A simple base class to listen for netlink broadcasts.
+ *
+ * Opens a netlink socket of the given family and binds to the specified groups. Polls the socket
+ * from the event loop of the passed-in {@link Handler}, and calls the subclass-defined
+ * {@link #processNetlinkMessage} method on the handler thread for each netlink message that
+ * arrives. Currently ignores all netlink errors.
+ * @hide
+ */
+public class NetlinkMonitor extends PacketReader {
+    protected final SharedLog mLog;
+    protected final String mTag;
+    private final int mFamily;
+    private final int mBindGroups;
+    private final int mSockRcvbufSize;
+
+    private static final boolean DBG = false;
+
+    // Default socket receive buffer size. This means the specific buffer size is not set.
+    private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
+
+    /**
+     * Constructs a new {@code NetlinkMonitor} instance.
+     *
+     * @param h The Handler on which to poll for messages and on which to call
+     *          {@link #processNetlinkMessage}.
+     * @param log A SharedLog to log to.
+     * @param tag The log tag to use for log messages.
+     * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
+     * @param bindGroups the netlink groups to bind to.
+     * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
+     *        set the specific socket receive buffer size in #createFd and use the default value in
+     *        /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
+     */
+    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
+            int family, int bindGroups, int sockRcvbufSize) {
+        super(h, NetlinkUtils.DEFAULT_RECV_BUFSIZE);
+        mLog = log.forSubComponent(tag);
+        mTag = tag;
+        mFamily = family;
+        mBindGroups = bindGroups;
+        mSockRcvbufSize = sockRcvbufSize;
+    }
+
+    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
+            int family, int bindGroups) {
+        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        FileDescriptor fd = null;
+
+        try {
+            fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
+            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
+                try {
+                    Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
+                } catch (ErrnoException e) {
+                    Log.wtf(mTag, "Failed to set SO_RCVBUF to " + mSockRcvbufSize, e);
+                }
+            }
+            Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
+            NetlinkUtils.connectSocketToNetlink(fd);
+
+            if (DBG) {
+                final SocketAddress nlAddr = Os.getsockname(fd);
+                Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
+            }
+        } catch (ErrnoException | SocketException e) {
+            logError("Failed to create rtnetlink socket", e);
+            closeSocketQuietly(fd);
+            return null;
+        }
+
+        return fd;
+    }
+
+    @Override
+    protected void handlePacket(byte[] recvbuf, int length) {
+        final long whenMs = SystemClock.elapsedRealtime();
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        while (byteBuffer.remaining() > 0) {
+            try {
+                final int position = byteBuffer.position();
+                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily);
+                if (nlMsg == null || nlMsg.getHeader() == null) {
+                    byteBuffer.position(position);
+                    mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
+                    break;
+                }
+
+                if (nlMsg instanceof NetlinkErrorMessage) {
+                    mLog.e("netlink error: " + nlMsg);
+                    continue;
+                }
+
+                processNetlinkMessage(nlMsg, whenMs);
+            } catch (Exception e) {
+                mLog.e("Error handling netlink message", e);
+            }
+        }
+    }
+
+    @Override
+    protected void logError(String msg, Exception e) {
+        mLog.e(msg, e);
+    }
+
+    // Ignoring ENOBUFS may miss any important netlink messages, there are some messages which
+    // cannot be recovered by dumping current state once missed since kernel doesn't keep state
+    // for it. In addition, dumping current state will not result in any RTM_DELxxx messages, so
+    // reconstructing current state from a dump will be difficult. However, for those netlink
+    // messages don't cause any state changes, e.g. RTM_NEWLINK with current link state, maybe
+    // it's okay to ignore them, because these netlink messages won't cause any changes on the
+    // LinkProperties. Given the above trade-offs, try to ignore ENOBUFS and that's similar to
+    // what netd does today.
+    //
+    // TODO: log metrics when ENOBUFS occurs, or even force a disconnect, it will help see how
+    // often this error occurs on fields with the associated socket receive buffer size.
+    @Override
+    protected boolean handleReadError(ErrnoException e) {
+        logError("readPacket error: ", e);
+        if (e.errno == ENOBUFS) {
+            Log.wtf(mTag, "Errno: ENOBUFS");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Processes one netlink message. Must be overridden by subclasses.
+     * @param nlMsg the message to process.
+     * @param whenMs the timestamp, as measured by {@link SystemClock#elapsedRealtime}, when the
+     *               message was received.
+     */
+    protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java
new file mode 100644
index 0000000..dfed3ef
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2017 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.net.module.util.netlink;
+
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.netlink.StructNlAttr.findNextAttrOfType;
+import static com.android.net.module.util.netlink.StructNlAttr.makeNestedType;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * A NetlinkMessage subclass for netlink conntrack messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+ *
+ * @hide
+ */
+public class ConntrackMessage extends NetlinkMessage {
+    public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+
+    // enum ctattr_type
+    public static final short CTA_TUPLE_ORIG  = 1;
+    public static final short CTA_TUPLE_REPLY = 2;
+    public static final short CTA_STATUS      = 3;
+    public static final short CTA_TIMEOUT     = 7;
+
+    // enum ctattr_tuple
+    public static final short CTA_TUPLE_IP    = 1;
+    public static final short CTA_TUPLE_PROTO = 2;
+
+    // enum ctattr_ip
+    public static final short CTA_IP_V4_SRC = 1;
+    public static final short CTA_IP_V4_DST = 2;
+
+    // enum ctattr_l4proto
+    public static final short CTA_PROTO_NUM      = 1;
+    public static final short CTA_PROTO_SRC_PORT = 2;
+    public static final short CTA_PROTO_DST_PORT = 3;
+
+    // enum ip_conntrack_status
+    public static final int IPS_EXPECTED      = 0x00000001;
+    public static final int IPS_SEEN_REPLY    = 0x00000002;
+    public static final int IPS_ASSURED       = 0x00000004;
+    public static final int IPS_CONFIRMED     = 0x00000008;
+    public static final int IPS_SRC_NAT       = 0x00000010;
+    public static final int IPS_DST_NAT       = 0x00000020;
+    public static final int IPS_SEQ_ADJUST    = 0x00000040;
+    public static final int IPS_SRC_NAT_DONE  = 0x00000080;
+    public static final int IPS_DST_NAT_DONE  = 0x00000100;
+    public static final int IPS_DYING         = 0x00000200;
+    public static final int IPS_FIXED_TIMEOUT = 0x00000400;
+    public static final int IPS_TEMPLATE      = 0x00000800;
+    public static final int IPS_UNTRACKED     = 0x00001000;
+    public static final int IPS_HELPER        = 0x00002000;
+    public static final int IPS_OFFLOAD       = 0x00004000;
+    public static final int IPS_HW_OFFLOAD    = 0x00008000;
+
+    // ip_conntrack_status mask
+    // Interesting on the NAT conntrack session which has already seen two direction traffic.
+    // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting.
+    public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY
+            | IPS_SRC_NAT;
+    // Interesting on the established NAT conntrack session which is dying.
+    public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING;
+
+    /**
+     * A tuple for the conntrack connection information.
+     *
+     * see also CTA_TUPLE_ORIG and CTA_TUPLE_REPLY.
+     */
+    public static class Tuple {
+        public final Inet4Address srcIp;
+        public final Inet4Address dstIp;
+
+        // Both port and protocol number are unsigned numbers stored in signed integers, and that
+        // callers that want to compare them to integers should either cast those integers, or
+        // convert them to unsigned using Byte.toUnsignedInt() and Short.toUnsignedInt().
+        public final short srcPort;
+        public final short dstPort;
+        public final byte protoNum;
+
+        public Tuple(TupleIpv4 ip, TupleProto proto) {
+            this.srcIp = ip.src;
+            this.dstIp = ip.dst;
+            this.srcPort = proto.srcPort;
+            this.dstPort = proto.dstPort;
+            this.protoNum = proto.protoNum;
+        }
+
+        @Override
+        @VisibleForTesting
+        public boolean equals(Object o) {
+            if (!(o instanceof Tuple)) return false;
+            Tuple that = (Tuple) o;
+            return Objects.equals(this.srcIp, that.srcIp)
+                    && Objects.equals(this.dstIp, that.dstIp)
+                    && this.srcPort == that.srcPort
+                    && this.dstPort == that.dstPort
+                    && this.protoNum == that.protoNum;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum);
+        }
+
+        @Override
+        public String toString() {
+            final String srcIpStr = (srcIp == null) ? "null" : srcIp.getHostAddress();
+            final String dstIpStr = (dstIp == null) ? "null" : dstIp.getHostAddress();
+            final String protoStr = NetlinkConstants.stringForProtocol(protoNum);
+
+            return "Tuple{"
+                    + protoStr + ": "
+                    + srcIpStr + ":" + Short.toUnsignedInt(srcPort) + " -> "
+                    + dstIpStr + ":" + Short.toUnsignedInt(dstPort)
+                    + "}";
+        }
+    }
+
+    /**
+     * A tuple for the conntrack connection address.
+     *
+     * see also CTA_TUPLE_IP.
+     */
+    public static class TupleIpv4 {
+        public final Inet4Address src;
+        public final Inet4Address dst;
+
+        public TupleIpv4(Inet4Address src, Inet4Address dst) {
+            this.src = src;
+            this.dst = dst;
+        }
+    }
+
+    /**
+     * A tuple for the conntrack connection protocol.
+     *
+     * see also CTA_TUPLE_PROTO.
+     */
+    public static class TupleProto {
+        public final byte protoNum;
+        public final short srcPort;
+        public final short dstPort;
+
+        public TupleProto(byte protoNum, short srcPort, short dstPort) {
+            this.protoNum = protoNum;
+            this.srcPort = srcPort;
+            this.dstPort = dstPort;
+        }
+    }
+
+    /**
+     * Create a netlink message to refresh IPv4 conntrack entry timeout.
+     */
+    public static byte[] newIPv4TimeoutUpdateRequest(
+            int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
+        // *** STYLE WARNING ***
+        //
+        // Code below this point uses extra block indentation to highlight the
+        // packing of nested tuple netlink attribute types.
+        final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
+                new StructNlAttr(CTA_TUPLE_IP,
+                        new StructNlAttr(CTA_IP_V4_SRC, src),
+                        new StructNlAttr(CTA_IP_V4_DST, dst)),
+                new StructNlAttr(CTA_TUPLE_PROTO,
+                        new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
+                        new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
+                        new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));
+
+        final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);
+
+        final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
+        final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final ConntrackMessage ctmsg = new ConntrackMessage();
+        ctmsg.mHeader.nlmsg_len = bytes.length;
+        ctmsg.mHeader.nlmsg_type = (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8)
+                | NetlinkConstants.IPCTNL_MSG_CT_NEW;
+        ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+        ctmsg.mHeader.nlmsg_seq = 1;
+        ctmsg.pack(byteBuffer);
+
+        ctaTupleOrig.pack(byteBuffer);
+        ctaTimeout.pack(byteBuffer);
+
+        return bytes;
+    }
+
+    /**
+     * Parses a netfilter conntrack message from a {@link ByteBuffer}.
+     *
+     * @param header the netlink message header.
+     * @param byteBuffer The buffer from which to parse the netfilter conntrack message.
+     * @return the parsed netfilter conntrack message, or {@code null} if the netfilter conntrack
+     *         message could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static ConntrackMessage parse(@NonNull StructNlMsgHdr header,
+            @NonNull ByteBuffer byteBuffer) {
+        // Just build the netlink header and netfilter header for now and pretend the whole message
+        // was consumed.
+        // TODO: Parse the conntrack attributes.
+        final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer);
+        if (nfGenMsg == null) {
+            return null;
+        }
+
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer);
+        int status = 0;
+        if (nlAttr != null) {
+            status = nlAttr.getValueAsBe32(0);
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = findNextAttrOfType(CTA_TIMEOUT, byteBuffer);
+        int timeoutSec = 0;
+        if (nlAttr != null) {
+            timeoutSec = nlAttr.getValueAsBe32(0);
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_ORIG), byteBuffer);
+        Tuple tupleOrig = null;
+        if (nlAttr != null) {
+            tupleOrig = parseTuple(nlAttr.getValueAsByteBuffer());
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_REPLY), byteBuffer);
+        Tuple tupleReply = null;
+        if (nlAttr != null) {
+            tupleReply = parseTuple(nlAttr.getValueAsByteBuffer());
+        }
+
+        // Advance to the end of the message.
+        byteBuffer.position(baseOffset);
+        final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+        final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
+                header.nlmsg_len - kMinConsumed);
+        if (byteBuffer.remaining() < kAdditionalSpace) {
+            return null;
+        }
+        byteBuffer.position(baseOffset + kAdditionalSpace);
+
+        return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec);
+    }
+
+    /**
+     * Parses a conntrack tuple from a {@link ByteBuffer}.
+     *
+     * The attribute parsing is interesting on:
+     * - CTA_TUPLE_IP
+     *     CTA_IP_V4_SRC
+     *     CTA_IP_V4_DST
+     * - CTA_TUPLE_PROTO
+     *     CTA_PROTO_NUM
+     *     CTA_PROTO_SRC_PORT
+     *     CTA_PROTO_DST_PORT
+     *
+     * Assume that the minimum size is the sum of CTA_TUPLE_IP (size: 20) and CTA_TUPLE_PROTO
+     * (size: 28). Here is an example for an expected CTA_TUPLE_ORIG message in raw data:
+     * +--------------------------------------------------------------------------------------+
+     * | CTA_TUPLE_ORIG                                                                       |
+     * +--------------------------+-----------------------------------------------------------+
+     * | 1400                     | nla_len = 20                                              |
+     * | 0180                     | nla_type = nested CTA_TUPLE_IP                            |
+     * |     0800 0100 C0A8500C   |     nla_type=CTA_IP_V4_SRC, ip=192.168.80.12              |
+     * |     0800 0200 8C700874   |     nla_type=CTA_IP_V4_DST, ip=140.112.8.116              |
+     * | 1C00                     | nla_len = 28                                              |
+     * | 0280                     | nla_type = nested CTA_TUPLE_PROTO                         |
+     * |     0500 0100 06 000000  |     nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)         |
+     * |     0600 0200 F3F1 0000  |     nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)  |
+     * |     0600 0300 01BB 0000  |     nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)    |
+     * +--------------------------+-----------------------------------------------------------+
+     *
+     * The position of the byte buffer doesn't set to the end when the function returns. It is okay
+     * because the caller ConntrackMessage#parse has passed a copy which is used for this parser
+     * only. Moreover, the parser behavior is the same as other existing netlink struct class
+     * parser. Ex: StructInetDiagMsg#parse.
+     */
+    @Nullable
+    private static Tuple parseTuple(@Nullable ByteBuffer byteBuffer) {
+        if (byteBuffer == null) return null;
+
+        TupleIpv4 tupleIpv4 = null;
+        TupleProto tupleProto = null;
+
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_IP), byteBuffer);
+        if (nlAttr != null) {
+            tupleIpv4 = parseTupleIpv4(nlAttr.getValueAsByteBuffer());
+        }
+        if (tupleIpv4 == null) return null;
+
+        byteBuffer.position(baseOffset);
+        nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_PROTO), byteBuffer);
+        if (nlAttr != null) {
+            tupleProto = parseTupleProto(nlAttr.getValueAsByteBuffer());
+        }
+        if (tupleProto == null) return null;
+
+        return new Tuple(tupleIpv4, tupleProto);
+    }
+
+    @Nullable
+    private static Inet4Address castToInet4Address(@Nullable InetAddress address) {
+        if (address == null || !(address instanceof Inet4Address)) return null;
+        return (Inet4Address) address;
+    }
+
+    @Nullable
+    private static TupleIpv4 parseTupleIpv4(@Nullable ByteBuffer byteBuffer) {
+        if (byteBuffer == null) return null;
+
+        Inet4Address src = null;
+        Inet4Address dst = null;
+
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = findNextAttrOfType(CTA_IP_V4_SRC, byteBuffer);
+        if (nlAttr != null) {
+            src = castToInet4Address(nlAttr.getValueAsInetAddress());
+        }
+        if (src == null) return null;
+
+        byteBuffer.position(baseOffset);
+        nlAttr = findNextAttrOfType(CTA_IP_V4_DST, byteBuffer);
+        if (nlAttr != null) {
+            dst = castToInet4Address(nlAttr.getValueAsInetAddress());
+        }
+        if (dst == null) return null;
+
+        return new TupleIpv4(src, dst);
+    }
+
+    @Nullable
+    private static TupleProto parseTupleProto(@Nullable ByteBuffer byteBuffer) {
+        if (byteBuffer == null) return null;
+
+        byte protoNum = 0;
+        short srcPort = 0;
+        short dstPort = 0;
+
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = findNextAttrOfType(CTA_PROTO_NUM, byteBuffer);
+        if (nlAttr != null) {
+            protoNum = nlAttr.getValueAsByte((byte) 0);
+        }
+        if (!(protoNum == IPPROTO_TCP || protoNum == IPPROTO_UDP)) return null;
+
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_SRC_PORT, byteBuffer);
+        if (nlAttr != null) {
+            srcPort = nlAttr.getValueAsBe16((short) 0);
+        }
+        if (srcPort == 0) return null;
+
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_DST_PORT, byteBuffer);
+        if (nlAttr != null) {
+            dstPort = nlAttr.getValueAsBe16((short) 0);
+        }
+        if (dstPort == 0) return null;
+
+        return new TupleProto(protoNum, srcPort, dstPort);
+    }
+
+    /**
+     * Netfilter header.
+     */
+    public final StructNfGenMsg nfGenMsg;
+    /**
+     * Original direction conntrack tuple.
+     *
+     * The tuple is determined by the parsed attribute value CTA_TUPLE_ORIG, or null if the
+     * tuple could not be parsed successfully (for example, if it was truncated or absent).
+     */
+    @Nullable
+    public final Tuple tupleOrig;
+    /**
+     * Reply direction conntrack tuple.
+     *
+     * The tuple is determined by the parsed attribute value CTA_TUPLE_REPLY, or null if the
+     * tuple could not be parsed successfully (for example, if it was truncated or absent).
+     */
+    @Nullable
+    public final Tuple tupleReply;
+    /**
+     * Connection status. A bitmask of ip_conntrack_status enum flags.
+     *
+     * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could
+     * not be parsed successfully (for example, if it was truncated or absent). For the message
+     * from kernel, the valid status is non-zero. For the message from user space, the status may
+     * be 0 (absent).
+     */
+    public final int status;
+    /**
+     * Conntrack timeout.
+     *
+     * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout
+     * could not be parsed successfully (for example, if it was truncated or absent). For
+     * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the
+     * timeout is 0 (absent).
+     */
+    public final int timeoutSec;
+
+    private ConntrackMessage() {
+        super(new StructNlMsgHdr());
+        nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+
+        // This constructor is only used by #newIPv4TimeoutUpdateRequest which doesn't use these
+        // data member for packing message. Simply fill them to null or 0.
+        tupleOrig = null;
+        tupleReply = null;
+        status = 0;
+        timeoutSec = 0;
+    }
+
+    private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg,
+            @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) {
+        super(header);
+        this.nfGenMsg = nfGenMsg;
+        this.tupleOrig = tupleOrig;
+        this.tupleReply = tupleReply;
+        this.status = status;
+        this.timeoutSec = timeoutSec;
+    }
+
+    /**
+     * Write a netfilter message to {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        mHeader.pack(byteBuffer);
+        nfGenMsg.pack(byteBuffer);
+    }
+
+    public short getMessageType() {
+        return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8));
+    }
+
+    /**
+     * Convert an ip conntrack status to a string.
+     */
+    public static String stringForIpConntrackStatus(int flags) {
+        final StringBuilder sb = new StringBuilder();
+
+        if ((flags & IPS_EXPECTED) != 0) {
+            sb.append("IPS_EXPECTED");
+        }
+        if ((flags & IPS_SEEN_REPLY) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_SEEN_REPLY");
+        }
+        if ((flags & IPS_ASSURED) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_ASSURED");
+        }
+        if ((flags & IPS_CONFIRMED) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_CONFIRMED");
+        }
+        if ((flags & IPS_SRC_NAT) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_SRC_NAT");
+        }
+        if ((flags & IPS_DST_NAT) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_DST_NAT");
+        }
+        if ((flags & IPS_SEQ_ADJUST) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_SEQ_ADJUST");
+        }
+        if ((flags & IPS_SRC_NAT_DONE) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_SRC_NAT_DONE");
+        }
+        if ((flags & IPS_DST_NAT_DONE) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_DST_NAT_DONE");
+        }
+        if ((flags & IPS_DYING) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_DYING");
+        }
+        if ((flags & IPS_FIXED_TIMEOUT) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_FIXED_TIMEOUT");
+        }
+        if ((flags & IPS_TEMPLATE) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_TEMPLATE");
+        }
+        if ((flags & IPS_UNTRACKED) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_UNTRACKED");
+        }
+        if ((flags & IPS_HELPER) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_HELPER");
+        }
+        if ((flags & IPS_OFFLOAD) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_OFFLOAD");
+        }
+        if ((flags & IPS_HW_OFFLOAD) != 0) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append("IPS_HW_OFFLOAD");
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String toString() {
+        return "ConntrackMessage{"
+                + "nlmsghdr{"
+                + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_NETFILTER))
+                + "}, "
+                + "nfgenmsg{" + nfGenMsg + "}, "
+                + "tuple_orig{" + tupleOrig + "}, "
+                + "tuple_reply{" + tupleReply + "}, "
+                + "status{" + status + "(" + stringForIpConntrackStatus(status) + ")" + "}, "
+                + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
new file mode 100644
index 0000000..f8b4716
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2018 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.net.module.util.netlink;
+
+import static android.os.Process.INVALID_UID;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.ENOENT;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
+import static com.android.net.module.util.netlink.NetlinkConstants.stringForAddressFamily;
+import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
+import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
+import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+import static com.android.net.module.util.netlink.NetlinkUtils.TCP_ALIVE_STATE_FILTER;
+import static com.android.net.module.util.netlink.NetlinkUtils.connectSocketToNetlink;
+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 android.net.util.SocketUtils;
+import android.os.Process;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.util.Log;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * A NetlinkMessage subclass for netlink inet_diag messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * @hide
+ */
+public class InetDiagMessage extends NetlinkMessage {
+    public static final String TAG = "InetDiagMessage";
+    private static final int TIMEOUT_MS = 500;
+
+    /**
+     * Construct an inet_diag_req_v2 message. This method will throw
+     * {@link IllegalArgumentException} if local and remote are not both null or both non-null.
+     */
+    public static byte[] inetDiagReqV2(int protocol, InetSocketAddress local,
+            InetSocketAddress remote, int family, short flags) {
+        return inetDiagReqV2(protocol, local, remote, family, flags, 0 /* pad */,
+                0 /* idiagExt */, StructInetDiagReqV2.INET_DIAG_REQ_V2_ALL_STATES);
+    }
+
+    /**
+     * Construct an inet_diag_req_v2 message. This method will throw
+     * {@code IllegalArgumentException} if local and remote are not both null or both non-null.
+     *
+     * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
+     *                 IPPROTO_UDP, or IPPROTO_UDPLITE.
+     * @param local local socket address of the target socket. This will be packed into a
+     *              {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of
+     *              local or remote address is null.
+     * @param remote remote socket address of the target socket. This will be packed into a
+     *              {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of
+     *              local or remote address is null.
+     * @param family the ip family of the request message. This should be set to either AF_INET or
+     *               AF_INET6 for IPv4 or IPv6 sockets respectively.
+     * @param flags message flags. See &lt;linux_src&gt;/include/uapi/linux/netlink.h.
+     * @param pad for raw socket protocol specification.
+     * @param idiagExt a set of flags defining what kind of extended information to report.
+     * @param state a bit mask that defines a filter of socket states.
+     *
+     * @return bytes array representation of the message
+     */
+    public static byte[] inetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
+            @Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt,
+            int state) throws IllegalArgumentException {
+        // Request for all sockets if no specific socket is requested. Specify the local and remote
+        // socket address information for target request socket.
+        if ((local == null) != (remote == null)) {
+            throw new IllegalArgumentException(
+                    "Local and remote must be both null or both non-null");
+        }
+        final StructInetDiagSockId id = ((local != null && remote != null)
+                ? new StructInetDiagSockId(local, remote) : null);
+        return inetDiagReqV2(protocol, id, family,
+                SOCK_DIAG_BY_FAMILY, flags, pad, idiagExt, state);
+    }
+
+    /**
+     * Construct an inet_diag_req_v2 message.
+     *
+     * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
+     *                 IPPROTO_UDP, or IPPROTO_UDPLITE.
+     * @param id inet_diag_sockid. See {@link StructInetDiagSockId}
+     * @param family the ip family of the request message. This should be set to either AF_INET or
+     *               AF_INET6 for IPv4 or IPv6 sockets respectively.
+     * @param type message types.
+     * @param flags message flags. See &lt;linux_src&gt;/include/uapi/linux/netlink.h.
+     * @param pad for raw socket protocol specification.
+     * @param idiagExt a set of flags defining what kind of extended information to report.
+     * @param state a bit mask that defines a filter of socket states.
+     * @return bytes array representation of the message
+     */
+    public static byte[] inetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family,
+            short type, short flags, int pad, int idiagExt, int state) {
+        final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr();
+        nlMsgHdr.nlmsg_len = bytes.length;
+        nlMsgHdr.nlmsg_type = type;
+        nlMsgHdr.nlmsg_flags = flags;
+        nlMsgHdr.pack(byteBuffer);
+        final StructInetDiagReqV2 inetDiagReqV2 =
+                new StructInetDiagReqV2(protocol, id, family, pad, idiagExt, state);
+
+        inetDiagReqV2.pack(byteBuffer);
+        return bytes;
+    }
+
+    public StructInetDiagMsg inetDiagMsg;
+
+    @VisibleForTesting
+    public InetDiagMessage(@NonNull StructNlMsgHdr header) {
+        super(header);
+        inetDiagMsg = new StructInetDiagMsg();
+    }
+
+    /**
+     * Parse an inet_diag_req_v2 message from buffer.
+     */
+    @Nullable
+    public static InetDiagMessage parse(@NonNull StructNlMsgHdr header,
+            @NonNull ByteBuffer byteBuffer) {
+        final InetDiagMessage msg = new InetDiagMessage(header);
+        msg.inetDiagMsg = StructInetDiagMsg.parse(byteBuffer);
+        if (msg.inetDiagMsg == null) {
+            return null;
+        }
+        return msg;
+    }
+
+    private static void closeSocketQuietly(final FileDescriptor fd) {
+        try {
+            SocketUtils.closeSocket(fd);
+        } catch (IOException ignored) {
+        }
+    }
+
+    private static int lookupUidByFamily(int protocol, InetSocketAddress local,
+                                         InetSocketAddress remote, int family, short flags,
+                                         FileDescriptor fd)
+            throws ErrnoException, InterruptedIOException {
+        byte[] msg = inetDiagReqV2(protocol, local, remote, family, flags);
+        NetlinkUtils.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
+        ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
+
+        final NetlinkMessage nlMsg = NetlinkMessage.parse(response, NETLINK_INET_DIAG);
+        if (nlMsg == null) {
+            return INVALID_UID;
+        }
+        final StructNlMsgHdr hdr = nlMsg.getHeader();
+        if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+            return INVALID_UID;
+        }
+        if (nlMsg instanceof InetDiagMessage) {
+            return ((InetDiagMessage) nlMsg).inetDiagMsg.idiag_uid;
+        }
+        return INVALID_UID;
+    }
+
+    private static final int[] FAMILY = {AF_INET6, AF_INET};
+
+    private static int lookupUid(int protocol, InetSocketAddress local,
+                                 InetSocketAddress remote, FileDescriptor fd)
+            throws ErrnoException, InterruptedIOException {
+        int uid;
+
+        for (int family : FAMILY) {
+            /**
+             * For exact match lookup, swap local and remote for UDP lookups due to kernel
+             * bug which will not be fixed. See aosp/755889 and
+             * https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
+             */
+            if (protocol == IPPROTO_UDP) {
+                uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd);
+            } else {
+                uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd);
+            }
+            if (uid != INVALID_UID) {
+                return uid;
+            }
+        }
+
+        /**
+         * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the
+         * socket is not connected (and even if the socket is connected to a different destination).
+         * If we want this API to work for such packets, then on miss we need to do a second lookup
+         * with only the local address and port filled in.
+         * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard.
+         */
+        if (protocol == IPPROTO_UDP) {
+            try {
+                InetSocketAddress wildcard = new InetSocketAddress(
+                        Inet6Address.getByName("::"), 0);
+                uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6,
+                        (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+                if (uid != INVALID_UID) {
+                    return uid;
+                }
+                wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0);
+                uid = lookupUidByFamily(protocol, local, wildcard, AF_INET,
+                        (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+                if (uid != INVALID_UID) {
+                    return uid;
+                }
+            } catch (UnknownHostException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+        return INVALID_UID;
+    }
+
+    /**
+     * Use an inet_diag socket to look up the UID associated with the input local and remote
+     * address/port and protocol of a connection.
+     */
+    public static int getConnectionOwnerUid(int protocol, InetSocketAddress local,
+                                            InetSocketAddress remote) {
+        int uid = INVALID_UID;
+        FileDescriptor fd = null;
+        try {
+            fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG);
+            NetlinkUtils.connectSocketToNetlink(fd);
+            uid = lookupUid(protocol, local, remote, fd);
+        } catch (ErrnoException | SocketException | IllegalArgumentException
+                | InterruptedIOException e) {
+            Log.e(TAG, e.toString());
+        } finally {
+            closeSocketQuietly(fd);
+        }
+        return uid;
+    }
+
+    /**
+     * Construct an inet_diag_req_v2 message for querying alive TCP sockets from kernel.
+     */
+    public static byte[] buildInetDiagReqForAliveTcpSockets(int family) {
+        return inetDiagReqV2(IPPROTO_TCP,
+                null /* local addr */,
+                null /* remote addr */,
+                family,
+                (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP) /* flag */,
+                0 /* pad */,
+                1 << NetlinkConstants.INET_DIAG_MEMINFO /* idiagExt */,
+                TCP_ALIVE_STATE_FILTER);
+    }
+
+    private static void sendNetlinkDestroyRequest(FileDescriptor fd, int proto,
+            InetDiagMessage diagMsg) throws InterruptedIOException, ErrnoException {
+        final byte[] destroyMsg = InetDiagMessage.inetDiagReqV2(
+                proto,
+                diagMsg.inetDiagMsg.id,
+                diagMsg.inetDiagMsg.idiag_family,
+                SOCK_DESTROY,
+                (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_ACK),
+                0 /* pad */,
+                0 /* idiagExt */,
+                1 << diagMsg.inetDiagMsg.idiag_state
+        );
+        NetlinkUtils.sendMessage(fd, destroyMsg, 0, destroyMsg.length, IO_TIMEOUT_MS);
+        NetlinkUtils.receiveNetlinkAck(fd);
+    }
+
+    private static void sendNetlinkDumpRequest(FileDescriptor fd, int proto, int states, int family)
+            throws InterruptedIOException, ErrnoException {
+        final byte[] dumpMsg = InetDiagMessage.inetDiagReqV2(
+                proto,
+                null /* id */,
+                family,
+                SOCK_DIAG_BY_FAMILY,
+                (short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP),
+                0 /* pad */,
+                0 /* idiagExt */,
+                states);
+        NetlinkUtils.sendMessage(fd, dumpMsg, 0, dumpMsg.length, IO_TIMEOUT_MS);
+    }
+
+    private static int processNetlinkDumpAndDestroySockets(FileDescriptor dumpFd,
+            FileDescriptor destroyFd, int proto, Predicate<InetDiagMessage> filter)
+            throws InterruptedIOException, ErrnoException {
+        int destroyedSockets = 0;
+
+        while (true) {
+            final ByteBuffer buf = NetlinkUtils.recvMessage(
+                    dumpFd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
+
+            while (buf.remaining() > 0) {
+                final int position = buf.position();
+                final NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_INET_DIAG);
+                if (nlMsg == null) {
+                    // Move to the position where parse started for error log.
+                    buf.position(position);
+                    Log.e(TAG, "Failed to parse netlink message: " + hexify(buf));
+                    break;
+                }
+
+                if (nlMsg.getHeader().nlmsg_type == NLMSG_DONE) {
+                    return destroyedSockets;
+                }
+
+                if (!(nlMsg instanceof InetDiagMessage)) {
+                    Log.wtf(TAG, "Received unexpected netlink message: " + nlMsg);
+                    continue;
+                }
+
+                final InetDiagMessage diagMsg = (InetDiagMessage) nlMsg;
+                if (filter.test(diagMsg)) {
+                    try {
+                        sendNetlinkDestroyRequest(destroyFd, proto, diagMsg);
+                        destroyedSockets++;
+                    } catch (InterruptedIOException | ErrnoException e) {
+                        if (!(e instanceof ErrnoException
+                                && ((ErrnoException) e).errno == ENOENT)) {
+                            Log.e(TAG, "Failed to destroy socket: diagMsg=" + diagMsg + ", " + e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether the InetDiagMessage is for adb socket or not
+     */
+    @VisibleForTesting
+    public static boolean isAdbSocket(final InetDiagMessage msg) {
+        // This is inaccurate since adb could run with ROOT_UID or other services can run with
+        // SHELL_UID. But this check covers most cases and enough.
+        // Note that getting service.adb.tcp.port system property is prohibited by sepolicy
+        // TODO: skip the socket only if there is a listen socket owned by SHELL_UID with the same
+        // source port as this socket
+        return msg.inetDiagMsg.idiag_uid == Process.SHELL_UID;
+    }
+
+    /**
+     * Returns whether the range contains the uid in the InetDiagMessage or not
+     */
+    @VisibleForTesting
+    public static boolean containsUid(InetDiagMessage msg, Set<Range<Integer>> ranges) {
+        for (final Range<Integer> range: ranges) {
+            if (range.contains(msg.inetDiagMsg.idiag_uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isLoopbackAddress(InetAddress addr) {
+        if (addr.isLoopbackAddress()) return true;
+        if (!(addr instanceof Inet6Address)) return false;
+
+        // Following check is for v4-mapped v6 address. StructInetDiagSockId contains v4-mapped v6
+        // address as Inet6Address, See StructInetDiagSockId#parse
+        final byte[] addrBytes = addr.getAddress();
+        for (int i = 0; i < 10; i++) {
+            if (addrBytes[i] != 0) return false;
+        }
+        return addrBytes[10] == (byte) 0xff
+                && addrBytes[11] == (byte) 0xff
+                && addrBytes[12] == 127;
+    }
+
+    /**
+     * Returns whether the socket address in the InetDiagMessage is loopback or not
+     */
+    @VisibleForTesting
+    public static boolean isLoopback(InetDiagMessage msg) {
+        final InetAddress srcAddr = msg.inetDiagMsg.id.locSocketAddress.getAddress();
+        final InetAddress dstAddr = msg.inetDiagMsg.id.remSocketAddress.getAddress();
+        return isLoopbackAddress(srcAddr)
+                || isLoopbackAddress(dstAddr)
+                || srcAddr.equals(dstAddr);
+    }
+
+    private static void destroySockets(int proto, int states, Predicate<InetDiagMessage> filter)
+            throws ErrnoException, SocketException, InterruptedIOException {
+        FileDescriptor dumpFd = null;
+        FileDescriptor destroyFd = null;
+
+        try {
+            dumpFd = NetlinkUtils.createNetLinkInetDiagSocket();
+            destroyFd = NetlinkUtils.createNetLinkInetDiagSocket();
+            connectSocketToNetlink(dumpFd);
+            connectSocketToNetlink(destroyFd);
+
+            for (int family : List.of(AF_INET, AF_INET6)) {
+                try {
+                    sendNetlinkDumpRequest(dumpFd, proto, states, family);
+                } catch (InterruptedIOException | ErrnoException e) {
+                    Log.e(TAG, "Failed to send netlink dump request: " + e);
+                    continue;
+                }
+                final int destroyedSockets = processNetlinkDumpAndDestroySockets(
+                        dumpFd, destroyFd, proto, filter);
+                Log.d(TAG, "Destroyed " + destroyedSockets + " sockets"
+                        + ", proto=" + stringForProtocol(proto)
+                        + ", family=" + stringForAddressFamily(family)
+                        + ", states=" + states);
+            }
+        } finally {
+            closeSocketQuietly(dumpFd);
+            closeSocketQuietly(destroyFd);
+        }
+    }
+
+    /**
+     * Close tcp sockets that match the following condition
+     *  1. TCP status is one of TCP_ESTABLISHED, TCP_SYN_SENT, and TCP_SYN_RECV
+     *  2. Owner uid of socket is not in the exemptUids
+     *  3. Owner uid of socket is in the ranges
+     *  4. Socket is not loopback
+     *  5. Socket is not adb socket
+     *
+     * @param ranges target uid ranges
+     * @param exemptUids uids to skip close socket
+     */
+    public static void destroyLiveTcpSockets(Set<Range<Integer>> ranges, Set<Integer> exemptUids)
+            throws SocketException, InterruptedIOException, ErrnoException {
+        final long startTimeMs = SystemClock.elapsedRealtime();
+        destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
+                (diagMsg) -> !exemptUids.contains(diagMsg.inetDiagMsg.idiag_uid)
+                        && containsUid(diagMsg, ranges)
+                        && !isLoopback(diagMsg)
+                        && !isAdbSocket(diagMsg));
+        final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+        Log.d(TAG, "Destroyed live tcp sockets for uids=" + ranges + " exemptUids=" + exemptUids
+                + " in " + durationMs + "ms");
+    }
+
+    /**
+     * Close tcp sockets that match the following condition
+     *  1. TCP status is one of TCP_ESTABLISHED, TCP_SYN_SENT, and TCP_SYN_RECV
+     *  2. Owner uid of socket is in the targetUids
+     *  3. Socket is not loopback
+     *  4. Socket is not adb socket
+     *
+     * @param ownerUids target uids to close sockets
+     */
+    public static void destroyLiveTcpSocketsByOwnerUids(Set<Integer> ownerUids)
+            throws SocketException, InterruptedIOException, ErrnoException {
+        final long startTimeMs = SystemClock.elapsedRealtime();
+        destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
+                (diagMsg) -> ownerUids.contains(diagMsg.inetDiagMsg.idiag_uid)
+                        && !isLoopback(diagMsg)
+                        && !isAdbSocket(diagMsg));
+        final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+        Log.d(TAG, "Destroyed live tcp sockets for uids=" + ownerUids + " in " + durationMs + "ms");
+    }
+
+    @Override
+    public String toString() {
+        return "InetDiagMessage{ "
+                + "nlmsghdr{"
+                + (mHeader == null ? "" : mHeader.toString(NETLINK_INET_DIAG)) + "}, "
+                + "inet_diag_msg{"
+                + (inetDiagMsg == null ? "" : inetDiagMsg.toString()) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
new file mode 100644
index 0000000..defc88a
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Base class for IPv6 neighbour discovery options.
+ */
+public class NdOption {
+    public static final int STRUCT_SIZE = 2;
+
+    /** The option type. */
+    public final byte type;
+    /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */
+    public final int length;
+
+    /** Constructs a new NdOption. */
+    NdOption(byte type, int length) {
+        this.type = type;
+        this.length = length;
+    }
+
+    /**
+     * Parses a neighbour discovery option.
+     *
+     * Parses (and consumes) the option if it is of a known type. If the option is of an unknown
+     * type, advances the buffer (so the caller can continue parsing if desired) and returns
+     * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot
+     * continue.
+     *
+     * No checks are performed on the length other than ensuring it is not 0, so if a caller wants
+     * to deal with options that might overflow the structure that contains them, it must explicitly
+     * set the buffer's limit to the position at which that structure ends.
+     *
+     * @param buf the buffer to parse.
+     * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option.
+     */
+    public static NdOption parse(@NonNull ByteBuffer buf) {
+        if (buf.remaining() < STRUCT_SIZE) return null;
+
+        // Peek the type without advancing the buffer.
+        byte type = buf.get(buf.position());
+        int length = Byte.toUnsignedInt(buf.get(buf.position() + 1));
+        if (length == 0) return null;
+
+        switch (type) {
+            case StructNdOptPref64.TYPE:
+                return StructNdOptPref64.parse(buf);
+
+            case StructNdOptRdnss.TYPE:
+                return StructNdOptRdnss.parse(buf);
+
+            default:
+                int newPosition = Math.min(buf.limit(), buf.position() + length * 8);
+                buf.position(newPosition);
+                return UNKNOWN;
+        }
+    }
+
+    void writeToByteBuffer(ByteBuffer buf) {
+        buf.put(type);
+        buf.put((byte) length);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length);
+    }
+
+    public static final NdOption UNKNOWN = new NdOption((byte) 0, 0);
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
new file mode 100644
index 0000000..bdf574d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import static android.system.OsConstants.AF_INET6;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages.
+ */
+public class NduseroptMessage extends NetlinkMessage {
+    public static final int STRUCT_SIZE = 16;
+
+    static final int NDUSEROPT_SRCADDR = 1;
+
+    /** The address family. Presumably always AF_INET6. */
+    public final byte family;
+    /**
+     * The total length in bytes of the options that follow this structure.
+     * Actually a 16-bit unsigned integer.
+     */
+    public final int opts_len;
+    /** The interface index on which the options were received. */
+    public final int ifindex;
+    /** The ICMP type of the packet that contained the options. */
+    public final byte icmp_type;
+    /** The ICMP code of the packet that contained the options. */
+    public final byte icmp_code;
+
+    /**
+     * ND option that was in this message.
+     * Even though the length field is called "opts_len", the kernel only ever sends one option per
+     * message. It is unlikely that this will ever change as it would break existing userspace code.
+     * But if it does, we can simply update this code, since userspace is typically newer than the
+     * kernel.
+     */
+    @Nullable
+    public final NdOption option;
+
+    /** The IP address that sent the packet containing the option. */
+    public final InetAddress srcaddr;
+
+    NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf)
+            throws UnknownHostException {
+        super(header);
+
+        // The structure itself.
+        buf.order(ByteOrder.nativeOrder());  // Restored in the finally clause inside parse().
+        final int start = buf.position();
+        family = buf.get();
+        buf.get();  // Skip 1 byte of padding.
+        opts_len = Short.toUnsignedInt(buf.getShort());
+        ifindex = buf.getInt();
+        icmp_type = buf.get();
+        icmp_code = buf.get();
+        buf.position(buf.position() + 6);  // Skip 6 bytes of padding.
+
+        // The ND option.
+        // Ensure we don't read past opts_len even if the option length is invalid.
+        // Note that this check is not really necessary since if the option length is not valid,
+        // this struct won't be very useful to the caller.
+        //
+        // It's safer to pass the slice of original ByteBuffer to just parse the ND option field,
+        // although parsing ND option might throw exception or return null, it won't break the
+        // original ByteBuffer position.
+        buf.order(ByteOrder.BIG_ENDIAN);
+        try {
+            final ByteBuffer slice = buf.slice();
+            slice.limit(opts_len);
+            option = NdOption.parse(slice);
+        } finally {
+            // Advance buffer position according to opts_len in the header. ND option length might
+            // be incorrect in the malformed packet.
+            int newPosition = start + STRUCT_SIZE + opts_len;
+            if (newPosition >= buf.limit()) {
+                throw new IllegalArgumentException("ND option extends past end of buffer");
+            }
+            buf.position(newPosition);
+        }
+
+        // The source address attribute.
+        StructNlAttr nla = StructNlAttr.parse(buf);
+        if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) {
+            throw new IllegalArgumentException("Invalid source address in ND useropt");
+        }
+        if (family == AF_INET6) {
+            // InetAddress.getByAddress only looks at the ifindex if the address type needs one.
+            srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex);
+        } else {
+            srcaddr = InetAddress.getByAddress(nla.nla_value);
+        }
+    }
+
+    /**
+     * Parses a StructNduseroptmsg from a {@link ByteBuffer}.
+     *
+     * @param header the netlink message header.
+     * @param buf The buffer from which to parse the option. The buffer's byte order must be
+     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
+     * @return the parsed option, or {@code null} if the option could not be parsed successfully
+     *         (for example, if it was truncated, or if the prefix length code was wrong).
+     */
+    @Nullable
+    public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) {
+        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
+        ByteOrder oldOrder = buf.order();
+        try {
+            return new NduseroptMessage(header, buf);
+        } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) {
+            // Not great, but better than throwing an exception that might crash the caller.
+            // Convention in this package is that null indicates that the option was truncated, so
+            // callers must already handle it.
+            return null;
+        } finally {
+            buf.order(oldOrder);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)",
+                family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type),
+                Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress());
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
new file mode 100644
index 0000000..44c51d8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2015 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.net.module.util.netlink;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Various constants and static helper methods for netlink communications.
+ *
+ * Values taken from:
+ *
+ *     include/uapi/linux/netfilter/nfnetlink.h
+ *     include/uapi/linux/netfilter/nfnetlink_conntrack.h
+ *     include/uapi/linux/netlink.h
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class NetlinkConstants {
+    private NetlinkConstants() {}
+
+    public static final int NLA_ALIGNTO = 4;
+    /**
+     * Flag for dumping struct tcp_info.
+     * Corresponding to enum definition in external/strace/linux/inet_diag.h.
+     */
+    public static final int INET_DIAG_MEMINFO = 1;
+
+    public static final int SOCKDIAG_MSG_HEADER_SIZE =
+            StructNlMsgHdr.STRUCT_SIZE + StructInetDiagMsg.STRUCT_SIZE;
+
+    /**
+     * Get the aligned length based on a Short type number.
+     */
+    public static final int alignedLengthOf(short length) {
+        final int intLength = (int) length & 0xffff;
+        return alignedLengthOf(intLength);
+    }
+
+    /**
+     * Get the aligned length based on a Integer type number.
+     */
+    public static final int alignedLengthOf(int length) {
+        if (length <= 0) return 0;
+        return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO);
+    }
+
+    /**
+     * Convert a address family type to a string.
+     */
+    public static String stringForAddressFamily(int family) {
+        if (family == OsConstants.AF_INET) return "AF_INET";
+        if (family == OsConstants.AF_INET6) return "AF_INET6";
+        if (family == OsConstants.AF_NETLINK) return "AF_NETLINK";
+        if (family == OsConstants.AF_UNSPEC) return "AF_UNSPEC";
+        return String.valueOf(family);
+    }
+
+    /**
+     * Convert a protocol type to a string.
+     */
+    public static String stringForProtocol(int protocol) {
+        if (protocol == OsConstants.IPPROTO_TCP) return "IPPROTO_TCP";
+        if (protocol == OsConstants.IPPROTO_UDP) return "IPPROTO_UDP";
+        return String.valueOf(protocol);
+    }
+
+    /**
+     * Convert a byte array to a hexadecimal string.
+     */
+    public static String hexify(byte[] bytes) {
+        if (bytes == null) return "(null)";
+        return toHexString(bytes, 0, bytes.length);
+    }
+
+    /**
+     * Convert a {@link ByteBuffer} to a hexadecimal string.
+     */
+    public static String hexify(ByteBuffer buffer) {
+        if (buffer == null) return "(null)";
+        return toHexString(
+                buffer.array(), buffer.position(), buffer.remaining());
+    }
+
+    // Known values for struct nlmsghdr nlm_type.
+    public static final short NLMSG_NOOP                    = 1;   // Nothing
+    public static final short NLMSG_ERROR                   = 2;   // Error
+    public static final short NLMSG_DONE                    = 3;   // End of a dump
+    public static final short NLMSG_OVERRUN                 = 4;   // Data lost
+    public static final short NLMSG_MAX_RESERVED            = 15;  // Max reserved value
+
+    public static final short RTM_NEWLINK                   = 16;
+    public static final short RTM_DELLINK                   = 17;
+    public static final short RTM_GETLINK                   = 18;
+    public static final short RTM_SETLINK                   = 19;
+    public static final short RTM_NEWADDR                   = 20;
+    public static final short RTM_DELADDR                   = 21;
+    public static final short RTM_GETADDR                   = 22;
+    public static final short RTM_NEWROUTE                  = 24;
+    public static final short RTM_DELROUTE                  = 25;
+    public static final short RTM_GETROUTE                  = 26;
+    public static final short RTM_NEWNEIGH                  = 28;
+    public static final short RTM_DELNEIGH                  = 29;
+    public static final short RTM_GETNEIGH                  = 30;
+    public static final short RTM_NEWRULE                   = 32;
+    public static final short RTM_DELRULE                   = 33;
+    public static final short RTM_GETRULE                   = 34;
+    public static final short RTM_NEWNDUSEROPT              = 68;
+
+    // Netfilter netlink message types are presented by two bytes: high byte subsystem and
+    // low byte operation. See the macro NFNL_SUBSYS_ID and NFNL_MSG_TYPE in
+    // include/uapi/linux/netfilter/nfnetlink.h
+    public static final short NFNL_SUBSYS_CTNETLINK         = 1;
+
+    public static final short IPCTNL_MSG_CT_NEW             = 0;
+    public static final short IPCTNL_MSG_CT_GET             = 1;
+    public static final short IPCTNL_MSG_CT_DELETE          = 2;
+    public static final short IPCTNL_MSG_CT_GET_CTRZERO     = 3;
+    public static final short IPCTNL_MSG_CT_GET_STATS_CPU   = 4;
+    public static final short IPCTNL_MSG_CT_GET_STATS       = 5;
+    public static final short IPCTNL_MSG_CT_GET_DYING       = 6;
+    public static final short IPCTNL_MSG_CT_GET_UNCONFIRMED = 7;
+
+    /* see include/uapi/linux/sock_diag.h */
+    public static final short SOCK_DIAG_BY_FAMILY = 20;
+    public static final short SOCK_DESTROY = 21;
+
+    // Netlink groups.
+    public static final int RTMGRP_LINK = 1;
+    public static final int RTMGRP_IPV4_IFADDR = 0x10;
+    public static final int RTMGRP_IPV6_IFADDR = 0x100;
+    public static final int RTMGRP_IPV6_ROUTE  = 0x400;
+    public static final int RTNLGRP_ND_USEROPT = 20;
+    public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
+
+    // Device flags.
+    public static final int IFF_UP       = 1 << 0;
+    public static final int IFF_LOWER_UP = 1 << 16;
+
+    // Known values for struct rtmsg rtm_protocol.
+    public static final short RTPROT_KERNEL     = 2;
+    public static final short RTPROT_RA         = 9;
+
+    // Known values for struct rtmsg rtm_scope.
+    public static final short RT_SCOPE_UNIVERSE = 0;
+
+    // Known values for struct rtmsg rtm_type.
+    public static final short RTN_UNICAST       = 1;
+
+    // Known values for struct rtmsg rtm_flags.
+    public static final int RTM_F_CLONED        = 0x200;
+
+    /**
+     * Convert a netlink message type to a string for control message.
+     */
+    @NonNull
+    private static String stringForCtlMsgType(short nlmType) {
+        switch (nlmType) {
+            case NLMSG_NOOP: return "NLMSG_NOOP";
+            case NLMSG_ERROR: return "NLMSG_ERROR";
+            case NLMSG_DONE: return "NLMSG_DONE";
+            case NLMSG_OVERRUN: return "NLMSG_OVERRUN";
+            default: return "unknown control message type: " + String.valueOf(nlmType);
+        }
+    }
+
+    /**
+     * Convert a netlink message type to a string for NETLINK_ROUTE.
+     */
+    @NonNull
+    private static String stringForRtMsgType(short nlmType) {
+        switch (nlmType) {
+            case RTM_NEWLINK: return "RTM_NEWLINK";
+            case RTM_DELLINK: return "RTM_DELLINK";
+            case RTM_GETLINK: return "RTM_GETLINK";
+            case RTM_SETLINK: return "RTM_SETLINK";
+            case RTM_NEWADDR: return "RTM_NEWADDR";
+            case RTM_DELADDR: return "RTM_DELADDR";
+            case RTM_GETADDR: return "RTM_GETADDR";
+            case RTM_NEWROUTE: return "RTM_NEWROUTE";
+            case RTM_DELROUTE: return "RTM_DELROUTE";
+            case RTM_GETROUTE: return "RTM_GETROUTE";
+            case RTM_NEWNEIGH: return "RTM_NEWNEIGH";
+            case RTM_DELNEIGH: return "RTM_DELNEIGH";
+            case RTM_GETNEIGH: return "RTM_GETNEIGH";
+            case RTM_NEWRULE: return "RTM_NEWRULE";
+            case RTM_DELRULE: return "RTM_DELRULE";
+            case RTM_GETRULE: return "RTM_GETRULE";
+            case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
+            default: return "unknown RTM type: " + String.valueOf(nlmType);
+        }
+    }
+
+    /**
+     * Convert a netlink message type to a string for NETLINK_INET_DIAG.
+     */
+    @NonNull
+    private static String stringForInetDiagMsgType(short nlmType) {
+        switch (nlmType) {
+            case SOCK_DIAG_BY_FAMILY: return "SOCK_DIAG_BY_FAMILY";
+            default: return "unknown SOCK_DIAG type: " + String.valueOf(nlmType);
+        }
+    }
+
+    /**
+     * Convert a netlink message type to a string for NETLINK_NETFILTER.
+     */
+    @NonNull
+    private static String stringForNfMsgType(short nlmType) {
+        final byte subsysId = (byte) (nlmType >> 8);
+        final byte msgType = (byte) nlmType;
+        switch (subsysId) {
+            case NFNL_SUBSYS_CTNETLINK:
+                switch (msgType) {
+                    case IPCTNL_MSG_CT_NEW: return "IPCTNL_MSG_CT_NEW";
+                    case IPCTNL_MSG_CT_GET: return "IPCTNL_MSG_CT_GET";
+                    case IPCTNL_MSG_CT_DELETE: return "IPCTNL_MSG_CT_DELETE";
+                    case IPCTNL_MSG_CT_GET_CTRZERO: return "IPCTNL_MSG_CT_GET_CTRZERO";
+                    case IPCTNL_MSG_CT_GET_STATS_CPU: return "IPCTNL_MSG_CT_GET_STATS_CPU";
+                    case IPCTNL_MSG_CT_GET_STATS: return "IPCTNL_MSG_CT_GET_STATS";
+                    case IPCTNL_MSG_CT_GET_DYING: return "IPCTNL_MSG_CT_GET_DYING";
+                    case IPCTNL_MSG_CT_GET_UNCONFIRMED: return "IPCTNL_MSG_CT_GET_UNCONFIRMED";
+                }
+                break;
+        }
+        return "unknown NETFILTER type: " + String.valueOf(nlmType);
+    }
+
+    /**
+     * Convert a netlink message type to a string by netlink family.
+     */
+    @NonNull
+    public static String stringForNlMsgType(short nlmType, int nlFamily) {
+        // Reserved control messages. The netlink family is ignored.
+        // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h.
+        if (nlmType <= NLMSG_MAX_RESERVED) return stringForCtlMsgType(nlmType);
+
+        // Netlink family messages. The netlink family is required. Note that the reason for using
+        // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are
+        // not constant.
+        if (nlFamily == OsConstants.NETLINK_ROUTE) return stringForRtMsgType(nlmType);
+        if (nlFamily == OsConstants.NETLINK_INET_DIAG) return stringForInetDiagMsgType(nlmType);
+        if (nlFamily == OsConstants.NETLINK_NETFILTER) return stringForNfMsgType(nlmType);
+
+        return "unknown type: " + String.valueOf(nlmType);
+    }
+
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            'A', 'B', 'C', 'D', 'E', 'F' };
+    /**
+     * Convert a byte array to a hexadecimal string.
+     */
+    public static String toHexString(byte[] array, int offset, int length) {
+        char[] buf = new char[length * 2];
+
+        int bufIndex = 0;
+        for (int i = offset; i < offset + length; i++) {
+            byte b = array[i];
+            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+        }
+
+        return new String(buf);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java
new file mode 100644
index 0000000..4831432
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for netlink error messages.
+ *
+ * @hide
+ */
+public class NetlinkErrorMessage extends NetlinkMessage {
+
+    /**
+     * Parse a netlink error message from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the netlink error message.
+     * @return the parsed netlink error message, or {@code null} if the netlink error message
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static NetlinkErrorMessage parse(@NonNull StructNlMsgHdr header,
+            @NonNull ByteBuffer byteBuffer) {
+        final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header);
+
+        errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer);
+        if (errorMsg.mNlMsgErr == null) {
+            return null;
+        }
+
+        return errorMsg;
+    }
+
+    private StructNlMsgErr mNlMsgErr;
+
+    NetlinkErrorMessage(@NonNull StructNlMsgHdr header) {
+        super(header);
+        mNlMsgErr = null;
+    }
+
+    public StructNlMsgErr getNlMsgError() {
+        return mNlMsgErr;
+    }
+
+    @Override
+    public String toString() {
+        return "NetlinkErrorMessage{ "
+                + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+                + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
new file mode 100644
index 0000000..9e1e26e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+/**
+ * NetlinkMessage base class for other, more specific netlink message types.
+ *
+ * Classes that extend NetlinkMessage should:
+ *     - implement a public static parse(StructNlMsgHdr, ByteBuffer) method
+ *     - returning either null (parse errors) or a new object of the subclass
+ *       type (cast-able to NetlinkMessage)
+ *
+ * NetlinkMessage.parse() should be updated to know which nlmsg_type values
+ * correspond with which message subclasses.
+ *
+ * @hide
+ */
+public class NetlinkMessage {
+    private static final String TAG = "NetlinkMessage";
+
+    /**
+     * Parsing netlink messages for reserved control message or specific netlink message. The
+     * netlink family is required for parsing specific netlink message. See man-pages/netlink.
+     */
+    @Nullable
+    public static NetlinkMessage parse(@NonNull ByteBuffer byteBuffer, int nlFamily) {
+        final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1;
+        final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer);
+        if (nlmsghdr == null) {
+            return null;
+        }
+
+        final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+        final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE;
+        if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) {
+            // Malformed message or runt buffer.  Pretend the buffer was consumed.
+            byteBuffer.position(byteBuffer.limit());
+            return null;
+        }
+
+        // Reserved control messages. The netlink family is ignored.
+        // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h.
+        if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) {
+            return parseCtlMessage(nlmsghdr, byteBuffer, payloadLength);
+        }
+
+        // Netlink family messages. The netlink family is required. Note that the reason for using
+        // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are
+        // not constant.
+        final NetlinkMessage parsed;
+        if (nlFamily == OsConstants.NETLINK_ROUTE) {
+            parsed = parseRtMessage(nlmsghdr, byteBuffer);
+        } else if (nlFamily == OsConstants.NETLINK_INET_DIAG) {
+            parsed = parseInetDiagMessage(nlmsghdr, byteBuffer);
+        } else if (nlFamily == OsConstants.NETLINK_NETFILTER) {
+            parsed = parseNfMessage(nlmsghdr, byteBuffer);
+        } else {
+            parsed = null;
+        }
+
+        // Advance to the end of the message, regardless of whether the parsing code consumed
+        // all of it or not.
+        byteBuffer.position(startPosition + messageLength);
+
+        return parsed;
+    }
+
+    @NonNull
+    protected final StructNlMsgHdr mHeader;
+
+    public NetlinkMessage(@NonNull StructNlMsgHdr nlmsghdr) {
+        mHeader = nlmsghdr;
+    }
+
+    @NonNull
+    public StructNlMsgHdr getHeader() {
+        return mHeader;
+    }
+
+    @Override
+    public String toString() {
+        // The netlink family is not provided to StructNlMsgHdr#toString because NetlinkMessage
+        // doesn't store the information. So the netlink message type can't be transformed into
+        // a string by StructNlMsgHdr#toString and just keep as an integer. The specific message
+        // which inherits NetlinkMessage could override NetlinkMessage#toString and provide the
+        // specific netlink family to StructNlMsgHdr#toString.
+        return "NetlinkMessage{" + mHeader.toString() + "}";
+    }
+
+    @NonNull
+    private static NetlinkMessage parseCtlMessage(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull ByteBuffer byteBuffer, int payloadLength) {
+        switch (nlmsghdr.nlmsg_type) {
+            case NetlinkConstants.NLMSG_ERROR:
+                return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer);
+            default: {
+                // Other netlink control messages. Just parse the header for now,
+                // pretending the whole message was consumed.
+                byteBuffer.position(byteBuffer.position() + payloadLength);
+                return new NetlinkMessage(nlmsghdr);
+            }
+        }
+    }
+
+    @Nullable
+    private static NetlinkMessage parseRtMessage(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull ByteBuffer byteBuffer) {
+        switch (nlmsghdr.nlmsg_type) {
+            case NetlinkConstants.RTM_NEWLINK:
+            case NetlinkConstants.RTM_DELLINK:
+                return (NetlinkMessage) RtNetlinkLinkMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWADDR:
+            case NetlinkConstants.RTM_DELADDR:
+                return (NetlinkMessage) RtNetlinkAddressMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWROUTE:
+            case NetlinkConstants.RTM_DELROUTE:
+                return (NetlinkMessage) RtNetlinkRouteMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWNEIGH:
+            case NetlinkConstants.RTM_DELNEIGH:
+            case NetlinkConstants.RTM_GETNEIGH:
+                return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWNDUSEROPT:
+                return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer);
+            default: return null;
+        }
+    }
+
+    @Nullable
+    private static NetlinkMessage parseInetDiagMessage(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull ByteBuffer byteBuffer) {
+        switch (nlmsghdr.nlmsg_type) {
+            case NetlinkConstants.SOCK_DIAG_BY_FAMILY:
+                return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer);
+            default: return null;
+        }
+    }
+
+    @Nullable
+    private static NetlinkMessage parseNfMessage(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull ByteBuffer byteBuffer) {
+        switch (nlmsghdr.nlmsg_type) {
+            case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+                    | NetlinkConstants.IPCTNL_MSG_CT_NEW:
+            case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+                    | NetlinkConstants.IPCTNL_MSG_CT_DELETE:
+                return (NetlinkMessage) ConntrackMessage.parse(nlmsghdr, byteBuffer);
+            default: return null;
+        }
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
new file mode 100644
index 0000000..33bd36d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -0,0 +1,311 @@
+/*
+ * 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.net.module.util.netlink;
+
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.EIO;
+import static android.system.OsConstants.EPROTO;
+import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+import static android.system.OsConstants.NETLINK_ROUTE;
+import static android.system.OsConstants.SOCK_CLOEXEC;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import android.net.util.SocketUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Inet6Address;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * Utilities for netlink related class that may not be able to fit into a specific class.
+ * @hide
+ */
+public class NetlinkUtils {
+    private static final String TAG = "NetlinkUtils";
+    /** Corresponds to enum from bionic/libc/include/netinet/tcp.h. */
+    private static final int TCP_ESTABLISHED = 1;
+    private static final int TCP_SYN_SENT = 2;
+    private static final int TCP_SYN_RECV = 3;
+
+    public static final int TCP_ALIVE_STATE_FILTER =
+            (1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV);
+
+    public static final int UNKNOWN_MARK = 0xffffffff;
+    public static final int NULL_MASK = 0;
+
+    // Initial mark value corresponds to the initValue in system/netd/include/Fwmark.h.
+    public static final int INIT_MARK_VALUE = 0;
+    // Corresponds to enum definition in bionic/libc/kernel/uapi/linux/inet_diag.h
+    public static final int INET_DIAG_INFO = 2;
+    public static final int INET_DIAG_MARK = 15;
+
+    public static final long IO_TIMEOUT_MS = 300L;
+
+    public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+    public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
+
+    /**
+     * Return whether the input ByteBuffer contains enough remaining bytes for
+     * {@code StructNlMsgHdr}.
+     */
+    public static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) {
+        return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE;
+    }
+
+    /**
+     * Parse netlink error message
+     *
+     * @param bytes byteBuffer to parse netlink error message
+     * @return NetlinkErrorMessage if bytes contains valid NetlinkErrorMessage, else {@code null}
+     */
+    @Nullable
+    private static NetlinkErrorMessage parseNetlinkErrorMessage(ByteBuffer bytes) {
+        final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
+        if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) {
+            return null;
+        }
+
+        final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+        final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE;
+        if (payloadLength < 0 || payloadLength > bytes.remaining()) {
+            // Malformed message or runt buffer.  Pretend the buffer was consumed.
+            bytes.position(bytes.limit());
+            return null;
+        }
+
+        return NetlinkErrorMessage.parse(nlmsghdr, bytes);
+    }
+
+    /**
+     * Receive netlink ack message and check error
+     *
+     * @param fd fd to read netlink message
+     */
+    public static void receiveNetlinkAck(final FileDescriptor fd)
+            throws InterruptedIOException, ErrnoException {
+        final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
+        // recvMessage() guaranteed to not return null if it did not throw.
+        final NetlinkErrorMessage response = parseNetlinkErrorMessage(bytes);
+        if (response != null && response.getNlMsgError() != null) {
+            final int errno = response.getNlMsgError().error;
+            if (errno != 0) {
+                // TODO: consider ignoring EINVAL (-22), which appears to be
+                // normal when probing a neighbor for which the kernel does
+                // not already have / no longer has a link layer address.
+                Log.e(TAG, "receiveNetlinkAck, errmsg=" + response.toString());
+                // Note: convert kernel errnos (negative) into userspace errnos (positive).
+                throw new ErrnoException(response.toString(), Math.abs(errno));
+            }
+        } else {
+            final String errmsg;
+            if (response == null) {
+                bytes.position(0);
+                errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+            } else {
+                errmsg = response.toString();
+            }
+            Log.e(TAG, "receiveNetlinkAck, errmsg=" + errmsg);
+            throw new ErrnoException(errmsg, EPROTO);
+        }
+    }
+
+    /**
+     * Send one netlink message to kernel via netlink socket.
+     *
+     * @param nlProto netlink protocol type.
+     * @param msg the raw bytes of netlink message to be sent.
+     */
+    public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
+        final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+        final FileDescriptor fd = netlinkSocketForProto(nlProto);
+
+        try {
+            connectSocketToNetlink(fd);
+            sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
+            receiveNetlinkAck(fd);
+        } catch (InterruptedIOException e) {
+            Log.e(TAG, errPrefix, e);
+            throw new ErrnoException(errPrefix, ETIMEDOUT, e);
+        } catch (SocketException e) {
+            Log.e(TAG, errPrefix, e);
+            throw new ErrnoException(errPrefix, EIO, e);
+        } finally {
+            try {
+                SocketUtils.closeSocket(fd);
+            } catch (IOException e) {
+                // Nothing we can do here
+            }
+        }
+    }
+
+    /**
+     * Send an RTM_NEWADDR message to kernel to add or update an IPv6 address.
+     *
+     * @param ifIndex interface index.
+     * @param ip IPv6 address to be added.
+     * @param prefixlen IPv6 address prefix length.
+     * @param flags IPv6 address flags.
+     * @param scope IPv6 address scope.
+     * @param preferred The preferred lifetime of IPv6 address.
+     * @param valid The valid lifetime of IPv6 address.
+     */
+    public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final Inet6Address ip,
+            short prefixlen, int flags, byte scope, long preferred, long valid) {
+        Objects.requireNonNull(ip, "IPv6 address to be added should not be null.");
+        final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip,
+                prefixlen, flags, scope, ifIndex, preferred, valid);
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+            return true;
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Fail to send RTM_NEWADDR to add " + ip.getHostAddress(), e);
+            return false;
+        }
+    }
+
+    /**
+     * Send an RTM_DELADDR message to kernel to delete an IPv6 address.
+     *
+     * @param ifIndex interface index.
+     * @param ip IPv6 address to be deleted.
+     * @param prefixlen IPv6 address prefix length.
+     */
+    public static boolean sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip,
+            short prefixlen) {
+        Objects.requireNonNull(ip, "IPv6 address to be deleted should not be null.");
+        final byte[] msg = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqNo*/, ip,
+                prefixlen, ifIndex);
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+            return true;
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Fail to send RTM_DELADDR to delete " + ip.getHostAddress(), e);
+            return false;
+        }
+    }
+
+    /**
+     * Create netlink socket with the given netlink protocol type.
+     *
+     * @return fd the fileDescriptor of the socket.
+     * @throws ErrnoException if the FileDescriptor not connect to be created successfully
+     */
+    public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException {
+        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+        return fd;
+    }
+
+    /**
+     * Construct a netlink inet_diag socket.
+     */
+    public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException {
+        return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG);
+    }
+
+    /**
+     * Connect the given file descriptor to the Netlink interface to the kernel.
+     *
+     * The fd must be a SOCK_DGRAM socket : create it with {@link #netlinkSocketForProto}
+     *
+     * @throws ErrnoException if the {@code fd} could not connect to kernel successfully
+     * @throws SocketException if there is an error accessing a socket.
+     */
+    public static void connectSocketToNetlink(FileDescriptor fd)
+            throws ErrnoException, SocketException {
+        Os.connect(fd, makeNetlinkSocketAddress(0, 0));
+    }
+
+    private static void checkTimeout(long timeoutMs) {
+        if (timeoutMs < 0) {
+            throw new IllegalArgumentException("Negative timeouts not permitted");
+        }
+    }
+
+    /**
+     * Wait up to |timeoutMs| (or until underlying socket error) for a
+     * netlink message of at most |bufsize| size.
+     *
+     * Multi-threaded calls with different timeouts will cause unexpected results.
+     */
+    public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
+            throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+        checkTimeout(timeoutMs);
+
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
+
+        final ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
+        final int length = Os.read(fd, byteBuffer);
+        if (length == bufsize) {
+            Log.w(TAG, "maximum read");
+        }
+        byteBuffer.position(0);
+        byteBuffer.limit(length);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        return byteBuffer;
+    }
+
+    /**
+     * Send a message to a peer to which this socket has previously connected.
+     *
+     * This waits at most |timeoutMs| milliseconds for the send to complete, will get the exception
+     * if it times out.
+     */
+    public static int sendMessage(
+            FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
+            throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+        checkTimeout(timeoutMs);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+        return Os.write(fd, bytes, offset, count);
+    }
+
+    private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+    /**
+     * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to
+     * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t
+     * ticks at CLOCKS_PER_SEC (1000000).
+     *
+     * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference
+     * of clock_t used in clock() and times().
+     */
+    public static long ticksToMilliSeconds(int intClockTicks) {
+        final long longClockTicks = intClockTicks & 0xffffffffL;
+        return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+    }
+
+    private NetlinkUtils() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
new file mode 100644
index 0000000..cbe0ab0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.HexDump;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink address messages.
+ *
+ * RtNetlinkAddressMessage.parse() must be called with a ByteBuffer that contains exactly one
+ * netlink message.
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkAddressMessage extends NetlinkMessage {
+    public static final short IFA_ADDRESS        = 1;
+    public static final short IFA_CACHEINFO      = 6;
+    public static final short IFA_FLAGS          = 8;
+
+    private int mFlags;
+    @NonNull
+    private StructIfaddrMsg mIfaddrmsg;
+    @NonNull
+    private InetAddress mIpAddress;
+    @Nullable
+    private StructIfacacheInfo mIfacacheInfo;
+
+    @VisibleForTesting
+    public RtNetlinkAddressMessage(@NonNull final StructNlMsgHdr header,
+            @NonNull final StructIfaddrMsg ifaddrMsg,
+            @NonNull final InetAddress ipAddress,
+            @Nullable final StructIfacacheInfo structIfacacheInfo,
+            int flags) {
+        super(header);
+        mIfaddrmsg = ifaddrMsg;
+        mIpAddress = ipAddress;
+        mIfacacheInfo = structIfacacheInfo;
+        mFlags = flags;
+    }
+    private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
+        this(header, null, null, null, 0);
+    }
+
+    public int getFlags() {
+        return mFlags;
+    }
+
+    @NonNull
+    public StructIfaddrMsg getIfaddrHeader() {
+        return mIfaddrmsg;
+    }
+
+    @NonNull
+    public InetAddress getIpAddress() {
+        return mIpAddress;
+    }
+
+    @Nullable
+    public StructIfacacheInfo getIfacacheInfo() {
+        return mIfacacheInfo;
+    }
+
+    /**
+     * Parse rtnetlink address message from {@link ByteBuffer}. This method must be called with a
+     * ByteBuffer that contains exactly one netlink message.
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @Nullable
+    public static RtNetlinkAddressMessage parse(@NonNull final StructNlMsgHdr header,
+            @NonNull final ByteBuffer byteBuffer) {
+        final RtNetlinkAddressMessage addrMsg = new RtNetlinkAddressMessage(header);
+
+        addrMsg.mIfaddrmsg = StructIfaddrMsg.parse(byteBuffer);
+        if (addrMsg.mIfaddrmsg == null) return null;
+
+        // IFA_ADDRESS
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFA_ADDRESS, byteBuffer);
+        if (nlAttr == null) return null;
+        addrMsg.mIpAddress = nlAttr.getValueAsInetAddress();
+        if (addrMsg.mIpAddress == null) return null;
+
+        // IFA_CACHEINFO
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFA_CACHEINFO, byteBuffer);
+        if (nlAttr != null) {
+            addrMsg.mIfacacheInfo = StructIfacacheInfo.parse(nlAttr.getValueAsByteBuffer());
+        }
+
+        // The first 8 bits of flags are in the ifaddrmsg.
+        addrMsg.mFlags = addrMsg.mIfaddrmsg.flags;
+        // IFA_FLAGS. All the flags are in the IF_FLAGS attribute. This should always be present,
+        // and will overwrite the flags set above.
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer);
+        if (nlAttr == null) return null;
+        final Integer value = nlAttr.getValueAsInteger();
+        if (value == null) return null;
+        addrMsg.mFlags = value;
+
+        return addrMsg;
+    }
+
+    /**
+     * Write a rtnetlink address message to {@link ByteBuffer}.
+     */
+    @VisibleForTesting
+    protected void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mIfaddrmsg.pack(byteBuffer);
+
+        final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, mIpAddress);
+        address.pack(byteBuffer);
+
+        if (mIfacacheInfo != null) {
+            final StructNlAttr cacheInfo = new StructNlAttr(IFA_CACHEINFO,
+                    mIfacacheInfo.writeToBytes());
+            cacheInfo.pack(byteBuffer);
+        }
+
+        // If IFA_FLAGS attribute isn't present on the wire at parsing netlink message, it will
+        // still be packed to ByteBuffer even if the flag is 0.
+        final StructNlAttr flags = new StructNlAttr(IFA_FLAGS, mFlags);
+        flags.pack(byteBuffer);
+    }
+
+    /**
+     * A convenience method to create a RTM_NEWADDR message.
+     */
+    public static byte[] newRtmNewAddressMessage(int seqNo, @NonNull final InetAddress ip,
+            short prefixlen, int flags, byte scope, int ifIndex, long preferred, long valid) {
+        Objects.requireNonNull(ip, "IP address to be set via netlink message cannot be null");
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWADDR;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
+        nlmsghdr.nlmsg_seq = seqNo;
+
+        final RtNetlinkAddressMessage msg = new RtNetlinkAddressMessage(nlmsghdr);
+        final byte family =
+                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+        // IFA_FLAGS attribute is always present within this method, just set flags from
+        // ifaddrmsg to 0. kernel will prefer the flags from IFA_FLAGS attribute.
+        msg.mIfaddrmsg =
+                new StructIfaddrMsg(family, prefixlen, (short) 0 /* flags */, scope, ifIndex);
+        msg.mIpAddress = ip;
+        msg.mIfacacheInfo = new StructIfacacheInfo(preferred, valid, 0 /* cstamp */,
+                0 /* tstamp */);
+        msg.mFlags = flags;
+
+        final byte[] bytes = new byte[msg.getRequiredSpace()];
+        nlmsghdr.nlmsg_len = bytes.length;
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        msg.pack(byteBuffer);
+        return bytes;
+    }
+
+    /**
+     * A convenience method to create a RTM_DELADDR message.
+     */
+    public static byte[] newRtmDelAddressMessage(int seqNo, @NonNull final InetAddress ip,
+            short prefixlen, int ifIndex) {
+        Objects.requireNonNull(ip, "IP address to be deleted via netlink message cannot be null");
+
+        final int ifaAddrAttrLength = NetlinkConstants.alignedLengthOf(
+                StructNlAttr.NLA_HEADERLEN + ip.getAddress().length);
+        final int length = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE
+                + ifaAddrAttrLength;
+        final byte[] bytes = new byte[length];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_len = length;
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+        nlmsghdr.nlmsg_seq = seqNo;
+        nlmsghdr.pack(byteBuffer);
+
+        final byte family =
+                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+        // Actually kernel ignores scope and flags(only deal with IFA_F_MANAGETEMPADDR, it
+        // indicates that all relevant IPv6 temporary addresses should be deleted as well when
+        // user space intends to delete a global IPv6 address with IFA_F_MANAGETEMPADDR), so
+        // far IFA_F_MANAGETEMPADDR flag isn't used in user space, it's fine to ignore it.
+        // However, we need to add IFA_FLAGS attribute in RTM_DELADDR if flags parsing should
+        // be supported in the future.
+        final StructIfaddrMsg ifaddrmsg = new StructIfaddrMsg(family, prefixlen,
+                (short) 0 /* flags */, (short) 0 /* scope */, ifIndex);
+        ifaddrmsg.pack(byteBuffer);
+
+        final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, ip);
+        address.pack(byteBuffer);
+
+        return bytes;
+    }
+
+    // This function helper gives the required buffer size for IFA_ADDRESS, IFA_CACHEINFO and
+    // IFA_FLAGS attributes encapsulation. However, that's not a mandatory requirement for all
+    // RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an
+    // IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes
+    // are necessary to carry when constructing a RtNetlinkAddressMessage.
+    private int getRequiredSpace() {
+        int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE;
+        // IFA_ADDRESS attr
+        spaceRequired += NetlinkConstants.alignedLengthOf(
+                StructNlAttr.NLA_HEADERLEN + mIpAddress.getAddress().length);
+        // IFA_CACHEINFO attr
+        spaceRequired += NetlinkConstants.alignedLengthOf(
+                StructNlAttr.NLA_HEADERLEN + StructIfacacheInfo.STRUCT_SIZE);
+        // IFA_FLAGS "u32" attr
+        spaceRequired += StructNlAttr.NLA_HEADERLEN + 4;
+        return spaceRequired;
+    }
+
+    @Override
+    public String toString() {
+        return "RtNetlinkAddressMessage{ "
+                + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+                + "Ifaddrmsg{" + mIfaddrmsg.toString() + "}, "
+                + "IP Address{" + mIpAddress.getHostAddress() + "}, "
+                + "IfacacheInfo{" + (mIfacacheInfo == null ? "" : mIfacacheInfo.toString()) + "}, "
+                + "Address Flags{" + HexDump.toHexString(mFlags) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
new file mode 100644
index 0000000..92ec0c4
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink link messages.
+ *
+ * RtNetlinkLinkMessage.parse() must be called with a ByteBuffer that contains exactly one netlink
+ * message.
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkLinkMessage extends NetlinkMessage {
+    public static final short IFLA_ADDRESS   = 1;
+    public static final short IFLA_IFNAME    = 3;
+    public static final short IFLA_MTU       = 4;
+
+    private int mMtu;
+    @NonNull
+    private StructIfinfoMsg mIfinfomsg;
+    @Nullable
+    private MacAddress mHardwareAddress;
+    @Nullable
+    private String mInterfaceName;
+
+    private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr header) {
+        super(header);
+        mIfinfomsg = null;
+        mMtu = 0;
+        mHardwareAddress = null;
+        mInterfaceName = null;
+    }
+
+    public int getMtu() {
+        return mMtu;
+    }
+
+    @NonNull
+    public StructIfinfoMsg getIfinfoHeader() {
+        return mIfinfomsg;
+    }
+
+    @Nullable
+    public MacAddress getHardwareAddress() {
+        return mHardwareAddress;
+    }
+
+    @Nullable
+    public String getInterfaceName() {
+        return mInterfaceName;
+    }
+
+    /**
+     * Parse rtnetlink link message from {@link ByteBuffer}. This method must be called with a
+     * ByteBuffer that contains exactly one netlink message.
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @Nullable
+    public static RtNetlinkLinkMessage parse(@NonNull final StructNlMsgHdr header,
+            @NonNull final ByteBuffer byteBuffer) {
+        final RtNetlinkLinkMessage linkMsg = new RtNetlinkLinkMessage(header);
+
+        linkMsg.mIfinfomsg = StructIfinfoMsg.parse(byteBuffer);
+        if (linkMsg.mIfinfomsg == null) return null;
+
+        // IFLA_MTU
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFLA_MTU, byteBuffer);
+        if (nlAttr != null) {
+            linkMsg.mMtu = nlAttr.getValueAsInt(0 /* default value */);
+        }
+
+        // IFLA_ADDRESS
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFLA_ADDRESS, byteBuffer);
+        if (nlAttr != null) {
+            linkMsg.mHardwareAddress = nlAttr.getValueAsMacAddress();
+        }
+
+        // IFLA_IFNAME
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFLA_IFNAME, byteBuffer);
+        if (nlAttr != null) {
+            linkMsg.mInterfaceName = nlAttr.getValueAsString();
+        }
+
+        return linkMsg;
+    }
+
+    /**
+     * Write a rtnetlink link message to {@link ByteBuffer}.
+     */
+    @VisibleForTesting
+    protected void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mIfinfomsg.pack(byteBuffer);
+
+        if (mMtu != 0) {
+            final StructNlAttr mtu = new StructNlAttr(IFLA_MTU, mMtu);
+            mtu.pack(byteBuffer);
+        }
+        if (mHardwareAddress != null) {
+            final StructNlAttr hardwareAddress = new StructNlAttr(IFLA_ADDRESS, mHardwareAddress);
+            hardwareAddress.pack(byteBuffer);
+        }
+        if (mInterfaceName != null) {
+            final StructNlAttr ifname = new StructNlAttr(IFLA_IFNAME, mInterfaceName);
+            ifname.pack(byteBuffer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RtNetlinkLinkMessage{ "
+                + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+                + "Ifinfomsg{" + mIfinfomsg.toString() + "}, "
+                + "Hardware Address{" + mHardwareAddress + "}, "
+                + "MTU{" + mMtu + "}, "
+                + "Ifname{" + mInterfaceName + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java
new file mode 100644
index 0000000..4a09015
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink neighbor messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class RtNetlinkNeighborMessage extends NetlinkMessage {
+    public static final short NDA_UNSPEC    = 0;
+    public static final short NDA_DST       = 1;
+    public static final short NDA_LLADDR    = 2;
+    public static final short NDA_CACHEINFO = 3;
+    public static final short NDA_PROBES    = 4;
+    public static final short NDA_VLAN      = 5;
+    public static final short NDA_PORT      = 6;
+    public static final short NDA_VNI       = 7;
+    public static final short NDA_IFINDEX   = 8;
+    public static final short NDA_MASTER    = 9;
+
+    /**
+     * Parse routing socket netlink neighbor message from ByteBuffer.
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @Nullable
+    public static RtNetlinkNeighborMessage parse(@NonNull StructNlMsgHdr header,
+            @NonNull ByteBuffer byteBuffer) {
+        final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);
+
+        neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer);
+        if (neighMsg.mNdmsg == null) {
+            return null;
+        }
+
+        // Some of these are message-type dependent, and not always present.
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer);
+        if (nlAttr != null) {
+            neighMsg.mDestination = nlAttr.getValueAsInetAddress();
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer);
+        if (nlAttr != null) {
+            neighMsg.mLinkLayerAddr = nlAttr.nla_value;
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer);
+        if (nlAttr != null) {
+            neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
+        }
+
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
+        if (nlAttr != null) {
+            neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
+        }
+
+        final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+        final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
+                neighMsg.mHeader.nlmsg_len - kMinConsumed);
+        if (byteBuffer.remaining() < kAdditionalSpace) {
+            byteBuffer.position(byteBuffer.limit());
+        } else {
+            byteBuffer.position(baseOffset + kAdditionalSpace);
+        }
+
+        return neighMsg;
+    }
+
+    /**
+     * A convenience method to create an RTM_GETNEIGH request message.
+     */
+    public static byte[] newGetNeighborsRequest(int seqNo) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+        final byte[] bytes = new byte[length];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_len = length;
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+        nlmsghdr.nlmsg_seq = seqNo;
+        nlmsghdr.pack(byteBuffer);
+
+        final StructNdMsg ndmsg = new StructNdMsg();
+        ndmsg.pack(byteBuffer);
+
+        return bytes;
+    }
+
+    /**
+     * A convenience method to create an RTM_NEWNEIGH message, to modify
+     * the kernel's state information for a specific neighbor.
+     */
+    public static byte[] newNewNeighborMessage(
+            int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) {
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+        nlmsghdr.nlmsg_seq = seqNo;
+
+        final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr);
+        msg.mNdmsg = new StructNdMsg();
+        msg.mNdmsg.ndm_family =
+                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+        msg.mNdmsg.ndm_ifindex = ifIndex;
+        msg.mNdmsg.ndm_state = nudState;
+        msg.mDestination = ip;
+        msg.mLinkLayerAddr = llAddr;  // might be null
+
+        final byte[] bytes = new byte[msg.getRequiredSpace()];
+        nlmsghdr.nlmsg_len = bytes.length;
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        msg.pack(byteBuffer);
+        return bytes;
+    }
+
+    private StructNdMsg mNdmsg;
+    private InetAddress mDestination;
+    private byte[] mLinkLayerAddr;
+    private int mNumProbes;
+    private StructNdaCacheInfo mCacheInfo;
+
+    private RtNetlinkNeighborMessage(@NonNull StructNlMsgHdr header) {
+        super(header);
+        mNdmsg = null;
+        mDestination = null;
+        mLinkLayerAddr = null;
+        mNumProbes = 0;
+        mCacheInfo = null;
+    }
+
+    public StructNdMsg getNdHeader() {
+        return mNdmsg;
+    }
+
+    public InetAddress getDestination() {
+        return mDestination;
+    }
+
+    public byte[] getLinkLayerAddress() {
+        return mLinkLayerAddr;
+    }
+
+    public int getProbes() {
+        return mNumProbes;
+    }
+
+    public StructNdaCacheInfo getCacheInfo() {
+        return mCacheInfo;
+    }
+
+    private int getRequiredSpace() {
+        int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+        if (mDestination != null) {
+            spaceRequired += NetlinkConstants.alignedLengthOf(
+                    StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length);
+        }
+        if (mLinkLayerAddr != null) {
+            spaceRequired += NetlinkConstants.alignedLengthOf(
+                    StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length);
+        }
+        // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO
+        // attributes appended.  Fix later, if necessary.
+        return spaceRequired;
+    }
+
+    private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) {
+        final StructNlAttr nlAttr = new StructNlAttr();
+        nlAttr.nla_type = nlType;
+        nlAttr.nla_value = nlValue;
+        nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length);
+        nlAttr.pack(byteBuffer);
+    }
+
+    /**
+     * Write a neighbor discovery netlink message to {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mNdmsg.pack(byteBuffer);
+
+        if (mDestination != null) {
+            packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer);
+        }
+        if (mLinkLayerAddr != null) {
+            packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress();
+        return "RtNetlinkNeighborMessage{ "
+                + "nlmsghdr{"
+                + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_ROUTE)) + "}, "
+                + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, "
+                + "destination{" + ipLiteral + "} "
+                + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} "
+                + "probes{" + mNumProbes + "} "
+                + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
new file mode 100644
index 0000000..9acac69
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
+
+import android.annotation.SuppressLint;
+import android.net.IpPrefix;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink route messages.
+ *
+ * RtNetlinkRouteMessage.parse() must be called with a ByteBuffer that contains exactly one
+ * netlink message.
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkRouteMessage extends NetlinkMessage {
+    public static final short RTA_DST           = 1;
+    public static final short RTA_OIF           = 4;
+    public static final short RTA_GATEWAY       = 5;
+    public static final short RTA_CACHEINFO     = 12;
+
+    private int mIfindex;
+    @NonNull
+    private StructRtMsg mRtmsg;
+    @NonNull
+    private IpPrefix mDestination;
+    @Nullable
+    private InetAddress mGateway;
+    @Nullable
+    private StructRtaCacheInfo mRtaCacheInfo;
+
+    private RtNetlinkRouteMessage(StructNlMsgHdr header) {
+        super(header);
+        mRtmsg = null;
+        mDestination = null;
+        mGateway = null;
+        mIfindex = 0;
+        mRtaCacheInfo = null;
+    }
+
+    public int getInterfaceIndex() {
+        return mIfindex;
+    }
+
+    @NonNull
+    public StructRtMsg getRtMsgHeader() {
+        return mRtmsg;
+    }
+
+    @NonNull
+    public IpPrefix getDestination() {
+        return mDestination;
+    }
+
+    @Nullable
+    public InetAddress getGateway() {
+        return mGateway;
+    }
+
+    @Nullable
+    public StructRtaCacheInfo getRtaCacheInfo() {
+        return mRtaCacheInfo;
+    }
+
+    /**
+     * Check whether the address families of destination and gateway match rtm_family in
+     * StructRtmsg.
+     *
+     * For example, IPv4-mapped IPv6 addresses as an IPv6 address will be always converted to IPv4
+     * address, that's incorrect when upper layer creates a new {@link RouteInfo} class instance
+     * for IPv6 route with the converted IPv4 gateway.
+     */
+    private static boolean matchRouteAddressFamily(@NonNull final InetAddress address,
+            int family) {
+        return ((address instanceof Inet4Address) && (family == AF_INET))
+                || ((address instanceof Inet6Address) && (family == AF_INET6));
+    }
+
+    /**
+     * Parse rtnetlink route message from {@link ByteBuffer}. This method must be called with a
+     * ByteBuffer that contains exactly one netlink message.
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @SuppressLint("NewApi")
+    @Nullable
+    public static RtNetlinkRouteMessage parse(@NonNull final StructNlMsgHdr header,
+            @NonNull final ByteBuffer byteBuffer) {
+        final RtNetlinkRouteMessage routeMsg = new RtNetlinkRouteMessage(header);
+
+        routeMsg.mRtmsg = StructRtMsg.parse(byteBuffer);
+        if (routeMsg.mRtmsg == null) return null;
+        int rtmFamily = routeMsg.mRtmsg.family;
+
+        // RTA_DST
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(RTA_DST, byteBuffer);
+        if (nlAttr != null) {
+            final InetAddress destination = nlAttr.getValueAsInetAddress();
+            // If the RTA_DST attribute is malformed, return null.
+            if (destination == null) return null;
+            // If the address family of destination doesn't match rtm_family, return null.
+            if (!matchRouteAddressFamily(destination, rtmFamily)) return null;
+            routeMsg.mDestination = new IpPrefix(destination, routeMsg.mRtmsg.dstLen);
+        } else if (rtmFamily == AF_INET) {
+            routeMsg.mDestination = new IpPrefix(IPV4_ADDR_ANY, 0);
+        } else if (rtmFamily == AF_INET6) {
+            routeMsg.mDestination = new IpPrefix(IPV6_ADDR_ANY, 0);
+        } else {
+            return null;
+        }
+
+        // RTA_GATEWAY
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(RTA_GATEWAY, byteBuffer);
+        if (nlAttr != null) {
+            routeMsg.mGateway = nlAttr.getValueAsInetAddress();
+            // If the RTA_GATEWAY attribute is malformed, return null.
+            if (routeMsg.mGateway == null) return null;
+            // If the address family of gateway doesn't match rtm_family, return null.
+            if (!matchRouteAddressFamily(routeMsg.mGateway, rtmFamily)) return null;
+        }
+
+        // RTA_OIF
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(RTA_OIF, byteBuffer);
+        if (nlAttr != null) {
+            // Any callers that deal with interface names are responsible for converting
+            // the interface index to a name themselves. This may not succeed or may be
+            // incorrect, because the interface might have been deleted, or even deleted
+            // and re-added with a different index, since the netlink message was sent.
+            routeMsg.mIfindex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */);
+        }
+
+        // RTA_CACHEINFO
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(RTA_CACHEINFO, byteBuffer);
+        if (nlAttr != null) {
+            routeMsg.mRtaCacheInfo = StructRtaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
+        }
+
+        return routeMsg;
+    }
+
+    /**
+     * Write a rtnetlink address message to {@link ByteBuffer}.
+     */
+    @VisibleForTesting
+    protected void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mRtmsg.pack(byteBuffer);
+
+        final StructNlAttr destination = new StructNlAttr(RTA_DST, mDestination.getAddress());
+        destination.pack(byteBuffer);
+
+        if (mGateway != null) {
+            final StructNlAttr gateway = new StructNlAttr(RTA_GATEWAY, mGateway.getAddress());
+            gateway.pack(byteBuffer);
+        }
+        if (mIfindex != 0) {
+            final StructNlAttr ifindex = new StructNlAttr(RTA_OIF, mIfindex);
+            ifindex.pack(byteBuffer);
+        }
+        if (mRtaCacheInfo != null) {
+            final StructNlAttr cacheInfo = new StructNlAttr(RTA_CACHEINFO,
+                    mRtaCacheInfo.writeToBytes());
+            cacheInfo.pack(byteBuffer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RtNetlinkRouteMessage{ "
+                + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+                + "Rtmsg{" + mRtmsg.toString() + "}, "
+                + "destination{" + mDestination.getAddress().getHostAddress() + "}, "
+                + "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, "
+                + "ifindex{" + mIfindex + "}, "
+                + "rta_cacheinfo{" + (mRtaCacheInfo == null ? "" : mRtaCacheInfo.toString()) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java
new file mode 100644
index 0000000..360f56d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ifa_cacheinfo
+ *
+ * see also:
+ *
+ *     include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructIfacacheInfo extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 16;
+
+    @Field(order = 0, type = Type.U32)
+    public final long preferred;
+    @Field(order = 1, type = Type.U32)
+    public final long valid;
+    @Field(order = 2, type = Type.U32)
+    public final long cstamp; // created timestamp, hundredths of seconds.
+    @Field(order = 3, type = Type.U32)
+    public final long tstamp; // updated timestamp, hundredths of seconds.
+
+    StructIfacacheInfo(long preferred, long valid, long cstamp, long tstamp) {
+        this.preferred = preferred;
+        this.valid = valid;
+        this.cstamp = cstamp;
+        this.tstamp = tstamp;
+    }
+
+    /**
+     * Parse an ifa_cacheinfo struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the ifa_cacheinfo.
+     * @return the parsed ifa_cacheinfo struct, or {@code null} if the ifa_cacheinfo struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructIfacacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructIfacacheInfo.class, byteBuffer);
+    }
+
+    /**
+     * Write an ifa_cacheinfo struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
new file mode 100644
index 0000000..f9781a7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ifaddrmsg
+ *
+ * see also:
+ *
+ *     include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructIfaddrMsg extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 8;
+
+    @Field(order = 0, type = Type.U8)
+    public final short family;
+    @Field(order = 1, type = Type.U8)
+    public final short prefixLen;
+    @Field(order = 2, type = Type.U8)
+    public final short flags;
+    @Field(order = 3, type = Type.U8)
+    public final short scope;
+    @Field(order = 4, type = Type.S32)
+    public final int index;
+
+    public StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
+        this.family = family;
+        this.prefixLen = prefixLen;
+        this.flags = flags;
+        this.scope = scope;
+        this.index = index;
+    }
+
+    /**
+     * Parse an ifaddrmsg struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the ifaddrmsg.
+     * @return the parsed ifaddrmsg struct, or {@code null} if the ifaddrmsg struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructIfaddrMsg parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructIfaddrMsg.class, byteBuffer);
+    }
+
+    /**
+     * Write an ifaddrmsg struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
new file mode 100644
index 0000000..02d1574
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ifinfomsg
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructIfinfoMsg extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 16;
+
+    @Field(order = 0, type = Type.U8, padding = 1)
+    public final short family;
+    @Field(order = 1, type = Type.U16)
+    public final int type;
+    @Field(order = 2, type = Type.S32)
+    public final int index;
+    @Field(order = 3, type = Type.U32)
+    public final long flags;
+    @Field(order = 4, type = Type.U32)
+    public final long change;
+
+    StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+        this.family = family;
+        this.type = type;
+        this.index = index;
+        this.flags = flags;
+        this.change = change;
+    }
+
+    /**
+     * Parse an ifinfomsg struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the ifinfomsg.
+     * @return the parsed ifinfomsg struct, or {@code null} if the ifinfomsg struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructIfinfoMsg parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructIfinfoMsg.class, byteBuffer);
+    }
+
+    /**
+     * Write an ifinfomsg struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
new file mode 100644
index 0000000..cbd895d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct inet_diag_msg
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_msg {
+ *      __u8    idiag_family;
+ *      __u8    idiag_state;
+ *      __u8    idiag_timer;
+ *      __u8    idiag_retrans;
+ *      struct  inet_diag_sockid id;
+ *      __u32   idiag_expires;
+ *      __u32   idiag_rqueue;
+ *      __u32   idiag_wqueue;
+ *      __u32   idiag_uid;
+ *      __u32   idiag_inode;
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagMsg {
+    public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
+    public short idiag_family;
+    public short idiag_state;
+    public short idiag_timer;
+    public short idiag_retrans;
+    @NonNull
+    public StructInetDiagSockId id;
+    public long idiag_expires;
+    public long idiag_rqueue;
+    public long idiag_wqueue;
+    // Use int for uid since other code use int for uid and uid fits to int
+    public int idiag_uid;
+    public long idiag_inode;
+
+    private static short unsignedByte(byte b) {
+        return (short) (b & 0xFF);
+    }
+
+    /**
+     * Parse inet diag netlink message from buffer.
+     */
+    @Nullable
+    public static StructInetDiagMsg parse(@NonNull ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) {
+            return null;
+        }
+        StructInetDiagMsg struct = new StructInetDiagMsg();
+        struct.idiag_family = unsignedByte(byteBuffer.get());
+        struct.idiag_state = unsignedByte(byteBuffer.get());
+        struct.idiag_timer = unsignedByte(byteBuffer.get());
+        struct.idiag_retrans = unsignedByte(byteBuffer.get());
+        struct.id = StructInetDiagSockId.parse(byteBuffer, struct.idiag_family);
+        if (struct.id == null) {
+            return null;
+        }
+        struct.idiag_expires = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_rqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_wqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_uid = byteBuffer.getInt();
+        struct.idiag_inode = Integer.toUnsignedLong(byteBuffer.getInt());
+        return struct;
+    }
+
+    @Override
+    public String toString() {
+        return "StructInetDiagMsg{ "
+                + "idiag_family{" + idiag_family + "}, "
+                + "idiag_state{" + idiag_state + "}, "
+                + "idiag_timer{" + idiag_timer + "}, "
+                + "idiag_retrans{" + idiag_retrans + "}, "
+                + "id{" + id + "}, "
+                + "idiag_expires{" + idiag_expires + "}, "
+                + "idiag_rqueue{" + idiag_rqueue + "}, "
+                + "idiag_wqueue{" + idiag_wqueue + "}, "
+                + "idiag_uid{" + idiag_uid + "}, "
+                + "idiag_inode{" + idiag_inode + "}, "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java
new file mode 100644
index 0000000..3b47008
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ *      struct inet_diag_req_v2 {
+ *          __u8    sdiag_family;
+ *          __u8    sdiag_protocol;
+ *          __u8    idiag_ext;
+ *          __u8    pad;
+ *          __u32   idiag_states;
+ *          struct  inet_diag_sockid id;
+ *      };
+ *
+ * @hide
+ */
+public class StructInetDiagReqV2 {
+    public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE;
+
+    private final byte mSdiagFamily;
+    private final byte mSdiagProtocol;
+    private final byte mIdiagExt;
+    private final byte mPad;
+    private final StructInetDiagSockId mId;
+    private final int mState;
+    public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff;
+
+    public StructInetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family, int pad,
+            int extension, int state) {
+        mSdiagFamily = (byte) family;
+        mSdiagProtocol = (byte) protocol;
+        mId = id;
+        mPad = (byte) pad;
+        mIdiagExt = (byte) extension;
+        mState = state;
+    }
+
+    /**
+     * Write the int diag request v2 message to ByteBuffer.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        // The ByteOrder must have already been set by the caller.
+        byteBuffer.put((byte) mSdiagFamily);
+        byteBuffer.put((byte) mSdiagProtocol);
+        byteBuffer.put((byte) mIdiagExt);
+        byteBuffer.put((byte) mPad);
+        byteBuffer.putInt(mState);
+        if (mId != null) mId.pack(byteBuffer);
+    }
+
+    @Override
+    public String toString() {
+        final String familyStr = NetlinkConstants.stringForAddressFamily(mSdiagFamily);
+        final String protocolStr = NetlinkConstants.stringForAddressFamily(mSdiagProtocol);
+
+        return "StructInetDiagReqV2{ "
+                + "sdiag_family{" + familyStr + "}, "
+                + "sdiag_protocol{" + protocolStr + "}, "
+                + "idiag_ext{" + mIdiagExt + ")}, "
+                + "pad{" + mPad + "}, "
+                + "idiag_states{" + Integer.toHexString(mState) + "}, "
+                + ((mId != null) ? mId.toString() : "inet_diag_sockid=null")
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
new file mode 100644
index 0000000..dd85934
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 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.net.module.util.netlink;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_sockid {
+ *        __be16    idiag_sport;
+ *        __be16    idiag_dport;
+ *        __be32    idiag_src[4];
+ *        __be32    idiag_dst[4];
+ *        __u32     idiag_if;
+ *        __u32     idiag_cookie[2];
+ * #define INET_DIAG_NOCOOKIE (~0U)
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagSockId {
+    private static final String TAG = StructInetDiagSockId.class.getSimpleName();
+    public static final int STRUCT_SIZE = 48;
+
+    private static final long INET_DIAG_NOCOOKIE = ~0L;
+    private static final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+    public final InetSocketAddress locSocketAddress;
+    public final InetSocketAddress remSocketAddress;
+    public final int ifIndex;
+    public final long cookie;
+
+    public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) {
+        this(loc, rem, 0 /* ifIndex */, INET_DIAG_NOCOOKIE);
+    }
+
+    public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem,
+            int ifIndex, long cookie) {
+        this.locSocketAddress = loc;
+        this.remSocketAddress = rem;
+        this.ifIndex = ifIndex;
+        this.cookie = cookie;
+    }
+
+    /**
+     * Parse inet diag socket id from buffer.
+     */
+    @Nullable
+    public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final short family) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) {
+            return null;
+        }
+
+        byteBuffer.order(BIG_ENDIAN);
+        final int srcPort = Short.toUnsignedInt(byteBuffer.getShort());
+        final int dstPort = Short.toUnsignedInt(byteBuffer.getShort());
+
+        final InetAddress srcAddr;
+        final InetAddress dstAddr;
+        if (family == AF_INET) {
+            final byte[] srcAddrByte = new byte[IPV4_ADDR_LEN];
+            final byte[] dstAddrByte = new byte[IPV4_ADDR_LEN];
+            byteBuffer.get(srcAddrByte);
+            // Address always uses IPV6_ADDR_LEN in the buffer. So if the address is IPv4, position
+            // needs to be advanced to the next field.
+            byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN));
+            byteBuffer.get(dstAddrByte);
+            byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN));
+            try {
+                srcAddr = Inet4Address.getByAddress(srcAddrByte);
+                dstAddr = Inet4Address.getByAddress(dstAddrByte);
+            } catch (UnknownHostException e) {
+                Log.wtf(TAG, "Failed to parse address: " + e);
+                return null;
+            }
+        } else if (family == AF_INET6) {
+            final byte[] srcAddrByte = new byte[IPV6_ADDR_LEN];
+            final byte[] dstAddrByte = new byte[IPV6_ADDR_LEN];
+            byteBuffer.get(srcAddrByte);
+            byteBuffer.get(dstAddrByte);
+            try {
+                // Using Inet6Address.getByAddress to be consistent with idiag_family field since
+                // InetAddress.getByAddress returns Inet4Address if the address is v4-mapped v6
+                // address.
+                srcAddr = Inet6Address.getByAddress(
+                        null /* host */, srcAddrByte, -1 /* scope_id */);
+                dstAddr = Inet6Address.getByAddress(
+                        null /* host */, dstAddrByte, -1 /* scope_id */);
+            } catch (UnknownHostException e) {
+                Log.wtf(TAG, "Failed to parse address: " + e);
+                return null;
+            }
+        } else {
+            Log.wtf(TAG, "Invalid address family: " + family);
+            return null;
+        }
+
+        final InetSocketAddress srcSocketAddr = new InetSocketAddress(srcAddr, srcPort);
+        final InetSocketAddress dstSocketAddr = new InetSocketAddress(dstAddr, dstPort);
+
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final int ifIndex = byteBuffer.getInt();
+        final long cookie = byteBuffer.getLong();
+        return new StructInetDiagSockId(srcSocketAddr, dstSocketAddr, ifIndex, cookie);
+    }
+
+    /**
+     * Write inet diag socket id message to ByteBuffer in big endian.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        byteBuffer.order(BIG_ENDIAN);
+        byteBuffer.putShort((short) locSocketAddress.getPort());
+        byteBuffer.putShort((short) remSocketAddress.getPort());
+        byteBuffer.put(locSocketAddress.getAddress().getAddress());
+        if (locSocketAddress.getAddress() instanceof Inet4Address) {
+            byteBuffer.put(IPV4_PADDING);
+        }
+        byteBuffer.put(remSocketAddress.getAddress().getAddress());
+        if (remSocketAddress.getAddress() instanceof Inet4Address) {
+            byteBuffer.put(IPV4_PADDING);
+        }
+        byteBuffer.order(ByteOrder.nativeOrder());
+        byteBuffer.putInt(ifIndex);
+        byteBuffer.putLong(cookie);
+    }
+
+    @Override
+    public String toString() {
+        return "StructInetDiagSockId{ "
+                + "idiag_sport{" + locSocketAddress.getPort() + "}, "
+                + "idiag_dport{" + remSocketAddress.getPort() + "}, "
+                + "idiag_src{" + locSocketAddress.getAddress().getHostAddress() + "}, "
+                + "idiag_dst{" + remSocketAddress.getAddress().getHostAddress() + "}, "
+                + "idiag_if{" + ifIndex + "}, "
+                + "idiag_cookie{"
+                + (cookie == INET_DIAG_NOCOOKIE ? "INET_DIAG_NOCOOKIE" : cookie) + "}"
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java
new file mode 100644
index 0000000..53ce899
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import android.system.OsConstants;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ndmsg
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdMsg {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 12;
+
+    // Neighbor Cache Entry States
+    public static final short NUD_NONE        = 0x00;
+    public static final short NUD_INCOMPLETE  = 0x01;
+    public static final short NUD_REACHABLE   = 0x02;
+    public static final short NUD_STALE       = 0x04;
+    public static final short NUD_DELAY       = 0x08;
+    public static final short NUD_PROBE       = 0x10;
+    public static final short NUD_FAILED      = 0x20;
+    public static final short NUD_NOARP       = 0x40;
+    public static final short NUD_PERMANENT   = 0x80;
+
+    /**
+     * Convert neighbor cache entry state integer to string.
+     */
+    public static String stringForNudState(short nudState) {
+        switch (nudState) {
+            case NUD_NONE: return "NUD_NONE";
+            case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
+            case NUD_REACHABLE: return "NUD_REACHABLE";
+            case NUD_STALE: return "NUD_STALE";
+            case NUD_DELAY: return "NUD_DELAY";
+            case NUD_PROBE: return "NUD_PROBE";
+            case NUD_FAILED: return "NUD_FAILED";
+            case NUD_NOARP: return "NUD_NOARP";
+            case NUD_PERMANENT: return "NUD_PERMANENT";
+            default:
+                return "unknown NUD state: " + String.valueOf(nudState);
+        }
+    }
+
+    /**
+     * Check whether a neighbor is connected or not.
+     */
+    public static boolean isNudStateConnected(short nudState) {
+        return ((nudState & (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE)) != 0);
+    }
+
+    /**
+     * Check whether a neighbor is in the valid NUD state or not.
+     */
+    public static boolean isNudStateValid(short nudState) {
+        return (isNudStateConnected(nudState)
+                || ((nudState & (NUD_PROBE | NUD_STALE | NUD_DELAY)) != 0));
+    }
+
+    // Neighbor Cache Entry Flags
+    public static byte NTF_USE       = (byte) 0x01;
+    public static byte NTF_SELF      = (byte) 0x02;
+    public static byte NTF_MASTER    = (byte) 0x04;
+    public static byte NTF_PROXY     = (byte) 0x08;
+    public static byte NTF_ROUTER    = (byte) 0x80;
+
+    private static String stringForNudFlags(byte flags) {
+        final StringBuilder sb = new StringBuilder();
+        if ((flags & NTF_USE) != 0) {
+            sb.append("NTF_USE");
+        }
+        if ((flags & NTF_SELF) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NTF_SELF");
+        }
+        if ((flags & NTF_MASTER) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NTF_MASTER");
+        }
+        if ((flags & NTF_PROXY) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NTF_PROXY");
+        }
+        if ((flags & NTF_ROUTER) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NTF_ROUTER");
+        }
+        return sb.toString();
+    }
+
+    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+    }
+
+    /**
+     * Parse a neighbor discovery netlink message header from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the nd netlink message header.
+     * @return the parsed nd netlink message header, or {@code null} if the nd netlink message
+     *         header could not be parsed successfully (for example, if it was truncated).
+     */
+    public static StructNdMsg parse(ByteBuffer byteBuffer) {
+        if (!hasAvailableSpace(byteBuffer)) return null;
+
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the possible
+        // exception of usage within unittests.
+        final StructNdMsg struct = new StructNdMsg();
+        struct.ndm_family = byteBuffer.get();
+        final byte pad1 = byteBuffer.get();
+        final short pad2 = byteBuffer.getShort();
+        struct.ndm_ifindex = byteBuffer.getInt();
+        struct.ndm_state = byteBuffer.getShort();
+        struct.ndm_flags = byteBuffer.get();
+        struct.ndm_type = byteBuffer.get();
+        return struct;
+    }
+
+    public byte ndm_family;
+    public int ndm_ifindex;
+    public short ndm_state;
+    public byte ndm_flags;
+    public byte ndm_type;
+
+    public StructNdMsg() {
+        ndm_family = (byte) OsConstants.AF_UNSPEC;
+    }
+
+    /**
+     * Write the neighbor discovery message header to {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the exception
+        // of usage within unittests.
+        byteBuffer.put(ndm_family);
+        byteBuffer.put((byte) 0);         // pad1
+        byteBuffer.putShort((short) 0);   // pad2
+        byteBuffer.putInt(ndm_ifindex);
+        byteBuffer.putShort(ndm_state);
+        byteBuffer.put(ndm_flags);
+        byteBuffer.put(ndm_type);
+    }
+
+    /**
+     * Check whether a neighbor is connected or not.
+     */
+    public boolean nudConnected() {
+        return isNudStateConnected(ndm_state);
+    }
+
+    /**
+     * Check whether a neighbor is in the valid NUD state or not.
+     */
+    public boolean nudValid() {
+        return isNudStateValid(ndm_state);
+    }
+
+    @Override
+    public String toString() {
+        final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")";
+        final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")";
+        return "StructNdMsg{ "
+                + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, "
+                + "ifindex{" + ndm_ifindex + "}, "
+                + "state{" + stateStr + "}, "
+                + "flags{" + flagsStr + "}, "
+                + "type{" + ndm_type + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java
new file mode 100644
index 0000000..8226346
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import android.annotation.SuppressLint;
+import android.net.IpPrefix;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * The PREF64 router advertisement option. RFC 8781.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     |     Scaled Lifetime     | PLC |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |              Highest 96 bits of the Prefix                    |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+public class StructNdOptPref64 extends NdOption {
+    public static final int STRUCT_SIZE = 16;
+    public static final int TYPE = 38;
+    public static final byte LENGTH = 2;
+
+    private static final String TAG = StructNdOptPref64.class.getSimpleName();
+
+    /**
+     * How many seconds the prefix is expected to remain valid.
+     * Valid values are from 0 to 65528 in multiples of 8.
+     */
+    public final int lifetime;
+    /** The NAT64 prefix. */
+    @NonNull public final IpPrefix prefix;
+
+    static int plcToPrefixLength(int plc) {
+        switch (plc) {
+            case 0: return 96;
+            case 1: return 64;
+            case 2: return 56;
+            case 3: return 48;
+            case 4: return 40;
+            case 5: return 32;
+            default:
+                throw new IllegalArgumentException("Invalid prefix length code " + plc);
+        }
+    }
+
+    static int prefixLengthToPlc(int prefixLength) {
+        switch (prefixLength) {
+            case 96: return 0;
+            case 64: return 1;
+            case 56: return 2;
+            case 48: return 3;
+            case 40: return 4;
+            case 32: return 5;
+            default:
+                throw new IllegalArgumentException("Invalid prefix length " + prefixLength);
+        }
+    }
+
+    /**
+     * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC
+     */
+    static short getScaledLifetimePlc(int lifetime, int prefixLengthCode) {
+        return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7));
+    }
+
+    public StructNdOptPref64(@NonNull IpPrefix prefix, int lifetime) {
+        super((byte) TYPE, LENGTH);
+
+        Objects.requireNonNull(prefix, "prefix must not be null");
+        if (!(prefix.getAddress() instanceof Inet6Address)) {
+            throw new IllegalArgumentException("Must be an IPv6 prefix: " + prefix);
+        }
+        prefixLengthToPlc(prefix.getPrefixLength());  // Throw if the prefix length is invalid.
+        this.prefix = prefix;
+
+        if (lifetime < 0 || lifetime > 0xfff8) {
+            throw new IllegalArgumentException("Invalid lifetime " + lifetime);
+        }
+        this.lifetime = lifetime & 0xfff8;
+    }
+
+    @SuppressLint("NewApi")
+    private StructNdOptPref64(@NonNull ByteBuffer buf) {
+        super(buf.get(), Byte.toUnsignedInt(buf.get()));
+        if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type);
+        if (length != LENGTH) throw new IllegalArgumentException("Invalid length " + length);
+
+        int scaledLifetimePlc = Short.toUnsignedInt(buf.getShort());
+        lifetime = scaledLifetimePlc & 0xfff8;
+
+        byte[] addressBytes = new byte[16];
+        buf.get(addressBytes, 0, 12);
+        InetAddress addr;
+        try {
+            addr = InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException e) {
+            throw new AssertionError("16-byte array not valid InetAddress?");
+        }
+        prefix = new IpPrefix(addr, plcToPrefixLength(scaledLifetimePlc & 7));
+    }
+
+    /**
+     * Parses an option from a {@link ByteBuffer}.
+     *
+     * @param buf The buffer from which to parse the option. The buffer's byte order must be
+     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
+     * @return the parsed option, or {@code null} if the option could not be parsed successfully
+     *         (for example, if it was truncated, or if the prefix length code was wrong).
+     */
+    public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) {
+        if (buf.remaining() < STRUCT_SIZE) return null;
+        try {
+            return new StructNdOptPref64(buf);
+        } catch (IllegalArgumentException e) {
+            // Not great, but better than throwing an exception that might crash the caller.
+            // Convention in this package is that null indicates that the option was truncated, so
+            // callers must already handle it.
+            Log.d(TAG, "Invalid PREF64 option: " + e);
+            return null;
+        }
+    }
+
+    protected void writeToByteBuffer(ByteBuffer buf) {
+        super.writeToByteBuffer(buf);
+        buf.putShort(getScaledLifetimePlc(lifetime,  prefixLengthToPlc(prefix.getPrefixLength())));
+        buf.put(prefix.getRawAddress(), 0, 12);
+    }
+
+    /** Outputs the wire format of the option to a new big-endian ByteBuffer. */
+    public ByteBuffer toByteBuffer() {
+        ByteBuffer buf = ByteBuffer.allocate(STRUCT_SIZE);
+        writeToByteBuffer(buf);
+        buf.flip();
+        return buf;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return String.format("NdOptPref64(%s, %d)", prefix, lifetime);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java
new file mode 100644
index 0000000..6dee0c4
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptRdnss.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.RdnssOption;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * The Recursive DNS Server Option. RFC 8106.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Length    |           Reserved            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Lifetime                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * :            Addresses of IPv6 Recursive DNS Servers            :
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class StructNdOptRdnss extends NdOption {
+    private static final String TAG = StructNdOptRdnss.class.getSimpleName();
+    public static final int TYPE = 25;
+    // Length in 8-byte units, only if one IPv6 address included.
+    public static final byte MIN_OPTION_LEN = 3;
+
+    public final RdnssOption header;
+    @NonNull
+    public final Inet6Address[] servers;
+
+    public StructNdOptRdnss(@NonNull final Inet6Address[] servers, long lifetime) {
+        super((byte) TYPE, servers.length * 2 + 1);
+
+        Objects.requireNonNull(servers, "Recursive DNS Servers address array must not be null");
+        if (servers.length == 0) {
+            throw new IllegalArgumentException("DNS server address array must not be empty");
+        }
+
+        this.header = new RdnssOption((byte) TYPE, (byte) (servers.length * 2 + 1),
+                (short) 0 /* reserved */, lifetime);
+        this.servers = servers.clone();
+    }
+
+    /**
+     * Parses an RDNSS option from a {@link ByteBuffer}.
+     *
+     * @param buf The buffer from which to parse the option. The buffer's byte order must be
+     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
+     * @return the parsed option, or {@code null} if the option could not be parsed successfully.
+     */
+    public static StructNdOptRdnss parse(@NonNull ByteBuffer buf) {
+        if (buf == null || buf.remaining() < MIN_OPTION_LEN * 8) return null;
+        try {
+            final RdnssOption header = Struct.parse(RdnssOption.class, buf);
+            if (header.type != TYPE) {
+                throw new IllegalArgumentException("Invalid type " + header.type);
+            }
+            if (header.length < MIN_OPTION_LEN || (header.length % 2 == 0)) {
+                throw new IllegalArgumentException("Invalid length " + header.length);
+            }
+
+            final int numOfDnses = (header.length - 1) / 2;
+            final Inet6Address[] servers = new Inet6Address[numOfDnses];
+            for (int i = 0; i < numOfDnses; i++) {
+                byte[] rawAddress = new byte[IPV6_ADDR_LEN];
+                buf.get(rawAddress);
+                servers[i] = (Inet6Address) InetAddress.getByAddress(rawAddress);
+            }
+            return new StructNdOptRdnss(servers, header.lifetime);
+        } catch (IllegalArgumentException | BufferUnderflowException | UnknownHostException e) {
+            // Not great, but better than throwing an exception that might crash the caller.
+            // Convention in this package is that null indicates that the option was truncated
+            // or malformed, so callers must already handle it.
+            Log.d(TAG, "Invalid RDNSS option: " + e);
+            return null;
+        }
+    }
+
+    protected void writeToByteBuffer(ByteBuffer buf) {
+        header.writeToByteBuffer(buf);
+        for (int i = 0; i < servers.length; i++) {
+            buf.put(servers[i].getAddress());
+        }
+    }
+
+    /** Outputs the wire format of the option to a new big-endian ByteBuffer. */
+    public ByteBuffer toByteBuffer() {
+        final ByteBuffer buf = ByteBuffer.allocate(Struct.getSize(RdnssOption.class)
+                + servers.length * IPV6_ADDR_LEN);
+        writeToByteBuffer(buf);
+        buf.flip();
+        return buf;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        final StringJoiner sj = new StringJoiner(",", "[", "]");
+        for (int i = 0; i < servers.length; i++) {
+            sj.add(servers[i].getHostAddress());
+        }
+        return String.format("NdOptRdnss(%s,servers:%s)", header.toString(), sj.toString());
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
new file mode 100644
index 0000000..1f9bb7e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct nda_cacheinfo
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdaCacheInfo {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 16;
+
+    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+    }
+
+    /**
+     * Parse a nd cacheinfo netlink attribute from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the nd cacheinfo attribute.
+     * @return the parsed nd cacheinfo attribute, or {@code null} if the nd cacheinfo attribute
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) {
+        if (!hasAvailableSpace(byteBuffer)) return null;
+
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the possible
+        // exception of usage within unittests.
+        final StructNdaCacheInfo struct = new StructNdaCacheInfo();
+        struct.ndm_used = byteBuffer.getInt();
+        struct.ndm_confirmed = byteBuffer.getInt();
+        struct.ndm_updated = byteBuffer.getInt();
+        struct.ndm_refcnt = byteBuffer.getInt();
+        return struct;
+    }
+
+    /**
+     * Explanatory notes, for reference.
+     *
+     * Before being returned to user space, the neighbor entry times are
+     * converted to clock_t's like so:
+     *
+     *     ndm_used      = jiffies_to_clock_t(now - neigh->used);
+     *     ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed);
+     *     ndm_updated   = jiffies_to_clock_t(now - neigh->updated);
+     *
+     * meaning that these values are expressed as "clock ticks ago".  To
+     * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK).
+     * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed
+     * in centiseconds.
+     *
+     * These values are unsigned, but fortunately being expressed as "some
+     * clock ticks ago", these values are typically very small (and
+     * 2^31 centiseconds = 248 days).
+     *
+     * By observation, it appears that:
+     *     ndm_used: the last time ARP/ND took place for this neighbor
+     *     ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR
+     *                    higher layer confirmation (TCP or MSG_CONFIRM)
+     *                    was received
+     *     ndm_updated: the time when the current NUD state was entered
+     */
+    public int ndm_used;
+    public int ndm_confirmed;
+    public int ndm_updated;
+    public int ndm_refcnt;
+
+    public StructNdaCacheInfo() {}
+
+    /**
+     * The last time ARP/ND took place for this neighbor.
+     */
+    public long lastUsed() {
+        return ticksToMilliSeconds(ndm_used);
+    }
+
+    /**
+     * The last time ARP/ND succeeded for this neighbor or higher layer confirmation (TCP or
+     * MSG_CONFIRM) was received.
+     */
+    public long lastConfirmed() {
+        return ticksToMilliSeconds(ndm_confirmed);
+    }
+
+    /**
+     * The time when the current NUD state was entered.
+     */
+    public long lastUpdated() {
+        return ticksToMilliSeconds(ndm_updated);
+    }
+
+    @Override
+    public String toString() {
+        return "NdaCacheInfo{ "
+                + "ndm_used{" + lastUsed() + "}, "
+                + "ndm_confirmed{" + lastConfirmed() + "}, "
+                + "ndm_updated{" + lastUpdated() + "}, "
+                + "ndm_refcnt{" + ndm_refcnt + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java
new file mode 100644
index 0000000..2de5490
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * struct nfgenmsg
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink.h
+ *
+ * @hide
+ */
+public class StructNfGenMsg {
+    public static final int STRUCT_SIZE = 2 + Short.BYTES;
+
+    public static final int NFNETLINK_V0 = 0;
+
+    public final byte nfgen_family;
+    public final byte version;
+    public final short res_id;  // N.B.: this is big endian in the kernel
+
+    /**
+     * Parse a netfilter netlink header from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the netfilter netlink header.
+     * @return the parsed netfilter netlink header, or {@code null} if the netfilter netlink header
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructNfGenMsg parse(@NonNull ByteBuffer byteBuffer) {
+        Objects.requireNonNull(byteBuffer);
+
+        if (!hasAvailableSpace(byteBuffer)) return null;
+
+        final byte nfgen_family = byteBuffer.get();
+        final byte version = byteBuffer.get();
+
+        final ByteOrder originalOrder = byteBuffer.order();
+        byteBuffer.order(ByteOrder.BIG_ENDIAN);
+        final short res_id = byteBuffer.getShort();
+        byteBuffer.order(originalOrder);
+
+        return new StructNfGenMsg(nfgen_family, version, res_id);
+    }
+
+    public StructNfGenMsg(byte family, byte ver, short id) {
+        nfgen_family = family;
+        version = ver;
+        res_id = id;
+    }
+
+    public StructNfGenMsg(byte family) {
+        nfgen_family = family;
+        version = (byte) NFNETLINK_V0;
+        res_id = (short) 0;
+    }
+
+    /**
+     * Write a netfilter netlink header to a {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        byteBuffer.put(nfgen_family);
+        byteBuffer.put(version);
+
+        final ByteOrder originalOrder = byteBuffer.order();
+        byteBuffer.order(ByteOrder.BIG_ENDIAN);
+        byteBuffer.putShort(res_id);
+        byteBuffer.order(originalOrder);
+    }
+
+    private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) {
+        return byteBuffer.remaining() >= STRUCT_SIZE;
+    }
+
+    @Override
+    public String toString() {
+        final String familyStr = NetlinkConstants.stringForAddressFamily(nfgen_family);
+
+        return "NfGenMsg{ "
+                + "nfgen_family{" + familyStr + "}, "
+                + "version{" + Byte.toUnsignedInt(version) + "}, "
+                + "res_id{" + Short.toUnsignedInt(res_id) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
new file mode 100644
index 0000000..a9b6495
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * struct nlattr
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlAttr {
+    // Already aligned.
+    public static final int NLA_HEADERLEN  = 4;
+    public static final int NLA_F_NESTED   = (1 << 15);
+
+    /**
+     * Set carries nested attributes bit.
+     */
+    public static short makeNestedType(short type) {
+        return (short) (type | NLA_F_NESTED);
+    }
+
+    /**
+     * Peek and parse the netlink attribute from {@link ByteBuffer}.
+     *
+     * Return a (length, type) object only, without consuming any bytes in
+     * |byteBuffer| and without copying or interpreting any value bytes.
+     * This is used for scanning over a packed set of struct nlattr's,
+     * looking for instances of a particular type.
+     */
+    public static StructNlAttr peek(ByteBuffer byteBuffer) {
+        if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) {
+            return null;
+        }
+        final int baseOffset = byteBuffer.position();
+
+        final StructNlAttr struct = new StructNlAttr();
+        final ByteOrder originalOrder = byteBuffer.order();
+        byteBuffer.order(ByteOrder.nativeOrder());
+        try {
+            struct.nla_len = byteBuffer.getShort();
+            struct.nla_type = byteBuffer.getShort();
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
+
+        byteBuffer.position(baseOffset);
+        if (struct.nla_len < NLA_HEADERLEN) {
+            // Malformed.
+            return null;
+        }
+        return struct;
+    }
+
+    /**
+     * Parse a netlink attribute from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the netlink attriute.
+     * @return the parsed netlink attribute, or {@code null} if the netlink attribute
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    public static StructNlAttr parse(ByteBuffer byteBuffer) {
+        final StructNlAttr struct = peek(byteBuffer);
+        if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) {
+            return null;
+        }
+
+        final int baseOffset = byteBuffer.position();
+        byteBuffer.position(baseOffset + NLA_HEADERLEN);
+
+        int valueLen = ((int) struct.nla_len) & 0xffff;
+        valueLen -= NLA_HEADERLEN;
+        if (valueLen > 0) {
+            struct.nla_value = new byte[valueLen];
+            byteBuffer.get(struct.nla_value, 0, valueLen);
+            byteBuffer.position(baseOffset + struct.getAlignedLength());
+        }
+        return struct;
+    }
+
+    /**
+     * Find next netlink attribute with a given type from {@link ByteBuffer}.
+     *
+     * @param attrType The given netlink attribute type is requested for.
+     * @param byteBuffer The buffer from which to find the netlink attribute.
+     * @return the found netlink attribute, or {@code null} if the netlink attribute could not be
+     *         found or parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructNlAttr findNextAttrOfType(short attrType,
+            @Nullable ByteBuffer byteBuffer) {
+        while (byteBuffer != null && byteBuffer.remaining() > 0) {
+            final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
+            if (nlAttr == null) {
+                break;
+            }
+            if (nlAttr.nla_type == attrType) {
+                return StructNlAttr.parse(byteBuffer);
+            }
+            if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
+                break;
+            }
+            byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
+        }
+        return null;
+    }
+
+    public short nla_len = (short) NLA_HEADERLEN;
+    public short nla_type;
+    public byte[] nla_value;
+
+    public StructNlAttr() {}
+
+    public StructNlAttr(short type, byte value) {
+        nla_type = type;
+        setValue(new byte[1]);
+        nla_value[0] = value;
+    }
+
+    public StructNlAttr(short type, short value) {
+        this(type, value, ByteOrder.nativeOrder());
+    }
+
+    public StructNlAttr(short type, short value, ByteOrder order) {
+        nla_type = type;
+        setValue(new byte[Short.BYTES]);
+        final ByteBuffer buf = getValueAsByteBuffer();
+        final ByteOrder originalOrder = buf.order();
+        try {
+            buf.order(order);
+            buf.putShort(value);
+        } finally {
+            buf.order(originalOrder);
+        }
+    }
+
+    public StructNlAttr(short type, int value) {
+        this(type, value, ByteOrder.nativeOrder());
+    }
+
+    public StructNlAttr(short type, int value, ByteOrder order) {
+        nla_type = type;
+        setValue(new byte[Integer.BYTES]);
+        final ByteBuffer buf = getValueAsByteBuffer();
+        final ByteOrder originalOrder = buf.order();
+        try {
+            buf.order(order);
+            buf.putInt(value);
+        } finally {
+            buf.order(originalOrder);
+        }
+    }
+
+    public StructNlAttr(short type, @NonNull final byte[] value) {
+        nla_type = type;
+        setValue(value);
+    }
+
+    public StructNlAttr(short type, @NonNull final InetAddress ip) {
+        nla_type = type;
+        setValue(ip.getAddress());
+    }
+
+    public StructNlAttr(short type, @NonNull final MacAddress mac) {
+        nla_type = type;
+        setValue(mac.toByteArray());
+    }
+
+    public StructNlAttr(short type, @NonNull final String string) {
+        nla_type = type;
+        byte[] value = null;
+        try {
+            final byte[] stringBytes = string.getBytes("UTF-8");
+            // Append '\0' at the end of interface name string bytes.
+            value = Arrays.copyOf(stringBytes, stringBytes.length + 1);
+        } catch (UnsupportedEncodingException ignored) {
+            // Do nothing.
+        } finally {
+            setValue(value);
+        }
+    }
+
+    public StructNlAttr(short type, StructNlAttr... nested) {
+        this();
+        nla_type = makeNestedType(type);
+
+        int payloadLength = 0;
+        for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength();
+        setValue(new byte[payloadLength]);
+
+        final ByteBuffer buf = getValueAsByteBuffer();
+        for (StructNlAttr nla : nested) {
+            nla.pack(buf);
+        }
+    }
+
+    /**
+     * Get aligned attribute length.
+     */
+    public int getAlignedLength() {
+        return NetlinkConstants.alignedLengthOf(nla_len);
+    }
+
+    /**
+     * Get attribute value as BE16.
+     */
+    public short getValueAsBe16(short defaultValue) {
+        final ByteBuffer byteBuffer = getValueAsByteBuffer();
+        if (byteBuffer == null || byteBuffer.remaining() != Short.BYTES) {
+            return defaultValue;
+        }
+        final ByteOrder originalOrder = byteBuffer.order();
+        try {
+            byteBuffer.order(ByteOrder.BIG_ENDIAN);
+            return byteBuffer.getShort();
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
+    }
+
+    /**
+     * Get attribute value as BE32.
+     */
+    public int getValueAsBe32(int defaultValue) {
+        final ByteBuffer byteBuffer = getValueAsByteBuffer();
+        if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
+            return defaultValue;
+        }
+        final ByteOrder originalOrder = byteBuffer.order();
+        try {
+            byteBuffer.order(ByteOrder.BIG_ENDIAN);
+            return byteBuffer.getInt();
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
+    }
+
+    /**
+     * Get attribute value as ByteBuffer.
+     */
+    public ByteBuffer getValueAsByteBuffer() {
+        if (nla_value == null) return null;
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value);
+        // By convention, all buffers in this library are in native byte order because netlink is in
+        // native byte order. It's the order that is used by NetlinkSocket.recvMessage and the only
+        // order accepted by NetlinkMessage.parse.
+        byteBuffer.order(ByteOrder.nativeOrder());
+        return byteBuffer;
+    }
+
+    /**
+     * Get attribute value as byte.
+     */
+    public byte getValueAsByte(byte defaultValue) {
+        final ByteBuffer byteBuffer = getValueAsByteBuffer();
+        if (byteBuffer == null || byteBuffer.remaining() != Byte.BYTES) {
+            return defaultValue;
+        }
+        return getValueAsByteBuffer().get();
+    }
+
+    /**
+     * Get attribute value as Integer, or null if malformed (e.g., length is not 4 bytes).
+     */
+    public Integer getValueAsInteger() {
+        final ByteBuffer byteBuffer = getValueAsByteBuffer();
+        if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
+            return null;
+        }
+        return byteBuffer.getInt();
+    }
+
+    /**
+     * Get attribute value as Int, default value if malformed.
+     */
+    public int getValueAsInt(int defaultValue) {
+        final Integer value = getValueAsInteger();
+        return (value != null) ? value : defaultValue;
+    }
+
+    /**
+     * Get attribute value as InetAddress.
+     *
+     * @return the InetAddress instance representation of attribute value or null if IP address
+     *         is of illegal length.
+     */
+    @Nullable
+    public InetAddress getValueAsInetAddress() {
+        if (nla_value == null) return null;
+
+        try {
+            return InetAddress.getByAddress(nla_value);
+        } catch (UnknownHostException ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * Get attribute value as MacAddress.
+     *
+     * @return the MacAddress instance representation of attribute value or null if the given byte
+     *         array is not a valid representation(e.g, not all link layers have 6-byte link-layer
+     *         addresses)
+     */
+    @Nullable
+    public MacAddress getValueAsMacAddress() {
+        if (nla_value == null) return null;
+
+        try {
+            return MacAddress.fromBytes(nla_value);
+        } catch (IllegalArgumentException ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * Get attribute value as a unicode string.
+     *
+     * @return a unicode string or null if UTF-8 charset is not supported.
+     */
+    @Nullable
+    public String getValueAsString() {
+        if (nla_value == null) return null;
+        // Check the attribute value length after removing string termination flag '\0'.
+        // This assumes that all netlink strings are null-terminated.
+        if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null;
+
+        try {
+            final byte[] array = Arrays.copyOf(nla_value, nla_len - NLA_HEADERLEN - 1);
+            return new String(array, "UTF-8");
+        } catch (UnsupportedEncodingException | NegativeArraySizeException ignored) {
+            return null;
+        }
+    }
+
+    /**
+     * Write the netlink attribute to {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        final ByteOrder originalOrder = byteBuffer.order();
+        final int originalPosition = byteBuffer.position();
+
+        byteBuffer.order(ByteOrder.nativeOrder());
+        try {
+            byteBuffer.putShort(nla_len);
+            byteBuffer.putShort(nla_type);
+            if (nla_value != null) byteBuffer.put(nla_value);
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
+        byteBuffer.position(originalPosition + getAlignedLength());
+    }
+
+    private void setValue(byte[] value) {
+        nla_value = value;
+        nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0));
+    }
+
+    @Override
+    public String toString() {
+        return "StructNlAttr{ "
+                + "nla_len{" + nla_len + "}, "
+                + "nla_type{" + nla_type + "}, "
+                + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java
new file mode 100644
index 0000000..b6620f3
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct nlmsgerr
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgErr {
+    public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
+
+    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+    }
+
+    /**
+     * Parse a netlink error message payload from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the netlink error message payload.
+     * @return the parsed netlink error message payload, or {@code null} if the netlink error
+     *         message payload could not be parsed successfully (for example, if it was truncated).
+     */
+    public static StructNlMsgErr parse(ByteBuffer byteBuffer) {
+        if (!hasAvailableSpace(byteBuffer)) return null;
+
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the exception
+        // of usage within unittests.
+        final StructNlMsgErr struct = new StructNlMsgErr();
+        struct.error = byteBuffer.getInt();
+        struct.msg = StructNlMsgHdr.parse(byteBuffer);
+        return struct;
+    }
+
+    public int error;
+    public StructNlMsgHdr msg;
+
+    /**
+     * Write the netlink error message payload to {@link ByteBuffer}.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the possible
+        // exception of usage within unittests.
+        byteBuffer.putInt(error);
+        if (msg != null) {
+            msg.pack(byteBuffer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "StructNlMsgErr{ "
+                + "error{" + error + "}, "
+                + "msg{" + (msg == null ? "" : msg.toString()) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
new file mode 100644
index 0000000..5052cb8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct nlmsghdr
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgHdr {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 16;
+
+    public static final short NLM_F_REQUEST = 0x0001;
+    public static final short NLM_F_MULTI   = 0x0002;
+    public static final short NLM_F_ACK     = 0x0004;
+    public static final short NLM_F_ECHO    = 0x0008;
+    // Flags for a GET request.
+    public static final short NLM_F_ROOT    = 0x0100;
+    public static final short NLM_F_MATCH   = 0x0200;
+    public static final short NLM_F_DUMP    = NLM_F_ROOT | NLM_F_MATCH;
+    // Flags for a NEW request.
+    public static final short NLM_F_REPLACE   = 0x100;
+    public static final short NLM_F_EXCL      = 0x200;
+    public static final short NLM_F_CREATE    = 0x400;
+    public static final short NLM_F_APPEND    = 0x800;
+
+    // TODO: Probably need to distinguish the flags which have the same value. For example,
+    // NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200).
+    private static String stringForNlMsgFlags(short flags) {
+        final StringBuilder sb = new StringBuilder();
+        if ((flags & NLM_F_REQUEST) != 0) {
+            sb.append("NLM_F_REQUEST");
+        }
+        if ((flags & NLM_F_MULTI) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NLM_F_MULTI");
+        }
+        if ((flags & NLM_F_ACK) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NLM_F_ACK");
+        }
+        if ((flags & NLM_F_ECHO) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NLM_F_ECHO");
+        }
+        if ((flags & NLM_F_ROOT) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NLM_F_ROOT");
+        }
+        if ((flags & NLM_F_MATCH) != 0) {
+            if (sb.length() > 0) {
+                sb.append("|");
+            }
+            sb.append("NLM_F_MATCH");
+        }
+        return sb.toString();
+    }
+
+    private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+        return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+    }
+
+    /**
+     * Parse netlink message header from buffer.
+     */
+    @Nullable
+    public static StructNlMsgHdr parse(@NonNull ByteBuffer byteBuffer) {
+        if (!hasAvailableSpace(byteBuffer)) return null;
+
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the exception
+        // of usage within unittests.
+        final StructNlMsgHdr struct = new StructNlMsgHdr();
+        struct.nlmsg_len = byteBuffer.getInt();
+        struct.nlmsg_type = byteBuffer.getShort();
+        struct.nlmsg_flags = byteBuffer.getShort();
+        struct.nlmsg_seq = byteBuffer.getInt();
+        struct.nlmsg_pid = byteBuffer.getInt();
+
+        if (struct.nlmsg_len < STRUCT_SIZE) {
+            // Malformed.
+            return null;
+        }
+        return struct;
+    }
+
+    public int nlmsg_len;
+    public short nlmsg_type;
+    public short nlmsg_flags;
+    public int nlmsg_seq;
+    public int nlmsg_pid;
+
+    public StructNlMsgHdr() {
+        nlmsg_len = 0;
+        nlmsg_type = 0;
+        nlmsg_flags = 0;
+        nlmsg_seq = 0;
+        nlmsg_pid = 0;
+    }
+
+    /**
+     * Write netlink message header to ByteBuffer.
+     */
+    public void pack(ByteBuffer byteBuffer) {
+        // The ByteOrder must have already been set by the caller.  In most
+        // cases ByteOrder.nativeOrder() is correct, with the possible
+        // exception of usage within unittests.
+        byteBuffer.putInt(nlmsg_len);
+        byteBuffer.putShort(nlmsg_type);
+        byteBuffer.putShort(nlmsg_flags);
+        byteBuffer.putInt(nlmsg_seq);
+        byteBuffer.putInt(nlmsg_pid);
+    }
+
+    @Override
+    public String toString() {
+        return toString(null /* unknown netlink family */);
+    }
+
+    /**
+     * Transform a netlink header into a string. The netlink family is required for transforming
+     * a netlink type integer into a string.
+     * @param nlFamily netlink family. Using Integer will not incur autoboxing penalties because
+     *                 family values are small, and all Integer objects between -128 and 127 are
+     *                 statically cached. See Integer.IntegerCache.
+     * @return A list of header elements.
+     */
+    @NonNull
+    public String toString(@Nullable Integer nlFamily) {
+        final String typeStr = "" + nlmsg_type
+                + "(" + (nlFamily == null
+                ? "" : NetlinkConstants.stringForNlMsgType(nlmsg_type, nlFamily))
+                + ")";
+        final String flagsStr = "" + nlmsg_flags
+                + "(" + stringForNlMsgFlags(nlmsg_flags) + ")";
+        return "StructNlMsgHdr{ "
+                + "nlmsg_len{" + nlmsg_len + "}, "
+                + "nlmsg_type{" + typeStr + "}, "
+                + "nlmsg_flags{" + flagsStr + "}, "
+                + "nlmsg_seq{" + nlmsg_seq + "}, "
+                + "nlmsg_pid{" + nlmsg_pid + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
new file mode 100644
index 0000000..3cd7292
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtMsg.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct rtmsg
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructRtMsg extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 12;
+
+    @Field(order = 0, type = Type.U8)
+    public final short family; // Address family of route.
+    @Field(order = 1, type = Type.U8)
+    public final short dstLen; // Length of destination.
+    @Field(order = 2, type = Type.U8)
+    public final short srcLen; // Length of source.
+    @Field(order = 3, type = Type.U8)
+    public final short tos;    // TOS filter.
+    @Field(order = 4, type = Type.U8)
+    public final short table;  // Routing table ID.
+    @Field(order = 5, type = Type.U8)
+    public final short protocol; // Routing protocol.
+    @Field(order = 6, type = Type.U8)
+    public final short scope;  // distance to the destination.
+    @Field(order = 7, type = Type.U8)
+    public final short type;   // route type
+    @Field(order = 8, type = Type.U32)
+    public final long flags;
+
+    StructRtMsg(short family, short dstLen, short srcLen, short tos, short table, short protocol,
+            short scope, short type, long flags) {
+        this.family = family;
+        this.dstLen = dstLen;
+        this.srcLen = srcLen;
+        this.tos = tos;
+        this.table = table;
+        this.protocol = protocol;
+        this.scope = scope;
+        this.type = type;
+        this.flags = flags;
+    }
+
+    /**
+     * Parse a rtmsg struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the rtmsg struct.
+     * @return the parsed rtmsg struct, or {@code null} if the rtmsg struct could not be
+     *         parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructRtMsg parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructRtMsg.class, byteBuffer);
+    }
+
+    /**
+     * Write the rtmsg struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java
new file mode 100644
index 0000000..fef1f9e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct rta_cacheinfo
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructRtaCacheInfo extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 32;
+
+    @Field(order = 0, type = Type.U32)
+    public final long clntref;
+    @Field(order = 1, type = Type.U32)
+    public final long lastuse;
+    @Field(order = 2, type = Type.S32)
+    public final int expires;
+    @Field(order = 3, type = Type.U32)
+    public final long error;
+    @Field(order = 4, type = Type.U32)
+    public final long used;
+    @Field(order = 5, type = Type.U32)
+    public final long id;
+    @Field(order = 6, type = Type.U32)
+    public final long ts;
+    @Field(order = 7, type = Type.U32)
+    public final long tsage;
+
+    StructRtaCacheInfo(long clntref, long lastuse, int expires, long error, long used, long id,
+            long ts, long tsage) {
+        this.clntref = clntref;
+        this.lastuse = lastuse;
+        this.expires = expires;
+        this.error = error;
+        this.used = used;
+        this.id = id;
+        this.ts = ts;
+        this.tsage = tsage;
+    }
+
+    /**
+     * Parse an rta_cacheinfo struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the rta_cacheinfo.
+     * @return the parsed rta_cacheinfo struct, or {@code null} if the rta_cacheinfo struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructRtaCacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructRtaCacheInfo.class, byteBuffer);
+    }
+
+    /**
+     * Write a rta_cacheinfo struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
new file mode 100644
index 0000000..92ef8a7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L2 ethernet header as per IEEE 802.3. Does not include a 802.1Q tag.
+ *
+ * 0                   1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Destination          |
+ * +-                             -+
+ * |            Ethernet           |
+ * +-                             -+
+ * |            Address            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |             Source            |
+ * +-                             -+
+ * |            Ethernet           |
+ * +-                             -+
+ * |            Address            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |            EtherType          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class EthernetHeader extends Struct {
+    @Field(order = 0, type = Type.EUI48)
+    public final MacAddress dstMac;
+    @Field(order = 1, type = Type.EUI48)
+    public final MacAddress srcMac;
+    @Field(order = 2, type = Type.U16)
+    public final int etherType;
+
+    public EthernetHeader(final MacAddress dstMac, final MacAddress srcMac,
+            final int etherType) {
+        this.dstMac = dstMac;
+        this.srcMac = srcMac;
+        this.etherType = etherType;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java b/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java
new file mode 100644
index 0000000..575a1f3
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/IaAddressOption.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IA_ADDR;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DHCPv6 IA Address option.
+ * https://tools.ietf.org/html/rfc8415. This does not contain any option.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          OPTION_IAADDR        |          option-len           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * |                         IPv6-address                          |
+ * |                                                               |
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      preferred-lifetime                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        valid-lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * .                                                               .
+ * .                        IAaddr-options                         .
+ * .                                                               .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class IaAddressOption extends Struct {
+    public static final int LENGTH = 24; // option length excluding IAaddr-options
+
+    @Field(order = 0, type = Type.S16)
+    public final short code;
+    @Field(order = 1, type = Type.S16)
+    public final short length;
+    @Field(order = 2, type = Type.Ipv6Address)
+    public final Inet6Address address;
+    @Field(order = 3, type = Type.U32)
+    public final long preferred;
+    @Field(order = 4, type = Type.U32)
+    public final long valid;
+
+    IaAddressOption(final short code, final short length, final Inet6Address address,
+            final long preferred, final long valid) {
+        this.code = code;
+        this.length = length;
+        this.address = address;
+        this.preferred = preferred;
+        this.valid = valid;
+    }
+
+    /**
+     * Build an IA Address option from the required specific parameters.
+     */
+    public static ByteBuffer build(final short length, final long id, final Inet6Address address,
+            final long preferred, final long valid) {
+        final IaAddressOption option = new IaAddressOption((short) DHCP6_OPTION_IA_ADDR,
+                length /* 24 + IAaddr-options length */, address, preferred, valid);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
new file mode 100644
index 0000000..dbf79dc
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IA_PD;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DHCPv6 IA_PD option.
+ * https://tools.ietf.org/html/rfc8415. This does not contain any option.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |         OPTION_IA_PD          |           option-len          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         IAID (4 octets)                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                              T1                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                              T2                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * .                                                               .
+ * .                          IA_PD-options                        .
+ * .                                                               .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+public class IaPdOption extends Struct {
+    public static final int LENGTH = 12; // option length excluding IA_PD options
+
+    @Field(order = 0, type = Type.S16)
+    public final short code;
+    @Field(order = 1, type = Type.S16)
+    public final short length;
+    @Field(order = 2, type = Type.U32)
+    public final long id;
+    @Field(order = 3, type = Type.U32)
+    public final long t1;
+    @Field(order = 4, type = Type.U32)
+    public final long t2;
+
+    IaPdOption(final short code, final short length, final long id, final long t1,
+            final long t2) {
+        this.code = code;
+        this.length = length;
+        this.id = id;
+        this.t1 = t1;
+        this.t2 = t2;
+    }
+
+    /**
+     * Build an IA_PD option from the required specific parameters.
+     */
+    public static ByteBuffer build(final short length, final long id, final long t1,
+            final long t2) {
+        final IaPdOption option = new IaPdOption((short) DHCP6_OPTION_IA_PD,
+                length /* 12 + IA_PD options length */, id, t1, t2);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
new file mode 100644
index 0000000..59d655c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DHCPv6 IA Prefix Option.
+ * https://tools.ietf.org/html/rfc8415. This does not contain any option.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |        OPTION_IAPREFIX        |           option-len          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      preferred-lifetime                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        valid-lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | prefix-length |                                               |
+ * +-+-+-+-+-+-+-+-+          IPv6-prefix                          |
+ * |                           (16 octets)                         |
+ * |                                                               |
+ * |                                                               |
+ * |                                                               |
+ * |               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |               |                                               .
+ * +-+-+-+-+-+-+-+-+                                               .
+ * .                       IAprefix-options                        .
+ * .                                                               .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class IaPrefixOption extends Struct {
+    public static final int LENGTH = 25; // option length excluding IAprefix-options
+
+    @Field(order = 0, type = Type.S16)
+    public final short code;
+    @Field(order = 1, type = Type.S16)
+    public final short length;
+    @Field(order = 2, type = Type.U32)
+    public final long preferred;
+    @Field(order = 3, type = Type.U32)
+    public final long valid;
+    @Field(order = 4, type = Type.S8)
+    public final byte prefixLen;
+    @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+    public final byte[] prefix;
+
+    public IaPrefixOption(final short code, final short length, final long preferred,
+            final long valid, final byte prefixLen, final byte[] prefix) {
+        this.code = code;
+        this.length = length;
+        this.preferred = preferred;
+        this.valid = valid;
+        this.prefixLen = prefixLen;
+        this.prefix = prefix.clone();
+    }
+
+    /**
+     * Build an IA_PD prefix option with given specific parameters.
+     */
+    public static ByteBuffer build(final short length, final long preferred, final long valid,
+            final byte prefixLen, final byte[] prefix) {
+        final IaPrefixOption option = new IaPrefixOption((byte) DHCP6_OPTION_IAPREFIX,
+                length /* 25 + IAPrefix options length */, preferred, valid, prefixLen, prefix);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java
new file mode 100644
index 0000000..454bb07
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java
@@ -0,0 +1,43 @@
+/*
+ * 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv4 header as per https://tools.ietf.org/html/rfc792.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Icmpv4Header extends Struct {
+    @Field(order = 0, type = Type.U8)
+    public short type;
+    @Field(order = 1, type = Type.U8)
+    public short code;
+    @Field(order = 2, type = Type.S16)
+    public short checksum;
+    public Icmpv4Header(final short type, final short code, final short checksum) {
+        this.type = type;
+        this.code = code;
+        this.checksum = checksum;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java
new file mode 100644
index 0000000..c82ae02
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 header as per https://tools.ietf.org/html/rfc4443.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Icmpv6Header extends Struct {
+    @Field(order = 0, type = Type.U8)
+    public short type;
+    @Field(order = 1, type = Type.U8)
+    public short code;
+    @Field(order = 2, type = Type.S16)
+    public short checksum;
+
+    public Icmpv6Header(final short type, final short code, final short checksum) {
+        this.type = type;
+        this.code = code;
+        this.checksum = checksum;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
new file mode 100644
index 0000000..5249454
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+
+/**
+ * L3 IPv4 header as per https://tools.ietf.org/html/rfc791.
+ * This class doesn't contain options field.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version|  IHL  |Type of Service|          Total Length         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |         Identification        |Flags|      Fragment Offset    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Time to Live |    Protocol   |         Header Checksum       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                       Source Address                          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Destination Address                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv4Header extends Struct {
+    // IP Version=IPv4, IHL is always 5(*4bytes) because options are not supported.
+    @VisibleForTesting
+    public static final byte IPHDR_VERSION_IHL = 0x45;
+
+    @Field(order = 0, type = Type.S8)
+    // version (4 bits), IHL (4 bits)
+    public final byte vi;
+    @Field(order = 1, type = Type.S8)
+    public final byte tos;
+    @Field(order = 2, type = Type.U16)
+    public final int totalLength;
+    @Field(order = 3, type = Type.S16)
+    public final short id;
+    @Field(order = 4, type = Type.S16)
+    // flags (3 bits), fragment offset (13 bits)
+    public final short flagsAndFragmentOffset;
+    @Field(order = 5, type = Type.U8)
+    public final short ttl;
+    @Field(order = 6, type = Type.S8)
+    public final byte protocol;
+    @Field(order = 7, type = Type.S16)
+    public final short checksum;
+    @Field(order = 8, type = Type.Ipv4Address)
+    public final Inet4Address srcIp;
+    @Field(order = 9, type = Type.Ipv4Address)
+    public final Inet4Address dstIp;
+
+    public Ipv4Header(final byte tos, final int totalLength, final short id,
+            final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+            final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+        this(IPHDR_VERSION_IHL, tos, totalLength, id, flagsAndFragmentOffset, ttl,
+                protocol, checksum, srcIp, dstIp);
+    }
+
+    private Ipv4Header(final byte vi, final byte tos, final int totalLength, final short id,
+            final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+            final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+        this.vi = vi;
+        this.tos = tos;
+        this.totalLength = totalLength;
+        this.id = id;
+        this.flagsAndFragmentOffset = flagsAndFragmentOffset;
+        this.ttl = ttl;
+        this.protocol = protocol;
+        this.checksum = checksum;
+        this.srcIp = srcIp;
+        this.dstIp = dstIp;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
new file mode 100644
index 0000000..a14e064
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * L3 IPv6 header as per https://tools.ietf.org/html/rfc8200.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version| Traffic Class |           Flow Label                  |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |         Payload Length        |  Next Header  |   Hop Limit   |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                         Source Address                        +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                      Destination Address                      +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv6Header extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public int vtf;
+    @Field(order = 1, type = Type.U16)
+    public int payloadLength;
+    @Field(order = 2, type = Type.S8)
+    public byte nextHeader;
+    @Field(order = 3, type = Type.U8)
+    public short hopLimit;
+    @Field(order = 4, type = Type.Ipv6Address)
+    public Inet6Address srcIp;
+    @Field(order = 5, type = Type.Ipv6Address)
+    public Inet6Address dstIp;
+
+    public Ipv6Header(final int vtf, final int payloadLength, final byte nextHeader,
+            final short hopLimit, final Inet6Address srcIp, final Inet6Address dstIp) {
+        this.vtf = vtf;
+        this.payloadLength = payloadLength;
+        this.nextHeader = nextHeader;
+        this.hopLimit = hopLimit;
+        this.srcIp = srcIp;
+        this.dstIp = dstIp;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java
new file mode 100644
index 0000000..0dccb72
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * structure in6_pktinfo
+ *
+ * see also:
+ *
+ *     include/uapi/linux/ipv6.h
+ */
+public class Ipv6PktInfo extends Struct {
+    @Field(order = 0, type = Type.Ipv6Address)
+    public final Inet6Address addr; // IPv6 source or destination address
+    @Field(order = 1, type = Type.S32)
+    public final int ifindex;       // interface index
+
+    public Ipv6PktInfo(final Inet6Address addr, final int ifindex) {
+        this.addr = addr;
+        this.ifindex = ifindex;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/LlaOption.java b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
new file mode 100644
index 0000000..fbaccab
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 source/target link-layer address option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     |    Link-Layer Address ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class LlaOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.EUI48)
+    // Link layer address length and format varies on different link layers, which is not
+    // guaranteed to be a 6-byte MAC address. However, Struct only supports 6-byte MAC
+    // addresses type(EUI-48) for now.
+    public final MacAddress linkLayerAddress;
+
+    LlaOption(final byte type, final byte length, final MacAddress linkLayerAddress) {
+        this.type = type;
+        this.length = length;
+        this.linkLayerAddress = linkLayerAddress;
+    }
+
+    /**
+     * Build a target link-layer address option from the required specified parameters.
+     */
+    public static ByteBuffer build(final byte type, final MacAddress linkLayerAddress) {
+        final LlaOption option = new LlaOption(type, (byte) 1 /* option len */, linkLayerAddress);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/MtuOption.java b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
new file mode 100644
index 0000000..34bc21c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 MTU option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     |           Reserved            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                              MTU                              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class MtuOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S16)
+    public final short reserved;
+    @Field(order = 3, type = Type.U32)
+    public final long mtu;
+
+    MtuOption(final byte type, final byte length, final short reserved,
+            final long mtu) {
+        this.type = type;
+        this.length = length;
+        this.reserved = reserved;
+        this.mtu = mtu;
+    }
+
+    /**
+     * Build a MTU option from the required specified parameters.
+     */
+    public static ByteBuffer build(final long mtu) {
+        final MtuOption option = new MtuOption((byte) ICMPV6_ND_OPTION_MTU,
+                (byte) 1 /* option len */, (short) 0 /* reserved */, mtu);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/NaHeader.java b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
new file mode 100644
index 0000000..90c078e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * ICMPv6 Neighbor Advertisement header, follow {@link Icmpv6Header}, as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |R|S|O|                     Reserved                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                       Target Address                          +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class NaHeader extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public int flags; // Router flag, Solicited flag, Override flag and 29 Reserved bits.
+    @Field(order = 1, type = Type.Ipv6Address)
+    public Inet6Address target;
+
+    public NaHeader(final int flags, final Inet6Address target) {
+        this.flags = flags;
+        this.target = target;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/NsHeader.java b/staticlibs/device/com/android/net/module/util/structs/NsHeader.java
new file mode 100644
index 0000000..2e8b77b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/NsHeader.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * ICMPv6 Neighbor Solicitation header, follow {@link Icmpv6Header}, as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Reserved                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                       Target Address                          +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class NsHeader extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public int reserved; // 32 Reserved bits.
+    @Field(order = 1, type = Type.Ipv6Address)
+    public Inet6Address target;
+
+    NsHeader(int reserved, final Inet6Address target) {
+        this.reserved = reserved;
+        this.target = target;
+    }
+
+    public NsHeader(final Inet6Address target) {
+        this(0, target);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
new file mode 100644
index 0000000..49d7654
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+
+import android.net.IpPrefix;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 prefix information option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     | Prefix Length |L|A| Reserved1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Valid Lifetime                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                       Preferred Lifetime                      |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Reserved2                           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                            Prefix                             +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class PrefixInformationOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S8)
+    public final byte prefixLen;
+    @Field(order = 3, type = Type.S8)
+    // On-link flag, Autonomous address configuration flag, 6-reserved bits
+    public final byte flags;
+    @Field(order = 4, type = Type.U32)
+    public final long validLifetime;
+    @Field(order = 5, type = Type.U32)
+    public final long preferredLifetime;
+    @Field(order = 6, type = Type.S32)
+    public final int reserved;
+    @Field(order = 7, type = Type.ByteArray, arraysize = 16)
+    public final byte[] prefix;
+
+    PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
+            final byte flags, final long validLifetime, final long preferredLifetime,
+            final int reserved, @NonNull final byte[] prefix) {
+        this.type = type;
+        this.length = length;
+        this.prefixLen = prefixLen;
+        this.flags = flags;
+        this.validLifetime = validLifetime;
+        this.preferredLifetime = preferredLifetime;
+        this.reserved = reserved;
+        this.prefix = prefix;
+    }
+
+    /**
+     * Build a Prefix Information option from the required specified parameters.
+     */
+    public static ByteBuffer build(final IpPrefix prefix, final byte flags,
+            final long validLifetime, final long preferredLifetime) {
+        final PrefixInformationOption option = new PrefixInformationOption(
+                (byte) ICMPV6_ND_OPTION_PIO, (byte) 4 /* option len */,
+                (byte) prefix.getPrefixLength(), flags, validLifetime, preferredLifetime,
+                (int) 0, prefix.getRawAddress());
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RaHeader.java b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java
new file mode 100644
index 0000000..31a5cb7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 Router Advertisement header, follow [Icmpv6Header], as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Cur Hop Limit |M|O|  Reserved |       Router Lifetime         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Reachable Time                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          Retrans Timer                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class RaHeader extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte hopLimit;
+    // "Managed address configuration", "Other configuration" bits, and 6 reserved bits
+    @Field(order = 1, type = Type.S8)
+    public final byte flags;
+    @Field(order = 2, type = Type.U16)
+    public final int lifetime;
+    @Field(order = 3, type = Type.U32)
+    public final long reachableTime;
+    @Field(order = 4, type = Type.U32)
+    public final long retransTimer;
+
+    public RaHeader(final byte hopLimit, final byte flags, final int lifetime,
+            final long reachableTime, final long retransTimer) {
+        this.hopLimit = hopLimit;
+        this.flags = flags;
+        this.lifetime = lifetime;
+        this.reachableTime = reachableTime;
+        this.retransTimer = retransTimer;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
new file mode 100644
index 0000000..4a5bd7e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
+
+import android.net.InetAddresses;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * IPv6 RA recursive DNS server option, as per https://tools.ietf.org/html/rfc8106.
+ * This should be followed by a series of DNSv6 server addresses.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Length    |           Reserved            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Lifetime                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * :            Addresses of IPv6 Recursive DNS Servers            :
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RdnssOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S16)
+    public final short reserved;
+    @Field(order = 3, type = Type.U32)
+    public final long lifetime;
+
+    public RdnssOption(final byte type, final byte length, final short reserved,
+            final long lifetime) {
+        this.type = type;
+        this.length = length;
+        this.reserved = reserved;
+        this.lifetime = lifetime;
+    }
+
+    /**
+     * Build a RDNSS option from the required specified Inet6Address parameters.
+     */
+    public static ByteBuffer build(final long lifetime, final Inet6Address... servers) {
+        final byte length = (byte) (1 + 2 * servers.length);
+        final RdnssOption option = new RdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                length, (short) 0, lifetime);
+        final ByteBuffer buffer = ByteBuffer.allocate(length * 8);
+        option.writeToByteBuffer(buffer);
+        for (Inet6Address server : servers) {
+            buffer.put(server.getAddress());
+        }
+        buffer.flip();
+        return buffer;
+    }
+
+    /**
+     * Build a RDNSS option from the required specified String parameters.
+     *
+     * @throws IllegalArgumentException if {@code servers} does not contain only numeric addresses.
+     */
+    public static ByteBuffer build(final long lifetime, final String... servers) {
+        final Inet6Address[] serverArray = new Inet6Address[servers.length];
+        for (int i = 0; i < servers.length; i++) {
+            serverArray[i] = (Inet6Address) InetAddresses.parseNumericAddress(servers[i]);
+        }
+        return build(lifetime, serverArray);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
new file mode 100644
index 0000000..49bafed
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO;
+
+import android.net.IpPrefix;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     | Prefix Length |Resvd|Prf|Resvd|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        Route Lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                   Prefix (Variable Length)                    |
+ * .                                                               .
+ * .                                                               .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RouteInformationOption extends Struct {
+    public enum Preference {
+        HIGH((byte) 0x1),
+        MEDIUM((byte) 0x0),
+        LOW((byte) 0x3),
+        RESERVED((byte) 0x2);
+
+        final byte mValue;
+        Preference(byte value) {
+            this.mValue = value;
+        }
+    }
+
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte octets
+    @Field(order = 2, type = Type.U8)
+    public final short prefixLen;
+    @Field(order = 3, type = Type.S8)
+    public final byte prf;
+    @Field(order = 4, type = Type.U32)
+    public final long routeLifetime;
+    @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+    public final byte[] prefix;
+
+    RouteInformationOption(final byte type, final byte length, final short prefixLen,
+            final byte prf, final long routeLifetime, @NonNull final byte[] prefix) {
+        this.type = type;
+        this.length = length;
+        this.prefixLen = prefixLen;
+        this.prf = prf;
+        this.routeLifetime = routeLifetime;
+        this.prefix = prefix;
+    }
+
+    /**
+     * Build a Route Information option from the required specified parameters.
+     */
+    public static ByteBuffer build(final IpPrefix prefix, final Preference prf,
+            final long routeLifetime) {
+        // The prefix field is always assumed to have 16 bytes, but the number of leading
+        // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the
+        // option length to 3.
+        final RouteInformationOption option = new RouteInformationOption(
+                (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */,
+                (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime,
+                prefix.getRawAddress());
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RsHeader.java b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java
new file mode 100644
index 0000000..0b51ff2
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 Router Solicitation header, follow [Icmpv6Header], as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                            Reserved                           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class RsHeader extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public final int reserved;
+
+    public RsHeader(final int reserved) {
+        this.reserved = reserved;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
new file mode 100644
index 0000000..0c97401
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 TCP header as per https://tools.ietf.org/html/rfc793.
+ * This class does not contain option and data fields.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Source Port          |       Destination Port        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        Sequence Number                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Acknowledgment Number                      |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Data |           |U|A|P|R|S|F|                               |
+ * | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
+ * |       |           |G|K|H|T|N|N|                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Checksum            |         Urgent Pointer        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Options                    |    Padding    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                             data                              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class TcpHeader extends Struct {
+    @Field(order = 0, type = Type.U16)
+    public final int srcPort;
+    @Field(order = 1, type = Type.U16)
+    public final int dstPort;
+    @Field(order = 2, type = Type.U32)
+    public final long seq;
+    @Field(order = 3, type = Type.U32)
+    public final long ack;
+    @Field(order = 4, type = Type.S16)
+    // data Offset (4 bits), reserved (6 bits), control bits (6 bits)
+    // TODO: update with bitfields once class Struct supports it
+    public final short dataOffsetAndControlBits;
+    @Field(order = 5, type = Type.U16)
+    public final int window;
+    @Field(order = 6, type = Type.S16)
+    public final short checksum;
+    @Field(order = 7, type = Type.U16)
+    public final int urgentPointer;
+
+    public TcpHeader(final int srcPort, final int dstPort, final long seq, final long ack,
+            final short dataOffsetAndControlBits, final int window, final short checksum,
+            final int urgentPointer) {
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+        this.seq = seq;
+        this.ack = ack;
+        this.dataOffsetAndControlBits = dataOffsetAndControlBits;
+        this.window = window;
+        this.checksum = checksum;
+        this.urgentPointer = urgentPointer;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
new file mode 100644
index 0000000..8b0316b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 UDP header as per https://tools.ietf.org/html/rfc768.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Source Port          |       Destination Port        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Length              |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          data octets  ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ...
+ */
+public class UdpHeader extends Struct {
+    @Field(order = 0, type = Type.U16)
+    public final int srcPort;
+    @Field(order = 1, type = Type.U16)
+    public final int dstPort;
+    @Field(order = 2, type = Type.U16)
+    public final int length;
+    @Field(order = 3, type = Type.S16)
+    public final short checksum;
+
+    public UdpHeader(final int srcPort, final int dstPort, final int length,
+            final short checksum) {
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+        this.length = length;
+        this.checksum = checksum;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java b/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java
new file mode 100644
index 0000000..341c44b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/wear/NetPacketHelpers.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import com.android.net.module.util.async.ReadableByteBuffer;
+
+/**
+ * Implements utilities for decoding parts of TCP/UDP/IP headers.
+ *
+ * @hide
+ */
+final class NetPacketHelpers {
+    static void encodeNetworkUnsignedInt16(int value, byte[] dst, final int dstPos) {
+        dst[dstPos] = (byte) ((value >> 8) & 0xFF);
+        dst[dstPos + 1] = (byte) (value & 0xFF);
+    }
+
+    static int decodeNetworkUnsignedInt16(byte[] data, final int pos) {
+        return ((data[pos] & 0xFF) << 8) | (data[pos + 1] & 0xFF);
+    }
+
+    static int decodeNetworkUnsignedInt16(ReadableByteBuffer data, final int pos) {
+        return ((data.peek(pos) & 0xFF) << 8) | (data.peek(pos + 1) & 0xFF);
+    }
+
+    private NetPacketHelpers() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/wear/PacketFile.java b/staticlibs/device/com/android/net/module/util/wear/PacketFile.java
new file mode 100644
index 0000000..7f5ed78
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/wear/PacketFile.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+/**
+ * Defines bidirectional file where all transmissions are made as complete packets.
+ *
+ * Automatically manages all readability and writeability events in EventManager:
+ *   - When read buffer has more space - asks EventManager to notify on more data
+ *   - When write buffer has more space - asks the user to provide more data
+ *   - When underlying file cannot accept more data - registers EventManager callback
+ *
+ * @hide
+ */
+public interface PacketFile {
+    /** @hide */
+    public enum ErrorCode {
+        UNEXPECTED_ERROR,
+        IO_ERROR,
+        INBOUND_PACKET_TOO_LARGE,
+        OUTBOUND_PACKET_TOO_LARGE,
+    }
+
+    /**
+     * Receives notifications when new data or output space is available.
+     *
+     * @hide
+     */
+    public interface Listener {
+        /**
+         * Handles the initial part of the stream, which on some systems provides lower-level
+         * configuration data.
+         *
+         * Returns the number of bytes consumed, or zero if the preamble has been fully read.
+         */
+        int onPreambleData(byte[] data, int pos, int len);
+
+        /** Handles one extracted packet. */
+        void onInboundPacket(byte[] data, int pos, int len);
+
+        /** Notifies on new data being added to the buffer. */
+        void onInboundBuffered(int newByteCount, int totalBufferedSize);
+
+        /** Notifies on data being flushed from output buffer. */
+        void onOutboundPacketSpace();
+
+        /** Notifies on unrecoverable error in the packet processing. */
+        void onPacketFileError(ErrorCode error, String message);
+    }
+
+    /** Requests this file to be closed. */
+    void close();
+
+    /** Permanently disables reading of this file, and clears all buffered data. */
+    void shutdownReading();
+
+    /** Starts or resumes async read operations on this file. */
+    void continueReading();
+
+    /** Returns the number of bytes currently buffered as input. */
+    int getInboundBufferSize();
+
+    /** Returns the number of bytes currently available for buffering for output. */
+    int getOutboundFreeSize();
+
+    /**
+     * Queues the given data for output.
+     * Throws runtime exception if there is not enough space.
+     */
+    boolean enqueueOutboundPacket(byte[] data, int pos, int len);
+}
diff --git a/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java b/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java
new file mode 100644
index 0000000..52dbee4
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/wear/StreamingPacketFile.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import com.android.net.module.util.async.BufferedFile;
+import com.android.net.module.util.async.EventManager;
+import com.android.net.module.util.async.FileHandle;
+import com.android.net.module.util.async.Assertions;
+import com.android.net.module.util.async.ReadableByteBuffer;
+
+import java.io.IOException;
+
+/**
+ * Implements PacketFile based on a streaming file descriptor.
+ *
+ * Packets are delineated using network-order 2-byte length indicators.
+ *
+ * @hide
+ */
+public final class StreamingPacketFile implements PacketFile, BufferedFile.Listener {
+    private static final int HEADER_SIZE = 2;
+
+    private final EventManager mEventManager;
+    private final Listener mListener;
+    private final BufferedFile mFile;
+    private final int mMaxPacketSize;
+    private final ReadableByteBuffer mInboundBuffer;
+    private boolean mIsInPreamble = true;
+
+    private final byte[] mTempPacketReadBuffer;
+    private final byte[] mTempHeaderWriteBuffer;
+
+    public StreamingPacketFile(
+            EventManager eventManager,
+            FileHandle fileHandle,
+            Listener listener,
+            int maxPacketSize,
+            int maxBufferedInboundPackets,
+            int maxBufferedOutboundPackets) throws IOException {
+        if (eventManager == null || fileHandle == null || listener == null) {
+            throw new NullPointerException();
+        }
+
+        mEventManager = eventManager;
+        mListener = listener;
+        mMaxPacketSize = maxPacketSize;
+
+        final int maxTotalLength = HEADER_SIZE + maxPacketSize;
+
+        mFile = BufferedFile.create(eventManager, fileHandle, this,
+            maxTotalLength * maxBufferedInboundPackets,
+            maxTotalLength * maxBufferedOutboundPackets);
+        mInboundBuffer = mFile.getInboundBuffer();
+
+        mTempPacketReadBuffer = new byte[maxTotalLength];
+        mTempHeaderWriteBuffer = new byte[HEADER_SIZE];
+    }
+
+    @Override
+    public void close() {
+        mFile.close();
+    }
+
+    public BufferedFile getUnderlyingFileForTest() {
+        return mFile;
+    }
+
+    @Override
+    public void shutdownReading() {
+        mFile.shutdownReading();
+    }
+
+    @Override
+    public void continueReading() {
+        mFile.continueReading();
+    }
+
+    @Override
+    public int getInboundBufferSize() {
+        return mInboundBuffer.size();
+    }
+
+    @Override
+    public void onBufferedFileClosed() {
+    }
+
+    @Override
+    public void onBufferedFileInboundData(int readByteCount) {
+        if (mFile.isReadingShutdown()) {
+            return;
+        }
+
+        if (readByteCount > 0) {
+            mListener.onInboundBuffered(readByteCount, mInboundBuffer.size());
+        }
+
+        if (extractOnePacket() && !mFile.isReadingShutdown()) {
+            // There could be more packets already buffered, continue parsing next
+            // packet even before another read event comes
+            mEventManager.execute(() -> {
+                onBufferedFileInboundData(0);
+            });
+        } else {
+            continueReading();
+        }
+    }
+
+    private boolean extractOnePacket() {
+        while (mIsInPreamble) {
+            final int directReadSize = Math.min(
+                mInboundBuffer.getDirectReadSize(), mTempPacketReadBuffer.length);
+            if (directReadSize == 0) {
+                return false;
+            }
+
+            // Copy for safety, so higher-level callback cannot modify the data.
+            System.arraycopy(mInboundBuffer.getDirectReadBuffer(),
+                mInboundBuffer.getDirectReadPos(), mTempPacketReadBuffer, 0, directReadSize);
+
+            final int preambleConsumedBytes = mListener.onPreambleData(
+                mTempPacketReadBuffer, 0, directReadSize);
+            if (mFile.isReadingShutdown()) {
+                return false;  // The callback has called shutdownReading().
+            }
+
+            if (preambleConsumedBytes == 0) {
+                mIsInPreamble = false;
+                break;
+            }
+
+            mInboundBuffer.accountForDirectRead(preambleConsumedBytes);
+        }
+
+        final int bufferedSize = mInboundBuffer.size();
+        if (bufferedSize < HEADER_SIZE) {
+            return false;
+        }
+
+        final int dataLength = NetPacketHelpers.decodeNetworkUnsignedInt16(mInboundBuffer, 0);
+        if (dataLength > mMaxPacketSize) {
+            mListener.onPacketFileError(
+                PacketFile.ErrorCode.INBOUND_PACKET_TOO_LARGE,
+                "Inbound packet length: " + dataLength);
+            return false;
+        }
+
+        final int totalLength = HEADER_SIZE + dataLength;
+        if (bufferedSize < totalLength) {
+            return false;
+        }
+
+        mInboundBuffer.readBytes(mTempPacketReadBuffer, 0, totalLength);
+
+        mListener.onInboundPacket(mTempPacketReadBuffer, HEADER_SIZE, dataLength);
+        return true;
+    }
+
+    @Override
+    public int getOutboundFreeSize() {
+        final int freeSize = mFile.getOutboundBufferFreeSize();
+        return (freeSize > HEADER_SIZE ? freeSize - HEADER_SIZE : 0);
+    }
+
+    @Override
+    public boolean enqueueOutboundPacket(byte[] buffer, int pos, int len) {
+        Assertions.throwsIfOutOfBounds(buffer, pos, len);
+
+        if (len == 0) {
+            return true;
+        }
+
+        if (len > mMaxPacketSize) {
+            mListener.onPacketFileError(
+                PacketFile.ErrorCode.OUTBOUND_PACKET_TOO_LARGE,
+                "Outbound packet length: " + len);
+            return false;
+        }
+
+        NetPacketHelpers.encodeNetworkUnsignedInt16(len, mTempHeaderWriteBuffer, 0);
+
+        mFile.enqueueOutboundData(
+            mTempHeaderWriteBuffer, 0, mTempHeaderWriteBuffer.length,
+            buffer, pos, len);
+        return true;
+    }
+
+    @Override
+    public void onBufferedFileOutboundSpace() {
+        mListener.onOutboundPacketSpace();
+    }
+
+    @Override
+    public void onBufferedFileIoError(String message) {
+        mListener.onPacketFileError(PacketFile.ErrorCode.IO_ERROR, message);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("maxPacket=");
+        sb.append(mMaxPacketSize);
+        sb.append(", file={");
+        sb.append(mFile);
+        sb.append("}");
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/BestClock.java b/staticlibs/framework/com/android/net/module/util/BestClock.java
new file mode 100644
index 0000000..35391ad
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/BestClock.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import android.util.Log;
+
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.Arrays;
+
+/**
+ * Single {@link Clock} that will return the best available time from a set of
+ * prioritized {@link Clock} instances.
+ * <p>
+ * For example, when {@link SystemClock#currentNetworkTimeClock()} isn't able to
+ * provide the time, this class could use {@link Clock#systemUTC()} instead.
+ *
+ * Note that this is re-implemented based on {@code android.os.BestClock} to be used inside
+ * the mainline module. And the class does NOT support serialization.
+ *
+ * @hide
+ */
+final public class BestClock extends Clock {
+    private static final String TAG = "BestClock";
+    private final ZoneId mZone;
+    private final Clock[] mClocks;
+
+    public BestClock(ZoneId zone, Clock... clocks) {
+        super();
+        this.mZone = zone;
+        this.mClocks = clocks;
+    }
+
+    @Override
+    public long millis() {
+        for (Clock clock : mClocks) {
+            try {
+                return clock.millis();
+            } catch (DateTimeException e) {
+                // Ignore and attempt the next clock
+                Log.w(TAG, e.toString());
+            }
+        }
+        throw new DateTimeException(
+                "No clocks in " + Arrays.toString(mClocks) + " were able to provide time");
+    }
+
+    @Override
+    public ZoneId getZone() {
+        return mZone;
+    }
+
+    @Override
+    public Clock withZone(ZoneId zone) {
+        return new BestClock(zone, mClocks);
+    }
+
+    @Override
+    public Instant instant() {
+        return Instant.ofEpochMilli(millis());
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/BinderUtils.java b/staticlibs/framework/com/android/net/module/util/BinderUtils.java
new file mode 100644
index 0000000..e4d14ea
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/BinderUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+
+import java.util.function.Supplier;
+
+/**
+ * Collection of utilities for {@link Binder} and related classes.
+ * @hide
+ */
+public class BinderUtils {
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will be caught and rethrown after the call to
+     * {@link Binder#restoreCallingIdentity}
+     *
+     * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+     * since it is not public.
+     *
+     * @hide
+     */
+    public static final <T extends Exception> void withCleanCallingIdentity(
+            @NonNull ThrowingRunnable<T> action) throws T {
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            action.run();
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
+     * Like a Runnable, but declared to throw an exception.
+     *
+     * @param <T> The exception class which is declared to be thrown.
+     */
+    @FunctionalInterface
+    public interface ThrowingRunnable<T extends Exception> {
+        /** @see java.lang.Runnable */
+        void run() throws T;
+    }
+
+    /**
+     * Convenience method for running the provided action enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the
+     * result.
+     *
+     * <p>Any exception thrown by the given action will be caught and rethrown after
+     * the call to {@link Binder#restoreCallingIdentity}.
+     *
+     * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+     * since it is not public.
+     *
+     * @hide
+     */
+    public static final <T, E extends Exception> T withCleanCallingIdentity(
+            @NonNull ThrowingSupplier<T, E> action) throws E {
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return action.get();
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    /**
+     * An equivalent of {@link Supplier}
+     *
+     * @param <T> The class which is declared to be returned.
+     * @param <E> The exception class which is declared to be thrown.
+     */
+    @FunctionalInterface
+    public interface ThrowingSupplier<T, E extends Exception> {
+        /** @see java.util.function.Supplier */
+        T get() throws E;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/BitUtils.java b/staticlibs/framework/com/android/net/module/util/BitUtils.java
new file mode 100644
index 0000000..3062d8c
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/BitUtils.java
@@ -0,0 +1,140 @@
+/*
+ * 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ */
+public class BitUtils {
+    /**
+     * Unpacks long value into an array of bits.
+     */
+    public static int[] unpackBits(long val) {
+        int size = Long.bitCount(val);
+        int[] result = new int[size];
+        int index = 0;
+        int bitPos = 0;
+        while (val != 0) {
+            if ((val & 1) == 1) result[index++] = bitPos;
+            val = val >>> 1;
+            bitPos++;
+        }
+        return result;
+    }
+
+    /**
+     * Packs a list of ints in the same way as packBits()
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing (1,3) will return in 0b00001010 and passing (5,6,0) will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
+     */
+    public static long packBitList(int... bits) {
+        return packBits(bits);
+    }
+
+    /**
+     * Packs array of bits into a long value.
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing [1,3] will return in 0b00001010 and passing [5,6,0] will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
+     */
+    public static long packBits(int[] bits) {
+        long packed = 0;
+        for (int b : bits) {
+            packed |= (1L << b);
+        }
+        return packed;
+    }
+
+    /**
+     * An interface for a function that can retrieve a name associated with an int.
+     *
+     * This is useful for bitfields like network capabilities or network score policies.
+     */
+    @FunctionalInterface
+    public interface NameOf {
+        /** Retrieve the name associated with the passed value */
+        String nameOf(int value);
+    }
+
+    /**
+     * Given a bitmask and a name fetcher, append names of all set bits to the builder
+     *
+     * This method takes all bit sets in the passed bitmask, will figure out the name associated
+     * with the weight of each bit with the passed name fetcher, and append each name to the
+     * passed StringBuilder, separated by the passed separator.
+     *
+     * For example, if the bitmask is 0110, and the name fetcher return "BIT_1" to "BIT_4" for
+     * numbers from 1 to 4, and the separator is "&", this method appends "BIT_2&BIT3" to the
+     * StringBuilder.
+     */
+    public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb,
+            long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) {
+        int bitPos = 0;
+        boolean firstElementAdded = false;
+        while (bitMask != 0) {
+            if ((bitMask & 1) != 0) {
+                if (firstElementAdded) {
+                    sb.append(separator);
+                } else {
+                    firstElementAdded = true;
+                }
+                sb.append(nameFetcher.nameOf(bitPos));
+            }
+            bitMask >>>= 1;
+            ++bitPos;
+        }
+    }
+
+    /**
+     * Returns a short but human-readable string of updates between an old and a new bit fields.
+     *
+     * @param oldVal the old bit field to diff from
+     * @param newVal the new bit field to diff to
+     * @return a string fit for logging differences, or null if no differences.
+     *         this method cannot return the empty string.
+     */
+    @Nullable
+    public static String describeDifferences(final long oldVal, final long newVal,
+            @NonNull final NameOf nameFetcher) {
+        final long changed = oldVal ^ newVal;
+        if (0 == changed) return null;
+        // If the control reaches here, there are changes (additions, removals, or both) so
+        // the code below is guaranteed to add something to the string and can't return "".
+        final long removed = oldVal & changed;
+        final long added = newVal & changed;
+        final StringBuilder sb = new StringBuilder();
+        if (0 != removed) {
+            sb.append("-");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, removed, nameFetcher, "-");
+        }
+        if (0 != added) {
+            sb.append("+");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, added, nameFetcher, "+");
+        }
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/ByteUtils.java b/staticlibs/framework/com/android/net/module/util/ByteUtils.java
new file mode 100644
index 0000000..290ed46
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/ByteUtils.java
@@ -0,0 +1,67 @@
+/*
+ * 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.net.module.util;
+
+import android.annotation.NonNull;
+
+/**
+ * Byte utility functions.
+ * @hide
+ */
+public class ByteUtils {
+    /**
+     * Returns the index of the first appearance of the value {@code target} in {@code array}.
+     *
+     * @param array an array of {@code byte} values, possibly empty
+     * @param target a primitive {@code byte} value
+     * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no
+     *     such index exists.
+     */
+    public static int indexOf(@NonNull byte[] array, byte target) {
+        return indexOf(array, target, 0, array.length);
+    }
+
+    private static int indexOf(byte[] array, byte target, int start, int end) {
+        for (int i = start; i < end; i++) {
+            if (array[i] == target) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the values from each provided array combined into a single array. For example, {@code
+     * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}.
+     *
+     * @param arrays zero or more {@code byte} arrays
+     * @return a single array containing all the values from the source arrays, in order
+     */
+    public static byte[] concat(@NonNull byte[]... arrays) {
+        int length = 0;
+        for (byte[] array : arrays) {
+            length += array.length;
+        }
+        byte[] result = new byte[length];
+        int pos = 0;
+        for (byte[] array : arrays) {
+            System.arraycopy(array, 0, result, pos, array.length);
+            pos += array.length;
+        }
+        return result;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
new file mode 100644
index 0000000..39e7ce9
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Utilities for {@link Collection} and arrays.
+ * @hide
+ */
+public final class CollectionUtils {
+    private CollectionUtils() {}
+
+    /**
+     * @return True if the array is null or 0-length.
+     */
+    public static <T> boolean isEmpty(@Nullable T[] array) {
+        return array == null || array.length == 0;
+    }
+
+    /**
+     * @return True if the collection is null or 0-length.
+     */
+    public static <T> boolean isEmpty(@Nullable Collection<T> collection) {
+        return collection == null || collection.isEmpty();
+    }
+
+    /**
+     * Returns an int array from the given Integer list.
+     */
+    @NonNull
+    public static int[] toIntArray(@NonNull Collection<Integer> list) {
+        int[] array = new int[list.size()];
+        int i = 0;
+        for (Integer item : list) {
+            array[i] = item;
+            i++;
+        }
+        return array;
+    }
+
+    /**
+     * Returns a long array from the given long list.
+     */
+    @NonNull
+    public static long[] toLongArray(@NonNull Collection<Long> list) {
+        long[] array = new long[list.size()];
+        int i = 0;
+        for (Long item : list) {
+            array[i] = item;
+            i++;
+        }
+        return array;
+    }
+
+    /**
+     * @return True if all elements satisfy the predicate, false otherwise.
+     *   Note that means this always returns true for empty collections.
+     */
+    public static <T> boolean all(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+        for (final T e : elem) {
+            if (!predicate.test(e)) return false;
+        }
+        return true;
+
+    }
+
+    /**
+     * @return True if any element satisfies the predicate, false otherwise.
+     *   Note that means this always returns false for empty collections.
+     */
+    public static <T> boolean any(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+        return indexOf(elem, predicate) >= 0;
+    }
+
+    /**
+     * @return The index of the first element that matches the predicate, or -1 if none.
+     */
+    public static <T> int indexOf(@NonNull final Collection<T> elem,
+            @NonNull final Predicate<? super T> predicate) {
+        int idx = 0;
+        for (final T e : elem) {
+            if (predicate.test(e)) return idx;
+            idx++;
+        }
+        return -1;
+    }
+
+    /**
+     * @return True if there exists at least one element in the sparse array for which
+     * condition {@code predicate}
+     */
+    public static <T> boolean any(@NonNull SparseArray<T> array, @NonNull Predicate<T> predicate) {
+        for (int i = 0; i < array.size(); ++i) {
+            if (predicate.test(array.valueAt(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the array contains the specified value.
+     */
+    public static boolean contains(@Nullable short[] array, short value) {
+        if (array == null) return false;
+        for (int element : array) {
+            if (element == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the array contains the specified value.
+     */
+    public static boolean contains(@Nullable int[] array, int value) {
+        if (array == null) return false;
+        for (int element : array) {
+            if (element == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the array contains the specified value.
+     */
+    public static <T> boolean contains(@Nullable T[] array, @Nullable T value) {
+        return indexOf(array, value) != -1;
+    }
+
+    /**
+     * Return first index of value in given array, or -1 if not found.
+     */
+    public static <T> int indexOf(@Nullable T[] array, @Nullable T value) {
+        if (array == null) return -1;
+        for (int i = 0; i < array.length; i++) {
+            if (Objects.equals(array[i], value)) return i;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the index of the needle array in the haystack array, or -1 if it can't be found.
+     * This is a byte array equivalent of Collections.indexOfSubList().
+     */
+    public static int indexOfSubArray(@NonNull byte[] haystack, @NonNull byte[] needle) {
+        for (int i = 0; i < haystack.length - needle.length + 1; i++) {
+            boolean found = true;
+            for (int j = 0; j < needle.length; j++) {
+                if (haystack[i + j] != needle[j]) {
+                    found = false;
+                    break;
+                }
+            }
+            if (found) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns a new collection of elements that match the passed predicate.
+     * @param source the elements to filter.
+     * @param test the predicate to test for.
+     * @return a new collection containing only the source elements that satisfy the predicate.
+     */
+    @NonNull public static <T> ArrayList<T> filter(@NonNull final Collection<T> source,
+            @NonNull final Predicate<T> test) {
+        final ArrayList<T> matches = new ArrayList<>();
+        for (final T e : source) {
+            if (test.test(e)) {
+                matches.add(e);
+            }
+        }
+        return matches;
+    }
+
+    /**
+     * Return sum of the given long array.
+     */
+    public static long total(@Nullable long[] array) {
+        long total = 0;
+        if (array != null) {
+            for (long value : array) {
+                total += value;
+            }
+        }
+        return total;
+    }
+
+    /**
+     * Returns true if the first collection contains any of the elements of the second.
+     * @param haystack where to search
+     * @param needles what to search for
+     * @param <T> type of elements
+     * @return true if |haystack| contains any of the |needles|, false otherwise
+     */
+    public static <T> boolean containsAny(@NonNull final Collection<T> haystack,
+            @NonNull final Collection<? extends T> needles) {
+        for (T needle : needles) {
+            if (haystack.contains(needle)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the first collection contains all of the elements of the second.
+     * @param haystack where to search
+     * @param needles what to search for
+     * @param <T> type of elements
+     * @return true if |haystack| contains all of the |needles|, false otherwise
+     */
+    public static <T> boolean containsAll(@NonNull final Collection<T> haystack,
+            @NonNull final Collection<? extends T> needles) {
+        return haystack.containsAll(needles);
+    }
+
+    /**
+     * Returns the first item of a collection that matches the predicate.
+     * @param haystack The collection to search.
+     * @param condition The predicate to match.
+     * @param <T> The type of element in the collection.
+     * @return The first element matching the predicate, or null if none.
+     */
+    @Nullable
+    public static <T> T findFirst(@NonNull final Collection<T> haystack,
+            @NonNull final Predicate<? super T> condition) {
+        for (T needle : haystack) {
+            if (condition.test(needle)) return needle;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the last item of a List that matches the predicate.
+     * @param haystack The List to search.
+     * @param condition The predicate to match.
+     * @param <T> The type of element in the list.
+     * @return The last element matching the predicate, or null if none.
+     */
+    // There is no way to reverse iterate a Collection in Java (e.g. the collection may
+    // be a single-linked list), so implementing this on Collection is necessarily very
+    // wasteful (store and reverse a copy, test all elements, or recurse to the end of the
+    // list to test on the up path and possibly blow the call stack)
+    @Nullable
+    public static <T> T findLast(@NonNull final List<T> haystack,
+            @NonNull final Predicate<? super T> condition) {
+        for (int i = haystack.size() - 1; i >= 0; --i) {
+            final T needle = haystack.get(i);
+            if (condition.test(needle)) return needle;
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether a collection contains an element matching a condition
+     * @param haystack The collection to search.
+     * @param condition The predicate to match.
+     * @param <T> The type of element in the collection.
+     * @return Whether the collection contains any element matching the condition.
+     */
+    public static <T> boolean contains(@NonNull final Collection<T> haystack,
+            @NonNull final Predicate<? super T> condition) {
+        return -1 != indexOf(haystack, condition);
+    }
+
+    /**
+     * Standard map function, but returns a new modifiable ArrayList
+     *
+     * This returns a new list that contains, for each element of the source collection, its
+     * image through the passed transform.
+     * Elements in the source can be null if the transform accepts null inputs.
+     * Elements in the output can be null if the transform ever returns null.
+     * This function never returns null. If the source collection is empty, it returns the
+     * empty list.
+     * Contract : this method calls the transform function exactly once for each element in the
+     * list, in iteration order.
+     *
+     * @param source the source collection
+     * @param transform the function to transform the elements
+     * @param <T> type of source elements
+     * @param <R> type of destination elements
+     * @return an unmodifiable list of transformed elements
+     */
+    @NonNull
+    public static <T, R> ArrayList<R> map(@NonNull final Collection<T> source,
+            @NonNull final Function<? super T, ? extends R> transform) {
+        final ArrayList<R> dest = new ArrayList<>(source.size());
+        for (final T e : source) {
+            dest.add(transform.apply(e));
+        }
+        return dest;
+    }
+
+    /**
+     * Standard zip function, but returns a new modifiable ArrayList
+     *
+     * This returns a list of pairs containing, at each position, a pair of the element from the
+     * first list at that index and the element from the second list at that index.
+     * Both lists must be the same size. They may contain null.
+     *
+     * The easiest way to visualize what's happening is to think of two lists being laid out next
+     * to each other and stitched together with a zipper.
+     *
+     * Contract : this method will read each element of each list exactly once, in some unspecified
+     * order. If it throws, it will not read any element.
+     *
+     * @param first the first list of elements
+     * @param second the second list of elements
+     * @param <T> the type of first elements
+     * @param <R> the type of second elements
+     * @return the zipped list
+     */
+    @NonNull
+    public static <T, R> ArrayList<Pair<T, R>> zip(@NonNull final List<T> first,
+            @NonNull final List<R> second) {
+        final int size = first.size();
+        if (size != second.size()) {
+            throw new IllegalArgumentException("zip : collections must be the same size");
+        }
+        final ArrayList<Pair<T, R>> dest = new ArrayList<>(size);
+        for (int i = 0; i < size; ++i) {
+            dest.add(new Pair<>(first.get(i), second.get(i)));
+        }
+        return dest;
+    }
+
+    /**
+     * Returns a new ArrayMap that associates each key with the value at the same index.
+     *
+     * Both lists must be the same size.
+     * Both keys and values may contain null.
+     * Keys may not contain the same value twice.
+     *
+     * Contract : this method will read each element of each list exactly once, but does not
+     * specify the order, except if it throws in which case the number of reads is undefined.
+     *
+     * @param keys The list of keys
+     * @param values The list of values
+     * @param <T> The type of keys
+     * @param <R> The type of values
+     * @return The associated map
+     */
+    @NonNull
+    public static <T, R> ArrayMap<T, R> assoc(
+            @NonNull final List<T> keys, @NonNull final List<R> values) {
+        final int size = keys.size();
+        if (size != values.size()) {
+            throw new IllegalArgumentException("assoc : collections must be the same size");
+        }
+        final ArrayMap<T, R> dest = new ArrayMap<>(size);
+        for (int i = 0; i < size; ++i) {
+            final T key = keys.get(i);
+            if (dest.containsKey(key)) {
+                throw new IllegalArgumentException(
+                        "assoc : keys may not contain the same value twice");
+            }
+            dest.put(key, values.get(i));
+        }
+        return dest;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java
new file mode 100644
index 0000000..f4856b3
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+/**
+ * Collection of connectivity settings utilities.
+ *
+ * @hide
+ */
+public class ConnectivitySettingsUtils {
+    public static final int PRIVATE_DNS_MODE_OFF = 1;
+    public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+    public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+
+    public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
+    public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+    public static final String PRIVATE_DNS_MODE_OFF_STRING = "off";
+    public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic";
+    public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
+    public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
+
+    /**
+     * Get private DNS mode as string.
+     *
+     * @param mode One of the private DNS values.
+     * @return A string of private DNS mode.
+     */
+    public static String getPrivateDnsModeAsString(int mode) {
+        switch (mode) {
+            case PRIVATE_DNS_MODE_OFF:
+                return PRIVATE_DNS_MODE_OFF_STRING;
+            case PRIVATE_DNS_MODE_OPPORTUNISTIC:
+                return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING;
+            case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
+                return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING;
+            default:
+                throw new IllegalArgumentException("Invalid private dns mode: " + mode);
+        }
+    }
+
+    private static int getPrivateDnsModeAsInt(String mode) {
+        // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
+        // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
+        if (TextUtils.isEmpty(mode))
+            return PRIVATE_DNS_MODE_OPPORTUNISTIC;
+        switch (mode) {
+            case "off":
+                return PRIVATE_DNS_MODE_OFF;
+            case "hostname":
+                return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+            case "opportunistic":
+                return PRIVATE_DNS_MODE_OPPORTUNISTIC;
+            default:
+                // b/260211513: adb shell settings put global private_dns_mode foo
+                // can result in arbitrary strings - treat any unknown value as empty string.
+                // throw new IllegalArgumentException("Invalid private dns mode: " + mode);
+                return PRIVATE_DNS_MODE_OPPORTUNISTIC;
+        }
+    }
+
+    /**
+     * Get private DNS mode from settings.
+     *
+     * @param context The Context to query the private DNS mode from settings.
+     * @return An integer of private DNS mode.
+     */
+    public static int getPrivateDnsMode(@NonNull Context context) {
+        final ContentResolver cr = context.getContentResolver();
+        String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
+        if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
+        return getPrivateDnsModeAsInt(mode);
+    }
+
+    /**
+     * Set private DNS mode to settings.
+     *
+     * @param context The {@link Context} to set the private DNS mode.
+     * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
+     */
+    public static void setPrivateDnsMode(@NonNull Context context, int mode) {
+        if (!(mode == PRIVATE_DNS_MODE_OFF
+                || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
+                || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
+            throw new IllegalArgumentException("Invalid private dns mode: " + mode);
+        }
+        Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE,
+                getPrivateDnsModeAsString(mode));
+    }
+
+    /**
+     * Get specific private dns provider name from {@link Settings}.
+     *
+     * @param context The {@link Context} to query the setting.
+     * @return The specific private dns provider name, or null if no setting value.
+     */
+    @Nullable
+    public static String getPrivateDnsHostname(@NonNull Context context) {
+        return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+    }
+
+    /**
+     * Set specific private dns provider name to {@link Settings}.
+     *
+     * @param context The {@link Context} to set the setting.
+     * @param specifier The specific private dns provider name.
+     */
+    public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) {
+        Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java
new file mode 100644
index 0000000..c135e46
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/ConnectivityUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+
+import android.annotation.Nullable;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+/**
+ * Various utilities used in connectivity code.
+ * @hide
+ */
+public final class ConnectivityUtils {
+    private ConnectivityUtils() {}
+
+
+    /**
+     * Return IP address and port in a string format.
+     */
+    public static String addressAndPortToString(InetAddress address, int port) {
+        return String.format(
+                (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
+                        address.getHostAddress(), port);
+    }
+
+    /**
+     * Return true if the provided address is non-null and an IPv6 Unique Local Address (RFC4193).
+     */
+    public static boolean isIPv6ULA(@Nullable InetAddress addr) {
+        return addr instanceof Inet6Address
+                && ((addr.getAddress()[0] & 0xfe) == 0xfc);
+    }
+
+    /**
+     * Returns the {@code int} nearest in value to {@code value}.
+     *
+     * @param value any {@code long} value
+     * @return the same value cast to {@code int} if it is in the range of the {@code int}
+     * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if
+     * it is too small
+     */
+    public static int saturatedCast(long value) {
+        if (value > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        }
+        if (value < Integer.MIN_VALUE) {
+            return Integer.MIN_VALUE;
+        }
+        return (int) value;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
new file mode 100644
index 0000000..0dcdf1e
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+    /**
+     * Type of the canonical name for an alias. Refer to RFC 1035 section 3.2.2.
+     */
+    // TODO: Define the constant as a public constant in DnsResolver since it can never change.
+    private static final int TYPE_CNAME = 5;
+
+    /**
+     * Thrown when parsing packet failed.
+     */
+    public static class ParseException extends RuntimeException {
+        public String reason;
+        public ParseException(@NonNull String reason) {
+            super(reason);
+            this.reason = reason;
+        }
+
+        public ParseException(@NonNull String reason, @NonNull Throwable cause) {
+            super(reason, cause);
+            this.reason = reason;
+        }
+    }
+
+    /**
+     * DNS header for DNS protocol based on RFC 1035 section 4.1.1.
+     *
+     *                                     1  1  1  1  1  1
+     *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                      ID                       |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                    QDCOUNT                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                    ANCOUNT                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                    NSCOUNT                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                    ARCOUNT                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     */
+    public static class DnsHeader {
+        private static final String TAG = "DnsHeader";
+        private static final int SIZE_IN_BYTES = 12;
+        private final int mId;
+        private final int mFlags;
+        private final int[] mRecordCount;
+
+        /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this
+         * header is a query; otherwise, it is a response.
+         */
+        private static final int FLAGS_SECTION_QR_BIT = 15;
+
+        /**
+         * Create a new DnsHeader from a positioned ByteBuffer.
+         *
+         * The ByteBuffer must be in network byte order (which is the default).
+         * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS header record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         */
+        @VisibleForTesting
+        public DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+            Objects.requireNonNull(buf);
+            mId = Short.toUnsignedInt(buf.getShort());
+            mFlags = Short.toUnsignedInt(buf.getShort());
+            mRecordCount = new int[NUM_SECTIONS];
+            for (int i = 0; i < NUM_SECTIONS; ++i) {
+                mRecordCount[i] = Short.toUnsignedInt(buf.getShort());
+            }
+        }
+
+        /**
+         * Determines if the DNS message corresponding to this header is a response, as defined in
+         * RFC 1035 Section 4.1.1.
+         */
+        public boolean isResponse() {
+            return (mFlags & (1 << FLAGS_SECTION_QR_BIT)) != 0;
+        }
+
+        /**
+         * Create a new DnsHeader from specified parameters.
+         *
+         * This constructor only builds the question and answer sections. Authority
+         * and additional sections are not supported. Useful when synthesizing dns
+         * responses from query or reply packets.
+         */
+        @VisibleForTesting
+        public DnsHeader(int id, int flags, int qdcount, int ancount) {
+            this.mId = id;
+            this.mFlags = flags;
+            mRecordCount = new int[NUM_SECTIONS];
+            mRecordCount[QDSECTION] = qdcount;
+            mRecordCount[ANSECTION] = ancount;
+        }
+
+        /**
+         * Get record count by type.
+         */
+        public int getRecordCount(int type) {
+            return mRecordCount[type];
+        }
+
+        /**
+         * Get flags of this instance.
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Get id of this instance.
+         */
+        public int getId() {
+            return mId;
+        }
+
+        @Override
+        public String toString() {
+            return "DnsHeader{" + "id=" + mId + ", flags=" + mFlags
+                    + ", recordCounts=" + Arrays.toString(mRecordCount) + '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+            final DnsHeader other = (DnsHeader) o;
+            return mId == other.mId
+                    && mFlags == other.mFlags
+                    && Arrays.equals(mRecordCount, other.mRecordCount);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mId + 37 * mFlags + Arrays.hashCode(mRecordCount);
+        }
+
+        /**
+         * Get DnsHeader as byte array.
+         */
+        @NonNull
+        public byte[] getBytes() {
+            // TODO: if this is called often, optimize the ByteBuffer out and write to the
+            //  array directly.
+            final ByteBuffer buf = ByteBuffer.allocate(SIZE_IN_BYTES);
+            buf.putShort((short) mId);
+            buf.putShort((short) mFlags);
+            for (int i = 0; i < NUM_SECTIONS; ++i) {
+                buf.putShort((short) mRecordCount[i]);
+            }
+            return buf.array();
+        }
+    }
+
+    /**
+     * Superclass for DNS questions and DNS resource records.
+     *
+     * DNS questions (No TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.2.
+     *                                     1  1  1  1  1  1
+     *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                                               |
+     *     /                     QNAME                     /
+     *     /                                               /
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                     QTYPE                     |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                     QCLASS                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *
+     * DNS resource records (With TTL/RDLENGTH/RDATA) based on RFC 1035 section 4.1.3.
+     *                                     1  1  1  1  1  1
+     *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                                               |
+     *     /                                               /
+     *     /                      NAME                     /
+     *     |                                               |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                      TYPE                     |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                     CLASS                     |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                      TTL                      |
+     *     |                                               |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *     |                   RDLENGTH                    |
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+     *     /                     RDATA                     /
+     *     /                                               /
+     *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *
+     * Note that this class is meant to be used by composition and not inheritance, and
+     * that classes implementing more specific DNS records should call #parse.
+     */
+    // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord.
+    public static class DnsRecord {
+        // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE.
+        // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression,
+        // refer to rfc 1035 section 4.1.4.
+        public static final int MAXNAMESIZE = 255;
+        public static final int NAME_NORMAL = 0;
+        public static final int NAME_COMPRESSION = 0xC0;
+
+        private static final String TAG = "DnsRecord";
+
+        public final String dName;
+        public final int nsType;
+        public final int nsClass;
+        public final long ttl;
+        private final byte[] mRdata;
+        /**
+         * Type of this DNS record.
+         */
+        @RecordType
+        public final int rType;
+
+        /**
+         * Create a new DnsRecord from a positioned ByteBuffer.
+         *
+         * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+         * When this constructor returns, the reading position of the ByteBuffer has been
+         * advanced to the end of the DNS resource record.
+         * This is meant to chain with other methods reading a DNS response in sequence.
+         *
+         * @param rType Type of the record.
+         * @param buf ByteBuffer input of record, must be in network byte order
+         *         (which is the default).
+         */
+        private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
+                throws BufferUnderflowException, ParseException {
+            Objects.requireNonNull(buf);
+            this.rType = rType;
+            dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+                    true /* isNameCompressionSupported */);
+            if (dName.length() > MAXNAMESIZE) {
+                throw new ParseException(
+                        "Parse name fail, name size is too long: " + dName.length());
+            }
+            nsType = Short.toUnsignedInt(buf.getShort());
+            nsClass = Short.toUnsignedInt(buf.getShort());
+
+            if (rType != QDSECTION) {
+                ttl = Integer.toUnsignedLong(buf.getInt());
+                final int length = Short.toUnsignedInt(buf.getShort());
+                mRdata = new byte[length];
+                buf.get(mRdata);
+            } else {
+                ttl = 0;
+                mRdata = null;
+            }
+        }
+
+        /**
+         * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer.
+         *
+         * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors
+         * to allow constructing the corresponding object.
+         */
+        @VisibleForTesting(visibility = PRIVATE)
+        public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf)
+                throws BufferUnderflowException, ParseException {
+            Objects.requireNonNull(buf);
+            final int oldPos = buf.position();
+            // Parsed name not used, just for jumping to nsType position.
+            DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+                    true /* isNameCompressionSupported */);
+            // Peek the nsType.
+            final int nsType = Short.toUnsignedInt(buf.getShort());
+            buf.position(oldPos);
+            // Return a DnsRecord instance by default for backward compatibility, this is useful
+            // when a partner supports new type of DnsRecord but does not inherit DnsRecord.
+            switch (nsType) {
+                default:
+                    return new DnsRecord(rType, buf);
+            }
+        }
+
+        /**
+         * Make an A or AAAA record based on the specified parameters.
+         *
+         * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION}
+         *              or {@link #NSSECTION}.
+         * @param dName Domain name of the record.
+         * @param nsClass Class of the record. See RFC 1035 section 3.2.4.
+         * @param ttl time interval (in seconds) that the resource record may be
+         *            cached before it should be discarded. Zero values are
+         *            interpreted to mean that the RR can only be used for the
+         *            transaction in progress, and should not be cached.
+         * @param address Instance of {@link InetAddress}
+         * @return A record if the {@code address} is an IPv4 address, or AAAA record if the
+         *         {@code address} is an IPv6 address.
+         */
+        public static DnsRecord makeAOrAAAARecord(int rType, @NonNull String dName,
+                int nsClass, long ttl, @NonNull InetAddress address) throws IOException {
+            final int nsType = (address.getAddress().length == 4) ? TYPE_A : TYPE_AAAA;
+            return new DnsRecord(rType, dName, nsType, nsClass, ttl, address, null /* rDataStr */);
+        }
+
+        /**
+         * Make an CNAME record based on the specified parameters.
+         *
+         * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION}
+         *              or {@link #NSSECTION}.
+         * @param dName Domain name of the record.
+         * @param nsClass Class of the record. See RFC 1035 section 3.2.4.
+         * @param ttl time interval (in seconds) that the resource record may be
+         *            cached before it should be discarded. Zero values are
+         *            interpreted to mean that the RR can only be used for the
+         *            transaction in progress, and should not be cached.
+         * @param domainName Canonical name of the {@code dName}.
+         * @return A record if the {@code address} is an IPv4 address, or AAAA record if the
+         *         {@code address} is an IPv6 address.
+         */
+        public static DnsRecord makeCNameRecord(int rType, @NonNull String dName, int nsClass,
+                long ttl, @NonNull String domainName) throws IOException {
+            return new DnsRecord(rType, dName, TYPE_CNAME, nsClass, ttl, null /* address */,
+                    domainName);
+        }
+
+        /**
+         * Make a DNS question based on the specified parameters.
+         */
+        public static DnsRecord makeQuestion(@NonNull String dName, int nsType, int nsClass) {
+            return new DnsRecord(dName, nsType, nsClass);
+        }
+
+        private static String requireHostName(@NonNull String name) {
+            if (!DnsRecordParser.isHostName(name)) {
+                throw new IllegalArgumentException("Expected domain name but got " + name);
+            }
+            return name;
+        }
+
+        /**
+         * Create a new query DnsRecord from specified parameters, useful when synthesizing
+         * dns response.
+         */
+        private DnsRecord(@NonNull String dName, int nsType, int nsClass) {
+            this.rType = QDSECTION;
+            this.dName = requireHostName(dName);
+            this.nsType = nsType;
+            this.nsClass = nsClass;
+            mRdata = null;
+            this.ttl = 0;
+        }
+
+        /**
+         * Create a new CNAME/A/AAAA DnsRecord from specified parameters.
+         *
+         * @param address The address only used when synthesizing A or AAAA record.
+         * @param rDataStr The alias of the domain, only used when synthesizing CNAME record.
+         */
+        private DnsRecord(@RecordType int rType, @NonNull String dName, int nsType, int nsClass,
+                long ttl, @Nullable InetAddress address, @Nullable String rDataStr)
+                throws IOException {
+            this.rType = rType;
+            this.dName = requireHostName(dName);
+            this.nsType = nsType;
+            this.nsClass = nsClass;
+            if (rType < 0 || rType >= NUM_SECTIONS || rType == QDSECTION) {
+                throw new IllegalArgumentException("Unexpected record type: " + rType);
+            }
+            mRdata = nsType == TYPE_CNAME ? domainNameToLabels(rDataStr) : address.getAddress();
+            this.ttl = ttl;
+        }
+
+        /**
+         * Get a copy of rdata.
+         */
+        @Nullable
+        public byte[] getRR() {
+            return (mRdata == null) ? null : mRdata.clone();
+        }
+
+        /**
+         * Get DnsRecord as byte array.
+         */
+        @NonNull
+        public byte[] getBytes() throws IOException {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            final DataOutputStream dos = new DataOutputStream(baos);
+            dos.write(domainNameToLabels(dName));
+            dos.writeShort(nsType);
+            dos.writeShort(nsClass);
+            if (rType != QDSECTION) {
+                dos.writeInt((int) ttl);
+                if (mRdata == null) {
+                    dos.writeShort(0);
+                } else {
+                    dos.writeShort(mRdata.length);
+                    dos.write(mRdata);
+                }
+            }
+            return baos.toByteArray();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+            final DnsRecord other = (DnsRecord) o;
+            return rType == other.rType
+                    && nsType == other.nsType
+                    && nsClass == other.nsClass
+                    && ttl == other.ttl
+                    && TextUtils.equals(dName, other.dName)
+                    && Arrays.equals(mRdata, other.mRdata);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * Objects.hash(dName)
+                    + 37 * ((int) (ttl & 0xFFFFFFFF))
+                    + 41 * ((int) (ttl >> 32))
+                    + 43 * nsType
+                    + 47 * nsClass
+                    + 53 * rType
+                    + Arrays.hashCode(mRdata);
+        }
+
+        @Override
+        public String toString() {
+            return "DnsRecord{"
+                    + "rType=" + rType
+                    + ", dName='" + dName + '\''
+                    + ", nsType=" + nsType
+                    + ", nsClass=" + nsClass
+                    + ", ttl=" + ttl
+                    + ", mRdata=" + Arrays.toString(mRdata)
+                    + '}';
+        }
+    }
+
+    /**
+     * Header section types, refer to RFC 1035 section 4.1.1.
+     */
+    public static final int QDSECTION = 0;
+    public static final int ANSECTION = 1;
+    public static final int NSSECTION = 2;
+    public static final int ARSECTION = 3;
+    @VisibleForTesting(visibility = PRIVATE)
+    static final int NUM_SECTIONS = ARSECTION + 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            QDSECTION,
+            ANSECTION,
+            NSSECTION,
+            ARSECTION,
+    })
+    public @interface RecordType {}
+
+
+    private static final String TAG = DnsPacket.class.getSimpleName();
+
+    protected final DnsHeader mHeader;
+    protected final List<DnsRecord>[] mRecords;
+
+    protected DnsPacket(@NonNull byte[] data) throws ParseException {
+        if (null == data) {
+            throw new ParseException("Parse header failed, null input data");
+        }
+
+        final ByteBuffer buffer;
+        try {
+            buffer = ByteBuffer.wrap(data);
+            mHeader = new DnsHeader(buffer);
+        } catch (BufferUnderflowException e) {
+            throw new ParseException("Parse Header fail, bad input data", e);
+        }
+
+        mRecords = new ArrayList[NUM_SECTIONS];
+
+        for (int i = 0; i < NUM_SECTIONS; ++i) {
+            final int count = mHeader.getRecordCount(i);
+            mRecords[i] = new ArrayList(count);
+            for (int j = 0; j < count; ++j) {
+                try {
+                    mRecords[i].add(DnsRecord.parse(i, buffer));
+                } catch (BufferUnderflowException e) {
+                    throw new ParseException("Parse record fail", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new {@link #DnsPacket} from specified parameters.
+     *
+     * Note that authority records section and additional records section is not supported.
+     */
+    protected DnsPacket(@NonNull DnsHeader header, @NonNull List<DnsRecord> qd,
+            @NonNull List<DnsRecord> an) {
+        mHeader = Objects.requireNonNull(header);
+        mRecords = new List[NUM_SECTIONS];
+        mRecords[QDSECTION] = Collections.unmodifiableList(new ArrayList<>(qd));
+        mRecords[ANSECTION] = Collections.unmodifiableList(new ArrayList<>(an));
+        mRecords[NSSECTION] = new ArrayList<>();
+        mRecords[ARSECTION] = new ArrayList<>();
+        for (int i = 0; i < NUM_SECTIONS; i++) {
+            if (mHeader.mRecordCount[i] != mRecords[i].size()) {
+                throw new IllegalArgumentException("Record count mismatch: expected "
+                        + mHeader.mRecordCount[i] + " but was " + mRecords[i]);
+            }
+        }
+    }
+
+    /**
+     * Get DnsPacket as byte array.
+     */
+    public @NonNull byte[] getBytes() throws IOException {
+        final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        buf.write(mHeader.getBytes());
+
+        for (int i = 0; i < NUM_SECTIONS; ++i) {
+            for (final DnsRecord record : mRecords[i]) {
+                buf.write(record.getBytes());
+            }
+        }
+        return buf.toByteArray();
+    }
+
+    @Override
+    public String toString() {
+        return "DnsPacket{" + "header=" + mHeader + ", records='" + Arrays.toString(mRecords) + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o.getClass() != getClass()) return false;
+        final DnsPacket other = (DnsPacket) o;
+        return Objects.equals(mHeader, other.mHeader)
+                && Arrays.deepEquals(mRecords, other.mRecords);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mHeader);
+        result = 31 * result + Arrays.hashCode(mRecords);
+        return result;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
new file mode 100644
index 0000000..105d783
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_COMPRESSION;
+import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_NORMAL;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.ParseException;
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * Utilities for decoding the contents of a DnsPacket.
+ *
+ * @hide
+ */
+public final class DnsPacketUtils {
+    /**
+     * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+     */
+    public static class DnsRecordParser {
+        private static final int MAXLABELSIZE = 63;
+        private static final int MAXNAMESIZE = 255;
+        private static final int MAXLABELCOUNT = 128;
+
+        private static final DecimalFormat sByteFormat = new DecimalFormat();
+        private static final FieldPosition sPos = new FieldPosition(0);
+
+        /**
+         * Convert label from {@code byte[]} to {@code String}
+         *
+         * <p>Follows the same conversion rules of the native code (ns_name.c in libc).
+         */
+        @VisibleForTesting
+        static String labelToString(@NonNull byte[] label) {
+            final StringBuffer sb = new StringBuffer();
+
+            for (int i = 0; i < label.length; ++i) {
+                int b = Byte.toUnsignedInt(label[i]);
+                // Control characters and non-ASCII characters.
+                if (b <= 0x20 || b >= 0x7f) {
+                    // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
+                    sb.append('\\');
+                    sByteFormat.format(b, sb, sPos);
+                } else if (b == '"' || b == '.' || b == ';' || b == '\\' || b == '(' || b == ')'
+                        || b == '@' || b == '$') {
+                    // Append the byte as an escaped character, e.g., "\:" for 0x3a.
+                    sb.append('\\');
+                    sb.append((char) b);
+                } else {
+                    // Append the byte as a character, e.g., "a" for 0x61.
+                    sb.append((char) b);
+                }
+            }
+            return sb.toString();
+        }
+
+        /**
+         * Converts domain name to labels according to RFC 1035.
+         *
+         * @param name Domain name as String that needs to be converted to labels.
+         * @return An encoded byte array that is constructed out of labels,
+         *         and ends with zero-length label.
+         * @throws ParseException if failed to parse the given domain name or
+         *         IOException if failed to output labels.
+         */
+        public static @NonNull byte[] domainNameToLabels(@NonNull String name) throws
+                IOException, ParseException {
+            if (name.length() > MAXNAMESIZE) {
+                throw new ParseException("Domain name exceeds max length: " + name.length());
+            }
+            if (!isHostName(name)) {
+                throw new ParseException("Failed to parse domain name: " + name);
+            }
+            final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+            final String[] labels = name.split("\\.");
+            for (final String label : labels) {
+                if (label.length() > MAXLABELSIZE) {
+                    throw new ParseException("label is too long: " + label);
+                }
+                buf.write(label.length());
+                // Encode as UTF-8 as suggested in RFC 6055 section 3.
+                buf.write(label.getBytes(StandardCharsets.UTF_8));
+            }
+            buf.write(0x00); // end with zero-length label
+            return buf.toByteArray();
+        }
+
+        /**
+         * Check whether the input is a valid hostname based on rfc 1035 section 3.3.
+         *
+         * @param hostName the target host name.
+         * @return true if the input is a valid hostname.
+         */
+        public static boolean isHostName(@Nullable String hostName) {
+            // TODO: Use {@code Patterns.HOST_NAME} if available.
+            // Patterns.DOMAIN_NAME accepts host names or IP addresses, so reject
+            // IP addresses.
+            return hostName != null
+                    && Patterns.DOMAIN_NAME.matcher(hostName).matches()
+                    && !InetAddresses.isNumericAddress(hostName);
+        }
+
+        /**
+         * Parses the domain / target name of a DNS record.
+         */
+        public static String parseName(final ByteBuffer buf, int depth,
+                boolean isNameCompressionSupported) throws
+                BufferUnderflowException, DnsPacket.ParseException {
+            return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported);
+        }
+
+        /**
+         * Parses the domain / target name of a DNS record.
+         *
+         * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
+         * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
+         * record may or may not support Name Compression, depending on the record TYPE. Moreover,
+         * even if Name Compression is supported, its usage is left to the implementation.
+         */
+        public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount,
+                boolean isNameCompressionSupported) throws
+                BufferUnderflowException, DnsPacket.ParseException {
+            if (depth > maxLabelCount) {
+                throw new DnsPacket.ParseException("Failed to parse name, too many labels");
+            }
+            final int len = Byte.toUnsignedInt(buf.get());
+            final int mask = len & NAME_COMPRESSION;
+            if (0 == len) {
+                return "";
+            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION
+                    || (!isNameCompressionSupported && mask == NAME_COMPRESSION)) {
+                throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask);
+            } else if (mask == NAME_COMPRESSION) {
+                // Name compression based on RFC 1035 - 4.1.4 Message compression
+                final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
+                final int oldPos = buf.position();
+                if (offset >= oldPos - 2) {
+                    throw new DnsPacket.ParseException(
+                            "Parse compression name fail, invalid compression");
+                }
+                buf.position(offset);
+                final String pointed = parseName(buf, depth + 1, maxLabelCount,
+                        isNameCompressionSupported);
+                buf.position(oldPos);
+                return pointed;
+            } else {
+                final byte[] label = new byte[len];
+                buf.get(label);
+                final String head = labelToString(label);
+                if (head.length() > MAXLABELSIZE) {
+                    throw new DnsPacket.ParseException("Parse name fail, invalid label length");
+                }
+                final String tail = parseName(buf, depth + 1, maxLabelCount,
+                        isNameCompressionSupported);
+                return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+            }
+        }
+
+        private DnsRecordParser() {}
+    }
+
+    private DnsPacketUtils() {}
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java b/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java
new file mode 100644
index 0000000..760891b
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsSdTxtRecord.java
@@ -0,0 +1,325 @@
+/* -*- Mode: Java; tab-width: 4 -*-
+ *
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * 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.
+
+ To do:
+ - implement remove()
+ - fix set() to replace existing values
+ */
+
+package com.android.net.module.util;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.Arrays;
+
+/**
+ * This class handles TXT record data for DNS based service discovery as specified at
+ * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
+ *
+ * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
+ * a packed array of bytes, each preceded by a length byte. Each string
+ * is an attribute-value pair.
+ *
+ * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
+ * as need be to implement its various methods.
+ * @hide
+ *
+ */
+public class DnsSdTxtRecord implements Parcelable {
+    private static final byte mSeparator = '=';
+
+    private byte[] mData;
+
+    /** Constructs a new, empty TXT record. */
+    public DnsSdTxtRecord()  {
+        mData = new byte[0];
+    }
+
+    /** Constructs a new TXT record from a byte array in the standard format. */
+    public DnsSdTxtRecord(byte[] data) {
+        mData = (byte[]) data.clone();
+    }
+
+    /** Copy constructor */
+    public DnsSdTxtRecord(DnsSdTxtRecord src) {
+        if (src != null && src.mData != null) {
+            mData = (byte[]) src.mData.clone();
+        }
+    }
+
+    /**
+     * Set a key/value pair. Setting an existing key will replace its value.
+     * @param key Must be ascii with no '='
+     * @param value matching value to key
+     */
+    public void set(String key, String value) {
+        byte[] keyBytes;
+        byte[] valBytes;
+        int valLen;
+
+        if (value != null) {
+            valBytes = value.getBytes();
+            valLen = valBytes.length;
+        } else {
+            valBytes = null;
+            valLen = 0;
+        }
+
+        try {
+            keyBytes = key.getBytes("US-ASCII");
+        }
+        catch (java.io.UnsupportedEncodingException e) {
+            throw new IllegalArgumentException("key should be US-ASCII");
+        }
+
+        for (int i = 0; i < keyBytes.length; i++) {
+            if (keyBytes[i] == '=') {
+                throw new IllegalArgumentException("= is not a valid character in key");
+            }
+        }
+
+        if (keyBytes.length + valLen >= 255) {
+            throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
+        }
+
+        int currentLoc = remove(key);
+        if (currentLoc == -1)
+            currentLoc = keyCount();
+
+        insert(keyBytes, valBytes, currentLoc);
+    }
+
+    /**
+     * Get a value for a key
+     *
+     * @param key
+     * @return The value associated with the key
+     */
+    public String get(String key) {
+        byte[] val = this.getValue(key);
+        return val != null ? new String(val) : null;
+    }
+
+    /** Remove a key/value pair. If found, returns the index or -1 if not found */
+    public int remove(String key) {
+        int avStart = 0;
+
+        for (int i=0; avStart < mData.length; i++) {
+            int avLen = mData[avStart];
+            if (key.length() <= avLen &&
+                    (key.length() == avLen || mData[avStart + key.length() + 1] == mSeparator)) {
+                String s = new String(mData, avStart + 1, key.length());
+                if (0 == key.compareToIgnoreCase(s)) {
+                    byte[] oldBytes = mData;
+                    mData = new byte[oldBytes.length - avLen - 1];
+                    System.arraycopy(oldBytes, 0, mData, 0, avStart);
+                    System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
+                            oldBytes.length - avStart - avLen - 1);
+                    return i;
+                }
+            }
+            avStart += (0xFF & (avLen + 1));
+        }
+        return -1;
+    }
+
+    /** Return the count of keys */
+    public int keyCount() {
+        int count = 0, nextKey;
+        for (nextKey = 0; nextKey < mData.length; count++) {
+            nextKey += (0xFF & (mData[nextKey] + 1));
+        }
+        return count;
+    }
+
+    /** Return true if key is present, false if not. */
+    public boolean contains(String key) {
+        String s = null;
+        for (int i = 0; null != (s = this.getKey(i)); i++) {
+            if (0 == key.compareToIgnoreCase(s)) return true;
+        }
+        return false;
+    }
+
+    /* Gets the size in bytes */
+    public int size() {
+        return mData.length;
+    }
+
+    /* Gets the raw data in bytes */
+    public byte[] getRawData() {
+        return (byte[]) mData.clone();
+    }
+
+    private void insert(byte[] keyBytes, byte[] value, int index) {
+        byte[] oldBytes = mData;
+        int valLen = (value != null) ? value.length : 0;
+        int insertion = 0;
+        int newLen, avLen;
+
+        for (int i = 0; i < index && insertion < mData.length; i++) {
+            insertion += (0xFF & (mData[insertion] + 1));
+        }
+
+        avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
+        newLen = avLen + oldBytes.length + 1;
+
+        mData = new byte[newLen];
+        System.arraycopy(oldBytes, 0, mData, 0, insertion);
+        int secondHalfLen = oldBytes.length - insertion;
+        System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
+        mData[insertion] = (byte) avLen;
+        System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
+        if (value != null) {
+            mData[insertion + 1 + keyBytes.length] = mSeparator;
+            System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
+        }
+    }
+
+    /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
+    private String getKey(int index) {
+        int avStart = 0;
+
+        for (int i=0; i < index && avStart < mData.length; i++) {
+            avStart += mData[avStart] + 1;
+        }
+
+        if (avStart < mData.length) {
+            int avLen = mData[avStart];
+            int aLen = 0;
+
+            for (aLen=0; aLen < avLen; aLen++) {
+                if (mData[avStart + aLen + 1] == mSeparator) break;
+            }
+            return new String(mData, avStart + 1, aLen);
+        }
+        return null;
+    }
+
+    /**
+     * Look up a key in the TXT record by zero-based index and return its value.
+     * Returns null if index exceeds the total number of keys.
+     * Returns null if the key is present with no value.
+     */
+    private byte[] getValue(int index) {
+        int avStart = 0;
+        byte[] value = null;
+
+        for (int i=0; i < index && avStart < mData.length; i++) {
+            avStart += mData[avStart] + 1;
+        }
+
+        if (avStart < mData.length) {
+            int avLen = mData[avStart];
+            int aLen = 0;
+
+            for (aLen=0; aLen < avLen; aLen++) {
+                if (mData[avStart + aLen + 1] == mSeparator) {
+                    value = new byte[avLen - aLen - 1];
+                    System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
+                    break;
+                }
+            }
+        }
+        return value;
+    }
+
+    private String getValueAsString(int index) {
+        byte[] value = this.getValue(index);
+        return value != null ? new String(value) : null;
+    }
+
+    private byte[] getValue(String forKey) {
+        String s = null;
+        int i;
+
+        for (i = 0; null != (s = this.getKey(i)); i++) {
+            if (0 == forKey.compareToIgnoreCase(s)) {
+                return this.getValue(i);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Return a string representation.
+     * Example : {key1=value1},{key2=value2}..
+     *
+     * For a key say like "key3" with null value
+     * {key1=value1},{key2=value2}{key3}
+     */
+    public String toString() {
+        String a, result = null;
+
+        for (int i = 0; null != (a = this.getKey(i)); i++) {
+            String av =  "{" + a;
+            String val = this.getValueAsString(i);
+            if (val != null)
+                av += "=" + val + "}";
+            else
+                av += "}";
+            if (result == null)
+                result = av;
+            else
+                result = result + ", " + av;
+        }
+        return result != null ? result : "";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof DnsSdTxtRecord)) {
+            return false;
+        }
+
+        DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+        return  Arrays.equals(record.mData, mData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mData);
+    }
+
+    /** Implement the Parcelable interface */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mData);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
+        new Creator<DnsSdTxtRecord>() {
+            public DnsSdTxtRecord createFromParcel(Parcel in) {
+                DnsSdTxtRecord info = new DnsSdTxtRecord();
+                in.readByteArray(info.mData);
+                return info;
+            }
+
+            public DnsSdTxtRecord[] newArray(int size) {
+                return new DnsSdTxtRecord[size];
+            }
+        };
+}
diff --git a/staticlibs/framework/com/android/net/module/util/HexDump.java b/staticlibs/framework/com/android/net/module/util/HexDump.java
new file mode 100644
index 0000000..a22c258
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/HexDump.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Hex utility functions.
+ *
+ * @hide
+ */
+public class HexDump {
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            'A', 'B', 'C', 'D', 'E', 'F' };
+    private static final char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    /**
+     * Dump the hex string corresponding to the specified byte array.
+     *
+     * @param array byte array to be dumped.
+     */
+    public static String dumpHexString(@Nullable byte[] array) {
+        if (array == null) return "(null)";
+        return dumpHexString(array, 0, array.length);
+    }
+
+    /**
+     * Dump the hex string corresponding to the specified byte array.
+     *
+     * @param array byte array to be dumped.
+     * @param offset the offset in array where dump should start.
+     * @param length the length of bytes to be dumped.
+     */
+    public static String dumpHexString(@Nullable byte[] array, int offset, int length) {
+        if (array == null) return "(null)";
+        StringBuilder result = new StringBuilder();
+
+        byte[] line = new byte[16];
+        int lineIndex = 0;
+
+        result.append("\n0x");
+        result.append(toHexString(offset));
+
+        for (int i = offset; i < offset + length; i++) {
+            if (lineIndex == 16) {
+                result.append(" ");
+
+                for (int j = 0; j < 16; j++) {
+                    if (line[j] > ' ' && line[j] < '~') {
+                        result.append(new String(line, j, 1));
+                    } else {
+                        result.append(".");
+                    }
+                }
+
+                result.append("\n0x");
+                result.append(toHexString(i));
+                lineIndex = 0;
+            }
+
+            byte b = array[i];
+            result.append(" ");
+            result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
+            result.append(HEX_DIGITS[b & 0x0F]);
+
+            line[lineIndex++] = b;
+        }
+
+        if (lineIndex != 16) {
+            int count = (16 - lineIndex) * 3;
+            count++;
+            for (int i = 0; i < count; i++) {
+                result.append(" ");
+            }
+
+            for (int i = 0; i < lineIndex; i++) {
+                if (line[i] > ' ' && line[i] < '~') {
+                    result.append(new String(line, i, 1));
+                } else {
+                    result.append(".");
+                }
+            }
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Convert a byte to an uppercase hex string.
+     *
+     * @param b the byte to be converted.
+     */
+    public static String toHexString(byte b) {
+        return toHexString(toByteArray(b));
+    }
+
+    /**
+     * Convert a byte array to an uppercase hex string.
+     *
+     * @param array the byte array to be converted.
+     */
+    public static String toHexString(byte[] array) {
+        return toHexString(array, 0, array.length, true);
+    }
+
+    /**
+     * Convert a byte array to a hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static String toHexString(byte[] array, boolean upperCase) {
+        return toHexString(array, 0, array.length, upperCase);
+    }
+
+    /**
+     * Convert a byte array to hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param offset the offset in array where conversion should start.
+     * @param length the length of bytes to be converted.
+     */
+    public static String toHexString(byte[] array, int offset, int length) {
+        return toHexString(array, offset, length, true);
+    }
+
+    /**
+     * Convert a byte array to hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param offset the offset in array where conversion should start.
+     * @param length the length of bytes to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static String toHexString(byte[] array, int offset, int length, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        char[] buf = new char[length * 2];
+
+        int bufIndex = 0;
+        for (int i = offset; i < offset + length; i++) {
+            byte b = array[i];
+            buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = digits[b & 0x0F];
+        }
+
+        return new String(buf);
+    }
+
+    /**
+     * Convert an integer to hex string.
+     *
+     * @param i the integer to be converted.
+     */
+    public static String toHexString(int i) {
+        return toHexString(toByteArray(i));
+    }
+
+    /**
+     * Convert a byte to byte array.
+     *
+     * @param b the byte to be converted.
+     */
+    public static byte[] toByteArray(byte b) {
+        byte[] array = new byte[1];
+        array[0] = b;
+        return array;
+    }
+
+    /**
+     * Convert an integer to byte array.
+     *
+     * @param i the integer to be converted.
+     */
+    public static byte[] toByteArray(int i) {
+        byte[] array = new byte[4];
+
+        array[3] = (byte) (i & 0xFF);
+        array[2] = (byte) ((i >> 8) & 0xFF);
+        array[1] = (byte) ((i >> 16) & 0xFF);
+        array[0] = (byte) ((i >> 24) & 0xFF);
+
+        return array;
+    }
+
+    private static int toByte(char c) {
+        if (c >= '0' && c <= '9') return (c - '0');
+        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+        throw new RuntimeException("Invalid hex char '" + c + "'");
+    }
+
+    /**
+     * Convert a hex string to a byte array.
+     *
+     * @param hexString the string to be converted.
+     */
+    public static byte[] hexStringToByteArray(String hexString) {
+        int length = hexString.length();
+        byte[] buffer = new byte[length / 2];
+
+        for (int i = 0; i < length; i += 2) {
+            buffer[i / 2] =
+                    (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1)));
+        }
+
+        return buffer;
+    }
+
+    /**
+     * Convert a byte to hex string and append it to StringBuilder.
+     *
+     * @param sb StringBuilder instance.
+     * @param b the byte to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        sb.append(digits[(b >> 4) & 0xf]);
+        sb.append(digits[b & 0xf]);
+        return sb;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java b/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java
new file mode 100644
index 0000000..87f43d5
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/Inet4AddressUtils.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to work with IPv4 addresses.
+ * @hide
+ */
+public class Inet4AddressUtils {
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
+     *
+     * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
+     * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
+     * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
+     *                    lower-order IPv4 address byte
+     */
+    public static Inet4Address intToInet4AddressHTL(int hostAddress) {
+        return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
+    }
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
+     * @param hostAddress an int coding for an IPv4 address
+     */
+    public static Inet4Address intToInet4AddressHTH(int hostAddress) {
+        byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
+                (byte) (0xff & (hostAddress >> 16)),
+                (byte) (0xff & (hostAddress >> 8)),
+                (byte) (0xff & hostAddress) };
+
+        try {
+            return (Inet4Address) InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
+     *
+     * <p>This conversion can help order IP addresses: considering the ordering
+     * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
+     * integers with {@link Integer#toUnsignedLong}.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTH(Inet4Address inetAddr)
+            throws IllegalArgumentException {
+        byte [] addr = inetAddr.getAddress();
+        return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
+                | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
+    }
+
+    /**
+     * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
+        return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
+            throws IllegalArgumentException {
+        if (prefixLength < 0 || prefixLength > 32) {
+            throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
+        }
+        // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
+        return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
+            throws IllegalArgumentException {
+        return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+
+    /**
+     * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+     * @param netmask as a {@code Inet4Address}.
+     * @return the network prefix length
+     * @throws IllegalArgumentException the specified netmask was not contiguous.
+     * @hide
+     */
+    public static int netmaskToPrefixLength(Inet4Address netmask) {
+        // inetAddressToInt returns an int in *network* byte order.
+        int i = inet4AddressToIntHTH(netmask);
+        int prefixLength = Integer.bitCount(i);
+        int trailingZeros = Integer.numberOfTrailingZeros(i);
+        if (trailingZeros != 32 - prefixLength) {
+            throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
+        }
+        return prefixLength;
+    }
+
+    /**
+     * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+     */
+    public static int getImplicitNetmask(Inet4Address address) {
+        int firstByte = address.getAddress()[0] & 0xff;  // Convert to an unsigned value.
+        if (firstByte < 128) {
+            return 8;
+        } else if (firstByte < 192) {
+            return 16;
+        } else if (firstByte < 224) {
+            return 24;
+        } else {
+            return 32;  // Will likely not end well for other reasons.
+        }
+    }
+
+    /**
+     * Get the broadcast address for a given prefix.
+     *
+     * <p>For example 192.168.0.1/24 -> 192.168.0.255
+     */
+    public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
+            throws IllegalArgumentException {
+        final int intBroadcastAddr = inet4AddressToIntHTH(addr)
+                | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
+        return intToInet4AddressHTH(intBroadcastAddr);
+    }
+
+    /**
+     * Get a prefix mask as Inet4Address for a given prefix length.
+     *
+     * <p>For example 20 -> 255.255.240.0
+     */
+    public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
+            throws IllegalArgumentException {
+        return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+
+    /**
+     * Trim leading zeros from IPv4 address strings
+     * Non-v4 addresses and host names remain unchanged.
+     * For example, 192.168.000.010 -> 192.168.0.10
+     * @param addr a string representing an ip address
+     * @return a string properly trimmed
+     */
+    public static String trimAddressZeros(String addr) {
+        if (addr == null) return null;
+        String[] octets = addr.split("\\.");
+        if (octets.length != 4) return addr;
+        StringBuilder builder = new StringBuilder(16);
+        String result = null;
+        for (int i = 0; i < 4; i++) {
+            try {
+                if (octets[i].length() > 3) return addr;
+                builder.append(Integer.parseInt(octets[i]));
+            } catch (NumberFormatException e) {
+                return addr;
+            }
+            if (i < 3) builder.append('.');
+        }
+        result = builder.toString();
+        return result;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java
new file mode 100644
index 0000000..40fc59f
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/InetAddressUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.util.Log;
+
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to interact with {@link InetAddress}
+ * @hide
+ */
+public class InetAddressUtils {
+
+    private static final String TAG = InetAddressUtils.class.getSimpleName();
+    private static final int INET6_ADDR_LENGTH = 16;
+
+    /**
+     * Writes an InetAddress to a parcel. The address may be null. This is likely faster than
+     * calling writeSerializable.
+     * @hide
+     */
+    public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) {
+        byte[] addressArray = (address != null) ? address.getAddress() : null;
+        parcel.writeByteArray(addressArray);
+        if (address instanceof Inet6Address) {
+            final Inet6Address v6Address = (Inet6Address) address;
+            final boolean hasScopeId = v6Address.getScopeId() != 0;
+            parcel.writeBoolean(hasScopeId);
+            if (hasScopeId) parcel.writeInt(v6Address.getScopeId());
+        }
+
+    }
+
+    /**
+     * Reads an InetAddress from a parcel. Returns null if the address that was written was null
+     * or if the data is invalid.
+     * @hide
+     */
+    public static InetAddress unparcelInetAddress(Parcel in) {
+        byte[] addressArray = in.createByteArray();
+        if (addressArray == null) {
+            return null;
+        }
+
+        try {
+            if (addressArray.length == INET6_ADDR_LENGTH) {
+                final boolean hasScopeId = in.readBoolean();
+                final int scopeId = hasScopeId ? in.readInt() : 0;
+                return Inet6Address.getByAddress(null /* host */, addressArray, scopeId);
+            }
+
+            return InetAddress.getByAddress(addressArray);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Create a Inet6Address with scope id if it is a link local address. Otherwise, returns the
+     * original address.
+     */
+    public static Inet6Address withScopeId(@NonNull final Inet6Address addr, int scopeid) {
+        if (!addr.isLinkLocalAddress()) {
+            return addr;
+        }
+        try {
+            return Inet6Address.getByAddress(null /* host */, addr.getAddress(),
+                    scopeid);
+        } catch (UnknownHostException impossible) {
+            Log.wtf(TAG, "Cannot construct scoped Inet6Address with Inet6Address.getAddress("
+                    + addr.getHostAddress() + "): ", impossible);
+            return null;
+        }
+    }
+
+    private InetAddressUtils() {}
+}
diff --git a/staticlibs/framework/com/android/net/module/util/InterfaceParams.java b/staticlibs/framework/com/android/net/module/util/InterfaceParams.java
new file mode 100644
index 0000000..30762eb
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/InterfaceParams.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import android.net.MacAddress;
+import android.text.TextUtils;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+
+
+/**
+ * Encapsulate the interface parameters common to IpClient/IpServer components.
+ *
+ * Basically all java.net.NetworkInterface methods throw Exceptions. IpClient
+ * and IpServer (sub)components need most or all of this information at some
+ * point during their lifecycles, so pass only this simplified object around
+ * which can be created once when IpClient/IpServer are told to start.
+ *
+ * @hide
+ */
+public class InterfaceParams {
+    public final String name;
+    public final int index;
+    public final boolean hasMacAddress;
+    public final MacAddress macAddr;
+    public final int defaultMtu;
+
+    // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+    private static final int ETHER_MTU = 1500;
+    private static final int IPV6_MIN_MTU = 1280;
+
+
+    /**
+     * Return InterfaceParams corresponding with an interface name
+     * @param name the interface name
+     */
+    public static InterfaceParams getByName(String name) {
+        final NetworkInterface netif = getNetworkInterfaceByName(name);
+        if (netif == null) return null;
+
+        // Not all interfaces have MAC addresses, e.g. rmnet_data0.
+        final MacAddress macAddr = getMacAddress(netif);
+
+        try {
+            return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU());
+        } catch (IllegalArgumentException | SocketException e) {
+            return null;
+        }
+    }
+
+    public InterfaceParams(String name, int index, MacAddress macAddr) {
+        this(name, index, macAddr, ETHER_MTU);
+    }
+
+    public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) {
+        if (TextUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("impossible interface name");
+        }
+
+        if (index <= 0) throw new IllegalArgumentException("invalid interface index");
+
+        this.name = name;
+        this.index = index;
+        this.hasMacAddress = (macAddr != null);
+        this.macAddr = hasMacAddress ? macAddr : MacAddress.fromBytes(new byte[] {
+                0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
+        this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu);
+    }
+
+    private static NetworkInterface getNetworkInterfaceByName(String name) {
+        try {
+            return NetworkInterface.getByName(name);
+        } catch (NullPointerException | SocketException e) {
+            return null;
+        }
+    }
+
+    private static MacAddress getMacAddress(NetworkInterface netif) {
+        try {
+            return MacAddress.fromBytes(netif.getHardwareAddress());
+        } catch (IllegalArgumentException | NullPointerException | SocketException e) {
+            return null;
+        }
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/IpRange.java b/staticlibs/framework/com/android/net/module/util/IpRange.java
new file mode 100644
index 0000000..0a3f95b
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/IpRange.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.IpPrefix;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+/**
+ * This class represents an IP range, i.e., a contiguous block of IP addresses defined by a starting
+ * and ending IP address. These addresses may not be power-of-two aligned.
+ *
+ * <p>Conversion to prefixes are deterministic, and will always return the same set of {@link
+ * IpPrefix}(es). Ordering of IpPrefix instances is not guaranteed.
+ *
+ * @hide
+ */
+public final class IpRange {
+    private static final int SIGNUM_POSITIVE = 1;
+
+    private final byte[] mStartAddr;
+    private final byte[] mEndAddr;
+
+    public IpRange(@NonNull InetAddress startAddr, @NonNull InetAddress endAddr) {
+        Objects.requireNonNull(startAddr, "startAddr must not be null");
+        Objects.requireNonNull(endAddr, "endAddr must not be null");
+
+        if (!startAddr.getClass().equals(endAddr.getClass())) {
+            throw new IllegalArgumentException("Invalid range: Address family mismatch");
+        }
+        if (addrToBigInteger(startAddr.getAddress()).compareTo(
+                addrToBigInteger(endAddr.getAddress())) >= 0) {
+            throw new IllegalArgumentException(
+                    "Invalid range; start address must be before end address");
+        }
+
+        mStartAddr = startAddr.getAddress();
+        mEndAddr = endAddr.getAddress();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public IpRange(@NonNull IpPrefix prefix) {
+        Objects.requireNonNull(prefix, "prefix must not be null");
+
+        // Use masked address from IpPrefix to zero out lower order bits.
+        mStartAddr = prefix.getRawAddress();
+
+        // Set all non-prefix bits to max.
+        mEndAddr = prefix.getRawAddress();
+        for (int bitIndex = prefix.getPrefixLength(); bitIndex < 8 * mEndAddr.length; ++bitIndex) {
+            mEndAddr[bitIndex / 8] |= (byte) (0x80 >> (bitIndex % 8));
+        }
+    }
+
+    private static InetAddress getAsInetAddress(byte[] address) {
+        try {
+            return InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
+            // array is the wrong length, but are always generated from InetAddress(es).
+            throw new IllegalArgumentException("Address is invalid");
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public InetAddress getStartAddr() {
+        return getAsInetAddress(mStartAddr);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public InetAddress getEndAddr() {
+        return getAsInetAddress(mEndAddr);
+    }
+
+    /**
+     * Converts this IP range to a list of IpPrefix instances.
+     *
+     * <p>This method outputs the IpPrefix instances for use in the routing architecture.
+     *
+     * <p>For example, the range 192.0.2.4 - 192.0.3.1 converts to the following prefixes:
+     *
+     * <ul>
+     *   <li>192.0.2.128/25
+     *   <li>192.0.2.64/26
+     *   <li>192.0.2.32/27
+     *   <li>192.0.2.16/28
+     *   <li>192.0.2.8/29
+     *   <li>192.0.2.4/30
+     *   <li>192.0.3.0/31
+     * </ul>
+     */
+    public List<IpPrefix> asIpPrefixes() {
+        final boolean isIpv6 = (mStartAddr.length == 16);
+        final List<IpPrefix> result = new ArrayList<>();
+        final Queue<IpPrefix> workingSet = new LinkedList<>();
+
+        // Start with the any-address. Inet4/6Address.ANY requires compiling against
+        // core-platform-api.
+        workingSet.add(new IpPrefix(isIpv6 ? getAsInetAddress(new byte[16]) /* IPv6_ANY */
+                : getAsInetAddress(new byte[4]) /* IPv4_ANY */, 0));
+
+        // While items are still in the queue, test and narrow to subsets.
+        while (!workingSet.isEmpty()) {
+            final IpPrefix workingPrefix = workingSet.poll();
+            final IpRange workingRange = new IpRange(workingPrefix);
+
+            // If the other range is contained within, it's part of the output. Do not test subsets,
+            // or we will end up with duplicates.
+            if (containsRange(workingRange)) {
+                result.add(workingPrefix);
+                continue;
+            }
+
+            // If there is any overlap, split the working range into it's two subsets, and
+            // reevaluate those.
+            if (overlapsRange(workingRange)) {
+                workingSet.addAll(getSubsetPrefixes(workingPrefix));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the two prefixes that comprise the given prefix.
+     *
+     * <p>For example, for the prefix 192.0.2.0/24, this will return the two prefixes that combined
+     * make up the current prefix:
+     *
+     * <ul>
+     *   <li>192.0.2.0/25
+     *   <li>192.0.2.128/25
+     * </ul>
+     */
+    private static List<IpPrefix> getSubsetPrefixes(IpPrefix prefix) {
+        final List<IpPrefix> result = new ArrayList<>();
+
+        final int currentPrefixLen = prefix.getPrefixLength();
+        result.add(new IpPrefix(prefix.getAddress(), currentPrefixLen + 1));
+
+        final byte[] other = prefix.getRawAddress();
+        other[currentPrefixLen / 8] =
+                (byte) (other[currentPrefixLen / 8] ^ (0x80 >> (currentPrefixLen % 8)));
+        result.add(new IpPrefix(getAsInetAddress(other), currentPrefixLen + 1));
+
+        return result;
+    }
+
+    /**
+     * Checks if the other IP range is contained within this one
+     *
+     * <p>Checks based on byte values. For other to be contained within this IP range, other's
+     * starting address must be greater or equal to the current IpRange's starting address, and the
+     * other's ending address must be less than or equal to the current IP range's ending address.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public boolean containsRange(IpRange other) {
+        return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mStartAddr)) <= 0
+                && addrToBigInteger(mEndAddr).compareTo(addrToBigInteger(other.mEndAddr)) >= 0;
+    }
+
+    /**
+     * Checks if the other IP range overlaps with this one
+     *
+     * <p>Checks based on byte values. For there to be overlap, this IpRange's starting address must
+     * be less than the other's ending address, and vice versa.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public boolean overlapsRange(IpRange other) {
+        return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mEndAddr)) <= 0
+                && addrToBigInteger(other.mStartAddr).compareTo(addrToBigInteger(mEndAddr)) <= 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(mStartAddr), Arrays.hashCode(mEndAddr));
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof IpRange)) {
+            return false;
+        }
+
+        final IpRange other = (IpRange) obj;
+        return Arrays.equals(mStartAddr, other.mStartAddr)
+                && Arrays.equals(mEndAddr, other.mEndAddr);
+    }
+
+    /** Gets the InetAddress in BigInteger form */
+    private static BigInteger addrToBigInteger(byte[] addr) {
+        // Since addr.getAddress() returns network byte order (big-endian), it is compatible with
+        // the BigInteger constructor (which assumes big-endian).
+        return new BigInteger(SIGNUM_POSITIVE, addr);
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/IpUtils.java b/staticlibs/framework/com/android/net/module/util/IpUtils.java
new file mode 100644
index 0000000..18d96f3
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/IpUtils.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 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.net.module.util;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * @hide
+ */
+public class IpUtils {
+    /**
+     * Converts a signed short value to an unsigned int value.  Needed
+     * because Java does not have unsigned types.
+     */
+    private static int intAbs(short v) {
+        return v & 0xFFFF;
+    }
+
+    /**
+     * Performs an IP checksum (used in IP header and across UDP
+     * payload) or ICMP checksum on the specified portion of a ByteBuffer.  The seed
+     * allows the checksum to commence with a specified value.
+     */
+    @VisibleForTesting
+    public static int checksum(ByteBuffer buf, int seed, int start, int end) {
+        int sum = seed + 0xFFFF;  // to make things work with empty / zero-filled buffer
+        final int bufPosition = buf.position();
+
+        // set position of original ByteBuffer, so that the ShortBuffer
+        // will be correctly initialized
+        buf.position(start);
+        ShortBuffer shortBuf = buf.asShortBuffer();
+
+        // re-set ByteBuffer position
+        buf.position(bufPosition);
+
+        final int numShorts = (end - start) / 2;
+        for (int i = 0; i < numShorts; i++) {
+            sum += intAbs(shortBuf.get(i));
+        }
+        start += numShorts * 2;
+
+        // see if a singleton byte remains
+        if (end != start) {
+            short b = buf.get(start);
+
+            // make it unsigned
+            if (b < 0) {
+                b += 256;
+            }
+
+            sum += b * 256;  // assumes bytebuffer is network order (ie. big endian)
+        }
+
+        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);  // max sum is 0x1FFFE
+        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);  // max sum is 0xFFFF
+        return sum ^ 0xFFFF;  // u16 bitwise negation
+    }
+
+    private static int pseudoChecksumIPv4(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        partial += intAbs(buf.getShort(headerOffset + 12));
+        partial += intAbs(buf.getShort(headerOffset + 14));
+        partial += intAbs(buf.getShort(headerOffset + 16));
+        partial += intAbs(buf.getShort(headerOffset + 18));
+        return partial;
+    }
+
+    private static int pseudoChecksumIPv6(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        for (int offset = 8; offset < 40; offset += 2) {
+            partial += intAbs(buf.getShort(headerOffset + offset));
+        }
+        return partial;
+    }
+
+    private static byte ipversion(ByteBuffer buf, int headerOffset) {
+        return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4);
+   }
+
+    public static short ipChecksum(ByteBuffer buf, int headerOffset) {
+        byte ihl = (byte) (buf.get(headerOffset) & 0x0f);
+        return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4);
+    }
+
+    private static short transportChecksum(ByteBuffer buf, int protocol,
+            int ipOffset, int transportOffset, int transportLen) {
+        if (transportLen < 0) {
+            throw new IllegalArgumentException("Transport length < 0: " + transportLen);
+        }
+        int sum;
+        byte ver = ipversion(buf, ipOffset);
+        if (ver == 4) {
+            sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen);
+        } else if (ver == 6) {
+            sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen);
+        } else {
+            throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6");
+        }
+
+        sum = checksum(buf, sum, transportOffset, transportOffset + transportLen);
+        if (protocol == IPPROTO_UDP && sum == 0) {
+            sum = (short) 0xffff;
+        }
+        return (short) sum;
+    }
+
+    /**
+     * Calculate the UDP checksum for an UDP packet.
+     */
+    public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) {
+        int transportLen = intAbs(buf.getShort(transportOffset + 4));
+        return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen);
+    }
+
+    /**
+     * Calculate the TCP checksum for a TCP packet.
+     */
+    public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen);
+    }
+
+    /**
+     * Calculate the ICMP checksum for an ICMPv4 packet.
+     */
+    public static short icmpChecksum(ByteBuffer buf, int transportOffset, int transportLen) {
+        // ICMP checksum doesn't include pseudo-header. See RFC 792.
+        return (short) checksum(buf, 0, transportOffset, transportOffset + transportLen);
+    }
+
+    /**
+     * Calculate the ICMPv6 checksum for an ICMPv6 packet.
+     */
+    public static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        return transportChecksum(buf, IPPROTO_ICMPV6, ipOffset, transportOffset, transportLen);
+    }
+
+    public static String addressAndPortToString(InetAddress address, int port) {
+        return String.format(
+                (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
+                address.getHostAddress(), port);
+    }
+
+    public static boolean isValidUdpOrTcpPort(int port) {
+        return port > 0 && port < 65536;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java b/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java
new file mode 100644
index 0000000..e271f64
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/LinkPropertiesUtils.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Collection of link properties utilities.
+ * @hide
+ */
+public final class LinkPropertiesUtils {
+
+    /**
+     * @param <T> The type of data to compare.
+     */
+    public static class CompareResult<T> {
+        public final List<T> removed = new ArrayList<>();
+        public final List<T> added = new ArrayList<>();
+
+        public CompareResult() {}
+
+        public CompareResult(@Nullable Collection<T> oldItems, @Nullable Collection<T> newItems) {
+            if (oldItems != null) {
+                removed.addAll(oldItems);
+            }
+            if (newItems != null) {
+                for (T newItem : newItems) {
+                    if (!removed.remove(newItem)) {
+                        added.add(newItem);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "removed=[" + TextUtils.join(",", removed)
+                    + "] added=[" + TextUtils.join(",", added)
+                    + "]";
+        }
+    }
+
+    /**
+     * Generic class to compare two lists of items of type {@code T} whose properties can change.
+     * The items to be compared must provide a way to calculate a corresponding key of type
+     * {@code K} such that if (and only if) an old and a new item have the same key, then the new
+     * item is an update of the old item. Both the old list and the new list may not contain more
+     * than one item with the same key, and may not contain any null items.
+     *
+     * @param <K> A class that represents the key of the items to be compared.
+     * @param <T> The class that represents the object to be compared.
+     */
+    public static class CompareOrUpdateResult<K, T> {
+        public final List<T> added = new ArrayList<>();
+        public final List<T> removed = new ArrayList<>();
+        public final List<T> updated = new ArrayList<>();
+
+        /**
+         * Compares two lists of items.
+         * @param oldItems the old list of items.
+         * @param newItems the new list of items.
+         * @param keyCalculator a {@link Function} that calculates an item's key.
+         */
+        public CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems,
+                Function<T, K> keyCalculator) {
+            HashMap<K, T> updateTracker = new HashMap<>();
+
+            if (oldItems != null) {
+                for (T oldItem : oldItems) {
+                    updateTracker.put(keyCalculator.apply(oldItem), oldItem);
+                }
+            }
+
+            if (newItems != null) {
+                for (T newItem : newItems) {
+                    T oldItem = updateTracker.remove(keyCalculator.apply(newItem));
+                    if (oldItem != null) {
+                        if (!oldItem.equals(newItem)) {
+                            // Update of existing item.
+                            updated.add(newItem);
+                        }
+                    } else {
+                        // New item.
+                        added.add(newItem);
+                    }
+                }
+            }
+
+            removed.addAll(updateTracker.values());
+        }
+
+        @Override
+        public String toString() {
+            return "removed=[" + TextUtils.join(",", removed)
+                    + "] added=[" + TextUtils.join(",", added)
+                    + "] updated=[" + TextUtils.join(",", updated)
+                    + "]";
+        }
+    }
+
+    /**
+     * Compares the addresses in {@code left} LinkProperties with {@code right}
+     * LinkProperties, examining only addresses on the base link.
+     *
+     * @param left A LinkProperties with the old list of addresses.
+     * @param right A LinkProperties with the new list of addresses.
+     * @return the differences between the addresses.
+     */
+    public static @NonNull CompareResult<LinkAddress> compareAddresses(
+            @Nullable LinkProperties left, @Nullable LinkProperties right) {
+        /*
+         * Duplicate the LinkAddresses into removed, we will be removing
+         * address which are common between mLinkAddresses and target
+         * leaving the addresses that are different. And address which
+         * are in target but not in mLinkAddresses are placed in the
+         * addedAddresses.
+         */
+        return new CompareResult<>(left != null ? left.getLinkAddresses() : null,
+                right != null ? right.getLinkAddresses() : null);
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} allLinkAddresses against the {@code right}.
+     *
+     * @param left A LinkProperties or null
+     * @param right A LinkProperties or null
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @see LinkProperties#getAllLinkAddresses()
+     */
+    public static boolean isIdenticalAllLinkAddresses(@Nullable LinkProperties left,
+            @Nullable LinkProperties right) {
+        if (left == right) return true;
+        if (left == null || right == null) return false;
+        final List<LinkAddress> leftAddresses = left.getAllLinkAddresses();
+        final List<LinkAddress> rightAddresses = right.getAllLinkAddresses();
+        if (leftAddresses.size() != rightAddresses.size()) return false;
+        return leftAddresses.containsAll(rightAddresses);
+    }
+
+   /**
+     * Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalAddresses(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<InetAddress> leftAddresses = left.getAddresses();
+        final Collection<InetAddress> rightAddresses = right.getAddresses();
+        return (leftAddresses.size() == rightAddresses.size())
+                    ? leftAddresses.containsAll(rightAddresses) : false;
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} DNS addresses against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalDnses(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<InetAddress> leftDnses = left.getDnsServers();
+        final Collection<InetAddress> rightDnses = right.getDnsServers();
+
+        final String leftDomains = left.getDomains();
+        final String rightDomains = right.getDomains();
+        if (leftDomains == null) {
+            if (rightDomains != null) return false;
+        } else {
+            if (!leftDomains.equals(rightDomains)) return false;
+        }
+        return (leftDnses.size() == rightDnses.size())
+                ? leftDnses.containsAll(rightDnses) : false;
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} HttpProxy against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalHttpProxy(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        return Objects.equals(left.getHttpProxy(), right.getHttpProxy());
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} interface name against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalInterfaceName(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        return TextUtils.equals(left.getInterfaceName(), right.getInterfaceName());
+    }
+
+    /**
+     * Compares {@code left} {@code LinkProperties} Routes against the {@code right}.
+     *
+     * @param left A LinkProperties.
+     * @param right A LinkProperties to be compared with {@code left}.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public static boolean isIdenticalRoutes(@NonNull LinkProperties left,
+            @NonNull LinkProperties right) {
+        final Collection<RouteInfo> leftRoutes = left.getRoutes();
+        final Collection<RouteInfo> rightRoutes = right.getRoutes();
+        return (leftRoutes.size() == rightRoutes.size())
+                ? leftRoutes.containsAll(rightRoutes) : false;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
new file mode 100644
index 0000000..cd1f31c
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.NetworkStack;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * The methods used for location permission and location mode checking.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public class LocationPermissionChecker {
+
+    private static final String TAG = "LocationPermissionChecker";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = {
+        SUCCEEDED,
+        ERROR_LOCATION_MODE_OFF,
+        ERROR_LOCATION_PERMISSION_MISSING,
+    })
+    public @interface LocationPermissionCheckStatus{}
+
+    // The location permission check succeeded.
+    public static final int SUCCEEDED = 0;
+    // The location mode turns off for the caller.
+    public static final int ERROR_LOCATION_MODE_OFF = 1;
+    // The location permission isn't granted for the caller.
+    public static final int ERROR_LOCATION_PERMISSION_MISSING = 2;
+
+    private final Context mContext;
+    private final AppOpsManager mAppOpsManager;
+
+    public LocationPermissionChecker(Context context) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            throw new UnsupportedOperationException("This utility is not supported before R");
+        }
+
+        mContext = context;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+    }
+
+    /**
+     * Check location permission granted by the caller.
+     *
+     * This API check if the location mode enabled for the caller and the caller has
+     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+     *
+     * @param pkgName package name of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     *
+     * @return {@code true} returns if the caller has location permission and the location mode is
+     *         enabled.
+     */
+    public boolean checkLocationPermission(String pkgName, @Nullable String featureId,
+            int uid, @Nullable String message) {
+        return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED;
+    }
+
+    /**
+     * Check location permission granted by the caller.
+     *
+     * This API check if the location mode enabled for the caller and the caller has
+     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+     * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns
+     * the detail information about the checking result, including the reason why it's failed and
+     * logs the error for the caller.
+     *
+     * @param pkgName package name of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     *
+     * @return {@link LocationPermissionCheckStatus} the result of the location permission check.
+     */
+    public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo(
+            String pkgName, @Nullable String featureId, int uid, @Nullable String message) {
+        final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
+        switch (result) {
+            case ERROR_LOCATION_MODE_OFF:
+                Log.e(TAG, "Location mode is disabled for the device");
+                break;
+            case ERROR_LOCATION_PERMISSION_MISSING:
+                Log.e(TAG, "UID " + uid + " has no location permission");
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * Enforce the caller has location permission.
+     *
+     * This API determines if the location mode enabled for the caller and the caller has
+     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+     * SecurityException is thrown if the caller has no permission or the location mode is disabled.
+     *
+     * @param pkgName package name of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     */
+    public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
+            @Nullable String message) throws SecurityException {
+        final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
+
+        switch (result) {
+            case ERROR_LOCATION_MODE_OFF:
+                throw new SecurityException("Location mode is disabled for the device");
+            case ERROR_LOCATION_PERMISSION_MISSING:
+                throw new SecurityException("UID " + uid + " has no location permission");
+        }
+    }
+
+    private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId,
+            int uid, @Nullable String message) {
+        checkPackage(uid, pkgName);
+
+        // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK
+        // are granted a bypass.
+        if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)
+                || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) {
+            return SUCCEEDED;
+        }
+
+        // Location mode must be enabled
+        if (!isLocationModeEnabled()) {
+            return ERROR_LOCATION_MODE_OFF;
+        }
+
+        // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
+        // location information.
+        if (!checkCallersLocationPermission(pkgName, featureId, uid,
+                true /* coarseForTargetSdkLessThanQ */, message)) {
+            return ERROR_LOCATION_PERMISSION_MISSING;
+        }
+        return SUCCEEDED;
+    }
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
+     * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
+     * and a corresponding app op is allowed for this package and uid.
+     *
+     * @param pkgName PackageName of the application requesting access
+     * @param featureId The feature in the package
+     * @param uid The uid of the package
+     * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
+     *                                    else (false or targetSDK >= Q) then will check for FINE
+     * @param message A message describing why the permission was checked. Only needed if this is
+     *                not inside of a two-way binder call from the data receiver
+     */
+    public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
+            int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
+
+        boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
+
+        String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
+        if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+            // Having FINE permission implies having COARSE permission (but not the reverse)
+            permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
+        }
+        if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) {
+            return false;
+        }
+
+        // Always checking FINE - even if will not enforce. This will record the request for FINE
+        // so that a location request by the app is surfaced to the user.
+        boolean isFineLocationAllowed = noteAppOpAllowed(
+                AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
+        if (isFineLocationAllowed) {
+            return true;
+        }
+        if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+            return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
+                    message);
+        }
+        return false;
+    }
+
+    /**
+     * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
+     */
+    public boolean isLocationModeEnabled() {
+        final LocationManager LocationManager = mContext.getSystemService(LocationManager.class);
+        try {
+            return LocationManager.isLocationEnabledForUser(UserHandle.of(
+                    getCurrentUser()));
+        } catch (Exception e) {
+            Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
+            return false;
+        }
+    }
+
+    private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0,
+                    UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
+                    < versionCode) {
+                return true;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, assume unknown app (more strict checking)
+            // Note: This case will never happen since checkPackage is
+            // called to verify validity before checking App's version.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return false;
+    }
+
+    private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
+            int uid, @Nullable String message) {
+        return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message)
+                == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private void checkPackage(int uid, String pkgName)
+            throws SecurityException {
+        if (pkgName == null) {
+            throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
+        }
+        mAppOpsManager.checkPackage(uid, pkgName);
+    }
+
+    @VisibleForTesting
+    protected int getCurrentUser() {
+        return ActivityManager.getCurrentUser();
+    }
+
+    private int getUidPermission(String permissionType, int uid) {
+        // We don't care about pid, pass in -1
+        return mContext.checkPermission(permissionType, -1, uid);
+    }
+
+    /**
+     * Returns true if the |uid| holds NETWORK_SETTINGS permission.
+     */
+    public boolean checkNetworkSettingsPermission(int uid) {
+        return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission.
+     */
+    public boolean checkNetworkSetupWizardPermission(int uid) {
+        return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the |uid| holds NETWORK_STACK permission.
+     */
+    public boolean checkNetworkStackPermission(int uid) {
+        return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission.
+     */
+    public boolean checkMainlineNetworkStackPermission(int uid) {
+        return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+}
diff --git a/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java b/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java
new file mode 100644
index 0000000..ab0040c
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/MacAddressUtils.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Random;
+
+/**
+ * Collection of MAC address utilities.
+ * @hide
+ */
+public final class MacAddressUtils {
+
+    private static final long VALID_LONG_MASK = (1L << 48) - 1;
+    private static final long LOCALLY_ASSIGNED_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("2:0:0:0:0:0").toByteArray());
+    private static final long MULTICAST_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("1:0:0:0:0:0").toByteArray());
+    private static final long OUI_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("ff:ff:ff:0:0:0").toByteArray());
+    private static final long NIC_MASK = longAddrFromByteAddr(
+            MacAddress.fromString("0:0:0:ff:ff:ff").toByteArray());
+    // Matches WifiInfo.DEFAULT_MAC_ADDRESS
+    private static final MacAddress DEFAULT_MAC_ADDRESS =
+            MacAddress.fromString("02:00:00:00:00:00");
+    private static final int ETHER_ADDR_LEN = 6;
+
+    /**
+     * @return true if this MacAddress is a multicast address.
+     */
+    public static boolean isMulticastAddress(@NonNull MacAddress address) {
+        return (longAddrFromByteAddr(address.toByteArray()) & MULTICAST_MASK) != 0;
+    }
+
+    /**
+     * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
+     * unicast bit, are randomly selected.
+     *
+     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+     *
+     * @return a random locally assigned, unicast MacAddress.
+     */
+    public static @NonNull MacAddress createRandomUnicastAddress() {
+        return createRandomUnicastAddress(null, new SecureRandom());
+    }
+
+    /**
+     * Returns a randomly generated MAC address using the given Random object and the same
+     * OUI values as the given MacAddress.
+     *
+     * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+     *
+     * @param base a base MacAddress whose OUI is used for generating the random address.
+     *             If base == null then the OUI will also be randomized.
+     * @param r a standard Java Random object used for generating the random address.
+     * @return a random locally assigned MacAddress.
+     */
+    public static @NonNull MacAddress createRandomUnicastAddress(@Nullable MacAddress base,
+            @NonNull Random r) {
+        long addr;
+
+        if (base == null) {
+            addr = r.nextLong() & VALID_LONG_MASK;
+        } else {
+            addr = (longAddrFromByteAddr(base.toByteArray()) & OUI_MASK)
+                    | (NIC_MASK & r.nextLong());
+        }
+        addr |= LOCALLY_ASSIGNED_MASK;
+        addr &= ~MULTICAST_MASK;
+        MacAddress mac = MacAddress.fromBytes(byteAddrFromLongAddr(addr));
+        if (mac.equals(DEFAULT_MAC_ADDRESS)) {
+            return createRandomUnicastAddress(base, r);
+        }
+        return mac;
+    }
+
+    /**
+     * Convert a byte address to long address.
+     */
+    public static long longAddrFromByteAddr(byte[] addr) {
+        Objects.requireNonNull(addr);
+        if (!isMacAddress(addr)) {
+            throw new IllegalArgumentException(
+                    Arrays.toString(addr) + " was not a valid MAC address");
+        }
+        long longAddr = 0;
+        for (byte b : addr) {
+            final int uint8Byte = b & 0xff;
+            longAddr = (longAddr << 8) + uint8Byte;
+        }
+        return longAddr;
+    }
+
+    /**
+     * Convert a long address to byte address.
+     */
+    public static byte[] byteAddrFromLongAddr(long addr) {
+        byte[] bytes = new byte[ETHER_ADDR_LEN];
+        int index = ETHER_ADDR_LEN;
+        while (index-- > 0) {
+            bytes[index] = (byte) addr;
+            addr = addr >> 8;
+        }
+        return bytes;
+    }
+
+    /**
+     * Returns true if the given byte array is a valid MAC address.
+     * A valid byte array representation for a MacAddress is a non-null array of length 6.
+     *
+     * @param addr a byte array.
+     * @return true if the given byte array is not null and has the length of a MAC address.
+     *
+     */
+    public static boolean isMacAddress(byte[] addr) {
+        return addr != null && addr.length == ETHER_ADDR_LEN;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetUtils.java b/staticlibs/framework/com/android/net/module/util/NetUtils.java
new file mode 100644
index 0000000..ae1b9bb
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2019 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.RouteInfo;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+
+/**
+ * Collection of network common utilities.
+ * @hide
+ */
+public final class NetUtils {
+
+    /**
+     * Check if IP address type is consistent between two InetAddress.
+     * @return true if both are the same type. False otherwise.
+     */
+    public static boolean addressTypeMatches(@NonNull InetAddress left,
+            @NonNull InetAddress right) {
+        return (((left instanceof Inet4Address) && (right instanceof Inet4Address))
+                || ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
+    }
+
+    /**
+     * Find the route from a Collection of routes that best matches a given address.
+     * May return null if no routes are applicable, or the best route is not a route of
+     * {@link RouteInfo.RTN_UNICAST} type.
+     *
+     * @param routes a Collection of RouteInfos to chose from
+     * @param dest the InetAddress your trying to get to
+     * @return the RouteInfo from the Collection that best fits the given address
+     */
+    @Nullable
+    public static RouteInfo selectBestRoute(@Nullable Collection<RouteInfo> routes,
+            @Nullable InetAddress dest) {
+        if ((routes == null) || (dest == null)) return null;
+
+        RouteInfo bestRoute = null;
+
+        // pick a longest prefix match under same address type
+        for (RouteInfo route : routes) {
+            if (addressTypeMatches(route.getDestination().getAddress(), dest)) {
+                if ((bestRoute != null)
+                        && (bestRoute.getDestination().getPrefixLength()
+                        >= route.getDestination().getPrefixLength())) {
+                    continue;
+                }
+                if (route.matches(dest)) bestRoute = route;
+            }
+        }
+
+        if (bestRoute != null && bestRoute.getType() == RouteInfo.RTN_UNICAST) {
+            return bestRoute;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get InetAddress masked with prefixLength.  Will never return null.
+     * @param address the IP address to mask with
+     * @param prefixLength the prefixLength used to mask the IP
+     */
+    public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+        byte[] array = address.getAddress();
+        maskRawAddress(array, prefixLength);
+
+        InetAddress netPart = null;
+        try {
+            netPart = InetAddress.getByAddress(array);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("getNetworkPart error - " + e.toString());
+        }
+        return netPart;
+    }
+
+    /**
+     *  Masks a raw IP address byte array with the specified prefix length.
+     */
+    public static void maskRawAddress(byte[] array, int prefixLength) {
+        if (prefixLength < 0 || prefixLength > array.length * 8) {
+            throw new RuntimeException("IP address with " + array.length
+                    + " bytes has invalid prefix length " + prefixLength);
+        }
+
+        int offset = prefixLength / 8;
+        int remainder = prefixLength % 8;
+        byte mask = (byte) (0xFF << (8 - remainder));
+
+        if (offset < array.length) array[offset] = (byte) (array[offset] & mask);
+
+        offset++;
+
+        for (; offset < array.length; offset++) {
+            array[offset] = 0;
+        }
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
new file mode 100644
index 0000000..54ce01e
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MCX;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMTEL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_USB;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+
+import static com.android.net.module.util.BitUtils.packBitList;
+import static com.android.net.module.util.BitUtils.unpackBits;
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Utilities to examine {@link android.net.NetworkCapabilities}.
+ * @hide
+ */
+public final class NetworkCapabilitiesUtils {
+    // Transports considered to classify networks in UI, in order of which transport should be
+    // surfaced when there are multiple transports. Transports not in this list do not have
+    // an ordering preference (in practice they will have a deterministic order based on the
+    // transport int itself).
+    private static final int[] DISPLAY_TRANSPORT_PRIORITIES = new int[] {
+        // Users think of their VPNs as VPNs, not as any of the underlying nets
+        TRANSPORT_VPN,
+        // If the network has cell, prefer showing that because it's usually metered.
+        TRANSPORT_CELLULAR,
+        // If the network has WiFi aware, prefer showing that as it's a more specific use case.
+        // Ethernet can masquerade as other transports, where the device uses ethernet to connect to
+        // a box providing cell or wifi. Today this is represented by only the masqueraded type for
+        // backward compatibility, but these networks should morally have Ethernet & the masqueraded
+        // type. Because of this, prefer other transports instead of Ethernet.
+        TRANSPORT_WIFI_AWARE,
+        TRANSPORT_BLUETOOTH,
+        TRANSPORT_WIFI,
+        TRANSPORT_ETHERNET,
+        TRANSPORT_USB
+
+        // Notably, TRANSPORT_TEST is not in this list as any network that has TRANSPORT_TEST and
+        // one of the above transports should be counted as that transport, to keep tests as
+        // realistic as possible.
+    };
+
+    /**
+     * Capabilities that suggest that a network is restricted.
+     * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted},
+      * and {@code FORCE_RESTRICTED_CAPABILITIES}.
+     */
+    @VisibleForTesting
+    public static final long RESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_BIP,
+            NET_CAPABILITY_CBS,
+            NET_CAPABILITY_DUN,
+            NET_CAPABILITY_EIMS,
+            NET_CAPABILITY_ENTERPRISE,
+            NET_CAPABILITY_FOTA,
+            NET_CAPABILITY_IA,
+            NET_CAPABILITY_IMS,
+            NET_CAPABILITY_MCX,
+            NET_CAPABILITY_RCS,
+            NET_CAPABILITY_VEHICLE_INTERNAL,
+            NET_CAPABILITY_VSIM,
+            NET_CAPABILITY_XCAP,
+            NET_CAPABILITY_MMTEL);
+
+    /**
+     * Capabilities that force network to be restricted.
+     * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
+     */
+    private static final long FORCE_RESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_ENTERPRISE,
+            NET_CAPABILITY_OEM_PAID,
+            NET_CAPABILITY_OEM_PRIVATE);
+
+    /**
+     * Capabilities that suggest that a network is unrestricted.
+     * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
+     */
+    @VisibleForTesting
+    public static final long UNRESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_INTERNET,
+            NET_CAPABILITY_MMS,
+            NET_CAPABILITY_SUPL,
+            NET_CAPABILITY_WIFI_P2P);
+
+    /**
+     * Get a transport that can be used to classify a network when displaying its info to users.
+     *
+     * While networks can have multiple transports, users generally think of them as "wifi",
+     * "mobile data", "vpn" and expect them to be classified as such in UI such as settings.
+     * @param transports Non-empty array of transports on a network
+     * @return A single transport
+     * @throws IllegalArgumentException The array is empty
+     */
+    public static int getDisplayTransport(@NonNull int[] transports) {
+        for (int transport : DISPLAY_TRANSPORT_PRIORITIES) {
+            if (CollectionUtils.contains(transports, transport)) {
+                return transport;
+            }
+        }
+
+        if (transports.length < 1) {
+            // All NetworkCapabilities representing a network have at least one transport, so an
+            // empty transport array would be created by the caller instead of extracted from
+            // NetworkCapabilities.
+            throw new IllegalArgumentException("No transport in the provided array");
+        }
+        return transports[0];
+    }
+
+
+    /**
+     * Infers that all the capabilities it provides are typically provided by restricted networks
+     * or not.
+     *
+     * @param nc the {@link NetworkCapabilities} to infer the restricted capabilities.
+     *
+     * @return {@code true} if the network should be restricted.
+     */
+    // TODO: Use packBits(nc.getCapabilities()) to check more easily using bit masks.
+    public static boolean inferRestrictedCapability(NetworkCapabilities nc) {
+        // Check if we have any capability that forces the network to be restricted.
+        for (int capability : unpackBits(FORCE_RESTRICTED_CAPABILITIES)) {
+            if (nc.hasCapability(capability)) {
+                return true;
+            }
+        }
+
+        // Verify there aren't any unrestricted capabilities.  If there are we say
+        // the whole thing is unrestricted unless it is forced to be restricted.
+        for (int capability : unpackBits(UNRESTRICTED_CAPABILITIES)) {
+            if (nc.hasCapability(capability)) {
+                return false;
+            }
+        }
+
+        // Must have at least some restricted capabilities.
+        for (int capability : unpackBits(RESTRICTED_CAPABILITIES)) {
+            if (nc.hasCapability(capability)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java
new file mode 100644
index 0000000..b641753
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Utilities to examine {@link android.net.NetworkIdentity}.
+ * @hide
+ */
+public class NetworkIdentityUtils {
+    /**
+     * Scrub given IMSI on production builds.
+     */
+    @NonNull
+    public static String scrubSubscriberId(@Nullable String subscriberId) {
+        if (subscriberId != null) {
+            // TODO: parse this as MCC+MNC instead of hard-coding
+            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
+        } else {
+            return "null";
+        }
+    }
+
+    /**
+     * Scrub given IMSI on production builds.
+     */
+    @Nullable
+    public static String[] scrubSubscriberIds(@Nullable String[] subscriberIds) {
+        if (subscriberIds == null) return null;
+        final String[] res = new String[subscriberIds.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = scrubSubscriberId(subscriberIds[i]);
+        }
+        return res;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
new file mode 100644
index 0000000..f9895c6
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Network constants used by the network stack.
+ * @hide
+ */
+public final class NetworkStackConstants {
+
+    /**
+     * Ethernet constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc894
+     *     - https://tools.ietf.org/html/rfc2464
+     *     - https://tools.ietf.org/html/rfc7042
+     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+     */
+    public static final int ETHER_DST_ADDR_OFFSET = 0;
+    public static final int ETHER_SRC_ADDR_OFFSET = 6;
+    public static final int ETHER_ADDR_LEN = 6;
+    public static final int ETHER_TYPE_OFFSET = 12;
+    public static final int ETHER_TYPE_LENGTH = 2;
+    public static final int ETHER_TYPE_ARP  = 0x0806;
+    public static final int ETHER_TYPE_IPV4 = 0x0800;
+    public static final int ETHER_TYPE_IPV6 = 0x86dd;
+    public static final int ETHER_HEADER_LEN = 14;
+    public static final int ETHER_MTU = 1500;
+    public static final byte[] ETHER_BROADCAST = new byte[] {
+            (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff,
+    };
+
+    /**
+     * ARP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc826
+     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+     */
+    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
+    public static final int ARP_ETHER_IPV4_LEN = ARP_PAYLOAD_LEN + ETHER_HEADER_LEN;
+    public static final int ARP_REQUEST = 1;
+    public static final int ARP_REPLY   = 2;
+    public static final int ARP_HWTYPE_RESERVED_LO = 0;
+    public static final int ARP_HWTYPE_ETHER       = 1;
+    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+    /**
+     * IPv4 Address Conflict Detection constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc5227
+     */
+    public static final int IPV4_CONFLICT_PROBE_NUM = 3;
+    public static final int IPV4_CONFLICT_ANNOUNCE_NUM = 2;
+
+    /**
+     * IPv4 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc791
+     */
+    public static final int IPV4_ADDR_BITS = 32;
+    public static final int IPV4_MIN_MTU = 68;
+    public static final int IPV4_MAX_MTU = 65_535;
+    public static final int IPV4_HEADER_MIN_LEN = 20;
+    public static final int IPV4_IHL_MASK = 0xf;
+    public static final int IPV4_LENGTH_OFFSET = 2;
+    public static final int IPV4_FLAGS_OFFSET = 6;
+    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+    public static final int IPV4_PROTOCOL_OFFSET = 9;
+    public static final int IPV4_CHECKSUM_OFFSET = 10;
+    public static final int IPV4_SRC_ADDR_OFFSET = 12;
+    public static final int IPV4_DST_ADDR_OFFSET = 16;
+    public static final int IPV4_ADDR_LEN = 4;
+    public static final int IPV4_FLAG_MF = 0x2000;
+    public static final int IPV4_FLAG_DF = 0x4000;
+    // getSockOpt() for v4 MTU
+    public static final int IP_MTU = 14;
+    public static final Inet4Address IPV4_ADDR_ALL = makeInet4Address(
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff);
+    public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address(
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0);
+    public static final Inet6Address IPV6_ADDR_ANY = makeInet6Address(new byte[]{
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0 });
+
+    /**
+     * CLAT constants
+     */
+    public static final IpPrefix CLAT_PREFIX = new IpPrefix("192.0.0.0/29");
+
+    /**
+     * IPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2460
+     */
+    public static final int IPV6_ADDR_LEN = 16;
+    public static final int IPV6_HEADER_LEN = 40;
+    public static final int IPV6_LEN_OFFSET = 4;
+    public static final int IPV6_PROTOCOL_OFFSET = 6;
+    public static final int IPV6_SRC_ADDR_OFFSET = 8;
+    public static final int IPV6_DST_ADDR_OFFSET = 24;
+    public static final int IPV6_MIN_MTU = 1280;
+    public static final int IPV6_FRAGMENT_HEADER_LEN = 8;
+    public static final int RFC7421_PREFIX_LENGTH = 64;
+    // getSockOpt() for v6 MTU
+    public static final int IPV6_MTU = 24;
+    public static final Inet6Address IPV6_ADDR_ALL_NODES_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::1");
+    public static final Inet6Address IPV6_ADDR_ALL_ROUTERS_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
+    public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::3");
+
+    /**
+     * ICMP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc792
+     */
+    public static final int ICMP_CHECKSUM_OFFSET = 2;
+    public static final int ICMP_HEADER_LEN = 8;
+    /**
+     * ICMPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc4191
+     *     - https://tools.ietf.org/html/rfc4443
+     *     - https://tools.ietf.org/html/rfc4861
+     */
+    public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_CHECKSUM_OFFSET = 2;
+    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
+    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
+    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
+    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+    public static final int ICMPV6_ND_OPTION_SLLA  = 1;
+    public static final int ICMPV6_ND_OPTION_TLLA  = 2;
+    public static final int ICMPV6_ND_OPTION_PIO   = 3;
+    public static final int ICMPV6_ND_OPTION_MTU   = 5;
+    public static final int ICMPV6_ND_OPTION_RIO   = 24;
+    public static final int ICMPV6_ND_OPTION_RDNSS = 25;
+    public static final int ICMPV6_ND_OPTION_PREF64 = 38;
+
+    public static final int ICMPV6_RS_HEADER_LEN = 8;
+    public static final int ICMPV6_RA_HEADER_LEN = 16;
+    public static final int ICMPV6_NS_HEADER_LEN = 24;
+    public static final int ICMPV6_NA_HEADER_LEN = 24;
+
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER    = 1 << 31;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE  = 1 << 29;
+
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_MANAGED_ADDRESS = (byte) (1 << 7);
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_OTHER = (byte) (1 << 6);
+
+    public static final byte PIO_FLAG_ON_LINK = (byte) (1 << 7);
+    public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6);
+
+    /**
+     * TCP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc793
+     */
+    public static final int TCP_HEADER_MIN_LEN = 20;
+    public static final int TCP_CHECKSUM_OFFSET = 16;
+    public static final byte TCPHDR_FIN = (byte) (1 << 0);
+    public static final byte TCPHDR_SYN = (byte) (1 << 1);
+    public static final byte TCPHDR_RST = (byte) (1 << 2);
+    public static final byte TCPHDR_PSH = (byte) (1 << 3);
+    public static final byte TCPHDR_ACK = (byte) (1 << 4);
+    public static final byte TCPHDR_URG = (byte) (1 << 5);
+
+    /**
+     * UDP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc768
+     */
+    public static final int UDP_HEADER_LEN = 8;
+    public static final int UDP_SRCPORT_OFFSET = 0;
+    public static final int UDP_DSTPORT_OFFSET = 2;
+    public static final int UDP_LENGTH_OFFSET = 4;
+    public static final int UDP_CHECKSUM_OFFSET = 6;
+
+    /**
+     * DHCP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2131
+     */
+    public static final int INFINITE_LEASE = 0xffffffff;
+    public static final int DHCP4_CLIENT_PORT = 68;
+    // The maximum length of a DHCP packet that can be constructed.
+    public static final int DHCP_MAX_LENGTH = 1500;
+    public static final int DHCP_MAX_OPTION_LEN = 255;
+
+    /**
+     * DHCPv6 constants.
+     *
+     * See also:
+     *     - https://datatracker.ietf.org/doc/html/rfc8415
+     *     - https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml
+     */
+    public static final int DHCP6_CLIENT_PORT = 546;
+    public static final int DHCP6_SERVER_PORT = 547;
+    public static final Inet6Address ALL_DHCP_RELAY_AGENTS_AND_SERVERS =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::1:2");
+    public static final int DHCP6_OPTION_IA_ADDR = 5;
+    public static final int DHCP6_OPTION_IA_PD = 25;
+    public static final int DHCP6_OPTION_IAPREFIX = 26;
+
+    /**
+     * DNS constants.
+     *
+     * See also:
+     *     - https://datatracker.ietf.org/doc/html/rfc7858#section-3.1
+     */
+    public static final short DNS_OVER_TLS_PORT = 853;
+
+    /**
+     * IEEE802.11 standard constants.
+     *
+     * See also:
+     *     - https://ieeexplore.ieee.org/document/7786995
+     */
+    public static final int VENDOR_SPECIFIC_IE_ID = 0xdd;
+
+
+    /**
+     * TrafficStats constants.
+     */
+    // These tags are used by the network stack to do traffic for its own purposes. Traffic
+    // tagged with these will be counted toward the network stack and must stay inside the
+    // range defined by
+    // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_START} and
+    // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_END}.
+    public static final int TAG_SYSTEM_DHCP = 0xFFFFFE01;
+    public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFE02;
+    public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFE03;
+
+    // These tags are used by the network stack to do traffic on behalf of apps. Traffic
+    // tagged with these will be counted toward the app on behalf of which the network
+    // stack is doing this traffic. These values must stay inside the range defined by
+    // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_START} and
+    // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_END}.
+    public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
+    public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
+
+    /**
+     * A test URL used to override configuration settings and overlays for the network validation
+     * HTTPS URL, when set in {@link android.provider.DeviceConfig} configuration.
+     *
+     * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+     * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+     *
+     * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+     */
+    public static final String TEST_CAPTIVE_PORTAL_HTTPS_URL = "test_captive_portal_https_url";
+    /**
+     * A test URL used to override configuration settings and overlays for the network validation
+     * HTTP URL, when set in {@link android.provider.DeviceConfig} configuration.
+     *
+     * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+     * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+     *
+     * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+     */
+    public static final String TEST_CAPTIVE_PORTAL_HTTP_URL = "test_captive_portal_http_url";
+    /**
+     * Expiration time of the test URL, in ms, relative to {@link System#currentTimeMillis()}.
+     *
+     * <p>After this expiration time, test URLs will be ignored. They will also be ignored if
+     * the expiration time is more than 10 minutes in the future, to avoid misconfiguration
+     * following test runs.
+     */
+    public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time";
+
+    // TODO: Move to Inet4AddressUtils
+    // See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
+    // for users of both the net-utils-device-common and net-utils-framework-common libraries.
+    // Jarjar rule management needs to be simplified for that: b/170445871
+
+    /**
+     * Make an Inet4Address from 4 bytes in network byte order.
+     */
+    private static Inet4Address makeInet4Address(byte b1, byte b2, byte b3, byte b4) {
+        try {
+            return (Inet4Address) InetAddress.getByAddress(new byte[] { b1, b2, b3, b4 });
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("addr must be 4 bytes: this should never happen");
+        }
+    }
+
+    /**
+     * Make an Inet6Address from 16 bytes in network byte order.
+     */
+    private static Inet6Address makeInet6Address(byte[] bytes) {
+        try {
+            return (Inet6Address) InetAddress.getByAddress(bytes);
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("addr must be 16 bytes: this should never happen");
+        }
+    }
+    private NetworkStackConstants() {
+        throw new UnsupportedOperationException("This class is not to be instantiated");
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
new file mode 100644
index 0000000..41a9428
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import android.app.usage.NetworkStats;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Various utilities used for NetworkStats related code.
+ *
+ * @hide
+ */
+public class NetworkStatsUtils {
+    // These constants must be synced with the definition in android.net.NetworkStats.
+    // TODO: update to formal APIs once all downstreams have these APIs.
+    private static final int SET_ALL = -1;
+    private static final int METERED_ALL = -1;
+    private static final int ROAMING_ALL = -1;
+    private static final int DEFAULT_NETWORK_ALL = -1;
+
+    /**
+     * Safely multiple a value by a rational.
+     * <p>
+     * Internally it uses integer-based math whenever possible, but switches
+     * over to double-based math if values would overflow.
+     * @hide
+     */
+    public static long multiplySafeByRational(long value, long num, long den) {
+        if (den == 0) {
+            throw new ArithmeticException("Invalid Denominator");
+        }
+        long x = value;
+        long y = num;
+
+        // Logic shamelessly borrowed from Math.multiplyExact()
+        long r = x * y;
+        long ax = Math.abs(x);
+        long ay = Math.abs(y);
+        if (((ax | ay) >>> 31 != 0)) {
+            // Some bits greater than 2^31 that might cause overflow
+            // Check the result using the divide operator
+            // and check for the special case of Long.MIN_VALUE * -1
+            if (((y != 0) && (r / y != x))
+                    || (x == Long.MIN_VALUE && y == -1)) {
+                // Use double math to avoid overflowing
+                return (long) (((double) num / den) * value);
+            }
+        }
+        return r / den;
+    }
+
+    /**
+     * Value of the match rule of the subscriberId to match networks with specific subscriberId.
+     *
+     * @hide
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0;
+    /**
+     * Value of the match rule of the subscriberId to match networks with any subscriberId which
+     * includes null and non-null.
+     *
+     * @hide
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1;
+
+    /**
+     * Name representing {@link #bandwidthSetGlobalAlert(long)} limit when delivered to
+     * {@link AlertObserver#onQuotaLimitReached(String, String)}.
+     */
+    public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
+
+    /**
+     * Return the constrained value by given the lower and upper bounds.
+     */
+    public static int constrain(int amount, int low, int high) {
+        if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")");
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    /**
+     * Return the constrained value by given the lower and upper bounds.
+     */
+    public static long constrain(long amount, long low, long high) {
+        if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")");
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    /**
+     * Convert structure from android.app.usage.NetworkStats to android.net.NetworkStats.
+     */
+    public static android.net.NetworkStats fromPublicNetworkStats(
+            NetworkStats publiceNetworkStats) {
+        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+        while (publiceNetworkStats.hasNextBucket()) {
+            NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+            publiceNetworkStats.getNextBucket(bucket);
+            final android.net.NetworkStats.Entry entry = fromBucket(bucket);
+            stats = stats.addEntry(entry);
+        }
+        return stats;
+    }
+
+    @VisibleForTesting
+    public static android.net.NetworkStats.Entry fromBucket(NetworkStats.Bucket bucket) {
+        return new android.net.NetworkStats.Entry(
+                null /* IFACE_ALL */, bucket.getUid(), convertBucketState(bucket.getState()),
+                convertBucketTag(bucket.getTag()), convertBucketMetered(bucket.getMetered()),
+                convertBucketRoaming(bucket.getRoaming()),
+                convertBucketDefaultNetworkStatus(bucket.getDefaultNetworkStatus()),
+                bucket.getRxBytes(), bucket.getRxPackets(),
+                bucket.getTxBytes(), bucket.getTxPackets(), 0 /* operations */);
+    }
+
+    private static int convertBucketState(int networkStatsSet) {
+        switch (networkStatsSet) {
+            case NetworkStats.Bucket.STATE_ALL: return SET_ALL;
+            case NetworkStats.Bucket.STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
+            case NetworkStats.Bucket.STATE_FOREGROUND:
+                return android.net.NetworkStats.SET_FOREGROUND;
+        }
+        return 0;
+    }
+
+    private static int convertBucketTag(int tag) {
+        switch (tag) {
+            case NetworkStats.Bucket.TAG_NONE: return android.net.NetworkStats.TAG_NONE;
+        }
+        return tag;
+    }
+
+    private static int convertBucketMetered(int metered) {
+        switch (metered) {
+            case NetworkStats.Bucket.METERED_ALL: return METERED_ALL;
+            case NetworkStats.Bucket.METERED_NO: return android.net.NetworkStats.METERED_NO;
+            case NetworkStats.Bucket.METERED_YES: return android.net.NetworkStats.METERED_YES;
+        }
+        return 0;
+    }
+
+    private static int convertBucketRoaming(int roaming) {
+        switch (roaming) {
+            case NetworkStats.Bucket.ROAMING_ALL: return ROAMING_ALL;
+            case NetworkStats.Bucket.ROAMING_NO: return android.net.NetworkStats.ROAMING_NO;
+            case NetworkStats.Bucket.ROAMING_YES: return android.net.NetworkStats.ROAMING_YES;
+        }
+        return 0;
+    }
+
+    private static int convertBucketDefaultNetworkStatus(int defaultNetworkStatus) {
+        switch (defaultNetworkStatus) {
+            case NetworkStats.Bucket.DEFAULT_NETWORK_ALL:
+                return DEFAULT_NETWORK_ALL;
+            case NetworkStats.Bucket.DEFAULT_NETWORK_NO:
+                return android.net.NetworkStats.DEFAULT_NETWORK_NO;
+            case NetworkStats.Bucket.DEFAULT_NETWORK_YES:
+                return android.net.NetworkStats.DEFAULT_NETWORK_YES;
+        }
+        return 0;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
new file mode 100644
index 0000000..463b0c4
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.net.module.util;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Keeps track of the counters under different uid, fire exception if the counter
+ * exceeded the specified maximum value.
+ *
+ * @hide
+ */
+public class PerUidCounter {
+    private final int mMaxCountPerUid;
+
+    // Map from UID to count that UID has filed.
+    @VisibleForTesting
+    @GuardedBy("this")
+    final SparseIntArray mUidToCount = new SparseIntArray();
+
+    /**
+     * Constructor
+     *
+     * @param maxCountPerUid the maximum count per uid allowed
+     */
+    public PerUidCounter(final int maxCountPerUid) {
+        if (maxCountPerUid <= 0) {
+            throw new IllegalArgumentException("Maximum counter value must be positive");
+        }
+        mMaxCountPerUid = maxCountPerUid;
+    }
+
+    /**
+     * Increments the count of the given uid.  Throws an exception if the number
+     * of the counter for the uid exceeds the value of maxCounterPerUid which is the value
+     * passed into the constructor. see: {@link #PerUidCounter(int)}.
+     *
+     * @throws IllegalStateException if the number of counter for the uid exceed
+     *         the allowed number.
+     *
+     * @param uid the uid that the counter was made under
+     */
+    public synchronized void incrementCountOrThrow(final int uid) {
+        final long newCount = ((long) mUidToCount.get(uid, 0)) + 1;
+        if (newCount > mMaxCountPerUid) {
+            throw new IllegalStateException("Uid " + uid + " exceeded its allowed limit");
+        }
+        // Since the count cannot be greater than Integer.MAX_VALUE here since mMaxCountPerUid
+        // is an integer, it is safe to cast to int.
+        mUidToCount.put(uid, (int) newCount);
+    }
+
+    /**
+     * Decrements the count of the given uid. Throws an exception if the number
+     * of the counter goes below zero.
+     *
+     * @throws IllegalStateException if the number of counter for the uid goes below
+     *         zero.
+     *
+     * @param uid the uid that the count was made under
+     */
+    public synchronized void decrementCountOrThrow(final int uid) {
+        final int newCount = mUidToCount.get(uid, 0) - 1;
+        if (newCount < 0) {
+            throw new IllegalStateException("BUG: too small count " + newCount + " for UID " + uid);
+        } else if (newCount == 0) {
+            mUidToCount.delete(uid);
+        } else {
+            mUidToCount.put(uid, newCount);
+        }
+    }
+
+    @VisibleForTesting
+    public synchronized int get(int uid) {
+        return mUidToCount.get(uid, 0);
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
new file mode 100644
index 0000000..d5b4c90
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2021 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.net.module.util;
+
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Binder;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Collection of permission utilities.
+ * @hide
+ */
+public final class PermissionUtils {
+    /**
+     * Return true if the context has one of given permission.
+     */
+    public static boolean checkAnyPermissionOf(@NonNull Context context,
+            @NonNull String... permissions) {
+        for (String permission : permissions) {
+            if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the permission has system signature.
+     */
+    public static boolean isSystemSignaturePermission(@NonNull Context context,
+            @NonNull String permission) {
+        try {
+            PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo(
+                    permission, 0 /* flags */);
+            if (permissionInfo == null) {
+                return false;
+            }
+            return "android".equals(permissionInfo.packageName)
+                    && permissionInfo.getProtection() == PROTECTION_SIGNATURE;
+        } catch (PackageManager.NameNotFoundException ignored) {
+            // Ignored the NameNotFoundException and return false
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the context has one of give permission that is allowed
+     * for a particular process and user ID running in the system.
+     */
+    public static boolean checkAnyPermissionOf(@NonNull Context context,
+            int pid, int uid, @NonNull String... permissions) {
+        for (String permission : permissions) {
+            if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Enforce permission check on the context that should have one of given permission.
+     */
+    public static void enforceAnyPermissionOf(@NonNull Context context,
+            @NonNull String... permissions) {
+        if (!checkAnyPermissionOf(context, permissions)) {
+            throw new SecurityException("Requires one of the following permissions: "
+                    + String.join(", ", permissions) + ".");
+        }
+    }
+
+    /**
+     * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     */
+    public static void enforceNetworkStackPermission(final @NonNull Context context) {
+        enforceNetworkStackPermissionOr(context);
+    }
+
+    /**
+     * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed
+     * for a particular process, throw a {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param otherPermissions The set of permissions that could be the candidate permissions , or
+     *                         empty string if none of other permissions needed.
+     */
+    public static void enforceNetworkStackPermissionOr(final @NonNull Context context,
+            final @NonNull String... otherPermissions) {
+        ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions));
+        permissions.add(NETWORK_STACK);
+        permissions.add(PERMISSION_MAINLINE_NETWORK_STACK);
+        enforceAnyPermissionOf(context, permissions.toArray(new String[0]));
+    }
+
+    /**
+     * If the CONNECTIVITY_USE_RESTRICTED_NETWORKS is not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param message A message to include in the exception if it is thrown.
+     */
+    public static void enforceRestrictedNetworkPermission(
+            final @NonNull Context context, final @Nullable String message) {
+        context.enforceCallingOrSelfPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, message);
+    }
+
+    /**
+     * If the ACCESS_NETWORK_STATE is not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param message A message to include in the exception if it is thrown.
+     */
+    public static void enforceAccessNetworkStatePermission(
+            final @NonNull Context context, final @Nullable String message) {
+        context.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, message);
+    }
+
+    /**
+     * Return true if the context has DUMP permission.
+     */
+    public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+        if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump " + tag + " from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " due to missing android.permission.DUMP permission");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Enforce that a given feature is available and if not, throw an
+     * {@link UnsupportedOperationException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param feature the feature name to enforce.
+     * @param errorMessage an optional error message to include.
+     */
+    public static void enforceSystemFeature(final @NonNull Context context,
+            final @NonNull String feature, final @Nullable String errorMessage) {
+        final boolean hasSystemFeature =
+                context.getPackageManager().hasSystemFeature(feature);
+        if (!hasSystemFeature) {
+            if (null == errorMessage) {
+                throw new UnsupportedOperationException();
+            }
+            throw new UnsupportedOperationException(errorMessage);
+        }
+    }
+
+    /**
+     * Get the list of granted permissions for a package info.
+     *
+     * PackageInfo contains the list of requested permissions, and their state (whether they
+     * were granted or not, in particular) as a parallel array. Most users care only about
+     * granted permissions. This method returns the list of them.
+     *
+     * @param packageInfo the package info for the relevant uid.
+     * @return the list of granted permissions.
+     */
+    public static List<String> getGrantedPermissions(final @NonNull PackageInfo packageInfo) {
+        if (null == packageInfo.requestedPermissions) return Collections.emptyList();
+        final ArrayList<String> result = new ArrayList<>(packageInfo.requestedPermissions.length);
+        for (int i = 0; i < packageInfo.requestedPermissions.length; ++i) {
+            if (0 != (REQUESTED_PERMISSION_GRANTED & packageInfo.requestedPermissionsFlags[i])) {
+                result.add(packageInfo.requestedPermissions[i]);
+            }
+        }
+        return result;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/ProxyUtils.java b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java
new file mode 100644
index 0000000..fdd7dca
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 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.net.module.util;
+
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of network common utilities.
+ *
+ * @hide
+ */
+public final class ProxyUtils {
+
+    public static final int PROXY_VALID             = 0;
+    public static final int PROXY_HOSTNAME_EMPTY    = 1;
+    public static final int PROXY_HOSTNAME_INVALID  = 2;
+    public static final int PROXY_PORT_EMPTY        = 3;
+    public static final int PROXY_PORT_INVALID      = 4;
+    public static final int PROXY_EXCLLIST_INVALID  = 5;
+
+    // Hostname / IP REGEX validation
+    // Matches blank input, ips, and domain names
+    private static final String NAME_IP_REGEX =
+            "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
+    private static final Pattern HOSTNAME_PATTERN;
+    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
+    private static final Pattern EXCLLIST_PATTERN;
+    private static final String EXCL_REGEX =
+            "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
+    private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
+    static {
+        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
+    }
+
+    /** Converts exclusion list from String to List. */
+    public static List<String> exclusionStringAsList(String exclusionList) {
+        if (exclusionList == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(exclusionList.toLowerCase(Locale.ROOT).split(","));
+    }
+
+    /** Converts exclusion list from List to string */
+    public static String exclusionListAsString(String[] exclusionList) {
+        if (exclusionList == null) {
+            return "";
+        }
+        return TextUtils.join(",", exclusionList);
+    }
+
+    /**
+     * Validate syntax of hostname, port and exclusion list entries
+     */
+    public static int validate(String hostname, String port, String exclList) {
+        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
+
+        if (!match.matches()) return PROXY_HOSTNAME_INVALID;
+
+        if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
+
+        if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
+
+        if (port.length() > 0) {
+            if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
+            int portVal = -1;
+            try {
+                portVal = Integer.parseInt(port);
+            } catch (NumberFormatException ex) {
+                return PROXY_PORT_INVALID;
+            }
+            if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
+        }
+        return PROXY_VALID;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/RouteUtils.java b/staticlibs/framework/com/android/net/module/util/RouteUtils.java
new file mode 100644
index 0000000..c241680
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/RouteUtils.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+/** @hide */
+// RouteUtils is now empty, because some new methods will be added to it soon and it is less
+// expensive to keep it empty than to remove it now and add it again later.
+public class RouteUtils {
+}
diff --git a/staticlibs/jarjar-rules-shared.txt b/staticlibs/jarjar-rules-shared.txt
new file mode 100644
index 0000000..e26999d
--- /dev/null
+++ b/staticlibs/jarjar-rules-shared.txt
@@ -0,0 +1,3 @@
+rule android.annotation.** com.android.net.module.annotation.@1
+rule com.android.internal.annotations.** com.android.net.module.annotation.@1
+rule com.android.modules.utils.build.** com.android.net.module.util.build.$1
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
new file mode 100644
index 0000000..d413b2a
--- /dev/null
+++ b/staticlibs/lint-baseline.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
+        errorLine1="        final Collection&lt;InetAddress&gt; leftAddresses = left.getAddresses();"
+        errorLine2="                                                           ~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
+            line="158"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
+        errorLine1="        final Collection&lt;InetAddress&gt; rightAddresses = right.getAddresses();"
+        errorLine2="                                                             ~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
+            line="159"
+            column="62"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.NetworkStats#addEntry`"
+        errorLine1="            stats = stats.addEntry(entry);"
+        errorLine2="                          ~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="113"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats.Entry`"
+        errorLine1="        return new android.net.NetworkStats.Entry("
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="120"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats`"
+        errorLine1="        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="108"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 29): `new android.system.NetlinkSocketAddress`"
+        errorLine1="        return new NetlinkSocketAddress(portId, groupsMask);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/device/com/android/net/module/util/SocketUtils.java"
+            line="44"
+            column="16"/>
+    </issue>
+
+</issues>
\ No newline at end of file
diff --git a/staticlibs/native/README.md b/staticlibs/native/README.md
new file mode 100644
index 0000000..1f505c4
--- /dev/null
+++ b/staticlibs/native/README.md
@@ -0,0 +1,30 @@
+# JNI
+As a general rule, jarjar every static library dependency used in a mainline module into the
+modules's namespace (especially if it is also used by other modules)
+
+Fully-qualified name of java class needs to be hard-coded into the JNI .so, because JNI_OnLoad
+does not take any parameters. This means that there needs to be a different .so target for each
+post-jarjared package, so for each module.
+
+This is the guideline to provide JNI library shared with modules:
+
+* provide a common java library in frameworks/libs/net with the Java class (e.g. BpfMap.java).
+
+* provide a common native library in frameworks/libs/net with the JNI and provide the native
+  register function with class_name parameter. See register_com_android_net_module_util_BpfMap
+  function in frameworks/libs/net/common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+  as an example.
+
+When you want to use JNI library from frameworks/lib/net:
+
+* Each module includes the java library (e.g. net-utils-device-common-bpf) and applies its jarjar
+  rules after build.
+
+* Each module creates a native library in their directory, which statically links against the
+  common native library (e.g. libnet_utils_device_common_bpf), and calls the native registered
+  function by hardcoding the post-jarjar class_name. Linkage *MUST* be static because common
+  functions in the file (e.g., `register_com_android_net_module_util_BpfMap`) will appear in the
+  library (`.so`) file, and different versions of the library loaded in the same process by
+  different modules will in general have different versions. It's important that each of these
+  libraries loads the common function from its own library. Static linkage should guarantee this
+  because static linkage resolves symbols at build time, not runtime.
\ No newline at end of file
diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp
new file mode 100644
index 0000000..41184ea
--- /dev/null
+++ b/staticlibs/native/bpf_headers/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "bpf_headers",
+    vendor_available: true,
+    recovery_available: true,
+    host_supported: true,
+    native_bridge_supported: true,
+    header_libs: ["bpf_syscall_wrappers"],
+    export_header_lib_headers: ["bpf_syscall_wrappers"],
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.art.debug",
+        "com.android.os.statsd",
+        "com.android.resolv",
+        "com.android.tethering",
+    ],
+}
+
+cc_test {
+    // TODO: Rename to bpf_map_test and modify .gcls as well.
+    name: "libbpf_android_test",
+    srcs: [
+        "BpfMapTest.cpp",
+        "BpfRingbufTest.cpp",
+    ],
+    defaults: ["bpf_defaults"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=unused-variable",
+    ],
+    header_libs: ["bpf_headers"],
+    static_libs: ["libgmock"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libutils",
+    ],
+    require_root: true,
+    test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/bpf_headers/BpfMapTest.cpp b/staticlibs/native/bpf_headers/BpfMapTest.cpp
new file mode 100644
index 0000000..10afe86
--- /dev/null
+++ b/staticlibs/native/bpf_headers/BpfMapTest.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint32_t TEST_KEY1 = 1;
+constexpr uint32_t TEST_VALUE1 = 10;
+constexpr const char PINNED_MAP_PATH[] = "/sys/fs/bpf/testMap";
+
+class BpfMapTest : public testing::Test {
+  protected:
+    BpfMapTest() {}
+
+    void SetUp() {
+        EXPECT_EQ(0, setrlimitForTest());
+        if (!access(PINNED_MAP_PATH, R_OK)) {
+            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+        }
+    }
+
+    void TearDown() {
+        if (!access(PINNED_MAP_PATH, R_OK)) {
+            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+        }
+    }
+
+    void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_FALSE(map.isValid());
+        EXPECT_EQ(-1, map.getMap().get());
+    }
+
+    void checkMapValid(BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_LE(0, map.getMap().get());
+        EXPECT_TRUE(map.isValid());
+    }
+
+    void writeToMapAndCheck(BpfMap<uint32_t, uint32_t>& map, uint32_t key, uint32_t value) {
+        ASSERT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+        uint32_t value_read;
+        ASSERT_EQ(0, findMapEntry(map.getMap(), &key, &value_read));
+        checkValueAndStatus(value, value_read);
+    }
+
+    void checkValueAndStatus(uint32_t refValue, Result<uint32_t> value) {
+        ASSERT_RESULT_OK(value);
+        ASSERT_EQ(refValue, value.value());
+    }
+
+    void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) {
+        for (uint32_t key = 0; key < total; key++) {
+            uint32_t value = key * 10;
+            EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+        }
+    }
+
+    void expectMapEmpty(BpfMap<uint32_t, uint32_t>& map) {
+        Result<bool> isEmpty = map.isEmpty();
+        ASSERT_RESULT_OK(isEmpty);
+        ASSERT_TRUE(isEmpty.value());
+    }
+};
+
+TEST_F(BpfMapTest, constructor) {
+    BpfMap<uint32_t, uint32_t> testMap1;
+    checkMapInvalid(testMap1);
+
+    BpfMap<uint32_t, uint32_t> testMap2(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    checkMapValid(testMap2);
+}
+
+TEST_F(BpfMapTest, basicHelpers) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+    Result<uint32_t> value_read = testMap.readValue(key);
+    checkValueAndStatus(value_write, value_read);
+    Result<uint32_t> key_read = testMap.getFirstKey();
+    checkValueAndStatus(key, key_read);
+    ASSERT_RESULT_OK(testMap.deleteValue(key));
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_read));
+    ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(BpfMapTest, reset) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+
+    testMap.reset(-1);
+    checkMapInvalid(testMap);
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+    ASSERT_EQ(EBADF, errno);
+}
+
+TEST_F(BpfMapTest, moveConstructor) {
+    BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    BpfMap<uint32_t, uint32_t> testMap2;
+    testMap2 = std::move(testMap1);
+    uint32_t key = TEST_KEY1;
+    checkMapInvalid(testMap1);
+    uint32_t value = TEST_VALUE1;
+    writeToMapAndCheck(testMap2, key, value);
+}
+
+TEST_F(BpfMapTest, SetUpMap) {
+    EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK));
+    BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    ASSERT_EQ(0, bpfFdPin(testMap1.getMap(), PINNED_MAP_PATH));
+    EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
+    checkMapValid(testMap1);
+    BpfMap<uint32_t, uint32_t> testMap2;
+    EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH));
+    checkMapValid(testMap2);
+    uint32_t key = TEST_KEY1;
+    uint32_t value = TEST_VALUE1;
+    writeToMapAndCheck(testMap1, key, value);
+    Result<uint32_t> value_read = testMap2.readValue(key);
+    checkValueAndStatus(value, value_read);
+}
+
+TEST_F(BpfMapTest, iterate) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    int totalCount = 0;
+    int totalSum = 0;
+    const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+                                                              BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+        totalCount++;
+        totalSum += key;
+        return map.deleteValue(key);
+    };
+    EXPECT_RESULT_OK(testMap.iterate(iterateWithDeletion));
+    EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+    EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) / 2, (uint32_t)totalSum);
+    expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, iterateWithValue) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    int totalCount = 0;
+    int totalSum = 0;
+    const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+                                                              const uint32_t& value,
+                                                              BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+        EXPECT_EQ(value, key * 10);
+        totalCount++;
+        totalSum += value;
+        return map.deleteValue(key);
+    };
+    EXPECT_RESULT_OK(testMap.iterateWithValue(iterateWithDeletion));
+    EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+    EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) * 5, (uint32_t)totalSum);
+    expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, mapIsEmpty) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    expectMapEmpty(testMap);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+    Result<bool> isEmpty = testMap.isEmpty();
+    ASSERT_RESULT_OK(isEmpty);
+    ASSERT_FALSE(isEmpty.value());
+    ASSERT_RESULT_OK(testMap.deleteValue(key));
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+    ASSERT_EQ(ENOENT, errno);
+    expectMapEmpty(testMap);
+    int entriesSeen = 0;
+    EXPECT_RESULT_OK(testMap.iterate(
+            [&entriesSeen](const unsigned int&,
+                           const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+                entriesSeen++;
+                return {};
+            }));
+    EXPECT_EQ(0, entriesSeen);
+    EXPECT_RESULT_OK(testMap.iterateWithValue(
+            [&entriesSeen](const unsigned int&, const unsigned int&,
+                           const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+                entriesSeen++;
+                return {};
+            }));
+    EXPECT_EQ(0, entriesSeen);
+}
+
+TEST_F(BpfMapTest, mapClear) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    Result<bool> isEmpty = testMap.isEmpty();
+    ASSERT_RESULT_OK(isEmpty);
+    ASSERT_FALSE(*isEmpty);
+    ASSERT_RESULT_OK(testMap.clear());
+    expectMapEmpty(testMap);
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
new file mode 100644
index 0000000..6c0841c
--- /dev/null
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/result-gmock.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "BpfSyscallWrappers.h"
+#include "bpf/BpfRingbuf.h"
+#include "bpf/BpfUtils.h"
+#include "bpf/KernelUtils.h"
+
+#define TEST_RINGBUF_MAGIC_NUM 12345
+
+namespace android {
+namespace bpf {
+using ::android::base::testing::HasError;
+using ::android::base::testing::HasValue;
+using ::android::base::testing::WithCode;
+using ::testing::AllOf;
+using ::testing::Gt;
+using ::testing::HasSubstr;
+using ::testing::Lt;
+
+class BpfRingbufTest : public ::testing::Test {
+ protected:
+  BpfRingbufTest()
+      : mProgPath("/sys/fs/bpf/prog_bpfRingbufProg_skfilter_ringbuf_test"),
+        mRingbufPath("/sys/fs/bpf/map_bpfRingbufProg_test_ringbuf") {}
+
+  void SetUp() {
+    if (!android::bpf::isAtLeastKernelVersion(5, 8, 0)) {
+      GTEST_SKIP() << "BPF ring buffers not supported below 5.8";
+    }
+
+    errno = 0;
+    mProgram.reset(retrieveProgram(mProgPath.c_str()));
+    EXPECT_EQ(errno, 0);
+    ASSERT_GE(mProgram.get(), 0)
+        << mProgPath << " was either not found or inaccessible.";
+  }
+
+  void RunProgram() {
+    char fake_skb[128] = {};
+    EXPECT_EQ(runProgram(mProgram, fake_skb, sizeof(fake_skb)), 0);
+  }
+
+  void RunTestN(int n) {
+    int run_count = 0;
+    uint64_t output = 0;
+    auto callback = [&](const uint64_t& value) {
+      output = value;
+      run_count++;
+    };
+
+    auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
+    ASSERT_RESULT_OK(result);
+
+    for (int i = 0; i < n; i++) {
+      RunProgram();
+    }
+
+    EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n));
+    EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM);
+    EXPECT_EQ(run_count, n);
+  }
+
+  std::string mProgPath;
+  std::string mRingbufPath;
+  android::base::unique_fd mProgram;
+};
+
+TEST_F(BpfRingbufTest, ConsumeSingle) { RunTestN(1); }
+TEST_F(BpfRingbufTest, ConsumeMultiple) { RunTestN(3); }
+
+TEST_F(BpfRingbufTest, FillAndWrap) {
+  int run_count = 0;
+  auto callback = [&](const uint64_t&) { run_count++; };
+
+  auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
+  ASSERT_RESULT_OK(result);
+
+  // 4kb buffer with 16 byte payloads (8 byte data, 8 byte header) should fill
+  // after 255 iterations. Exceed that so that some events are dropped.
+  constexpr int iterations = 300;
+  for (int i = 0; i < iterations; i++) {
+    RunProgram();
+  }
+
+  // Some events were dropped, but consume all that succeeded.
+  EXPECT_THAT(result.value()->ConsumeAll(callback),
+              HasValue(AllOf(Gt(250), Lt(260))));
+  EXPECT_THAT(run_count, AllOf(Gt(250), Lt(260)));
+
+  // After consuming everything, we should be able to use the ring buffer again.
+  run_count = 0;
+  RunProgram();
+  EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(1));
+  EXPECT_EQ(run_count, 1);
+}
+
+TEST_F(BpfRingbufTest, WrongTypeSize) {
+  // The program under test writes 8-byte uint64_t values so a ringbuffer for
+  // 1-byte uint8_t values will fail to read from it. Note that the map_def does
+  // not specify the value size, so we fail on read, not creation.
+  auto result = BpfRingbuf<uint8_t>::Create(mRingbufPath.c_str());
+  ASSERT_RESULT_OK(result);
+
+  RunProgram();
+
+  EXPECT_THAT(result.value()->ConsumeAll([](const uint8_t&) {}),
+              HasError(WithCode(EMSGSIZE)));
+}
+
+TEST_F(BpfRingbufTest, InvalidPath) {
+  EXPECT_THAT(BpfRingbuf<int>::Create("/sys/fs/bpf/bad_path"),
+              HasError(WithCode(ENOENT)));
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/TEST_MAPPING b/staticlibs/native/bpf_headers/TEST_MAPPING
new file mode 100644
index 0000000..9ec8a40
--- /dev/null
+++ b/staticlibs/native/bpf_headers/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libbpf_android_test"
+    }
+  ]
+}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
new file mode 100644
index 0000000..dd0804c
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Accept the full packet
+#define BPF_ACCEPT BPF_STMT(BPF_RET | BPF_K, 0xFFFFFFFF)
+
+// Reject the packet
+#define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0)
+
+// *TWO* instructions: compare and if not equal jump over the accept statement
+#define BPF2_ACCEPT_IF_EQUAL(v) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \
+	BPF_ACCEPT
+
+// *TWO* instructions: compare and if equal jump over the reject statement
+#define BPF2_REJECT_IF_NOT_EQUAL(v) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \
+	BPF_REJECT
+
+// *TWO* instructions: compare and if none of the bits are set jump over the reject statement
+#define BPF2_REJECT_IF_ANY_BITS_SET(v) \
+	BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \
+	BPF_REJECT
+
+// loads skb->protocol
+#define BPF_LOAD_SKB_PROTOCOL \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL)
+
+// 8-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_U8(ofs) \
+	BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// Big/Network Endian 16-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_BE16(ofs) \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// Big/Network Endian 32-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_BE32(ofs) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// 8-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_U8(ofs) \
+	BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 16-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_BE16(ofs) \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 32-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_BE32(ofs) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+#define field_sizeof(struct_type,field) sizeof(((struct_type *)0)->field)
+
+// 8-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_U8(field) \
+	BPF_LOAD_NET_RELATIVE_U8(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 1, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// Big/Network Endian 16-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_BE16(field) \
+	BPF_LOAD_NET_RELATIVE_BE16(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 2, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// Big/Network Endian 32-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_BE32(field) \
+	BPF_LOAD_NET_RELATIVE_BE32(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 4, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// 8-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_U8(field) \
+	BPF_LOAD_NET_RELATIVE_U8(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 1, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
+
+// Big/Network Endian 16-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_BE16(field) \
+	BPF_LOAD_NET_RELATIVE_BE16(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 2, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
+
+// Big/Network Endian 32-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_BE32(field) \
+	BPF_LOAD_NET_RELATIVE_BE32(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 4, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
new file mode 100644
index 0000000..847083e
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include <linux/bpf.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <utils/Log.h>
+
+#include "BpfSyscallWrappers.h"
+#include "bpf/BpfUtils.h"
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+using std::function;
+
+// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
+// data structure that stores data in <Key, Value> pairs. It can be read/write
+// from userspace by passing syscalls with the map file descriptor. This class
+// is used to generalize the procedure of interacting with eBPF maps and hide
+// the implementation detail from other process. Besides the basic syscalls
+// wrapper, it also provides some useful helper functions as well as an iterator
+// nested class to iterate the map more easily.
+//
+// NOTE: A kernel eBPF map may be accessed by both kernel and userspace
+// processes at the same time. Or if the map is pinned as a virtual file, it can
+// be obtained by multiple eBPF map class object and accessed concurrently.
+// Though the map class object and the underlying kernel map are thread safe, it
+// is not safe to iterate over a map while another thread or process is deleting
+// from it. In this case the iteration can return duplicate entries.
+template <class Key, class Value>
+class BpfMap {
+  public:
+    BpfMap<Key, Value>() {};
+
+    // explicitly force no copy constructor, since it would need to dup the fd
+    // (later on, for testing, we still make available a copy assignment operator)
+    BpfMap<Key, Value>(const BpfMap<Key, Value>&) = delete;
+
+  private:
+    void abortOnKeyOrValueSizeMismatch() {
+        if (!mMapFd.ok()) abort();
+        if (isAtLeastKernelVersion(4, 14, 0)) {
+            if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
+            if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
+        }
+    }
+
+  protected:
+    // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
+    BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
+        mMapFd.reset(mapRetrieve(pathname, flags));
+        abortOnKeyOrValueSizeMismatch();
+    }
+
+  public:
+    explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
+
+#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+    // All bpf maps should be created by the bpfloader, so this constructor
+    // is reserved for tests
+    BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) {
+        mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags));
+        if (!mMapFd.ok()) abort();
+    }
+#endif
+
+    Result<Key> getFirstKey() const {
+        Key firstKey;
+        if (getFirstMapKey(mMapFd, &firstKey)) {
+            return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
+        }
+        return firstKey;
+    }
+
+    Result<Key> getNextKey(const Key& key) const {
+        Key nextKey;
+        if (getNextMapKey(mMapFd, &key, &nextKey)) {
+            return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
+        }
+        return nextKey;
+    }
+
+    Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+        if (writeToMapEntry(mMapFd, &key, &value, flags)) {
+            return ErrnoErrorf("Write to map {} failed", mMapFd.get());
+        }
+        return {};
+    }
+
+    Result<Value> readValue(const Key key) const {
+        Value value;
+        if (findMapEntry(mMapFd, &key, &value)) {
+            return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
+        }
+        return value;
+    }
+
+    Result<void> deleteValue(const Key& key) {
+        if (deleteMapEntry(mMapFd, &key)) {
+            return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
+        }
+        return {};
+    }
+
+  protected:
+    [[clang::reinitializes]] Result<void> init(const char* path, int fd) {
+        mMapFd.reset(fd);
+        if (!mMapFd.ok()) {
+            return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
+        }
+        // Normally we should return an error here instead of calling abort,
+        // but this cannot happen at runtime without a massive code bug (K/V type mismatch)
+        // and as such it's better to just blow the system up and let the developer fix it.
+        // Crashes are much more likely to be noticed than logs and missing functionality.
+        abortOnKeyOrValueSizeMismatch();
+        return {};
+    }
+
+  public:
+    // Function that tries to get map from a pinned path.
+    [[clang::reinitializes]] Result<void> init(const char* path) {
+        return init(path, mapRetrieveRW(path));
+    }
+
+
+#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+    // due to Android SELinux limitations which prevent map creation by anyone besides the bpfloader
+    // this should only ever be used by test code, it is equivalent to:
+    //   .reset(createMap(type, keysize, valuesize, max_entries, map_flags)
+    // TODO: derive map_flags from BpfMap vs BpfMapRO
+    [[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type,
+                                                         uint32_t max_entries,
+                                                         uint32_t map_flags = 0) {
+        mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags));
+        if (!mMapFd.ok()) return ErrnoErrorf("Unable to create map.");
+        return {};
+    }
+#endif
+
+    // Iterate through the map and handle each key retrieved based on the filter
+    // without modification of map content.
+    Result<void> iterate(
+            const function<Result<void>(const Key& key,
+                                        const BpfMap<Key, Value>& map)>& filter) const;
+
+    // Iterate through the map and get each <key, value> pair, handle each <key,
+    // value> pair based on the filter without modification of map content.
+    Result<void> iterateWithValue(
+            const function<Result<void>(const Key& key, const Value& value,
+                                        const BpfMap<Key, Value>& map)>& filter) const;
+
+    // Iterate through the map and handle each key retrieved based on the filter
+    Result<void> iterate(
+            const function<Result<void>(const Key& key,
+                                        BpfMap<Key, Value>& map)>& filter);
+
+    // Iterate through the map and get each <key, value> pair, handle each <key,
+    // value> pair based on the filter.
+    Result<void> iterateWithValue(
+            const function<Result<void>(const Key& key, const Value& value,
+                                        BpfMap<Key, Value>& map)>& filter);
+
+#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+    const unique_fd& getMap() const { return mMapFd; };
+
+    // Copy assignment operator - due to need for fd duping, should not be used in non-test code.
+    BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
+        if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
+        return *this;
+    }
+#else
+    BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>&) = delete;
+#endif
+
+    // Move assignment operator
+    BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept {
+        if (this != &other) {
+            mMapFd = std::move(other.mMapFd);
+            other.reset();
+        }
+        return *this;
+    }
+
+    void reset(unique_fd fd) = delete;
+
+#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+    // Note that unique_fd.reset() carefully saves and restores the errno,
+    // and BpfMap.reset() won't touch the errno if passed in fd is negative either,
+    // hence you can do something like BpfMap.reset(systemcall()) and then
+    // check BpfMap.isValid() and look at errno and see why systemcall() failed.
+    [[clang::reinitializes]] void reset(int fd) {
+        mMapFd.reset(fd);
+        if (mMapFd.ok()) abortOnKeyOrValueSizeMismatch();
+    }
+#endif
+
+    [[clang::reinitializes]] void reset() {
+        mMapFd.reset();
+    }
+
+    bool isValid() const { return mMapFd.ok(); }
+
+    Result<void> clear() {
+        while (true) {
+            auto key = getFirstKey();
+            if (!key.ok()) {
+                if (key.error().code() == ENOENT) return {};  // empty: success
+                return key.error();                           // Anything else is an error
+            }
+            auto res = deleteValue(key.value());
+            if (!res.ok()) {
+                // Someone else could have deleted the key, so ignore ENOENT
+                if (res.error().code() == ENOENT) continue;
+                ALOGE("Failed to delete data %s", strerror(res.error().code()));
+                return res.error();
+            }
+        }
+    }
+
+    Result<bool> isEmpty() const {
+        auto key = getFirstKey();
+        if (key.ok()) return false;
+        if (key.error().code() == ENOENT) return true;
+        return key.error();
+    }
+
+  private:
+    unique_fd mMapFd;
+};
+
+template <class Key, class Value>
+Result<void> BpfMap<Key, Value>::iterate(
+        const function<Result<void>(const Key& key,
+                                    const BpfMap<Key, Value>& map)>& filter) const {
+    Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<void> status = filter(curKey.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const function<Result<void>(const Key& key, const Value& value,
+                                    const BpfMap<Key, Value>& map)>& filter) const {
+    Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<Value> curValue = readValue(curKey.value());
+        if (!curValue.ok()) return curValue.error();
+        Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+Result<void> BpfMap<Key, Value>::iterate(
+        const function<Result<void>(const Key& key,
+                                    BpfMap<Key, Value>& map)>& filter) {
+    Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<void> status = filter(curKey.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const function<Result<void>(const Key& key, const Value& value,
+                                    BpfMap<Key, Value>& map)>& filter) {
+    Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<Value> curValue = readValue(curKey.value());
+        if (!curValue.ok()) return curValue.error();
+        Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+class BpfMapRO : public BpfMap<Key, Value> {
+  public:
+    BpfMapRO<Key, Value>() {};
+
+    explicit BpfMapRO<Key, Value>(const char* pathname)
+        : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
+
+    // Function that tries to get map from a pinned path.
+    [[clang::reinitializes]] Result<void> init(const char* path) {
+        return BpfMap<Key,Value>::init(path, mapRetrieveRO(path));
+    }
+};
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
new file mode 100644
index 0000000..dd1504c
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <linux/bpf.h>
+#include <sys/mman.h>
+#include <utils/Log.h>
+
+#include "bpf/BpfUtils.h"
+
+#include <atomic>
+
+namespace android {
+namespace bpf {
+
+// BpfRingbufBase contains the non-templated functionality of BPF ring buffers.
+class BpfRingbufBase {
+ public:
+  ~BpfRingbufBase() {
+    if (mConsumerPos) munmap(mConsumerPos, mConsumerSize);
+    if (mProducerPos) munmap(mProducerPos, mProducerSize);
+    mConsumerPos = nullptr;
+    mProducerPos = nullptr;
+  }
+
+ protected:
+  // Non-initializing constructor, used by Create.
+  BpfRingbufBase(size_t value_size) : mValueSize(value_size) {}
+
+  // Full construction that aborts on error (use Create/Init to handle errors).
+  BpfRingbufBase(const char* path, size_t value_size) : mValueSize(value_size) {
+    if (auto status = Init(path); !status.ok()) {
+      ALOGE("BpfRingbuf init failed: %s", status.error().message().c_str());
+      abort();
+    }
+  }
+
+  // Delete copy constructor (class owns raw pointers).
+  BpfRingbufBase(const BpfRingbufBase&) = delete;
+
+  // Initialize the base ringbuffer components. Must be called exactly once.
+  base::Result<void> Init(const char* path);
+
+  // Consumes all messages from the ring buffer, passing them to the callback.
+  base::Result<int> ConsumeAll(
+      const std::function<void(const void*)>& callback);
+
+  // Replicates c-style void* "byte-wise" pointer addition.
+  template <typename Ptr>
+  static Ptr pointerAddBytes(void* base, ssize_t offset_bytes) {
+    return reinterpret_cast<Ptr>(reinterpret_cast<char*>(base) + offset_bytes);
+  }
+
+  // Rounds len by clearing bitmask, adding header, and aligning to 8 bytes.
+  static uint32_t roundLength(uint32_t len) {
+    len &= ~(BPF_RINGBUF_BUSY_BIT | BPF_RINGBUF_DISCARD_BIT);
+    len += BPF_RINGBUF_HDR_SZ;
+    return (len + 7) & ~7;
+  }
+
+  const size_t mValueSize;
+
+  size_t mConsumerSize;
+  size_t mProducerSize;
+  unsigned long mPosMask;
+  android::base::unique_fd mRingFd;
+
+  void* mDataPos = nullptr;
+  // The kernel uses an "unsigned long" type for both consumer and producer position.
+  // Unsigned long is a 4 byte value on a 32-bit kernel, and an 8 byte value on a 64-bit kernel.
+  // To support 32-bit kernels, producer pos is capped at 4 bytes (despite it being 8 bytes on
+  // 64-bit kernels) and all comparisons of consumer and producer pos only compare the low-order 4
+  // bytes (an inequality comparison is performed to support overflow).
+  // This solution is bitness agnostic. The consumer only increments the 8 byte consumer pos, which,
+  // in a little-endian architecture, is safe since the entire page is mapped into memory and a
+  // 32-bit kernel will just ignore the high-order bits.
+  std::atomic_uint64_t* mConsumerPos = nullptr;
+  std::atomic_uint32_t* mProducerPos = nullptr;
+
+  // In order to guarantee atomic access in a 32 bit userspace environment, atomic_uint64_t is used
+  // in addition to std::atomic<T>::is_always_lock_free that guarantees that read / write operations
+  // are indeed atomic.
+  // Since std::atomic does not support wrapping preallocated memory, an additional static assert on
+  // the size of the atomic and the underlying type is added to ensure a reinterpret_cast from type
+  // to its atomic version is safe (is_always_lock_free being true should provide additional
+  // confidence).
+  static_assert(std::atomic_uint64_t::is_always_lock_free);
+  static_assert(std::atomic_uint32_t::is_always_lock_free);
+  static_assert(sizeof(std::atomic_uint64_t) == sizeof(uint64_t));
+  static_assert(sizeof(std::atomic_uint32_t) == sizeof(uint32_t));
+};
+
+// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a
+// special type of eBPF map used for sending messages from eBPF to userspace.
+// The implementation relies on fast shared memory and atomics for the producer
+// and consumer management. Ring buffers are a faster alternative to eBPF perf
+// buffers.
+//
+// This class is thread compatible, but not thread safe.
+//
+// Note: A kernel eBPF ring buffer may be accessed by both kernel and userspace
+// processes at the same time. However, the userspace consumers of a given ring
+// buffer all share a single read pointer. There is no guarantee which readers
+// will read which messages.
+template <typename Value>
+class BpfRingbuf : public BpfRingbufBase {
+ public:
+  using MessageCallback = std::function<void(const Value&)>;
+
+  // Creates a ringbuffer wrapper from a pinned path. This initialization will
+  // abort on error. To handle errors, initialize with Create instead.
+  BpfRingbuf(const char* path) : BpfRingbufBase(path, sizeof(Value)) {}
+
+  // Creates a ringbuffer wrapper from a pinned path. There are no guarantees
+  // that the ringbuf outputs messaged of type `Value`, only that they are the
+  // same size. Size is only checked in ConsumeAll.
+  static base::Result<std::unique_ptr<BpfRingbuf<Value>>> Create(
+      const char* path);
+
+  // Consumes all messages from the ring buffer, passing them to the callback.
+  // Returns the number of messages consumed or a non-ok result on error. If the
+  // ring buffer has no pending messages an OK result with count 0 is returned.
+  base::Result<int> ConsumeAll(const MessageCallback& callback);
+
+ private:
+  // Empty ctor for use by Create.
+  BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {}
+};
+
+
+inline base::Result<void> BpfRingbufBase::Init(const char* path) {
+  mRingFd.reset(mapRetrieveRW(path));
+  if (!mRingFd.ok()) {
+    return android::base::ErrnoError()
+           << "failed to retrieve ringbuffer at " << path;
+  }
+
+  int map_type = android::bpf::bpfGetFdMapType(mRingFd);
+  if (map_type != BPF_MAP_TYPE_RINGBUF) {
+    errno = EINVAL;
+    return android::base::ErrnoError()
+           << "bpf map has wrong type: want BPF_MAP_TYPE_RINGBUF ("
+           << BPF_MAP_TYPE_RINGBUF << ") got " << map_type;
+  }
+
+  int max_entries = android::bpf::bpfGetFdMaxEntries(mRingFd);
+  if (max_entries < 0) {
+    return android::base::ErrnoError()
+           << "failed to read max_entries from ringbuf";
+  }
+  if (max_entries == 0) {
+    errno = EINVAL;
+    return android::base::ErrnoError() << "max_entries must be non-zero";
+  }
+
+  mPosMask = max_entries - 1;
+  mConsumerSize = getpagesize();
+  mProducerSize = getpagesize() + 2 * max_entries;
+
+  {
+    void* ptr = mmap(NULL, mConsumerSize, PROT_READ | PROT_WRITE, MAP_SHARED,
+                     mRingFd, 0);
+    if (ptr == MAP_FAILED) {
+      return android::base::ErrnoError()
+             << "failed to mmap ringbuf consumer pages";
+    }
+    mConsumerPos = reinterpret_cast<decltype(mConsumerPos)>(ptr);
+  }
+
+  {
+    void* ptr = mmap(NULL, mProducerSize, PROT_READ, MAP_SHARED, mRingFd,
+                     mConsumerSize);
+    if (ptr == MAP_FAILED) {
+      return android::base::ErrnoError()
+             << "failed to mmap ringbuf producer page";
+    }
+    mProducerPos = reinterpret_cast<decltype(mProducerPos)>(ptr);
+  }
+
+  mDataPos = pointerAddBytes<void*>(mProducerPos, getpagesize());
+  return {};
+}
+
+inline base::Result<int> BpfRingbufBase::ConsumeAll(
+    const std::function<void(const void*)>& callback) {
+  int64_t count = 0;
+  uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
+  // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire
+  uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed);
+  while ((cons_pos & 0xFFFFFFFF) != prod_pos) {
+    // Find the start of the entry for this read (wrapping is done here).
+    void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask);
+
+    // The entry has an 8 byte header containing the sample length.
+    // struct bpf_ringbuf_hdr {
+    //   u32 len;
+    //   u32 pg_off;
+    // };
+    uint32_t length = *reinterpret_cast<volatile uint32_t*>(start_ptr);
+
+    // If the sample isn't committed, we're caught up with the producer.
+    if (length & BPF_RINGBUF_BUSY_BIT) return count;
+
+    cons_pos += roundLength(length);
+
+    if ((length & BPF_RINGBUF_DISCARD_BIT) == 0) {
+      if (length != mValueSize) {
+        mConsumerPos->store(cons_pos, std::memory_order_release);
+        errno = EMSGSIZE;
+        return android::base::ErrnoError()
+               << "BPF ring buffer message has unexpected size (want "
+               << mValueSize << " bytes, got " << length << " bytes)";
+      }
+      callback(pointerAddBytes<const void*>(start_ptr, BPF_RINGBUF_HDR_SZ));
+      count++;
+    }
+
+    mConsumerPos->store(cons_pos, std::memory_order_release);
+  }
+
+  return count;
+}
+
+template <typename Value>
+inline base::Result<std::unique_ptr<BpfRingbuf<Value>>>
+BpfRingbuf<Value>::Create(const char* path) {
+  auto rb = std::unique_ptr<BpfRingbuf>(new BpfRingbuf);
+  if (auto status = rb->Init(path); !status.ok()) return status.error();
+  return rb;
+}
+
+template <typename Value>
+inline base::Result<int> BpfRingbuf<Value>::ConsumeAll(
+    const MessageCallback& callback) {
+  return BpfRingbufBase::ConsumeAll([&](const void* value) {
+    callback(*reinterpret_cast<const Value*>(value));
+  });
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
new file mode 100644
index 0000000..9dd5822
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include <errno.h>
+#include <linux/if_ether.h>
+#include <linux/pfkeyv2.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+
+#include <log/log.h>
+
+#include "KernelUtils.h"
+
+namespace android {
+namespace bpf {
+
+// See kernel's net/core/sock_diag.c __sock_gen_cookie()
+// the implementation of which guarantees 0 will never be returned,
+// primarily because 0 is used to mean not yet initialized,
+// and socket cookies are only assigned on first fetch.
+constexpr const uint64_t NONEXISTENT_COOKIE = 0;
+
+static inline uint64_t getSocketCookie(int sockFd) {
+    uint64_t sock_cookie;
+    socklen_t cookie_len = sizeof(sock_cookie);
+    if (getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len)) {
+        // Failure is almost certainly either EBADF or ENOTSOCK
+        const int err = errno;
+        ALOGE("Failed to get socket cookie: %s\n", strerror(err));
+        errno = err;
+        return NONEXISTENT_COOKIE;
+    }
+    if (cookie_len != sizeof(sock_cookie)) {
+        // This probably cannot actually happen, but...
+        ALOGE("Failed to get socket cookie: len %d != 8\n", cookie_len);
+        errno = 523; // EBADCOOKIE: kernel internal, seems reasonable enough...
+        return NONEXISTENT_COOKIE;
+    }
+    return sock_cookie;
+}
+
+static inline int synchronizeKernelRCU() {
+    // This is a temporary hack for network stats map swap on devices running
+    // 4.9 kernels. The kernel code of socket release on pf_key socket will
+    // explicitly call synchronize_rcu() which is exactly what we need.
+    //
+    // Linux 4.14/4.19/5.4/5.10/5.15/6.1 (and 6.3-rc5) still have this same behaviour.
+    // see net/key/af_key.c: pfkey_release() -> synchronize_rcu()
+    // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/key/af_key.c?h=v6.3-rc5#n185
+    const int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+    if (pfSocket < 0) {
+        const int err = errno;
+        ALOGE("create PF_KEY socket failed: %s", strerror(err));
+        return -err;
+    }
+
+    // When closing socket, synchronize_rcu() gets called in sock_release().
+    if (close(pfSocket)) {
+        const int err = errno;
+        ALOGE("failed to close the PF_KEY socket: %s", strerror(err));
+        return -err;
+    }
+    return 0;
+}
+
+static inline int setrlimitForTest() {
+    // Set the memory rlimit for the test process if the default MEMLOCK rlimit is not enough.
+    struct rlimit limit = {
+            .rlim_cur = 1073741824,  // 1 GiB
+            .rlim_max = 1073741824,  // 1 GiB
+    };
+    const int res = setrlimit(RLIMIT_MEMLOCK, &limit);
+    if (res) ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno));
+    return res;
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
new file mode 100644
index 0000000..59257b8
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/personality.h>
+#include <sys/utsname.h>
+
+namespace android {
+namespace bpf {
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+static inline unsigned uncachedKernelVersion() {
+    struct utsname buf;
+    if (uname(&buf)) return 0;
+
+    unsigned kver_major = 0;
+    unsigned kver_minor = 0;
+    unsigned kver_sub = 0;
+    (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
+    return KVER(kver_major, kver_minor, kver_sub);
+}
+
+static inline unsigned kernelVersion() {
+    static unsigned kver = uncachedKernelVersion();
+    return kver;
+}
+
+static inline __unused bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+    return kernelVersion() >= KVER(major, minor, sub);
+}
+
+// Figure out the bitness of userspace.
+// Trivial and known at compile time.
+static constexpr bool isUserspace32bit() {
+    return sizeof(void*) == 4;
+}
+
+static constexpr bool isUserspace64bit() {
+    return sizeof(void*) == 8;
+}
+
+#if defined(__LP64__)
+static_assert(isUserspace64bit(), "huh? LP64 must have 64-bit userspace");
+#elif defined(__ILP32__)
+static_assert(isUserspace32bit(), "huh? ILP32 must have 32-bit userspace");
+#else
+#error "huh? must be either LP64 (64-bit userspace) or ILP32 (32-bit userspace)"
+#endif
+
+static_assert(isUserspace32bit() || isUserspace64bit(), "must be either 32 or 64 bit");
+
+// Figure out the bitness of the kernel.
+static inline bool isKernel64Bit() {
+    // a 64-bit userspace requires a 64-bit kernel
+    if (isUserspace64bit()) return true;
+
+    static bool init = false;
+    static bool cache = false;
+    if (init) return cache;
+
+    // Retrieve current personality - on Linux this system call *cannot* fail.
+    int p = personality(0xffffffff);
+    // But if it does just assume kernel and userspace (which is 32-bit) match...
+    if (p == -1) return false;
+
+    // This will effectively mask out the bottom 8 bits, and switch to 'native'
+    // personality, and then return the previous personality of this thread
+    // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
+    int q = personality((p & ~PER_MASK) | PER_LINUX);
+    // Per man page this theoretically could error out with EINVAL,
+    // but kernel code analysis suggests setting PER_LINUX cannot fail.
+    // Either way, assume kernel and userspace (which is 32-bit) match...
+    if (q != p) return false;
+
+    struct utsname u;
+    (void)uname(&u);  // only possible failure is EFAULT, but u is on stack.
+
+    // Switch back to previous personality.
+    // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
+    // but then we wouldn't have fetched 'p' from the kernel in the first place.
+    // Either way there's nothing meaningful we can do in case of error.
+    // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
+    // really hurt us either.  We're really just switching back to be 'clean'.
+    (void)personality(p);
+
+    // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
+    //   x86_64 i686 aarch64 armv7l
+    // additionally observed on arm device:
+    //   armv8l
+    // presumably also might just be possible:
+    //   i386 i486 i586
+    // and there might be other weird arm32 cases.
+    // We note that the 64 is present in both 64-bit archs,
+    // and in general is likely to be present in only 64-bit archs.
+    cache = !!strstr(u.machine, "64");
+    init = true;
+    return cache;
+}
+
+static inline __unused bool isKernel32Bit() {
+    return !isKernel64Bit();
+}
+
+static constexpr bool isArm() {
+#if defined(__arm__)
+    static_assert(isUserspace32bit(), "huh? arm must be 32 bit");
+    return true;
+#elif defined(__aarch64__)
+    static_assert(isUserspace64bit(), "aarch64 must be LP64 - no support for ILP32");
+    return true;
+#else
+    return false;
+#endif
+}
+
+static constexpr bool isX86() {
+#if defined(__i386__)
+    static_assert(isUserspace32bit(), "huh? i386 must be 32 bit");
+    return true;
+#elif defined(__x86_64__)
+    static_assert(isUserspace64bit(), "x86_64 must be LP64 - no support for ILP32 (x32)");
+    return true;
+#else
+    return false;
+#endif
+}
+
+static constexpr bool isRiscV() {
+#if defined(__riscv)
+    static_assert(isUserspace64bit(), "riscv must be 64 bit");
+    return true;
+#else
+    return false;
+#endif
+}
+
+static_assert(isArm() || isX86() || isRiscV(), "Unknown architecture");
+
+static __unused const char * describeArch() {
+    // ordered so as to make it easier to compile time optimize,
+    // only thing not known at compile time is isKernel64Bit()
+    if (isUserspace64bit()) {
+        if (isArm()) return "64-on-aarch64";
+        if (isX86()) return "64-on-x86-64";
+        if (isRiscV()) return "64-on-riscv64";
+    } else if (isKernel64Bit()) {
+        if (isArm()) return "32-on-aarch64";
+        if (isX86()) return "32-on-x86-64";
+    } else {
+        if (isArm()) return "32-on-arm32";
+        if (isX86()) return "32-on-x86-32";
+    }
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
new file mode 100644
index 0000000..bc4168e
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <log/log.h>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace bpf {
+
+// Wait for bpfloader to load BPF programs.
+static inline void waitForProgsLoaded() {
+    // infinite loop until success with 5/10/20/40/60/60/60... delay
+    for (int delay = 5;; delay *= 2) {
+        if (delay > 60) delay = 60;
+        if (android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(delay)))
+            return;
+        ALOGW("Waited %ds for bpf.progs_loaded, still waiting...", delay);
+    }
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
new file mode 100644
index 0000000..67ac0e4
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -0,0 +1,425 @@
+/* Common BPF helpers to be used by all BPF programs loaded by Android */
+
+#include <linux/bpf.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "bpf_map_def.h"
+
+/******************************************************************************
+ * WARNING: CHANGES TO THIS FILE OUTSIDE OF AOSP/MASTER ARE LIKELY TO BREAK   *
+ * DEVICE COMPATIBILITY WITH MAINLINE MODULES SHIPPING EBPF CODE.             *
+ *                                                                            *
+ * THIS WILL LIKELY RESULT IN BRICKED DEVICES AT SOME ARBITRARY FUTURE TIME   *
+ *                                                                            *
+ * THAT GOES ESPECIALLY FOR THE 'SECTION' 'LICENSE' AND 'CRITICAL' MACROS     *
+ *                                                                            *
+ * We strongly suggest that if you need changes to bpfloader functionality    *
+ * you get your changes reviewed and accepted into aosp/master.               *
+ *                                                                            *
+ ******************************************************************************/
+
+// The actual versions of the bpfloader that shipped in various Android releases
+
+// Android P/Q/R: BpfLoader was initially part of netd,
+// this was later split out into a standalone binary, but was unversioned.
+
+// Android S / 12 (api level 31) - added 'tethering' mainline eBPF support
+#define BPFLOADER_S_VERSION 2u
+
+// Android T / 13 (api level 33) - support for shared/selinux_context/pindir
+#define BPFLOADER_T_VERSION 19u
+
+// BpfLoader v0.25+ support obj@ver.o files
+#define BPFLOADER_OBJ_AT_VER_VERSION 25u
+
+// Bpfloader v0.33+ supports {map,prog}.ignore_on_{eng,user,userdebug}
+#define BPFLOADER_IGNORED_ON_VERSION 33u
+
+// Android U / 14 (api level 34) - various new program types added
+#define BPFLOADER_U_VERSION 37u
+
+/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
+ * before #include "bpf_helpers.h" to change which bpfloaders will
+ * process the resulting .o file.
+ *
+ * While this will work outside of mainline too, there just is no point to
+ * using it when the .o and the bpfloader ship in sync with each other.
+ * In which case it's just best to use the default.
+ */
+#ifndef BPFLOADER_MIN_VER
+#define BPFLOADER_MIN_VER COMPILE_FOR_BPFLOADER_VERSION
+#endif
+
+#ifndef BPFLOADER_MAX_VER
+#define BPFLOADER_MAX_VER DEFAULT_BPFLOADER_MAX_VER
+#endif
+
+/* place things in different elf sections */
+#define SECTION(NAME) __attribute__((section(NAME), used))
+
+/* Must be present in every program, example usage:
+ *   LICENSE("GPL"); or LICENSE("Apache 2.0");
+ *
+ * We also take this opportunity to embed a bunch of other useful values in
+ * the resulting .o (This is to enable some limited forward compatibility
+ * with mainline module shipped ebpf programs)
+ *
+ * The bpfloader_{min/max}_ver defines the [min, max) range of bpfloader
+ * versions that should load this .o file (bpfloaders outside of this range
+ * will simply ignore/skip this *entire* .o)
+ * The [inclusive,exclusive) matches what we do for kernel ver dependencies.
+ *
+ * The size_of_bpf_{map,prog}_def allow the bpfloader to load programs where
+ * these structures have been extended with additional fields (they will of
+ * course simply be ignored then).
+ *
+ * If missing, bpfloader_{min/max}_ver default to 0/0x10000 ie. [v0.0, v1.0),
+ * while size_of_bpf_{map/prog}_def default to 32/20 which are the v0.0 sizes.
+ */
+#define LICENSE(NAME)                                                                           \
+    unsigned int _bpfloader_min_ver SECTION("bpfloader_min_ver") = BPFLOADER_MIN_VER;           \
+    unsigned int _bpfloader_max_ver SECTION("bpfloader_max_ver") = BPFLOADER_MAX_VER;           \
+    size_t _size_of_bpf_map_def SECTION("size_of_bpf_map_def") = sizeof(struct bpf_map_def);    \
+    size_t _size_of_bpf_prog_def SECTION("size_of_bpf_prog_def") = sizeof(struct bpf_prog_def); \
+    char _license[] SECTION("license") = (NAME)
+
+/* This macro disables loading BTF map debug information on Android <=U *and* all user builds.
+ *
+ * Note: Bpfloader v0.39+ honours 'btf_user_min_bpfloader_ver' on user builds,
+ * and 'btf_min_bpfloader_ver' on non-user builds.
+ * Older BTF capable versions unconditionally honour 'btf_min_bpfloader_ver'
+ */
+#define DISABLE_BTF_ON_USER_BUILDS() \
+    unsigned _btf_min_bpfloader_ver SECTION("btf_min_bpfloader_ver") = 39u; \
+    unsigned _btf_user_min_bpfloader_ver SECTION("btf_user_min_bpfloader_ver") = 0xFFFFFFFFu
+
+/* flag the resulting bpf .o file as critical to system functionality,
+ * loading all kernel version appropriate programs in it must succeed
+ * for bpfloader success
+ */
+#define CRITICAL(REASON) char _critical[] SECTION("critical") = (REASON)
+
+/*
+ * Helper functions called from eBPF programs written in C. These are
+ * implemented in the kernel sources.
+ */
+
+#define KVER_NONE 0
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+#define KVER_INF 0xFFFFFFFFu
+
+/*
+ * BPFFS (ie. /sys/fs/bpf) labelling is as follows:
+ *   subdirectory   selinux context      mainline  usecase / usable by
+ *   /              fs_bpf               no [*]    core operating system (ie. platform)
+ *   /loader        fs_bpf_loader        no, U+    (as yet unused)
+ *   /net_private   fs_bpf_net_private   yes, T+   network_stack
+ *   /net_shared    fs_bpf_net_shared    yes, T+   network_stack & system_server
+ *   /netd_readonly fs_bpf_netd_readonly yes, T+   network_stack & system_server & r/o to netd
+ *   /netd_shared   fs_bpf_netd_shared   yes, T+   network_stack & system_server & netd [**]
+ *   /tethering     fs_bpf_tethering     yes, S+   network_stack
+ *   /vendor        fs_bpf_vendor        no, T+    vendor
+ *
+ * [*] initial support for bpf was added back in P,
+ *     but things worked differently back then with no bpfloader,
+ *     and instead netd doing stuff by hand,
+ *     bpfloader with pinning into /sys/fs/bpf was (I believe) added in Q
+ *     (and was definitely there in R).
+ *
+ * [**] additionally bpf programs are accessible to netutils_wrapper
+ *      for use by iptables xt_bpf extensions.
+ *
+ * See cs/p:aosp-master%20-file:prebuilts/%20file:genfs_contexts%20"genfscon%20bpf"
+ */
+
+/* generic functions */
+
+/*
+ * Type-unsafe bpf map functions - avoid if possible.
+ *
+ * Using these it is possible to pass in keys/values of the wrong type/size,
+ * or, for 'bpf_map_lookup_elem_unsafe' receive into a pointer to the wrong type.
+ * You will not get a compile time failure, and for certain types of errors you
+ * might not even get a failure from the kernel's ebpf verifier during program load,
+ * instead stuff might just not work right at runtime.
+ *
+ * Instead please use:
+ *   DEFINE_BPF_MAP(foo_map, TYPE, KeyType, ValueType, num_entries)
+ * where TYPE can be something like HASH or ARRAY, and num_entries is an integer.
+ *
+ * This defines the map (hence this should not be used in a header file included
+ * from multiple locations) and provides type safe accessors:
+ *   ValueType * bpf_foo_map_lookup_elem(const KeyType *)
+ *   int bpf_foo_map_update_elem(const KeyType *, const ValueType *, flags)
+ *   int bpf_foo_map_delete_elem(const KeyType *)
+ *
+ * This will make sure that if you change the type of a map you'll get compile
+ * errors at any spots you forget to update with the new type.
+ *
+ * Note: these all take pointers to const map because from the C/eBPF point of view
+ * the map struct is really just a readonly map definition of the in kernel object.
+ * Runtime modification of the map defining struct is meaningless, since
+ * the contents is only ever used during bpf program loading & map creation
+ * by the bpf loader, and not by the eBPF program itself.
+ */
+static void* (*bpf_map_lookup_elem_unsafe)(const struct bpf_map_def* map,
+                                           const void* key) = (void*)BPF_FUNC_map_lookup_elem;
+static int (*bpf_map_update_elem_unsafe)(const struct bpf_map_def* map, const void* key,
+                                         const void* value, unsigned long long flags) = (void*)
+        BPF_FUNC_map_update_elem;
+static int (*bpf_map_delete_elem_unsafe)(const struct bpf_map_def* map,
+                                         const void* key) = (void*)BPF_FUNC_map_delete_elem;
+static int (*bpf_ringbuf_output_unsafe)(const struct bpf_map_def* ringbuf,
+                                        const void* data, __u64 size, __u64 flags) = (void*)
+        BPF_FUNC_ringbuf_output;
+static void* (*bpf_ringbuf_reserve_unsafe)(const struct bpf_map_def* ringbuf,
+                                           __u64 size, __u64 flags) = (void*)
+        BPF_FUNC_ringbuf_reserve;
+static void (*bpf_ringbuf_submit_unsafe)(const void* data, __u64 flags) = (void*)
+        BPF_FUNC_ringbuf_submit;
+
+#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val)  \
+        struct ____btf_map_##name {                     \
+                type_key key;                           \
+                type_val value;                         \
+        };                                              \
+        struct ____btf_map_##name                       \
+        __attribute__ ((section(".maps." #name), used)) \
+                ____btf_map_##name = { }
+
+#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug)  \
+    _Static_assert(                                                                       \
+        (min_loader) >= BPFLOADER_IGNORED_ON_VERSION ||                                   \
+            !((ignore_eng) || (ignore_user) || (ignore_userdebug)),                       \
+        "bpfloader min version must be >= 0.33 in order to use ignored_on");
+
+#define DEFINE_BPF_MAP_BASE(the_map, TYPE, keysize, valuesize, num_entries, \
+                            usr, grp, md, selinux, pindir, share, minkver,  \
+                            maxkver, minloader, maxloader, ignore_eng,      \
+                            ignore_user, ignore_userdebug)                  \
+    const struct bpf_map_def SECTION("maps") the_map = {                    \
+        .type = BPF_MAP_TYPE_##TYPE,                                        \
+        .key_size = (keysize),                                              \
+        .value_size = (valuesize),                                          \
+        .max_entries = (num_entries),                                       \
+        .map_flags = 0,                                                     \
+        .uid = (usr),                                                       \
+        .gid = (grp),                                                       \
+        .mode = (md),                                                       \
+        .bpfloader_min_ver = (minloader),                                   \
+        .bpfloader_max_ver = (maxloader),                                   \
+        .min_kver = (minkver),                                              \
+        .max_kver = (maxkver),                                              \
+        .selinux_context = (selinux),                                       \
+        .pin_subdir = (pindir),                                             \
+        .shared = (share),                                                  \
+        .ignore_on_eng = (ignore_eng),                                      \
+        .ignore_on_user = (ignore_user),                                    \
+        .ignore_on_userdebug = (ignore_userdebug),                          \
+    };                                                                      \
+    BPF_ASSERT_LOADER_VERSION(minloader, ignore_eng, ignore_user, ignore_userdebug);
+
+// Type safe macro to declare a ring buffer and related output functions.
+// Compatibility:
+// * BPF ring buffers are only available kernels 5.8 and above. Any program
+//   accessing the ring buffer should set a program level min_kver >= 5.8.
+// * The definition below sets a map min_kver of 5.8 which requires targeting
+//   a BPFLOADER_MIN_VER >= BPFLOADER_S_VERSION.
+#define DEFINE_BPF_RINGBUF_EXT(the_map, ValueType, size_bytes, usr, grp, md,   \
+                               selinux, pindir, share, min_loader, max_loader, \
+                               ignore_eng, ignore_user, ignore_userdebug)      \
+    DEFINE_BPF_MAP_BASE(the_map, RINGBUF, 0, 0, size_bytes, usr, grp, md,      \
+                        selinux, pindir, share, KVER(5, 8, 0), KVER_INF,       \
+                        min_loader, max_loader, ignore_eng, ignore_user,       \
+                        ignore_userdebug);                                     \
+                                                                               \
+    _Static_assert((size_bytes) >= 4096, "min 4 kiB ringbuffer size");         \
+    _Static_assert((size_bytes) <= 0x10000000, "max 256 MiB ringbuffer size"); \
+    _Static_assert(((size_bytes) & ((size_bytes) - 1)) == 0,                   \
+                   "ring buffer size must be a power of two");                 \
+                                                                               \
+    static inline __always_inline __unused int bpf_##the_map##_output(         \
+            const ValueType* v) {                                              \
+        return bpf_ringbuf_output_unsafe(&the_map, v, sizeof(*v), 0);          \
+    }                                                                          \
+                                                                               \
+    static inline __always_inline __unused                                     \
+            ValueType* bpf_##the_map##_reserve() {                             \
+        return bpf_ringbuf_reserve_unsafe(&the_map, sizeof(ValueType), 0);     \
+    }                                                                          \
+                                                                               \
+    static inline __always_inline __unused void bpf_##the_map##_submit(        \
+            const ValueType* v) {                                              \
+        bpf_ringbuf_submit_unsafe(v, 0);                                       \
+    }
+
+/* There exist buggy kernels with pre-T OS, that due to
+ * kernel patch "[ALPS05162612] bpf: fix ubsan error"
+ * do not support userspace writes into non-zero index of bpf map arrays.
+ *
+ * We use this assert to prevent us from being able to define such a map.
+ */
+
+#ifdef THIS_BPF_PROGRAM_IS_FOR_TEST_PURPOSES_ONLY
+#define BPF_MAP_ASSERT_OK(type, entries, mode)
+#elif BPFLOADER_MIN_VER >= BPFLOADER_T_VERSION
+#define BPF_MAP_ASSERT_OK(type, entries, mode)
+#else
+#define BPF_MAP_ASSERT_OK(type, entries, mode) \
+  _Static_assert(((type) != BPF_MAP_TYPE_ARRAY) || ((entries) <= 1) || !((mode) & 0222), \
+  "Writable arrays with more than 1 element not supported on pre-T devices.")
+#endif
+
+/* type safe macro to declare a map and related accessor functions */
+#define DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md,         \
+                           selinux, pindir, share, min_loader, max_loader, ignore_eng,           \
+                           ignore_user, ignore_userdebug)                                        \
+  DEFINE_BPF_MAP_BASE(the_map, TYPE, sizeof(KeyType), sizeof(ValueType),                         \
+                      num_entries, usr, grp, md, selinux, pindir, share,                         \
+                      KVER_NONE, KVER_INF, min_loader, max_loader,                               \
+                      ignore_eng, ignore_user, ignore_userdebug);                                \
+    BPF_MAP_ASSERT_OK(BPF_MAP_TYPE_##TYPE, (num_entries), (md));                                 \
+    _Static_assert(sizeof(KeyType) < 1024, "aosp/2370288 requires < 1024 byte keys");            \
+    _Static_assert(sizeof(ValueType) < 65536, "aosp/2370288 requires < 65536 byte values");      \
+    BPF_ANNOTATE_KV_PAIR(the_map, KeyType, ValueType);                                           \
+                                                                                                 \
+    static inline __always_inline __unused ValueType* bpf_##the_map##_lookup_elem(               \
+            const KeyType* k) {                                                                  \
+        return bpf_map_lookup_elem_unsafe(&the_map, k);                                          \
+    };                                                                                           \
+                                                                                                 \
+    static inline __always_inline __unused int bpf_##the_map##_update_elem(                      \
+            const KeyType* k, const ValueType* v, unsigned long long flags) {                    \
+        return bpf_map_update_elem_unsafe(&the_map, k, v, flags);                                \
+    };                                                                                           \
+                                                                                                 \
+    static inline __always_inline __unused int bpf_##the_map##_delete_elem(const KeyType* k) {   \
+        return bpf_map_delete_elem_unsafe(&the_map, k);                                          \
+    };
+
+#ifndef DEFAULT_BPF_MAP_SELINUX_CONTEXT
+#define DEFAULT_BPF_MAP_SELINUX_CONTEXT ""
+#endif
+
+#ifndef DEFAULT_BPF_MAP_PIN_SUBDIR
+#define DEFAULT_BPF_MAP_PIN_SUBDIR ""
+#endif
+
+#ifndef DEFAULT_BPF_MAP_UID
+#define DEFAULT_BPF_MAP_UID AID_ROOT
+#elif BPFLOADER_MIN_VER < 28u
+#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28"
+#endif
+
+#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md)   \
+    DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md,       \
+                       DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false, \
+                       BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,       \
+                       /*ignore_on_user*/false, /*ignore_on_userdebug*/false)
+
+#define DEFINE_BPF_MAP(the_map, TYPE, KeyType, ValueType, num_entries) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, AID_ROOT, 0600)
+
+#define DEFINE_BPF_MAP_RO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0440)
+
+#define DEFINE_BPF_MAP_GWO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0620)
+
+#define DEFINE_BPF_MAP_GRO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0640)
+
+#define DEFINE_BPF_MAP_GRW(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0660)
+
+// LLVM eBPF builtins: they directly generate BPF_LD_ABS/BPF_LD_IND (skb may be ignored?)
+unsigned long long load_byte(void* skb, unsigned long long off) asm("llvm.bpf.load.byte");
+unsigned long long load_half(void* skb, unsigned long long off) asm("llvm.bpf.load.half");
+unsigned long long load_word(void* skb, unsigned long long off) asm("llvm.bpf.load.word");
+
+static int (*bpf_probe_read)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read;
+static int (*bpf_probe_read_str)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_str;
+static int (*bpf_probe_read_user)(void* dst, int size, const void* unsafe_ptr) = (void*)BPF_FUNC_probe_read_user;
+static int (*bpf_probe_read_user_str)(void* dst, int size, const void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_user_str;
+static unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns;
+static unsigned long long (*bpf_ktime_get_boot_ns)(void) = (void*)BPF_FUNC_ktime_get_boot_ns;
+static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF_FUNC_trace_printk;
+static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void*) BPF_FUNC_get_current_pid_tgid;
+static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid;
+static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id;
+static long (*bpf_get_stackid)(void* ctx, void* map, uint64_t flags) = (void*) BPF_FUNC_get_stackid;
+static long (*bpf_get_current_comm)(void* buf, uint32_t buf_size) = (void*) BPF_FUNC_get_current_comm;
+
+#define DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv,  \
+                            min_loader, max_loader, opt, selinux, pindir, ignore_eng,    \
+                            ignore_user, ignore_userdebug)                               \
+    const struct bpf_prog_def SECTION("progs") the_prog##_def = {                        \
+        .uid = (prog_uid),                                                               \
+        .gid = (prog_gid),                                                               \
+        .min_kver = (min_kv),                                                            \
+        .max_kver = (max_kv),                                                            \
+        .optional = (opt),                                                               \
+        .bpfloader_min_ver = (min_loader),                                               \
+        .bpfloader_max_ver = (max_loader),                                               \
+        .selinux_context = (selinux),                                                    \
+        .pin_subdir = (pindir),                                                          \
+        .ignore_on_eng = (ignore_eng),                                                   \
+        .ignore_on_user = (ignore_user),                                                 \
+        .ignore_on_userdebug = (ignore_userdebug),                                       \
+    };                                                                                   \
+    SECTION(SECTION_NAME)                                                                \
+    int the_prog
+
+#ifndef DEFAULT_BPF_PROG_SELINUX_CONTEXT
+#define DEFAULT_BPF_PROG_SELINUX_CONTEXT ""
+#endif
+
+#ifndef DEFAULT_BPF_PROG_PIN_SUBDIR
+#define DEFAULT_BPF_PROG_PIN_SUBDIR ""
+#endif
+
+#define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+                                       opt)                                                        \
+    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv,                \
+                        BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, opt,                                 \
+                        DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR,             \
+                        false, false, false)
+
+// Programs (here used in the sense of functions/sections) marked optional are allowed to fail
+// to load (for example due to missing kernel patches).
+// The bpfloader will just ignore these failures and continue processing the next section.
+//
+// A non-optional program (function/section) failing to load causes a failure and aborts
+// processing of the entire .o, if the .o is additionally marked critical, this will result
+// in the entire bpfloader process terminating with a failure and not setting the bpf.progs_loaded
+// system property.  This in turn results in waitForProgsLoaded() never finishing.
+//
+// ie. a non-optional program in a critical .o is mandatory for kernels matching the min/max kver.
+
+// programs requiring a kernel version >= min_kv && < max_kv
+#define DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+                                   false)
+#define DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, \
+                                            max_kv)                                             \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, true)
+
+// programs requiring a kernel version >= min_kv
+#define DEFINE_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)                 \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
+                                   false)
+#define DEFINE_OPTIONAL_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)        \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
+                                   true)
+
+// programs with no kernel version requirements
+#define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, false)
+#define DEFINE_OPTIONAL_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, true)
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
new file mode 100644
index 0000000..e7428b6
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+/* This file is separate because it's included both by eBPF programs (via include
+ * in bpf_helpers.h) and directly by the boot time bpfloader (Loader.cpp).
+ */
+
+#include <linux/bpf.h>
+
+// Pull in AID_* constants from //system/core/libcutils/include/private/android_filesystem_config.h
+#include <cutils/android_filesystem_config.h>
+
+/******************************************************************************
+ *                                                                            *
+ *                          ! ! ! W A R N I N G ! ! !                         *
+ *                                                                            *
+ * CHANGES TO THESE STRUCTURE DEFINITIONS OUTSIDE OF AOSP/MASTER *WILL* BREAK *
+ * MAINLINE MODULE COMPATIBILITY                                              *
+ *                                                                            *
+ * AND THUS MAY RESULT IN YOUR DEVICE BRICKING AT SOME ARBITRARY POINT IN     *
+ * THE FUTURE                                                                 *
+ *                                                                            *
+ * (and even in aosp/master you may only append new fields at the very end,   *
+ *  you may *never* delete fields, change their types, ordering, insert in    *
+ *  the middle, etc.  If a mainline module using the old definition has       *
+ *  already shipped (which happens roughly monthly), then it's set in stone)  *
+ *                                                                            *
+ ******************************************************************************/
+
+// These are the values used if these fields are missing
+#define DEFAULT_BPFLOADER_MIN_VER 0u        // v0.0 (this is inclusive ie. >= v0.0)
+#define DEFAULT_BPFLOADER_MAX_VER 0x10000u  // v1.0 (this is exclusive ie. < v1.0)
+#define DEFAULT_SIZEOF_BPF_MAP_DEF 32       // v0.0 struct: enum (uint sized) + 7 uint
+#define DEFAULT_SIZEOF_BPF_PROG_DEF 20      // v0.0 struct: 4 uint + bool + 3 byte alignment pad
+
+// By default, unless otherwise specified, allow the use of features only supported by v0.37.
+#define COMPILE_FOR_BPFLOADER_VERSION 37u
+
+/*
+ * The bpf_{map,prog}_def structures are compiled for different architectures.
+ * Once by the BPF compiler for the BPF architecture, and once by a C++
+ * compiler for the native Android architecture for the bpfloader.
+ *
+ * For things to work, their layout must be the same between the two.
+ * The BPF architecture is platform independent ('64-bit LSB bpf').
+ * So this effectively means these structures must be the same layout
+ * on 5 architectures, all of them little endian:
+ *   64-bit BPF, x86_64, arm  and  32-bit x86 and arm
+ *
+ * As such for any types we use inside of these structs we must make sure that
+ * the size and alignment are the same, so the same amount of padding is used.
+ *
+ * Currently we only use: bool, enum bpf_map_type and unsigned int.
+ * Additionally we use char for padding.
+ *
+ * !!! WARNING: HERE BE DRAGONS !!!
+ *
+ * Be particularly careful with 64-bit integers.
+ * You will need to manually override their alignment to 8 bytes.
+ *
+ * To quote some parts of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69560
+ *
+ * Some types have weaker alignment requirements when they are structure members.
+ *
+ * unsigned long long on x86 is such a type.
+ *
+ * C distinguishes C11 _Alignof (the minimum alignment the type is guaranteed
+ * to have in all contexts, so 4, see min_align_of_type) from GNU C __alignof
+ * (the normal alignment of the type, so 8).
+ *
+ * alignof / _Alignof == minimum alignment required by target ABI
+ * __alignof / __alignof__ == preferred alignment
+ *
+ * When in a struct, apparently the minimum alignment is used.
+ */
+
+_Static_assert(sizeof(bool) == 1, "sizeof bool != 1");
+_Static_assert(__alignof__(bool) == 1, "__alignof__ bool != 1");
+_Static_assert(_Alignof(bool) == 1, "_Alignof bool != 1");
+
+_Static_assert(sizeof(char) == 1, "sizeof char != 1");
+_Static_assert(__alignof__(char) == 1, "__alignof__ char != 1");
+_Static_assert(_Alignof(char) == 1, "_Alignof char != 1");
+
+// This basically verifies that an enum is 'just' a 32-bit int
+_Static_assert(sizeof(enum bpf_map_type) == 4, "sizeof enum bpf_map_type != 4");
+_Static_assert(__alignof__(enum bpf_map_type) == 4, "__alignof__ enum bpf_map_type != 4");
+_Static_assert(_Alignof(enum bpf_map_type) == 4, "_Alignof enum bpf_map_type != 4");
+
+// Linux kernel requires sizeof(int) == 4, sizeof(void*) == sizeof(long), sizeof(long long) == 8
+_Static_assert(sizeof(unsigned int) == 4, "sizeof unsigned int != 4");
+_Static_assert(__alignof__(unsigned int) == 4, "__alignof__ unsigned int != 4");
+_Static_assert(_Alignof(unsigned int) == 4, "_Alignof unsigned int != 4");
+
+// We don't currently use any 64-bit types in these structs, so this is purely to document issue.
+// Here sizeof & __alignof__ are consistent, but _Alignof is not: compile for 'aosp_cf_x86_phone'
+_Static_assert(sizeof(unsigned long long) == 8, "sizeof unsigned long long != 8");
+_Static_assert(__alignof__(unsigned long long) == 8, "__alignof__ unsigned long long != 8");
+// BPF wants 8, but 32-bit x86 wants 4
+//_Static_assert(_Alignof(unsigned long long) == 8, "_Alignof unsigned long long != 8");
+
+// Length of strings (incl. selinux_context and pin_subdir)
+// in the bpf_map_def and bpf_prog_def structs.
+//
+// WARNING: YOU CANNOT *EVER* CHANGE THESE
+// as this would affect the structure size in backwards incompatible ways
+// and break mainline module loading on older Android T devices
+#define BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE 32
+#define BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE 32
+
+/*
+ * Map structure to be used by Android eBPF C programs. The Android eBPF loader
+ * uses this structure from eBPF object to create maps at boot time.
+ *
+ * The eBPF C program should define structure in the maps section using
+ * SECTION("maps") otherwise it will be ignored by the eBPF loader.
+ *
+ * For example:
+ *   const struct bpf_map_def SECTION("maps") mymap { .type=... , .key_size=... }
+ *
+ * See 'bpf_helpers.h' for helpful macros for eBPF program use.
+ */
+struct bpf_map_def {
+    enum bpf_map_type type;
+    unsigned int key_size;
+    unsigned int value_size;
+    unsigned int max_entries;
+    unsigned int map_flags;
+
+    // The following are not supported by the Android bpfloader:
+    //   unsigned int inner_map_idx;
+    //   unsigned int numa_node;
+
+    unsigned int zero;  // uid_t, for compat with old (buggy) bpfloader must be AID_ROOT == 0
+    unsigned int gid;   // gid_t
+    unsigned int mode;  // mode_t
+
+    // The following fields were added in version 0.1
+    unsigned int bpfloader_min_ver;  // if missing, defaults to 0, ie. v0.0
+    unsigned int bpfloader_max_ver;  // if missing, defaults to 0x10000, ie. v1.0
+
+    // The following fields were added in version 0.2 (S)
+    // kernelVersion() must be >= min_kver and < max_kver
+    unsigned int min_kver;
+    unsigned int max_kver;
+
+    // The following fields were added in version 0.18 (T)
+    //
+    // These are fixed length strings, padded with null bytes
+    //
+    // Warning: supported values depend on .o location
+    // (additionally a newer Android OS and/or bpfloader may support more values)
+    //
+    // overrides default selinux context (which is based on pin subdir)
+    char selinux_context[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE];
+    //
+    // overrides default prefix (which is based on .o location)
+    char pin_subdir[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE];
+
+    bool shared;  // use empty string as 'file' component of pin path - allows cross .o map sharing
+
+    // The following 3 ignore_on_* fields were added in version 0.32 (U). These are ignored in
+    // older bpfloader versions, and zero in programs compiled before 0.32.
+    bool ignore_on_eng:1;
+    bool ignore_on_user:1;
+    bool ignore_on_userdebug:1;
+    // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in
+    // older bpfloader versions, and zero in programs compiled before 0.38.
+    // These are tests on the kernel architecture, ie. they ignore userspace bit-ness.
+    bool ignore_on_arm32:1;
+    bool ignore_on_aarch64:1;
+    bool ignore_on_x86_32:1;
+    bool ignore_on_x86_64:1;
+    bool ignore_on_riscv64:1;
+
+    char pad0[2];  // manually pad up to 4 byte alignment, may be used for extensions in the future
+
+    unsigned int uid;   // uid_t
+};
+
+_Static_assert(sizeof(((struct bpf_map_def *)0)->selinux_context) == 32, "must be 32 bytes");
+_Static_assert(sizeof(((struct bpf_map_def *)0)->pin_subdir) == 32, "must be 32 bytes");
+
+// This needs to be updated whenever the above structure definition is expanded.
+_Static_assert(sizeof(struct bpf_map_def) == 120, "sizeof struct bpf_map_def != 120");
+_Static_assert(__alignof__(struct bpf_map_def) == 4, "__alignof__ struct bpf_map_def != 4");
+_Static_assert(_Alignof(struct bpf_map_def) == 4, "_Alignof struct bpf_map_def != 4");
+
+struct bpf_prog_def {
+    unsigned int uid;
+    unsigned int gid;
+
+    // kernelVersion() must be >= min_kver and < max_kver
+    unsigned int min_kver;
+    unsigned int max_kver;
+
+    bool optional;  // program section (ie. function) may fail to load, continue onto next func.
+
+    // The following 3 ignore_on_* fields were added in version 0.33 (U). These are ignored in
+    // older bpfloader versions, and zero in programs compiled before 0.33.
+    bool ignore_on_eng:1;
+    bool ignore_on_user:1;
+    bool ignore_on_userdebug:1;
+    // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in
+    // older bpfloader versions, and zero in programs compiled before 0.38.
+    // These are tests on the kernel architecture, ie. they ignore userspace bit-ness.
+    bool ignore_on_arm32:1;
+    bool ignore_on_aarch64:1;
+    bool ignore_on_x86_32:1;
+    bool ignore_on_x86_64:1;
+    bool ignore_on_riscv64:1;
+
+    char pad0[2];  // manually pad up to 4 byte alignment, may be used for extensions in the future
+
+    // The following fields were added in version 0.1
+    unsigned int bpfloader_min_ver;  // if missing, defaults to 0, ie. v0.0
+    unsigned int bpfloader_max_ver;  // if missing, defaults to 0x10000, ie. v1.0
+
+    // The following fields were added in version 0.18, see description up above in bpf_map_def
+    char selinux_context[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE];
+    char pin_subdir[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE];
+};
+
+_Static_assert(sizeof(((struct bpf_prog_def *)0)->selinux_context) == 32, "must be 32 bytes");
+_Static_assert(sizeof(((struct bpf_prog_def *)0)->pin_subdir) == 32, "must be 32 bytes");
+
+// This needs to be updated whenever the above structure definition is expanded.
+_Static_assert(sizeof(struct bpf_prog_def) == 92, "sizeof struct bpf_prog_def != 92");
+_Static_assert(__alignof__(struct bpf_prog_def) == 4, "__alignof__ struct bpf_prog_def != 4");
+_Static_assert(_Alignof(struct bpf_prog_def) == 4, "_Alignof struct bpf_prog_def != 4");
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
new file mode 100644
index 0000000..b3efc21
--- /dev/null
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "bpf_syscall_wrappers",
+    vendor_available: true,
+    recovery_available: true,
+    host_supported: true,
+    native_bridge_supported: true,
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.art.debug",
+        "com.android.mediaprovider",
+        "com.android.os.statsd",
+        "com.android.resolv",
+        "com.android.tethering",
+    ],
+}
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
new file mode 100644
index 0000000..13f7cb3
--- /dev/null
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <linux/bpf.h>
+#include <linux/unistd.h>
+
+#ifdef BPF_FD_JUST_USE_INT
+  #define BPF_FD_TYPE int
+  #define BPF_FD_TO_U32(x) static_cast<__u32>(x)
+#else
+  #include <android-base/unique_fd.h>
+  #define BPF_FD_TYPE base::unique_fd&
+  #define BPF_FD_TO_U32(x) static_cast<__u32>((x).get())
+#endif
+
+namespace android {
+namespace bpf {
+
+inline uint64_t ptr_to_u64(const void * const x) {
+    return (uint64_t)(uintptr_t)x;
+}
+
+/* Note: bpf_attr is a union which might have a much larger size then the anonymous struct portion
+ * of it that we are using.  The kernel's bpf() system call will perform a strict check to ensure
+ * all unused portions are zero.  It will fail with E2BIG if we don't fully zero bpf_attr.
+ */
+
+inline int bpf(enum bpf_cmd cmd, const bpf_attr& attr) {
+    return syscall(__NR_bpf, cmd, &attr, sizeof(attr));
+}
+
+inline int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
+                     uint32_t max_entries, uint32_t map_flags) {
+    return bpf(BPF_MAP_CREATE, {
+                                       .map_type = map_type,
+                                       .key_size = key_size,
+                                       .value_size = value_size,
+                                       .max_entries = max_entries,
+                                       .map_flags = map_flags,
+                               });
+}
+
+// Note:
+//   'map_type' must be one of BPF_MAP_TYPE_{ARRAY,HASH}_OF_MAPS
+//   'value_size' must be sizeof(u32), ie. 4
+//   'inner_map_fd' is basically a template specifying {map_type, key_size, value_size, max_entries, map_flags}
+//   of the inner map type (and possibly only key_size/value_size actually matter?).
+inline int createOuterMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
+                          uint32_t max_entries, uint32_t map_flags, const BPF_FD_TYPE inner_map_fd) {
+    return bpf(BPF_MAP_CREATE, {
+                                       .map_type = map_type,
+                                       .key_size = key_size,
+                                       .value_size = value_size,
+                                       .max_entries = max_entries,
+                                       .map_flags = map_flags,
+                                       .inner_map_fd = BPF_FD_TO_U32(inner_map_fd),
+                               });
+}
+
+inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value,
+                           uint64_t flags) {
+    return bpf(BPF_MAP_UPDATE_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                            .value = ptr_to_u64(value),
+                                            .flags = flags,
+                                    });
+}
+
+inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) {
+    return bpf(BPF_MAP_LOOKUP_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                            .value = ptr_to_u64(value),
+                                    });
+}
+
+inline int deleteMapEntry(const BPF_FD_TYPE map_fd, const void* key) {
+    return bpf(BPF_MAP_DELETE_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                    });
+}
+
+inline int getNextMapKey(const BPF_FD_TYPE map_fd, const void* key, void* next_key) {
+    return bpf(BPF_MAP_GET_NEXT_KEY, {
+                                             .map_fd = BPF_FD_TO_U32(map_fd),
+                                             .key = ptr_to_u64(key),
+                                             .next_key = ptr_to_u64(next_key),
+                                     });
+}
+
+inline int getFirstMapKey(const BPF_FD_TYPE map_fd, void* firstKey) {
+    return getNextMapKey(map_fd, NULL, firstKey);
+}
+
+inline int bpfFdPin(const BPF_FD_TYPE map_fd, const char* pathname) {
+    return bpf(BPF_OBJ_PIN, {
+                                    .pathname = ptr_to_u64(pathname),
+                                    .bpf_fd = BPF_FD_TO_U32(map_fd),
+                            });
+}
+
+inline int bpfFdGet(const char* pathname, uint32_t flag) {
+    return bpf(BPF_OBJ_GET, {
+                                    .pathname = ptr_to_u64(pathname),
+                                    .file_flags = flag,
+                            });
+}
+
+inline int mapRetrieve(const char* pathname, uint32_t flag) {
+    return bpfFdGet(pathname, flag);
+}
+
+inline int mapRetrieveRW(const char* pathname) {
+    return mapRetrieve(pathname, 0);
+}
+
+inline int mapRetrieveRO(const char* pathname) {
+    return mapRetrieve(pathname, BPF_F_RDONLY);
+}
+
+inline int mapRetrieveWO(const char* pathname) {
+    return mapRetrieve(pathname, BPF_F_WRONLY);
+}
+
+inline int retrieveProgram(const char* pathname) {
+    return bpfFdGet(pathname, BPF_F_RDONLY);
+}
+
+inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
+                         const BPF_FD_TYPE cg_fd, uint32_t flags = 0) {
+    return bpf(BPF_PROG_ATTACH, {
+                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+                                        .attach_type = type,
+                                        .attach_flags = flags,
+                                });
+}
+
+inline int detachProgram(bpf_attach_type type, const BPF_FD_TYPE cg_fd) {
+    return bpf(BPF_PROG_DETACH, {
+                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .attach_type = type,
+                                });
+}
+
+inline int detachSingleProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
+                               const BPF_FD_TYPE cg_fd) {
+    return bpf(BPF_PROG_DETACH, {
+                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+                                        .attach_type = type,
+                                });
+}
+
+// Available in 4.12 and later kernels.
+inline int runProgram(const BPF_FD_TYPE prog_fd, const void* data,
+                      const uint32_t data_size) {
+    return bpf(BPF_PROG_RUN, {
+                                     .test = {
+                                             .prog_fd = BPF_FD_TO_U32(prog_fd),
+                                             .data_size_in = data_size,
+                                             .data_in = ptr_to_u64(data),
+                                     },
+                             });
+}
+
+// BPF_OBJ_GET_INFO_BY_FD requires 4.14+ kernel
+//
+// Note: some fields are only defined in newer kernels (ie. the map_info struct grows
+// over time), so we need to check that the field we're interested in is actually
+// supported/returned by the running kernel.  We do this by checking it is fully
+// within the bounds of the struct size as reported by the kernel.
+#define DEFINE_BPF_GET_FD(TYPE, NAME, FIELD) \
+inline int bpfGetFd ## NAME(const BPF_FD_TYPE fd) { \
+    struct bpf_ ## TYPE ## _info info = {}; \
+    union bpf_attr attr = { .info = { \
+        .bpf_fd = BPF_FD_TO_U32(fd), \
+        .info_len = sizeof(info), \
+        .info = ptr_to_u64(&info), \
+    }}; \
+    int rv = bpf(BPF_OBJ_GET_INFO_BY_FD, attr); \
+    if (rv) return rv; \
+    if (attr.info.info_len < offsetof(bpf_ ## TYPE ## _info, FIELD) + sizeof(info.FIELD)) { \
+        errno = EOPNOTSUPP; \
+        return -1; \
+    }; \
+    return info.FIELD; \
+}
+
+// All 7 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
+// while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q)
+DEFINE_BPF_GET_FD(map, MapType, type)            // int bpfGetFdMapType(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MapId, id)                // int bpfGetFdMapId(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, KeySize, key_size)        // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, ValueSize, value_size)    // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MaxEntries, max_entries)  // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MapFlags, map_flags)      // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(prog, ProgId, id)              // int bpfGetFdProgId(const BPF_FD_TYPE prog_fd)
+
+#undef DEFINE_BPF_GET_FD
+
+}  // namespace bpf
+}  // namespace android
+
+#undef BPF_FD_TO_U32
+#undef BPF_FD_TYPE
+#undef BPF_FD_JUST_USE_INT
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
new file mode 100644
index 0000000..8babcce
--- /dev/null
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libnet_utils_device_common_bpfjni",
+    srcs: [
+        "com_android_net_module_util_BpfMap.cpp",
+        "com_android_net_module_util_TcUtils.cpp",
+    ],
+    header_libs: [
+        "bpf_headers",
+        "jni_headers",
+    ],
+    shared_libs: [
+        "liblog",
+        "libnativehelper_compat_libc++",
+    ],
+    whole_static_libs: [
+        "libtcutils",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        // TODO: remove after NetworkStatsService moves to the module.
+        "//frameworks/base/packages/ConnectivityT/service",
+    ],
+}
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
new file mode 100644
index 0000000..f93d6e1
--- /dev/null
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <errno.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "nativehelper/scoped_primitive_array.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
+#include "bpf/KernelUtils.h"
+
+namespace android {
+
+static jint com_android_net_module_util_BpfMap_nativeBpfFdGet(JNIEnv *env, jclass clazz,
+        jstring path, jint mode, jint keySize, jint valueSize) {
+    ScopedUtfChars pathname(env, path);
+
+    jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+
+    if (fd < 0) {
+        jniThrowErrnoException(env, "nativeBpfFdGet", errno);
+        return -1;
+    }
+
+    if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
+        // These likely fail with -1 and set errno to EINVAL on <4.14
+        if (bpf::bpfGetFdKeySize(fd) != keySize) {
+            close(fd);
+            jniThrowErrnoException(env, "nativeBpfFdGet KeySize", EBADFD);
+            return -1;
+        }
+        if (bpf::bpfGetFdValueSize(fd) != valueSize) {
+            close(fd);
+            jniThrowErrnoException(env, "nativeBpfFdGet ValueSize", EBADFD);
+            return -1;
+        }
+    }
+
+    return fd;
+}
+
+static void com_android_net_module_util_BpfMap_nativeWriteToMapEntry(JNIEnv *env, jobject self,
+        jint fd, jbyteArray key, jbyteArray value, jint flags) {
+    ScopedByteArrayRO keyRO(env, key);
+    ScopedByteArrayRO valueRO(env, value);
+
+    int ret = bpf::writeToMapEntry(static_cast<int>(fd), keyRO.get(), valueRO.get(),
+            static_cast<int>(flags));
+
+    if (ret) jniThrowErrnoException(env, "nativeWriteToMapEntry", errno);
+}
+
+static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) {
+    if (ret == 0) return true;
+
+    if (err != ENOENT) jniThrowErrnoException(env, functionName, err);
+    return false;
+}
+
+static jboolean com_android_net_module_util_BpfMap_nativeDeleteMapEntry(JNIEnv *env, jobject self,
+        jint fd, jbyteArray key) {
+    ScopedByteArrayRO keyRO(env, key);
+
+    // On success, zero is returned.  If the element is not found, -1 is returned and errno is set
+    // to ENOENT.
+    int ret = bpf::deleteMapEntry(static_cast<int>(fd), keyRO.get());
+
+    return throwIfNotEnoent(env, "nativeDeleteMapEntry", ret, errno);
+}
+
+static jboolean com_android_net_module_util_BpfMap_nativeGetNextMapKey(JNIEnv *env, jobject self,
+        jint fd, jbyteArray key, jbyteArray nextKey) {
+    // If key is found, the operation returns zero and sets the next key pointer to the key of the
+    // next element.  If key is not found, the operation returns zero and sets the next key pointer
+    // to the key of the first element.  If key is the last element, -1 is returned and errno is
+    // set to ENOENT.  Other possible errno values are ENOMEM, EFAULT, EPERM, and EINVAL.
+    ScopedByteArrayRW nextKeyRW(env, nextKey);
+    int ret;
+    if (key == nullptr) {
+        // Called by getFirstKey. Find the first key in the map.
+        ret = bpf::getNextMapKey(static_cast<int>(fd), nullptr, nextKeyRW.get());
+    } else {
+        ScopedByteArrayRO keyRO(env, key);
+        ret = bpf::getNextMapKey(static_cast<int>(fd), keyRO.get(), nextKeyRW.get());
+    }
+
+    return throwIfNotEnoent(env, "nativeGetNextMapKey", ret, errno);
+}
+
+static jboolean com_android_net_module_util_BpfMap_nativeFindMapEntry(JNIEnv *env, jobject self,
+        jint fd, jbyteArray key, jbyteArray value) {
+    ScopedByteArrayRO keyRO(env, key);
+    ScopedByteArrayRW valueRW(env, value);
+
+    // If an element is found, the operation returns zero and stores the element's value into
+    // "value".  If no element is found, the operation returns -1 and sets errno to ENOENT.
+    int ret = bpf::findMapEntry(static_cast<int>(fd), keyRO.get(), valueRW.get());
+
+    return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeBpfFdGet", "(Ljava/lang/String;III)I",
+        (void*) com_android_net_module_util_BpfMap_nativeBpfFdGet },
+    { "nativeWriteToMapEntry", "(I[B[BI)V",
+        (void*) com_android_net_module_util_BpfMap_nativeWriteToMapEntry },
+    { "nativeDeleteMapEntry", "(I[B)Z",
+        (void*) com_android_net_module_util_BpfMap_nativeDeleteMapEntry },
+    { "nativeGetNextMapKey", "(I[B[B)Z",
+        (void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey },
+    { "nativeFindMapEntry", "(I[B[B)Z",
+        (void*) com_android_net_module_util_BpfMap_nativeFindMapEntry },
+
+};
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name) {
+    return jniRegisterNativeMethods(env,
+            class_name,
+            gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
new file mode 100644
index 0000000..cb06afb
--- /dev/null
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <tcutils/tcutils.h>
+
+namespace android {
+
+static void throwIOException(JNIEnv *env, const char *msg, int error) {
+  jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg,
+                       strerror(error));
+}
+
+static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env,
+                                                               jobject clazz,
+                                                               jstring iface) {
+  ScopedUtfChars interface(env, iface);
+  bool result = false;
+  int error = isEthernet(interface.c_str(), result);
+  if (error) {
+    throwIOException(
+        env, "com_android_net_module_util_TcUtils_isEthernet error: ", -error);
+  }
+  // result is not touched when error is returned; leave false.
+  return result;
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
+// /sys/fs/bpf/... direct-action
+static void com_android_net_module_util_TcUtils_tcFilterAddDevBpf(
+    JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+    jshort proto, jstring bpfProgPath) {
+  ScopedUtfChars pathname(env, bpfProgPath);
+  int error = tcAddBpfFilter(ifIndex, ingress, prio, proto, pathname.c_str());
+  if (error) {
+    throwIOException(
+        env,
+        "com_android_net_module_util_TcUtils_tcFilterAddDevBpf error: ", -error);
+  }
+}
+
+// tc filter add dev .. ingress prio .. protocol .. matchall \
+//     action police rate .. burst .. conform-exceed pipe/continue \
+//     action bpf object-pinned .. \
+//     drop
+static void com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice(
+    JNIEnv *env, jobject clazz, jint ifIndex, jshort prio, jshort proto,
+    jint rateInBytesPerSec, jstring bpfProgPath) {
+  ScopedUtfChars pathname(env, bpfProgPath);
+  int error = tcAddIngressPoliceFilter(ifIndex, prio, proto, rateInBytesPerSec,
+                                       pathname.c_str());
+  if (error) {
+    throwIOException(env,
+                     "com_android_net_module_util_TcUtils_"
+                     "tcFilterAddDevIngressPolice error: ",
+                     -error);
+  }
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+static void com_android_net_module_util_TcUtils_tcFilterDelDev(
+    JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+    jshort proto) {
+  int error = tcDeleteFilter(ifIndex, ingress, prio, proto);
+  if (error) {
+    throwIOException(
+        env,
+        "com_android_net_module_util_TcUtils_tcFilterDelDev error: ", -error);
+  }
+}
+
+// tc qdisc add dev .. clsact
+static void com_android_net_module_util_TcUtils_tcQdiscAddDevClsact(JNIEnv *env,
+                                                                    jobject clazz,
+                                                                    jint ifIndex) {
+  int error = tcAddQdiscClsact(ifIndex);
+  if (error) {
+    throwIOException(
+        env,
+        "com_android_net_module_util_TcUtils_tcQdiscAddDevClsact error: ", -error);
+  }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    {"isEthernet", "(Ljava/lang/String;)Z",
+     (void *)com_android_net_module_util_TcUtils_isEthernet},
+    {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
+     (void *)com_android_net_module_util_TcUtils_tcFilterAddDevBpf},
+    {"tcFilterAddDevIngressPolice", "(ISSILjava/lang/String;)V",
+     (void *)com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice},
+    {"tcFilterDelDev", "(IZSS)V",
+     (void *)com_android_net_module_util_TcUtils_tcFilterDelDev},
+    {"tcQdiscAddDevClsact", "(I)V",
+     (void *)com_android_net_module_util_TcUtils_tcQdiscAddDevClsact},
+};
+
+int register_com_android_net_module_util_TcUtils(JNIEnv *env,
+                                                 char const *class_name) {
+  return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/native/bpfutiljni/Android.bp b/staticlibs/native/bpfutiljni/Android.bp
new file mode 100644
index 0000000..39a2795
--- /dev/null
+++ b/staticlibs/native/bpfutiljni/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libnet_utils_device_common_bpfutils",
+    srcs: ["com_android_net_module_util_BpfUtils.cpp"],
+    header_libs: [
+        "bpf_syscall_wrappers",
+        "jni_headers",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativehelper_compat_libc++",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity/service",
+    ],
+}
diff --git a/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp
new file mode 100644
index 0000000..0f2ebbd
--- /dev/null
+++ b/staticlibs/native/bpfutiljni/com_android_net_module_util_BpfUtils.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+
+using base::unique_fd;
+
+// If attach fails throw error and return false.
+static jboolean com_android_net_module_util_BpfUtil_attachProgramToCgroup(JNIEnv *env,
+        jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath, jint flags) {
+
+    ScopedUtfChars dirPath(env, cgroupPath);
+    unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to open the cgroup directory %s: %s",
+                             dirPath.c_str(), strerror(errno));
+        return false;
+    }
+
+    ScopedUtfChars bpfProg(env, bpfProgPath);
+    unique_fd bpf_fd(bpf::retrieveProgram(bpfProg.c_str()));
+    if (bpf_fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to retrieve bpf program from %s: %s",
+                             bpfProg.c_str(), strerror(errno));
+        return false;
+    }
+    if (bpf::attachProgram((bpf_attach_type) type, bpf_fd, cg_fd, flags)) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to attach bpf program %s to %s: %s",
+                             bpfProg.c_str(), dirPath.c_str(), strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+// If detach fails throw error and return false.
+static jboolean com_android_net_module_util_BpfUtil_detachProgramFromCgroup(JNIEnv *env,
+        jobject clazz, jint type, jstring cgroupPath) {
+
+    ScopedUtfChars dirPath(env, cgroupPath);
+    unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to open the cgroup directory %s: %s",
+                             dirPath.c_str(), strerror(errno));
+        return false;
+    }
+
+    if (bpf::detachProgram((bpf_attach_type) type, cg_fd)) {
+        jniThrowExceptionFmt(env, "Failed to detach bpf program from %s: %s",
+                dirPath.c_str(), strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+// If detach single program fails throw error and return false.
+static jboolean com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup(JNIEnv *env,
+        jobject clazz, jint type, jstring bpfProgPath, jstring cgroupPath) {
+
+    ScopedUtfChars dirPath(env, cgroupPath);
+    unique_fd cg_fd(open(dirPath.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to open the cgroup directory %s: %s",
+                             dirPath.c_str(), strerror(errno));
+        return false;
+    }
+
+    ScopedUtfChars bpfProg(env, bpfProgPath);
+    unique_fd bpf_fd(bpf::retrieveProgram(bpfProg.c_str()));
+    if (bpf_fd == -1) {
+        jniThrowExceptionFmt(env, "java/io/IOException",
+                             "Failed to retrieve bpf program from %s: %s",
+                             bpfProg.c_str(), strerror(errno));
+        return false;
+    }
+    if (bpf::detachSingleProgram((bpf_attach_type) type, bpf_fd, cg_fd)) {
+        jniThrowExceptionFmt(env, "Failed to detach bpf program %s from %s: %s",
+                bpfProg.c_str(), dirPath.c_str(), strerror(errno));
+        return false;
+    }
+    return true;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "native_attachProgramToCgroup", "(ILjava/lang/String;Ljava/lang/String;I)Z",
+        (void*) com_android_net_module_util_BpfUtil_attachProgramToCgroup },
+    { "native_detachProgramFromCgroup", "(ILjava/lang/String;)Z",
+        (void*) com_android_net_module_util_BpfUtil_detachProgramFromCgroup },
+    { "native_detachSingleProgramFromCgroup", "(ILjava/lang/String;Ljava/lang/String;)Z",
+        (void*) com_android_net_module_util_BpfUtil_detachSingleProgramFromCgroup },
+};
+
+int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name) {
+    return jniRegisterNativeMethods(env,
+            class_name,
+            gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
new file mode 100644
index 0000000..9878d73
--- /dev/null
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libip_checksum",
+
+    srcs: [
+        "checksum.c",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    export_include_dirs: ["."],
+
+    // Needed because libnetutils depends on libip_checksum, and libnetutils has
+    // vendor_available = true. Making this library vendor_available does not create any maintenance
+    // burden or version skew issues because this library is only static, not dynamic, and thus is
+    // not installed on the device.
+    //
+    // TODO: delete libnetutils from the VNDK in T, and remove this.
+    vendor_available: true,
+
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+}
diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c
new file mode 100644
index 0000000..5641fad
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.c - ipv4/ipv6 checksum calculation
+ */
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include "checksum.h"
+
+/* function: ip_checksum_add
+ * adds data to a checksum. only known to work on little-endian hosts
+ * current - the current checksum (or 0 to start a new checksum)
+ *   data        - the data to add to the checksum
+ *   len         - length of data
+ */
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len) {
+    const uint16_t* data_16 = data;
+
+    while (len >= 2) {
+        current += *data_16;
+        data_16++;
+        len -= 2;
+    }
+    if (len) current += *(uint8_t*)data_16;  // assumes little endian!
+
+    return current;
+}
+
+/* function: ip_checksum_fold
+ * folds a 32-bit partial checksum into 16 bits
+ *   temp_sum - sum from ip_checksum_add
+ *   returns: the folded checksum in network byte order
+ */
+uint16_t ip_checksum_fold(uint32_t temp_sum) {
+    temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+    temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+    return temp_sum;
+}
+
+/* function: ip_checksum_finish
+ * folds and closes the checksum
+ *   temp_sum - sum from ip_checksum_add
+ *   returns: a header checksum value in network byte order
+ */
+uint16_t ip_checksum_finish(uint32_t temp_sum) {
+    return ~ip_checksum_fold(temp_sum);
+}
+
+/* function: ip_checksum
+ * combined ip_checksum_add and ip_checksum_finish
+ *   data - data to checksum
+ *   len  - length of data
+ */
+uint16_t ip_checksum(const void* data, int len) {
+    return ip_checksum_finish(ip_checksum_add(0xFFFF, data, len));
+}
+
+/* function: ipv6_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp/icmp headers
+ *   ip6      - the ipv6 header
+ *   len      - the transport length (transport header + payload)
+ *   protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
+ */
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) {
+    uint32_t checksum_len = htonl(len);
+    uint32_t checksum_next = htonl(protocol);
+    uint32_t current = 0;
+
+    current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
+    current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
+    current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
+    current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));
+
+    return current;
+}
+
+/* function: ipv4_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp headers
+ *   ip      - the ipv4 header
+ *   len     - the transport length (transport header + payload)
+ */
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) {
+    uint16_t temp_protocol = htons(ip->protocol);
+    uint16_t temp_length = htons(len);
+    uint32_t current = 0;
+
+    current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
+    current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
+    current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
+    current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));
+
+    return current;
+}
+
+/* function: ip_checksum_adjust
+ * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
+ *   checksum    - the header checksum in the original packet in network byte order
+ *   old_hdr_sum - the pseudo-header checksum of the original packet
+ *   new_hdr_sum - the pseudo-header checksum of the translated packet
+ *   returns: the new header checksum in network byte order
+ */
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
+    // Algorithm suggested in RFC 1624.
+    // http://tools.ietf.org/html/rfc1624#section-3
+    checksum = ~checksum;
+    uint16_t folded_sum = ip_checksum_fold(new_hdr_sum + checksum);
+    uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
+    if (folded_sum > folded_old) {
+        return ~(folded_sum - folded_old);
+    } else {
+        return ~(folded_sum - folded_old - 1);  // end-around borrow
+    }
+}
diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h
new file mode 100644
index 0000000..87393c9
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ */
+#pragma once
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <stdint.h>
+
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len);
+uint16_t ip_checksum_finish(uint32_t temp_sum);
+uint16_t ip_checksum(const void* data, int len);
+
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol);
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len);
+
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp
new file mode 100644
index 0000000..22fd1fa
--- /dev/null
+++ b/staticlibs/native/netjniutils/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libnetjniutils",
+    srcs: ["netjniutils.cpp"],
+    static_libs: [
+        "libmodules-utils-build",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
+    export_header_lib_headers: ["jni_headers"],
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    sdk_version: "29",
+    min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
+    visibility: ["//visibility:public"],
+}
diff --git a/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h b/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h
new file mode 100644
index 0000000..1b2b035
--- /dev/null
+++ b/staticlibs/native/netjniutils/include/netjniutils/netjniutils.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 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.
+
+#pragma once
+
+#include <jni.h>
+
+namespace android {
+namespace netjniutils {
+
+int GetNativeFileDescriptor(JNIEnv* env, jobject javaFd);
+
+}  // namespace netjniutils
+}  // namepsace android
diff --git a/staticlibs/native/netjniutils/netjniutils.cpp b/staticlibs/native/netjniutils/netjniutils.cpp
new file mode 100644
index 0000000..8b7f903
--- /dev/null
+++ b/staticlibs/native/netjniutils/netjniutils.cpp
@@ -0,0 +1,85 @@
+// Copyright (C) 2020 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.
+
+#define LOG_TAG "netjniutils"
+
+#include "netjniutils/netjniutils.h"
+#include <android-modules-utils/sdk_level.h>
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/system_properties.h>
+
+#include <android/api-level.h>
+#include <android/log.h>
+
+namespace android {
+namespace netjniutils {
+
+namespace {
+
+
+int GetNativeFileDescriptorWithoutNdk(JNIEnv* env, jobject javaFd) {
+  // Prior to Android S, we need to find the descriptor field in the FileDescriptor class. The
+  // symbol name has been stable in libcore, but is a private implementation detail.
+  // Older libnativehelper_compat_c++ versions had a jniGetFdFromFileDescriptor method, but this
+  // was removed in S to replace it with the NDK API in libnativehelper.
+  // The code is copied here instead. This code can be removed once R is not supported anymore.
+  static const jfieldID descriptorFieldID = [env]() -> jfieldID {
+    jclass cls = env->FindClass("java/io/FileDescriptor");
+    jfieldID fieldID = env->GetFieldID(cls, "descriptor", "I");
+    env->DeleteLocalRef(cls);
+    if (fieldID == nullptr) {
+      __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "Failed to get descriptor field.");
+    }
+    return fieldID;
+  }();
+
+  return javaFd != nullptr ? env->GetIntField(javaFd, descriptorFieldID) : -1;
+}
+
+int GetNativeFileDescriptorWithNdk(JNIEnv* env, jobject javaFd) {
+  // Since Android S, there is an NDK API to get a file descriptor present in libnativehelper.so.
+  // libnativehelper is loaded into all processes by the zygote since the zygote uses it
+  // to load the Android Runtime and is also a public library (because of the NDK API).
+  typedef int (*ndkGetFd_t)(JNIEnv*, jobject);
+  static const ndkGetFd_t ndkGetFd = []() -> ndkGetFd_t {
+    void* handle = dlopen("libnativehelper.so", RTLD_NOLOAD | RTLD_NODELETE);
+    auto ndkGetFd = reinterpret_cast<ndkGetFd_t>(dlsym(handle, "AFileDescriptor_getFd"));
+    if (ndkGetFd == nullptr) {
+      __android_log_print(ANDROID_LOG_FATAL, LOG_TAG,
+                          "Failed to dlsym(AFileDescriptor_getFd): %s", dlerror());
+      dlclose(handle);
+    }
+    return ndkGetFd;
+  }();
+
+  return javaFd != nullptr ? ndkGetFd(env, javaFd) : -1;
+}
+
+}  //  namespace
+
+int GetNativeFileDescriptor(JNIEnv* env, jobject javaFd) {
+  static const bool preferNdkFileDescriptorApi = []() -> bool
+   { return android::modules::sdklevel::IsAtLeastS(); }();
+  if (preferNdkFileDescriptorApi) {
+    return GetNativeFileDescriptorWithNdk(env, javaFd);
+  } else {
+    return GetNativeFileDescriptorWithoutNdk(env, javaFd);
+  }
+}
+
+}  // namespace netjniutils
+}  // namespace android
diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp
new file mode 100644
index 0000000..df3bb42
--- /dev/null
+++ b/staticlibs/native/nettestutils/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libnettestutils",
+    export_include_dirs: ["include"],
+    srcs: ["DumpService.cpp"],
+
+    // Don't depend on libbinder, because some users of this library may not want to link to it.
+    // CtsNativeNetPlatformTestCases is one such user. See r.android.com/2599405 .
+    header_libs: [
+        "libbinder_headers",
+    ],
+
+    shared_libs: [
+        "libutils",
+        "libbinder_ndk",
+    ],
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/staticlibs/native/nettestutils/DumpService.cpp b/staticlibs/native/nettestutils/DumpService.cpp
new file mode 100644
index 0000000..40c3b9a
--- /dev/null
+++ b/staticlibs/native/nettestutils/DumpService.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#include "nettestutils/DumpService.h"
+
+#include <android-base/file.h>
+#include <android/binder_status.h>
+
+#include <sstream>
+#include <thread>
+
+// Version for code using libbinder (e.g., AIDL interfaces with the C++ backend).
+android::status_t dumpService(const android::sp<android::IBinder>& binder,
+                              const std::vector<std::string>& args,
+                              std::vector<std::string>& outputLines) {
+  if (!outputLines.empty()) return -EUCLEAN;
+
+  android::base::unique_fd localFd, remoteFd;
+  if (!Pipe(&localFd, &remoteFd)) return -errno;
+
+  android::Vector<android::String16> str16Args;
+  for (const auto& arg : args) {
+    str16Args.push(android::String16(arg.c_str()));
+  }
+  android::status_t ret;
+  // dump() blocks until another thread has consumed all its output.
+  std::thread dumpThread =
+      std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, str16Args]() {
+        ret = binder->dump(remoteFd, str16Args);
+      });
+
+  std::string dumpContent;
+  if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+    return -errno;
+  }
+  dumpThread.join();
+  if (ret != android::OK) return ret;
+
+  std::stringstream dumpStream(std::move(dumpContent));
+  std::string line;
+  while (std::getline(dumpStream, line)) {
+    outputLines.push_back(line);
+  }
+
+  return android::OK;
+}
+
+// Version for code using libbinder_ndk (e.g., AIDL interfaces with the NDK backend)..
+android::status_t dumpService(const ndk::SpAIBinder& binder,
+                              const char** args,
+                              uint32_t num_args,
+                              std::vector<std::string>& outputLines) {
+  if (!outputLines.empty()) return -EUCLEAN;
+
+  android::base::unique_fd localFd, remoteFd;
+  if (!Pipe(&localFd, &remoteFd)) return -errno;
+
+  android::status_t ret;
+  // dump() blocks until another thread has consumed all its output.
+  std::thread dumpThread =
+      std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, args, num_args]() {
+        ret = AIBinder_dump(binder.get(), remoteFd, args, num_args);
+  });
+
+  std::string dumpContent;
+  if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+    return -errno;
+  }
+  dumpThread.join();
+  if (ret != android::OK) return ret;
+
+  std::stringstream dumpStream(dumpContent);
+  std::string line;
+  while (std::getline(dumpStream, line)) {
+    outputLines.push_back(std::move(line));
+  }
+
+  return android::OK;
+}
diff --git a/staticlibs/native/nettestutils/include/nettestutils/DumpService.h b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
new file mode 100644
index 0000000..323d752
--- /dev/null
+++ b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#include <binder/Binder.h>
+#include <android/binder_auto_utils.h>
+
+#include <vector>
+
+android::status_t dumpService(const android::sp<android::IBinder>& binder,
+                              const std::vector<std::string>& args,
+                              std::vector<std::string>& outputLines);
+
+android::status_t dumpService(const ndk::SpAIBinder& binder,
+                              const char** args,
+                              uint32_t num_args,
+                              std::vector<std::string>& outputLines);
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
new file mode 100644
index 0000000..9a38745
--- /dev/null
+++ b/staticlibs/native/tcutils/Android.bp
@@ -0,0 +1,67 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libtcutils",
+    srcs: ["tcutils.cpp"],
+    export_include_dirs: ["include"],
+    header_libs: ["bpf_headers"],
+    shared_libs: [
+        "liblog",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+        "//system/netd:__subpackages__",
+    ],
+}
+
+cc_test {
+    name: "libtcutils_test",
+    srcs: [
+        "tests/tcutils_test.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=unused-variable",
+    ],
+    header_libs: ["bpf_headers"],
+    static_libs: [
+        "libgmock",
+        "libtcutils",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    min_sdk_version: "30",
+    require_root: true,
+    test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/tcutils/include/tcutils/tcutils.h b/staticlibs/native/tcutils/include/tcutils/tcutils.h
new file mode 100644
index 0000000..a8ec2e8
--- /dev/null
+++ b/staticlibs/native/tcutils/include/tcutils/tcutils.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <linux/rtnetlink.h>
+
+namespace android {
+
+int isEthernet(const char *iface, bool &isEthernet);
+
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
+
+static inline int tcAddQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+static inline int tcReplaceQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+static inline int tcDeleteQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
+}
+
+int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
+                   const char *bpfProgPath);
+int tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto,
+                             unsigned rateInBytesPerSec,
+                             const char *bpfProgPath);
+int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
+
+} // namespace android
diff --git a/staticlibs/native/tcutils/logging.h b/staticlibs/native/tcutils/logging.h
new file mode 100644
index 0000000..7ed8f66
--- /dev/null
+++ b/staticlibs/native/tcutils/logging.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android/log.h>
+#include <stdarg.h>
+
+#ifndef LOG_TAG
+#define LOG_TAG "TcUtils_Undef"
+#endif
+
+namespace android {
+
+static inline void ALOGE(const char *fmt...) {
+  va_list args;
+  va_start(args, fmt);
+  __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
+  va_end(args);
+}
+
+static inline void ALOGW(const char *fmt...) {
+  va_list args;
+  va_start(args, fmt);
+  __android_log_vprint(ANDROID_LOG_WARN, LOG_TAG, fmt, args);
+  va_end(args);
+}
+
+static inline void ALOGI(const char *fmt...) {
+  va_list args;
+  va_start(args, fmt);
+  __android_log_vprint(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
+  va_end(args);
+}
+
+static inline void ALOGD(const char *fmt...) {
+  va_list args;
+  va_start(args, fmt);
+  __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+  va_end(args);
+}
+
+}
diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h
new file mode 100644
index 0000000..76bbb93
--- /dev/null
+++ b/staticlibs/native/tcutils/scopeguard.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+// -----------------------------------------------------------------------------
+// TODO: figure out a way to use libbase_ndk. This is currently not working
+// because of missing apex availability. For now, we can use a copy of
+// ScopeGuard which is very lean compared to unique_fd. This code has been
+// copied verbatim from:
+// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h
+
+#pragma once
+
+#include <utility> // for std::move, std::forward
+
+namespace android {
+namespace base {
+
+// ScopeGuard ensures that the specified functor is executed no matter how the
+// current scope exits.
+template <typename F> class ScopeGuard {
+public:
+  ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {}
+
+  ScopeGuard(ScopeGuard &&that) noexcept
+      : f_(std::move(that.f_)), active_(that.active_) {
+    that.active_ = false;
+  }
+
+  template <typename Functor>
+  ScopeGuard(ScopeGuard<Functor> &&that)
+      : f_(std::move(that.f_)), active_(that.active_) {
+    that.active_ = false;
+  }
+
+  ~ScopeGuard() {
+    if (active_)
+      f_();
+  }
+
+  ScopeGuard() = delete;
+  ScopeGuard(const ScopeGuard &) = delete;
+  void operator=(const ScopeGuard &) = delete;
+  void operator=(ScopeGuard &&that) = delete;
+
+  void Disable() { active_ = false; }
+
+  bool active() const { return active_; }
+
+private:
+  template <typename Functor> friend class ScopeGuard;
+
+  F f_;
+  bool active_;
+};
+
+template <typename F> ScopeGuard<F> make_scope_guard(F &&f) {
+  return ScopeGuard<F>(std::forward<F>(f));
+}
+
+} // namespace base
+} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
new file mode 100644
index 0000000..c82390f
--- /dev/null
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -0,0 +1,742 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TcUtils"
+
+#include "tcutils/tcutils.h"
+
+#include "logging.h"
+#include "bpf/KernelUtils.h"
+#include "scopeguard.h"
+
+#include <arpa/inet.h>
+#include <cerrno>
+#include <cstring>
+#include <libgen.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
+#include <linux/tc_act/tc_bpf.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <utility>
+
+#define BPF_FD_JUST_USE_INT
+#include <BpfSyscallWrappers.h>
+#undef BPF_FD_JUST_USE_INT
+
+// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
+#define CLS_BPF_NAME_LEN 256
+
+// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
+#define CLS_BPF_KIND_NAME "bpf"
+
+namespace android {
+namespace {
+
+/**
+ * IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following
+ * tc command:
+ *
+ * tc filter add dev .. ingress prio .. protocol .. matchall \
+ *     action police rate .. burst .. conform-exceed pipe/continue \
+ *     action bpf object-pinned .. \
+ *     drop
+ */
+class IngressPoliceFilterBuilder final {
+  // default mtu is 2047, so the cell logarithm factor (cell_log) is 3.
+  // 0x7FF >> 0x3FF x 2^1 >> 0x1FF x 2^2 >> 0xFF x 2^3
+  static constexpr int RTAB_CELL_LOGARITHM = 3;
+  static constexpr size_t RTAB_SIZE = 256;
+  static constexpr unsigned TIME_UNITS_PER_SEC = 1000000;
+
+  struct Request {
+    nlmsghdr n;
+    tcmsg t;
+    struct {
+      nlattr attr;
+      char str[NLMSG_ALIGN(sizeof("matchall"))];
+    } kind;
+    struct {
+      nlattr attr;
+      struct {
+        nlattr attr;
+        struct {
+          nlattr attr;
+          struct {
+            nlattr attr;
+            char str[NLMSG_ALIGN(sizeof("police"))];
+          } kind;
+          struct {
+            nlattr attr;
+            struct {
+              nlattr attr;
+              struct tc_police obj;
+            } police;
+            struct {
+              nlattr attr;
+              uint32_t u32[RTAB_SIZE];
+            } rtab;
+            struct {
+              nlattr attr;
+              int32_t s32;
+            } notexceedact;
+          } opt;
+        } act1;
+        struct {
+          nlattr attr;
+          struct {
+            nlattr attr;
+            char str[NLMSG_ALIGN(sizeof("bpf"))];
+          } kind;
+          struct {
+            nlattr attr;
+            struct {
+              nlattr attr;
+              uint32_t u32;
+            } fd;
+            struct {
+              nlattr attr;
+              char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+            } name;
+            struct {
+              nlattr attr;
+              struct tc_act_bpf obj;
+            } parms;
+          } opt;
+        } act2;
+      } acts;
+    } opt;
+  };
+
+  // class members
+  const unsigned mBurstInBytes;
+  const char *mBpfProgPath;
+  int mBpfFd;
+  Request mRequest;
+
+  static double getTickInUsec() {
+    FILE *fp = fopen("/proc/net/psched", "re");
+    if (!fp) {
+      ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno));
+      return 0.0;
+    }
+    auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); });
+
+    uint32_t t2us;
+    uint32_t us2t;
+    uint32_t clockRes;
+    const bool isError =
+        fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clockRes) != 3;
+
+    if (isError) {
+      ALOGE("fscanf(/proc/net/psched, \"%%08x%%08x%%08x\"): %s",
+               strerror(errno));
+      return 0.0;
+    }
+
+    const double clockFactor =
+        static_cast<double>(clockRes) / TIME_UNITS_PER_SEC;
+    return static_cast<double>(t2us) / static_cast<double>(us2t) * clockFactor;
+  }
+
+  static inline const double kTickInUsec = getTickInUsec();
+
+public:
+  // clang-format off
+  IngressPoliceFilterBuilder(int ifIndex, uint16_t prio, uint16_t proto, unsigned rateInBytesPerSec,
+                      unsigned burstInBytes, const char* bpfProgPath)
+      : mBurstInBytes(burstInBytes),
+        mBpfProgPath(bpfProgPath),
+        mBpfFd(-1),
+        mRequest{
+            .n = {
+                .nlmsg_len = sizeof(mRequest),
+                .nlmsg_type = RTM_NEWTFILTER,
+                .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE,
+            },
+            .t = {
+                .tcm_family = AF_UNSPEC,
+                .tcm_ifindex = ifIndex,
+                .tcm_handle = TC_H_UNSPEC,
+                .tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS),
+                .tcm_info = (static_cast<uint32_t>(prio) << 16)
+                            | static_cast<uint32_t>(htons(proto)),
+            },
+            .kind = {
+                .attr = {
+                    .nla_len = sizeof(mRequest.kind),
+                    .nla_type = TCA_KIND,
+                },
+                .str = "matchall",
+            },
+            .opt = {
+                .attr = {
+                    .nla_len = sizeof(mRequest.opt),
+                    .nla_type = TCA_OPTIONS,
+                },
+                .acts = {
+                    .attr = {
+                        .nla_len = sizeof(mRequest.opt.acts),
+                        .nla_type = TCA_MATCHALL_ACT,
+                    },
+                    .act1 = {
+                        .attr = {
+                            .nla_len = sizeof(mRequest.opt.acts.act1),
+                            .nla_type = 1, // action priority
+                        },
+                        .kind = {
+                            .attr = {
+                                .nla_len = sizeof(mRequest.opt.acts.act1.kind),
+                                .nla_type = TCA_ACT_KIND,
+                            },
+                            .str = "police",
+                        },
+                        .opt = {
+                            .attr = {
+                                .nla_len = sizeof(mRequest.opt.acts.act1.opt),
+                                .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED,
+                            },
+                            .police = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act1.opt.police),
+                                    .nla_type = TCA_POLICE_TBF,
+                                },
+                                .obj = {
+                                    .action = TC_ACT_PIPE,
+                                    .burst = 0,
+                                    .rate = {
+                                        .cell_log = RTAB_CELL_LOGARITHM,
+                                        .linklayer = TC_LINKLAYER_ETHERNET,
+                                        .cell_align = -1,
+                                        .rate = rateInBytesPerSec,
+                                    },
+                                },
+                            },
+                            .rtab = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act1.opt.rtab),
+                                    .nla_type = TCA_POLICE_RATE,
+                                },
+                                .u32 = {},
+                            },
+                            .notexceedact = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act1.opt.notexceedact),
+                                    .nla_type = TCA_POLICE_RESULT,
+                                },
+                                .s32 = TC_ACT_UNSPEC,
+                            },
+                        },
+                    },
+                    .act2 = {
+                        .attr = {
+                            .nla_len = sizeof(mRequest.opt.acts.act2),
+                            .nla_type = 2, // action priority
+                        },
+                        .kind = {
+                            .attr = {
+                                .nla_len = sizeof(mRequest.opt.acts.act2.kind),
+                                .nla_type = TCA_ACT_KIND,
+                            },
+                            .str = "bpf",
+                        },
+                        .opt = {
+                            .attr = {
+                                .nla_len = sizeof(mRequest.opt.acts.act2.opt),
+                                .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED,
+                            },
+                            .fd = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act2.opt.fd),
+                                    .nla_type = TCA_ACT_BPF_FD,
+                                },
+                                .u32 = 0, // set during build()
+                            },
+                            .name = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act2.opt.name),
+                                    .nla_type = TCA_ACT_BPF_NAME,
+                                },
+                                .str = "placeholder",
+                            },
+                            .parms = {
+                                .attr = {
+                                    .nla_len = sizeof(mRequest.opt.acts.act2.opt.parms),
+                                    .nla_type = TCA_ACT_BPF_PARMS,
+                                },
+                                .obj = {
+                                    // default action to be executed when bpf prog
+                                    // returns TC_ACT_UNSPEC.
+                                    .action = TC_ACT_SHOT,
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        } {
+      // constructor body
+  }
+  // clang-format on
+
+  ~IngressPoliceFilterBuilder() {
+    // TODO: use unique_fd
+    if (mBpfFd != -1) {
+      close(mBpfFd);
+    }
+  }
+
+  constexpr unsigned getRequestSize() const { return sizeof(Request); }
+
+private:
+  unsigned calculateXmitTime(unsigned size) {
+    const uint32_t rate = mRequest.opt.acts.act1.opt.police.obj.rate.rate;
+    return (static_cast<double>(size) / static_cast<double>(rate)) *
+           TIME_UNITS_PER_SEC * kTickInUsec;
+  }
+
+  void initBurstRate() {
+    mRequest.opt.acts.act1.opt.police.obj.burst =
+        calculateXmitTime(mBurstInBytes);
+  }
+
+  // Calculates a table with 256 transmission times for different packet sizes
+  // (all the way up to MTU). RTAB_CELL_LOGARITHM is used as a scaling factor.
+  // In this case, MTU size is always 2048, so RTAB_CELL_LOGARITHM is always
+  // 3. Therefore, this function generates the transmission times for packets
+  // of size 1..256 x 2^3.
+  void initRateTable() {
+    for (unsigned i = 0; i < RTAB_SIZE; ++i) {
+      unsigned adjustedSize = (i + 1) << RTAB_CELL_LOGARITHM;
+      mRequest.opt.acts.act1.opt.rtab.u32[i] = calculateXmitTime(adjustedSize);
+    }
+  }
+
+  int initBpfFd() {
+    mBpfFd = bpf::retrieveProgram(mBpfProgPath);
+    if (mBpfFd == -1) {
+      int error = errno;
+      ALOGE("retrieveProgram failed: %d", error);
+      return -error;
+    }
+
+    mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd);
+    snprintf(mRequest.opt.acts.act2.opt.name.str,
+             sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]",
+             basename(mBpfProgPath));
+
+    return 0;
+  }
+
+public:
+  int build() {
+    if (kTickInUsec == 0.0) {
+      return -EINVAL;
+    }
+
+    initBurstRate();
+    initRateTable();
+    return initBpfFd();
+  }
+
+  const Request *getRequest() const {
+    // Make sure to call build() before calling this function. Otherwise, the
+    // request will be invalid.
+    return &mRequest;
+  }
+};
+
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+
+int sendAndProcessNetlinkResponse(const void *req, int len) {
+  // TODO: use unique_fd instead of ScopeGuard
+  int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+  if (fd == -1) {
+    int error = errno;
+    ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
+             error);
+    return -error;
+  }
+  auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
+
+  static constexpr int on = 1;
+  if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
+    int error = errno;
+    ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1): %d", error);
+    return -error;
+  }
+
+  if (setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &on, sizeof(on))) {
+    int error = errno;
+    ALOGW("setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, 1): %d", error);
+    // will fail on 4.9 kernels so don't: return -error;
+  }
+
+  // this is needed to get valid strace netlink parsing, it allocates the pid
+  if (bind(fd, (const struct sockaddr *)&KERNEL_NLADDR,
+           sizeof(KERNEL_NLADDR))) {
+    int error = errno;
+    ALOGE("bind(fd, {AF_NETLINK, 0, 0}: %d)", error);
+    return -error;
+  }
+
+  // we do not want to receive messages from anyone besides the kernel
+  if (connect(fd, (const struct sockaddr *)&KERNEL_NLADDR,
+              sizeof(KERNEL_NLADDR))) {
+    int error = errno;
+    ALOGE("connect(fd, {AF_NETLINK, 0, 0}): %d", error);
+    return -error;
+  }
+
+  int rv = send(fd, req, len, 0);
+
+  if (rv == -1) {
+    int error = errno;
+    ALOGE("send(fd, req, len, 0) failed: %d", error);
+    return -error;
+  }
+
+  if (rv != len) {
+    ALOGE("send(fd, req, len = %d, 0) returned invalid message size %d", len,
+             rv);
+    return -EMSGSIZE;
+  }
+
+  struct {
+    nlmsghdr h;
+    nlmsgerr e;
+    char buf[256];
+  } resp = {};
+
+  rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+  if (rv == -1) {
+    int error = errno;
+    ALOGE("recv() failed: %d", error);
+    return -error;
+  }
+
+  if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+    ALOGE("recv() returned short packet: %d", rv);
+    return -EBADMSG;
+  }
+
+  if (resp.h.nlmsg_len != (unsigned)rv) {
+    ALOGE("recv() returned invalid header length: %d != %d",
+             resp.h.nlmsg_len, rv);
+    return -EBADMSG;
+  }
+
+  if (resp.h.nlmsg_type != NLMSG_ERROR) {
+    ALOGE("recv() did not return NLMSG_ERROR message: %d",
+             resp.h.nlmsg_type);
+    return -ENOMSG;
+  }
+
+  if (resp.e.error) {
+    ALOGE("NLMSG_ERROR message return error: %d", resp.e.error);
+  }
+  return resp.e.error; // returns 0 on success
+}
+
+int hardwareAddressType(const char *interface) {
+  int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+  if (fd < 0)
+    return -errno;
+  auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
+
+  struct ifreq ifr = {};
+  // We use strncpy() instead of strlcpy() since kernel has to be able
+  // to handle non-zero terminated junk passed in by userspace anyway,
+  // and this way too long interface names (more than IFNAMSIZ-1 = 15
+  // characters plus terminating NULL) will not get truncated to 15
+  // characters and zero-terminated and thus potentially erroneously
+  // match a truncated interface if one were to exist.
+  strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+
+  if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
+    return -errno;
+  }
+  return ifr.ifr_hwaddr.sa_family;
+}
+
+} // namespace
+
+int isEthernet(const char *iface, bool &isEthernet) {
+  int rv = hardwareAddressType(iface);
+  if (rv < 0) {
+    ALOGE("Get hardware address type of interface %s failed: %s", iface,
+             strerror(-rv));
+    return rv;
+  }
+
+  // Backwards compatibility with pre-GKI kernels that use various custom
+  // ARPHRD_* for their cellular interface
+  switch (rv) {
+  // ARPHRD_PUREIP on at least some Mediatek Android kernels
+  // example: wembley with 4.19 kernel
+  case 520:
+  // in Linux 4.14+ rmnet support was upstreamed and ARHRD_RAWIP became 519,
+  // but it is 530 on at least some Qualcomm Android 4.9 kernels with rmnet
+  // example: Pixel 3 family
+  case 530:
+    // >5.4 kernels are GKI2.0 and thus upstream compatible, however 5.10
+    // shipped with Android S, so (for safety) let's limit ourselves to
+    // >5.10, ie. 5.11+ as a guarantee we're on Android T+ and thus no
+    // longer need this non-upstream compatibility logic
+    static bool is_pre_5_11_kernel = !bpf::isAtLeastKernelVersion(5, 11, 0);
+    if (is_pre_5_11_kernel)
+      return false;
+  }
+
+  switch (rv) {
+  case ARPHRD_ETHER:
+    isEthernet = true;
+    return 0;
+  case ARPHRD_NONE:
+  case ARPHRD_PPP:
+  case ARPHRD_RAWIP:
+    isEthernet = false;
+    return 0;
+  default:
+    ALOGE("Unknown hardware address type %d on interface %s", rv, iface);
+    return -EAFNOSUPPORT;
+  }
+}
+
+// ADD:     nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL:     nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
+  // This is the name of the qdisc we are attaching.
+  // Some hoop jumping to make this compile time constant with known size,
+  // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+  // sizeof() includes the terminating NULL
+  static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
+
+  const struct {
+    nlmsghdr n;
+    tcmsg t;
+    struct {
+      nlattr attr;
+      char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+    } kind;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = nlMsgType,
+              .nlmsg_flags =
+                  static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+              .tcm_parent = TC_H_CLSACT,
+          },
+      .kind =
+          {
+              .attr =
+                  {
+                      .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+                      .nla_type = TCA_KIND,
+                  },
+              .str = CLSACT,
+          },
+  };
+#undef CLSACT
+
+  return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
+// /sys/fs/bpf/... direct-action
+int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
+                   const char *bpfProgPath) {
+  const int bpfFd = bpf::retrieveProgram(bpfProgPath);
+  if (bpfFd == -1) {
+    ALOGE("retrieveProgram failed: %d", errno);
+    return -errno;
+  }
+  auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
+
+  struct {
+    nlmsghdr n;
+    tcmsg t;
+    struct {
+      nlattr attr;
+      // The maximum classifier name length is defined in
+      // tcf_proto_ops in include/net/sch_generic.h.
+      char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))];
+    } kind;
+    struct {
+      nlattr attr;
+      struct {
+        nlattr attr;
+        __u32 u32;
+      } fd;
+      struct {
+        nlattr attr;
+        char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+      } name;
+      struct {
+        nlattr attr;
+        __u32 u32;
+      } flags;
+    } options;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = RTM_NEWTFILTER,
+              .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_UNSPEC,
+              .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS
+                                                           : TC_H_MIN_EGRESS),
+              .tcm_info =
+                  static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+                                     htons(static_cast<uint16_t>(proto))),
+          },
+      .kind =
+          {
+              .attr =
+                  {
+                      .nla_len = sizeof(req.kind),
+                      .nla_type = TCA_KIND,
+                  },
+              .str = CLS_BPF_KIND_NAME,
+          },
+      .options =
+          {
+              .attr =
+                  {
+                      .nla_len = sizeof(req.options),
+                      .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+                  },
+              .fd =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.fd),
+                              .nla_type = TCA_BPF_FD,
+                          },
+                      .u32 = static_cast<__u32>(bpfFd),
+                  },
+              .name =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.name),
+                              .nla_type = TCA_BPF_NAME,
+                          },
+                      // Visible via 'tc filter show', but
+                      // is overwritten by strncpy below
+                      .str = "placeholder",
+                  },
+              .flags =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.flags),
+                              .nla_type = TCA_BPF_FLAGS,
+                          },
+                      .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+                  },
+          },
+  };
+
+  snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
+           basename(bpfProgPath));
+
+  int error = sendAndProcessNetlinkResponse(&req, sizeof(req));
+  return error;
+}
+
+// tc filter add dev .. ingress prio .. protocol .. matchall \
+//     action police rate .. burst .. conform-exceed pipe/continue \
+//     action bpf object-pinned .. \
+//     drop
+//
+// TODO: tc-police does not do ECN marking, so in the future, we should consider
+// adding a second tc-police filter at a lower priority that rate limits traffic
+// at something like 0.8 times the global rate limit and ecn marks exceeding
+// packets inside a bpf program (but does not drop them).
+int tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto,
+                             unsigned rateInBytesPerSec,
+                             const char *bpfProgPath) {
+  // TODO: this value needs to be validated.
+  // TCP IW10 (initial congestion window) means servers will send 10 mtus worth
+  // of data on initial connect.
+  // If nic is LRO capable it could aggregate up to 64KiB, so again probably a
+  // bad idea to set burst below that, because ingress packets could get
+  // aggregated to 64KiB at the nic.
+  // I don't know, but I wonder whether we shouldn't just do 128KiB and not do
+  // any math.
+  static constexpr unsigned BURST_SIZE_IN_BYTES = 128 * 1024; // 128KiB
+  IngressPoliceFilterBuilder filter(ifIndex, prio, proto, rateInBytesPerSec,
+                                    BURST_SIZE_IN_BYTES, bpfProgPath);
+  const int error = filter.build();
+  if (error) {
+    return error;
+  }
+  return sendAndProcessNetlinkResponse(filter.getRequest(),
+                                       filter.getRequestSize());
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
+  const struct {
+    nlmsghdr n;
+    tcmsg t;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = RTM_DELTFILTER,
+              .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_UNSPEC,
+              .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS
+                                                           : TC_H_MIN_EGRESS),
+              .tcm_info =
+                  static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+                                     htons(static_cast<uint16_t>(proto))),
+          },
+  };
+
+  return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+} // namespace android
diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp
new file mode 100644
index 0000000..7732247
--- /dev/null
+++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 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.
+ *
+ * TcUtilsTest.cpp - unit tests for TcUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "bpf/KernelUtils.h"
+#include <tcutils/tcutils.h>
+
+#include <BpfSyscallWrappers.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+
+namespace android {
+
+TEST(LibTcUtilsTest, IsEthernetOfNonExistingIf) {
+  bool result = false;
+  int error = isEthernet("not_existing_if", result);
+  ASSERT_FALSE(result);
+  ASSERT_EQ(-ENODEV, error);
+}
+
+TEST(LibTcUtilsTest, IsEthernetOfLoopback) {
+  bool result = false;
+  int error = isEthernet("lo", result);
+  ASSERT_FALSE(result);
+  ASSERT_EQ(-EAFNOSUPPORT, error);
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST(LibTcUtilsTest, IsEthernetOfWireless) {
+  bool result = false;
+  int error = isEthernet("wlan0", result);
+  if (!result && error == -ENODEV)
+    return;
+
+  ASSERT_EQ(0, error);
+  ASSERT_TRUE(result);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST(LibTcUtilsTest, IsEthernetOfCellular) {
+  bool result = false;
+  int error = isEthernet("rmnet_data0", result);
+  if (!result && error == -ENODEV)
+    return;
+
+  ASSERT_EQ(0, error);
+  ASSERT_FALSE(result);
+}
+
+// See Linux kernel source in include/net/flow.h
+static constexpr int LOOPBACK_IFINDEX = 1;
+
+TEST(LibTcUtilsTest, AttachReplaceDetachClsactLo) {
+  // This attaches and detaches a configuration-less and thus no-op clsact
+  // qdisc to loopback interface (and it takes fractions of a second)
+  EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(0, tcReplaceQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(-EINVAL, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+}
+
+TEST(LibTcUtilsTest, AddAndDeleteBpfFilter) {
+  // TODO: this should likely be in the tethering module, where using netd.h would be ok
+  static constexpr char bpfProgPath[] =
+      "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_ether";
+  const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+  // static test values
+  static constexpr bool ingress = true;
+  static constexpr uint16_t prio = 17;
+  static constexpr uint16_t proto = ETH_P_ALL;
+
+  // try to delete missing filter from missing qdisc
+  EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+  // try to attach bpf filter to missing qdisc
+  EXPECT_EQ(-EINVAL, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto,
+                                    bpfProgPath));
+  // add the clsact qdisc
+  EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+  // try to delete missing filter when there is a qdisc attached
+  EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+  // add and delete a bpf filter
+  EXPECT_EQ(
+      0, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto, bpfProgPath));
+  EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+  // try to remove the same filter a second time
+  EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+  // remove the clsact qdisc
+  EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+  // once again, try to delete missing filter from missing qdisc
+  EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+}
+
+TEST(LibTcUtilsTest, AddAndDeleteIngressPoliceFilter) {
+  // TODO: this should likely be in the tethering module, where using netd.h would be ok
+  static constexpr char bpfProgPath[] =
+      "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account";
+  int fd = bpf::retrieveProgram(bpfProgPath);
+  ASSERT_LE(3, fd);
+  close(fd);
+
+  const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+  // static test values
+  static constexpr unsigned rateInBytesPerSec =
+      1024 * 1024; // 8mbit/s => 1mbyte/s => 1024*1024 bytes/s.
+  static constexpr uint16_t prio = 17;
+  static constexpr uint16_t proto = ETH_P_ALL;
+
+  // try to delete missing filter from missing qdisc
+  EXPECT_EQ(-EINVAL,
+            tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+  // try to attach bpf filter to missing qdisc
+  EXPECT_EQ(-EINVAL, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto,
+                                              rateInBytesPerSec, bpfProgPath));
+  // add the clsact qdisc
+  EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+  // try to delete missing filter when there is a qdisc attached
+  EXPECT_EQ(-errNOENT,
+            tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+  // add and delete a bpf filter
+  EXPECT_EQ(0, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto,
+                                        rateInBytesPerSec, bpfProgPath));
+  EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+  // try to remove the same filter a second time
+  EXPECT_EQ(-errNOENT,
+            tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+  // remove the clsact qdisc
+  EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+  // once again, try to delete missing filter from missing qdisc
+  EXPECT_EQ(-EINVAL,
+            tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+}
+
+} // namespace android
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
new file mode 100644
index 0000000..d135a1c
--- /dev/null
+++ b/staticlibs/netd/Android.bp
@@ -0,0 +1,253 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "netd_aidl_interface-lateststable-java",
+    sdk_version: "system_current",
+    min_sdk_version: "29",
+    static_libs: [
+        "netd_aidl_interface-V13-java",
+    ],
+    apex_available: [
+        "//apex_available:platform", // used from services.net
+        "com.android.tethering",
+        "com.android.wifi",
+    ],
+}
+
+cc_library_static {
+    name: "netd_event_listener_interface-lateststable-ndk",
+    whole_static_libs: [
+        "netd_event_listener_interface-V1-ndk",
+    ],
+    apex_available: [
+        "com.android.resolv",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_library_static {
+    name: "netd_aidl_interface-lateststable-ndk",
+    whole_static_libs: [
+        "netd_aidl_interface-V13-ndk",
+    ],
+    apex_available: [
+        "com.android.resolv",
+        "com.android.tethering",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_defaults {
+    name: "netd_aidl_interface_lateststable_cpp_static",
+    static_libs: ["netd_aidl_interface-V13-cpp"],
+}
+
+cc_defaults {
+    name: "netd_aidl_interface_lateststable_cpp_shared",
+    shared_libs: ["netd_aidl_interface-V13-cpp"],
+}
+
+aidl_interface {
+    name: "netd_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/INetd.aidl",
+        // AIDL interface that callers can implement to receive networking events from netd.
+        "binder/android/net/INetdUnsolicitedEventListener.aidl",
+        "binder/android/net/InterfaceConfigurationParcel.aidl",
+        "binder/android/net/IpSecMigrateInfoParcel.aidl",
+        "binder/android/net/MarkMaskParcel.aidl",
+        "binder/android/net/NativeNetworkConfig.aidl",
+        "binder/android/net/NativeNetworkType.aidl",
+        "binder/android/net/NativeVpnType.aidl",
+        "binder/android/net/RouteInfoParcel.aidl",
+        "binder/android/net/TetherConfigParcel.aidl",
+        "binder/android/net/TetherOffloadRuleParcel.aidl",
+        "binder/android/net/TetherStatsParcel.aidl",
+        "binder/android/net/UidRangeParcel.aidl",
+        // Add new AIDL classes in android.net.netd.aidl to consist with other network modules.
+        "binder/android/net/netd/aidl/**/*.aidl",
+    ],
+    backend: {
+        cpp: {
+            gen_log: true,
+        },
+        java: {
+            // TODO: Remove apex_available and restrict visibility to only mainline modules that are
+            // either outside the system server or use jarjar to rename the generated AIDL classes.
+            apex_available: [
+                "//apex_available:platform", // used from services.net
+                "com.android.tethering",
+                "com.android.wifi",
+            ],
+            // this is part of updatable modules(NetworkStack) which targets 29(Q)
+            min_sdk_version: "29",
+        },
+        ndk: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.tethering",
+            ],
+            // This is necessary for the DnsResovler tests to run in Android Q.
+            // Soong would recognize this value and produce the Q compatible aidl library.
+            min_sdk_version: "29",
+        },
+    },
+    versions_with_info: [
+        {
+            version: "1",
+            imports: [],
+        },
+        {
+            version: "2",
+            imports: [],
+        },
+        {
+            version: "3",
+            imports: [],
+        },
+        {
+            version: "4",
+            imports: [],
+        },
+        {
+            version: "5",
+            imports: [],
+        },
+        {
+            version: "6",
+            imports: [],
+        },
+        {
+            version: "7",
+            imports: [],
+        },
+        {
+            version: "8",
+            imports: [],
+        },
+        {
+            version: "9",
+            imports: [],
+        },
+        {
+            version: "10",
+            imports: [],
+        },
+        {
+            version: "11",
+            imports: [],
+        },
+        {
+            version: "12",
+            imports: [],
+        },
+        {
+            version: "13",
+            imports: [],
+        },
+
+    ],
+
+}
+
+java_library {
+    name: "netd_event_listener_interface-lateststable-java",
+    sdk_version: "system_current",
+    min_sdk_version: "29",
+    static_libs: [
+        "netd_event_listener_interface-V1-java",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.wifi",
+        "com.android.tethering",
+    ],
+}
+
+aidl_interface {
+    name: "netd_event_listener_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/metrics/INetdEventListener.aidl",
+    ],
+
+    backend: {
+        ndk: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.resolv",
+            ],
+            min_sdk_version: "29",
+        },
+        java: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.wifi",
+                "com.android.tethering",
+            ],
+            min_sdk_version: "29",
+        },
+    },
+    versions_with_info: [
+        {
+            version: "1",
+            imports: [],
+        },
+        {
+            version: "2",
+            imports: [],
+        },
+
+    ],
+    frozen: true,
+
+}
+
+java_library {
+    name: "mdns_aidl_interface-lateststable-java",
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    static_libs: [
+        "mdns_aidl_interface-V1-java",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
+}
+
+aidl_interface {
+    name: "mdns_aidl_interface",
+    local_include_dir: "binder",
+    srcs: [
+        "binder/android/net/mdns/aidl/**/*.aidl",
+    ],
+    backend: {
+        java: {
+            sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.tethering",
+            ],
+            min_sdk_version: "30",
+        },
+    },
+    versions: ["1"],
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash
new file mode 100644
index 0000000..d952c59
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/.hash
@@ -0,0 +1 @@
+ae4cfe565d66acc7d816aabd0dfab991e64031ab
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl
new file mode 100644
index 0000000..d31a327
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/DiscoveryInfo.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable DiscoveryInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  @utf8InCpp String domainName;
+  int interfaceIdx;
+  int netId;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl
new file mode 100644
index 0000000..2049274
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/GetAddressInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable GetAddressInfo {
+  int id;
+  int result;
+  @utf8InCpp String hostname;
+  @utf8InCpp String address;
+  int interfaceIdx;
+  int netId;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl
new file mode 100644
index 0000000..ecbe966
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDns.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+interface IMDns {
+  void startDaemon();
+  void stopDaemon();
+  void registerService(in android.net.mdns.aidl.RegistrationInfo info);
+  void discover(in android.net.mdns.aidl.DiscoveryInfo info);
+  void resolve(in android.net.mdns.aidl.ResolutionInfo info);
+  void getServiceAddress(in android.net.mdns.aidl.GetAddressInfo info);
+  void stopOperation(int id);
+  void registerEventListener(in android.net.mdns.aidl.IMDnsEventListener listener);
+  void unregisterEventListener(in android.net.mdns.aidl.IMDnsEventListener listener);
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl
new file mode 100644
index 0000000..4625cac
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/IMDnsEventListener.aidl
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+interface IMDnsEventListener {
+  oneway void onServiceRegistrationStatus(in android.net.mdns.aidl.RegistrationInfo status);
+  oneway void onServiceDiscoveryStatus(in android.net.mdns.aidl.DiscoveryInfo status);
+  oneway void onServiceResolutionStatus(in android.net.mdns.aidl.ResolutionInfo status);
+  oneway void onGettingServiceAddressStatus(in android.net.mdns.aidl.GetAddressInfo status);
+  const int SERVICE_DISCOVERY_FAILED = 602;
+  const int SERVICE_FOUND = 603;
+  const int SERVICE_LOST = 604;
+  const int SERVICE_REGISTRATION_FAILED = 605;
+  const int SERVICE_REGISTERED = 606;
+  const int SERVICE_RESOLUTION_FAILED = 607;
+  const int SERVICE_RESOLVED = 608;
+  const int SERVICE_GET_ADDR_FAILED = 611;
+  const int SERVICE_GET_ADDR_SUCCESS = 612;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl
new file mode 100644
index 0000000..185111b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/RegistrationInfo.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable RegistrationInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  int port;
+  byte[] txtRecord;
+  int interfaceIdx;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl
new file mode 100644
index 0000000..4aa7d79
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/1/android/net/mdns/aidl/ResolutionInfo.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable ResolutionInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  @utf8InCpp String domain;
+  @utf8InCpp String serviceFullName;
+  @utf8InCpp String hostname;
+  int port;
+  byte[] txtRecord;
+  int interfaceIdx;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl
new file mode 100644
index 0000000..d31a327
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/DiscoveryInfo.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable DiscoveryInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  @utf8InCpp String domainName;
+  int interfaceIdx;
+  int netId;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl
new file mode 100644
index 0000000..2049274
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/GetAddressInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable GetAddressInfo {
+  int id;
+  int result;
+  @utf8InCpp String hostname;
+  @utf8InCpp String address;
+  int interfaceIdx;
+  int netId;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl
new file mode 100644
index 0000000..ecbe966
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDns.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+interface IMDns {
+  void startDaemon();
+  void stopDaemon();
+  void registerService(in android.net.mdns.aidl.RegistrationInfo info);
+  void discover(in android.net.mdns.aidl.DiscoveryInfo info);
+  void resolve(in android.net.mdns.aidl.ResolutionInfo info);
+  void getServiceAddress(in android.net.mdns.aidl.GetAddressInfo info);
+  void stopOperation(int id);
+  void registerEventListener(in android.net.mdns.aidl.IMDnsEventListener listener);
+  void unregisterEventListener(in android.net.mdns.aidl.IMDnsEventListener listener);
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl
new file mode 100644
index 0000000..4625cac
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/IMDnsEventListener.aidl
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+interface IMDnsEventListener {
+  oneway void onServiceRegistrationStatus(in android.net.mdns.aidl.RegistrationInfo status);
+  oneway void onServiceDiscoveryStatus(in android.net.mdns.aidl.DiscoveryInfo status);
+  oneway void onServiceResolutionStatus(in android.net.mdns.aidl.ResolutionInfo status);
+  oneway void onGettingServiceAddressStatus(in android.net.mdns.aidl.GetAddressInfo status);
+  const int SERVICE_DISCOVERY_FAILED = 602;
+  const int SERVICE_FOUND = 603;
+  const int SERVICE_LOST = 604;
+  const int SERVICE_REGISTRATION_FAILED = 605;
+  const int SERVICE_REGISTERED = 606;
+  const int SERVICE_RESOLUTION_FAILED = 607;
+  const int SERVICE_RESOLVED = 608;
+  const int SERVICE_GET_ADDR_FAILED = 611;
+  const int SERVICE_GET_ADDR_SUCCESS = 612;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl
new file mode 100644
index 0000000..185111b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/RegistrationInfo.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable RegistrationInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  int port;
+  byte[] txtRecord;
+  int interfaceIdx;
+}
diff --git a/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl
new file mode 100644
index 0000000..4aa7d79
--- /dev/null
+++ b/staticlibs/netd/aidl_api/mdns_aidl_interface/current/android/net/mdns/aidl/ResolutionInfo.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.mdns.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable ResolutionInfo {
+  int id;
+  int result;
+  @utf8InCpp String serviceName;
+  @utf8InCpp String registrationType;
+  @utf8InCpp String domain;
+  @utf8InCpp String serviceFullName;
+  @utf8InCpp String hostname;
+  int port;
+  byte[] txtRecord;
+  int interfaceIdx;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash
new file mode 100644
index 0000000..d33e903
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/.hash
@@ -0,0 +1 @@
+69c2ac134efbb31e9591d7e5c3640fb839e23bdb
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl
new file mode 100644
index 0000000..664c643
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetd.aidl
@@ -0,0 +1,132 @@
+package android.net;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..18631ff
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,14 @@
+package android.net;
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..93407dc
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..d1782bb
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,8 @@
+package android.net;
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..d3bc7ed
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/1/android/net/UidRangeParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash
new file mode 100644
index 0000000..6ec3613
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/.hash
@@ -0,0 +1 @@
+3943383e838f39851675e3640fcdf27b42f8c9fc
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl
new file mode 100644
index 0000000..e671fdb
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetd.aidl
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int CLAT_MARK = -559038041;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/10/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash
new file mode 100644
index 0000000..d22cb04
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/.hash
@@ -0,0 +1 @@
+d384a1d9f2f6dc0301a43d2b9d6d3a49e3898ae7
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl
new file mode 100644
index 0000000..e671fdb
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetd.aidl
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int CLAT_MARK = -559038041;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+  PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/11/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash
new file mode 100644
index 0000000..7a4d53c
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/.hash
@@ -0,0 +1 @@
+5a3a5f46fe31c118889d7a92be4faafb3df651ff
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl
new file mode 100644
index 0000000..763b92a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetd.aidl
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int CLAT_MARK = -559038041;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+  const int IPSEC_DIRECTION_IN = 0;
+  const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+  int requestId;
+  int selAddrFamily;
+  int direction;
+  @utf8InCpp String oldSourceAddress;
+  @utf8InCpp String oldDestinationAddress;
+  @utf8InCpp String newSourceAddress;
+  @utf8InCpp String newDestinationAddress;
+  int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+  PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/12/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash
new file mode 100644
index 0000000..d3c72cf
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/.hash
@@ -0,0 +1 @@
+38614f80a23b92603d4851177e57c460aec1b606
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl
new file mode 100644
index 0000000..3507784
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetd.aidl
@@ -0,0 +1,226 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+  void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int CLAT_MARK = 0xdeadc1a7;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = (-1);
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+  const int IPSEC_DIRECTION_IN = 0;
+  const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+  int requestId;
+  int selAddrFamily;
+  int direction;
+  @utf8InCpp String oldSourceAddress;
+  @utf8InCpp String oldDestinationAddress;
+  @utf8InCpp String newSourceAddress;
+  @utf8InCpp String newDestinationAddress;
+  int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+  PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/13/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash
new file mode 100644
index 0000000..5fc5b2d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/.hash
@@ -0,0 +1 @@
+e395d63302c47e7d2dac0d503045779029ff598b
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl
new file mode 100644
index 0000000..0e2d5f4
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetd.aidl
@@ -0,0 +1,153 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..621f1cf
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..18de61f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..c0ba676
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..c2c35db
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/2/android/net/UidRangeParcel.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash
new file mode 100644
index 0000000..59cf708
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/.hash
@@ -0,0 +1 @@
+e17c1f9b2068b539b22e3a4a447edea3c80aee4b
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl
new file mode 100644
index 0000000..135b738
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetd.aidl
@@ -0,0 +1,161 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..3abf0f8
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..71ffb9b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..84ff457
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/3/android/net/UidRangeParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash
new file mode 100644
index 0000000..0c3f810
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/.hash
@@ -0,0 +1 @@
+63adaa5098e4d8621e90c5a84f7cb93505c79311
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl
new file mode 100644
index 0000000..47e2931
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetd.aidl
@@ -0,0 +1,164 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isWhitelist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c9d8458
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..0b0960e
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..84ff457
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/4/android/net/UidRangeParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash
new file mode 100644
index 0000000..a6ced45
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/.hash
@@ -0,0 +1 @@
+d97c56dd789cee9eeb5cdcec43a99df0a01873a5
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl
new file mode 100644
index 0000000..b30748a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetd.aidl
@@ -0,0 +1,167 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  void networkCreatePhysical(int netId, int permission);
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  const @JavaPassthrough(annotation="@Deprecated") int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  const @JavaPassthrough(annotation="@Deprecated") int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..4459363
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..01e0f95
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..62be838
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5e0ee62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..b136454
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c9d8458
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..0b0960e
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..debc6be
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/5/android/net/UidRangeParcel.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash
new file mode 100644
index 0000000..f5acf5d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/.hash
@@ -0,0 +1 @@
+b08451d9673b09cba84f1fd8740e1fdac64ff7be
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl
new file mode 100644
index 0000000..a7952f2
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetd.aidl
@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..76562b2
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/6/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash
new file mode 100644
index 0000000..cad59df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/.hash
@@ -0,0 +1 @@
+850353de5d19a0dd718f8fd20791f0532e6a34c7
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl
new file mode 100644
index 0000000..ec03d86
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetd.aidl
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..76562b2
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/7/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash
new file mode 100644
index 0000000..0933816
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash
@@ -0,0 +1 @@
+e8cf8586fc5da9063818d8775e9a21c4b0addb5b
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl
new file mode 100644
index 0000000..ec03d86
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash
new file mode 100644
index 0000000..94dd240
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/.hash
@@ -0,0 +1 @@
+2bffe06ea8c13f35f90b86d6dfd1a2b4c4d4daf5
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl
new file mode 100644
index 0000000..c780a59
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetd.aidl
@@ -0,0 +1,221 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/9/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
new file mode 100644
index 0000000..3507784
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -0,0 +1,226 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+   */
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  /**
+   * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+   */
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+  void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int CLAT_MARK = 0xdeadc1a7;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = (-1);
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+  const int IPSEC_DIRECTION_IN = 0;
+  const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+  int requestId;
+  int selAddrFamily;
+  int direction;
+  @utf8InCpp String oldSourceAddress;
+  @utf8InCpp String oldDestinationAddress;
+  @utf8InCpp String newSourceAddress;
+  @utf8InCpp String newDestinationAddress;
+  int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+  PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash
new file mode 100644
index 0000000..f39f730
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/.hash
@@ -0,0 +1 @@
+8e27594d285ca7c567d87e8cf74766c27647e02b
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..9898a67
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/1/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
+// try to edit this file. It looks like you are doing that because you have
+// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
+// function from an interface or a field from a parcelable and it broke the
+// build. That breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash
new file mode 100644
index 0000000..67c55b7
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash
@@ -0,0 +1 @@
+1b765b02815e970a124de92e793e42e0ceff5384
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..1b0fe13
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+/* @hide */
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..d71c3f2
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/current/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,35 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+/* @hide */
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/staticlibs/netd/binder/android/net/INetd.aidl b/staticlibs/netd/binder/android/net/INetd.aidl
new file mode 100644
index 0000000..27d9a03
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/INetd.aidl
@@ -0,0 +1,1438 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.INetdUnsolicitedEventListener;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpSecMigrateInfoParcel;
+import android.net.MarkMaskParcel;
+import android.net.NativeNetworkConfig;
+import android.net.RouteInfoParcel;
+import android.net.TetherConfigParcel;
+import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
+import android.net.UidRangeParcel;
+import android.net.netd.aidl.NativeUidRangeConfig;
+
+/** {@hide} */
+interface INetd {
+    /**
+     * Returns true if the service is responding.
+     */
+    boolean isAlive();
+
+    /**
+     * Replaces the contents of the specified UID-based firewall chain.
+     *
+     * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
+     * rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
+     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+     * UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
+     *
+     * @param chainName The name of the chain to replace.
+     * @param isAllowlist Whether this is an allowlist or denylist chain.
+     * @param uids The list of UIDs to allow/deny.
+     * @return true if the chain was successfully replaced, false otherwise.
+     */
+    boolean firewallReplaceUidChain(in @utf8InCpp String chainName,
+                                    boolean isAllowlist,
+                                    in int[] uids);
+
+    /**
+     * Enables or disables data saver mode on costly network interfaces.
+     *
+     * - When disabled, all packets to/from apps in the penalty box chain are rejected on costly
+     *   interfaces. Traffic to/from other apps or on other network interfaces is allowed.
+     * - When enabled, only apps that are in the happy box chain and not in the penalty box chain
+     *   are allowed network connectivity on costly interfaces. All other packets on these
+     *   interfaces are rejected. The happy box chain always contains all system UIDs; to disallow
+     *   traffic from system UIDs, place them in the penalty box chain.
+     *
+     * By default, data saver mode is disabled. This command has no effect but might still return an
+     * error) if {@code enable} is the same as the current value.
+     *
+     * @param enable whether to enable or disable data saver mode.
+     * @return true if the if the operation was successful, false otherwise.
+     */
+    boolean bandwidthEnableDataSaver(boolean enable);
+
+    /**
+     * Creates a physical network (i.e., one containing physical interfaces.
+     * @deprecated use networkCreate() instead.
+     *
+     * @param netId the networkId to create.
+     * @param permission the permission necessary to use the network. Must be one of
+     *         PERMISSION_NONE/PERMISSION_NETWORK/PERMISSION_SYSTEM.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkCreatePhysical(int netId, int permission);
+
+    /**
+     * Creates a VPN network.
+     * @deprecated use networkCreate() instead.
+     *
+     * @param netId the network to create.
+     * @param secure whether unprivileged apps are allowed to bypass the VPN.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkCreateVpn(int netId, boolean secure);
+
+    /**
+     * Destroys a network. Any interfaces added to the network are removed, and the network ceases
+     * to be the default network.
+     *
+     * @param netId the network to destroy.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkDestroy(int netId);
+
+    /**
+     * Adds an interface to a network. The interface must not be assigned to any network, including
+     * the specified network.
+     *
+     * @param netId the network to add the interface to.
+     * @param interface the name of the interface to add.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkAddInterface(int netId, in @utf8InCpp String iface);
+
+    /**
+     * Adds an interface to a network. The interface must be assigned to the specified network.
+     *
+     * @param netId the network to remove the interface from.
+     * @param interface the name of the interface to remove.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+
+    /**
+     * Adds the specified UID ranges to the specified network. The network can be physical or
+     * virtual. Traffic from the UID ranges will be routed to the network by default.
+     *
+     * @param netId the network ID of the network to add the ranges to.
+     * @param uidRanges a set of non-overlapping ranges of UIDs to add. These exact ranges
+     *        must not overlap with existing ranges assigned to this network.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkAddUidRanges(int netId, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Remove the specified UID ranges from the specified network. The network can be physical or
+     * virtual. Traffic from the UID ranges will no longer be routed to the network by default.
+     *
+     * @param netId the network ID of the network to remove the ranges from.
+     * @param uidRanges a set of non-overlapping ranges of UIDs to remove. These exact ranges
+     *        must already be assigned to this network.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRemoveUidRanges(int netId, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Adds or removes one rule for each supplied UID range to prohibit all network activity outside
+     * of secure VPN.
+     *
+     * When a UID is covered by one of these rules, traffic sent through any socket that is not
+     * protected or explicitly overriden by the system will be rejected. The kernel will respond
+     * with an ICMP prohibit message.
+     *
+     * Initially, there are no such rules. Any rules that are added will only last until the next
+     * restart of netd or the device.
+     *
+     * @param add {@code true} if the specified UID ranges should be denied access to any network
+     *        which is not secure VPN by adding rules, {@code false} to remove existing rules.
+     * @param uidRanges a set of non-overlapping, contiguous ranges of UIDs to which to apply or
+     *        remove this restriction.
+     *        <p> Added rules should not overlap with existing rules. Likewise, removed rules should
+     *        each correspond to an existing rule.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRejectNonSecureVpn(boolean add, in UidRangeParcel[] uidRanges);
+
+    /**
+     * Administratively closes sockets belonging to the specified UIDs.
+     */
+    void socketDestroy(in UidRangeParcel[] uidRanges, in int[] exemptUids);
+
+    /**
+     * Instruct the tethering DNS server to reevaluated serving interfaces.
+     * This is needed to for the DNS server to observe changes in the set
+     * of potential listening IP addresses. (Listening on wildcard addresses
+     * can turn the device into an open resolver; b/7530468)
+     *
+     * TODO: Return something richer than just a boolean.
+     */
+    boolean tetherApplyDnsInterfaces();
+
+    /**
+     * Return tethering statistics.
+     *
+     * @return an array of TetherStatsParcel, where each entry contains the upstream interface
+     *         name and its tethering statistics since netd startup.
+     *         There will only ever be one entry for a given interface.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    TetherStatsParcel[] tetherGetStats();
+
+    /**
+     * Add/Remove and IP address from an interface.
+     *
+     * @param ifName the interface name
+     * @param addrString the IP address to add/remove as a string literal
+     * @param prefixLength the prefix length associated with this IP address
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString,
+            int prefixLength);
+    void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString,
+            int prefixLength);
+
+    /**
+     * Set and get /proc/sys/net interface configuration parameters.
+     *
+     * @param ipversion One of IPV4/IPV6 integers, indicating the desired IP version directory.
+     * @param which One of CONF/NEIGH integers, indicating the desired parameter category directory.
+     * @param ifname The interface name portion of the path; may also be "all" or "default".
+     * @param parameter The parameter name portion of the path.
+     * @param value The value string to be written into the assembled path.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+
+    const int IPV4  = 4;
+    const int IPV6  = 6;
+    const int CONF  = 1;
+    const int NEIGH = 2;
+    @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname,
+            in @utf8InCpp String parameter);
+    void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname,
+            in @utf8InCpp String parameter, in @utf8InCpp String value);
+
+   /**
+    * Sets owner of socket ParcelFileDescriptor to the new UID, checking to ensure that the caller's
+    * uid is that of the old owner's, and that this is a UDP-encap socket
+    *
+    * @param ParcelFileDescriptor socket Socket file descriptor
+    * @param int newUid UID of the new socket fd owner
+    */
+    void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+
+   /**
+    * Reserve an SPI from the kernel
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a requested 32-bit unique ID or 0 to request random allocation
+    * @return the SPI that was allocated or 0 if failed
+    */
+    int ipSecAllocateSpi(
+            int transformId,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi);
+
+   /**
+    * Update an IPsec SA (xfrm_state) describing how ip(v6) traffic will be encrypted
+    * or decrypted.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param mode either Transport or Tunnel mode
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param underlyingNetId the netId of the network to which the SA is applied. Only accepted for
+    *        tunnel mode SAs.
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param authAlgo a string identifying the authentication algorithm to be used
+    * @param authKey a byte array containing the authentication key
+    * @param authTruncBits the truncation length of the MAC produced by the authentication algorithm
+    * @param cryptAlgo a string identifying the encryption algorithm to be used
+    * @param cryptKey a byte arrray containing the encryption key
+    * @param cryptTruncBits unused parameter
+    * @param aeadAlgo a string identifying the authenticated encryption algorithm to be used
+    * @param aeadKey a byte arrray containing the key to be used in authenticated encryption
+    * @param aeadIcvBits the truncation length of the ICV produced by the authentication algorithm
+    *        (similar to authTruncBits in function)
+    * @param encapType encapsulation type used (if any) for the udp encap socket
+    * @param encapLocalPort the port number on the host to be used in encap packets
+    * @param encapRemotePort the port number of the remote to be used for encap packets
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    *        Only accepted for tunnel mode SAs.
+    */
+    void ipSecAddSecurityAssociation(
+            int transformId,
+            int mode,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int underlyingNetId,
+            int spi,
+            int markValue,
+            int markMask,
+            in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits,
+            in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits,
+            in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits,
+            int encapType,
+            int encapLocalPort,
+            int encapRemotePort,
+            int interfaceId);
+
+   /**
+    * Delete a previously created security association identified by the provided parameters
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a requested 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecDeleteSecurityAssociation(
+            int transformId,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Apply a previously created SA to a specified socket, starting IPsec on that socket
+    *
+    * @param socket a user-provided socket that will have IPsec applied
+    * @param transformId a unique identifier for allocated resources
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param sourceAddress InetAddress as string for the sending endpoint
+    * @param destinationAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user (socket owner)
+    */
+    void ipSecApplyTransportModeTransform(
+            in ParcelFileDescriptor socket,
+            int transformId,
+            int direction,
+            in @utf8InCpp String sourceAddress,
+            in @utf8InCpp String destinationAddress,
+            int spi);
+
+   /**
+    * Remove an IPsec SA from a given socket. This will allow unencrypted traffic to flow
+    * on that socket if a transform had been previously applied.
+    *
+    * @param socket a user-provided socket from which to remove any IPsec configuration
+    */
+    void ipSecRemoveTransportModeTransform(
+            in ParcelFileDescriptor socket);
+
+   /**
+    * Adds an IPsec global policy.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecAddSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Updates an IPsec global policy.
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param tmplSrcAddress InetAddress as string for the sending endpoint
+    * @param tmplDstAddress InetAddress as string for the receiving endpoint
+    * @param spi a 32-bit unique ID allocated to the user
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecUpdateSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            in @utf8InCpp String tmplSrcAddress,
+            in @utf8InCpp String tmplDstAddress,
+            int spi,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+   /**
+    * Deletes an IPsec global policy.
+    *
+    * Deletion of global policies does not do any matching based on the templates, thus
+    * template source/destination addresses are not needed (as opposed to add/update).
+    *
+    * @param transformId a unique identifier for allocated resources
+    * @param selAddrFamily the address family identifier for the selector
+    * @param direction DIRECTION_IN or DIRECTION_OUT
+    * @param markValue a 32-bit unique ID chosen by the user
+    * @param markMask a 32-bit mask chosen by the user
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecDeleteSecurityPolicy(
+            int transformId,
+            int selAddrFamily,
+            int direction,
+            int markValue,
+            int markMask,
+            int interfaceId);
+
+    // This could not be declared as @uft8InCpp; thus, when used in native code it must be
+    // converted from a UTF-16 string to an ASCII string.
+    const String IPSEC_INTERFACE_PREFIX = "ipsec";
+
+   /**
+    * Add a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param iKey, to match Policies and SAs for input packets.
+    * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecAddTunnelInterface(
+            in @utf8InCpp String deviceName,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int iKey,
+            int oKey,
+            int interfaceId);
+
+   /**
+    * Update a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    * @param localAddress InetAddress as string for the local endpoint
+    * @param remoteAddress InetAddress as string for the remote endpoint
+    * @param iKey, to match Policies and SAs for input packets.
+    * @param oKey, to match Policies and SAs for output packets.
+    * @param interfaceId the identifier for the IPsec tunnel interface.
+    */
+    void ipSecUpdateTunnelInterface(
+            in @utf8InCpp String deviceName,
+            in @utf8InCpp String localAddress,
+            in @utf8InCpp String remoteAddress,
+            int iKey,
+            int oKey,
+            int interfaceId);
+
+   /**
+    * Removes a IPsec Tunnel Interface.
+    *
+    * @param devName a unique identifier that represents the name of the device
+    */
+    void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+
+   /**
+    * Request notification of wakeup packets arriving on an interface. Notifications will be
+    * delivered to INetdEventListener.onWakeupEvent().
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+   /**
+    * Stop notification of wakeup packets arriving on an interface.
+    *
+    * @param ifName the interface
+    * @param prefix arbitrary string used to identify wakeup sources in onWakeupEvent
+    */
+    void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+
+    const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+    const int IPV6_ADDR_GEN_MODE_NONE = 1;
+    const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+    const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+
+    const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+   /**
+    * Set IPv6 address generation mode. IPv6 should be disabled before changing mode.
+    *
+    * @param mode SLAAC address generation mechanism to use
+    */
+    void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+
+   /**
+    * Add idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void idletimerAddInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+   /**
+    * Remove idletimer for specific interface
+    *
+    * @param ifName Name of target interface
+    * @param timeout The time in seconds that will trigger idletimer
+    * @param classLabel The unique identifier for this idletimer
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void idletimerRemoveInterface(
+            in @utf8InCpp String ifName,
+            int timeout,
+            in @utf8InCpp String classLabel);
+
+    const int PENALTY_POLICY_ACCEPT = 1;
+    const int PENALTY_POLICY_LOG = 2;
+    const int PENALTY_POLICY_REJECT = 3;
+
+   /**
+    * Offers to detect sockets sending data not wrapped inside a layer of SSL/TLS encryption.
+    *
+    * @param uid Uid of the app
+    * @param policyPenalty The penalty policy of the app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void strictUidCleartextPenalty(int uid, int policyPenalty);
+
+   /**
+    * Start clatd
+    *
+    * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd
+    *             control plane moved to the mainline module starting in T. See ClatCoordinator.
+    * @param ifName interface name to start clatd
+    * @param nat64Prefix the NAT64 prefix, e.g., "2001:db8:64::/96".
+    * @return a string, the IPv6 address that will be used for 464xlat.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+
+   /**
+    * Stop clatd
+    *
+    * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd
+    *             control plane moved to the mainline module starting in T. See ClatCoordinator.
+    * @param ifName interface name to stop clatd
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void clatdStop(in @utf8InCpp String ifName);
+
+    /**
+     * Packet mark that identifies non-offloaded ingress clat packets.
+     */
+    const int CLAT_MARK = 0xdeadc1a7;
+
+   /**
+    * Get status of IP forwarding
+    *
+    * @return true if IP forwarding is enabled, false otherwise.
+    */
+    boolean ipfwdEnabled();
+
+   /**
+    * Get requester list of IP forwarding
+    *
+    * @return An array of strings containing requester list of IP forwarding
+    */
+    @utf8InCpp String[] ipfwdGetRequesterList();
+
+   /**
+    * Enable IP forwarding for specific requester
+    *
+    * @param requester requester name to enable IP forwarding. It is a unique name which will be
+    *                  stored in Netd to make sure if any requester needs IP forwarding.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdEnableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Disable IP forwarding for specific requester
+    *
+    * @param requester requester name to disable IP forwarding. This name should match the
+    *                  names which are set by ipfwdEnableForwarding.
+    *                  IP forwarding would be disabled if it is the last requester.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdDisableForwarding(in @utf8InCpp String requester);
+
+   /**
+    * Add forwarding ip rule
+    *
+    * @param fromIface interface name to add forwarding ip rule
+    * @param toIface interface name to add forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Remove forwarding ip rule
+    *
+    * @param fromIface interface name to remove forwarding ip rule
+    * @param toIface interface name to remove forwarding ip rule
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+
+   /**
+    * Set quota for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Quota value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove quota for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+
+   /**
+    * Set alert for interface
+    *
+    * @param ifName Name of target interface
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+
+   /**
+    * Remove alert for interface
+    *
+    * @param ifName Name of target interface
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+
+   /**
+    * Set global alert
+    *
+    * @param bytes Alert value in bytes
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthSetGlobalAlert(long bytes);
+
+   /**
+    * Add naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNaughtyApp(int uid);
+
+   /**
+    * Remove naughty app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNaughtyApp(int uid);
+
+   /**
+    * Add nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthAddNiceApp(int uid);
+
+   /**
+    * Remove nice app bandwidth rule for specific app
+    *
+    * @param uid uid of target app
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void bandwidthRemoveNiceApp(int uid);
+
+   /**
+    * Start tethering
+    *
+    * @param dhcpRanges dhcp ranges to set.
+    *                   dhcpRanges might contain many addresss {addr1, addr2, aadr3, addr4...}
+    *                   Netd splits them into ranges: addr1-addr2, addr3-addr4, etc.
+    *                   An odd number of addrs will fail.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStart(in @utf8InCpp String[] dhcpRanges);
+
+   /**
+    * Stop tethering
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStop();
+
+   /**
+    * Get status of tethering
+    *
+    * @return true if tethering is enabled, false otherwise.
+    */
+    boolean tetherIsEnabled();
+
+   /**
+    * Setup interface for tethering
+    *
+    * @param ifName interface name to add
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceAdd(in @utf8InCpp String ifName);
+
+   /**
+    * Reset interface for tethering
+    *
+    * @param ifName interface name to remove
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherInterfaceRemove(in @utf8InCpp String ifName);
+
+   /**
+    * Get the interface list which is stored in netd
+    * The list contains the interfaces managed by tetherInterfaceAdd/tetherInterfaceRemove
+    *
+    * @return An array of strings containing interface list result
+    */
+    @utf8InCpp String[] tetherInterfaceList();
+
+   /**
+    * Set DNS forwarder server
+    *
+    * @param netId the upstream network to forward DNS queries to
+    * @param dnsAddrs DNS server address to set
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+
+   /**
+    * Return the DNS list set by tetherDnsSet
+    *
+    * @return An array of strings containing the list of DNS servers
+    */
+    @utf8InCpp String[] tetherDnsList();
+
+    const int LOCAL_NET_ID = 99;
+
+    /**
+     * Constant net ID for the "dummy" network.
+     *
+     * The dummy network is used to blackhole or reject traffic. Any attempt to use it will
+     * either drop the packets or fail with ENETUNREACH.
+     */
+    const int DUMMY_NET_ID = 51;
+
+    /**
+     * Constant net ID for the "unreachable" network.
+     *
+     * The unreachable network is used to reject traffic. Any attempt to use it will fail
+     * with ENETUNREACH.
+     */
+    const int UNREACHABLE_NET_ID = 52;
+
+    // Route does not specify a next hop
+    const String NEXTHOP_NONE = "";
+    // Route next hop is unreachable
+    const String NEXTHOP_UNREACHABLE = "unreachable";
+    // Route next hop is throw
+    const String NEXTHOP_THROW = "throw";
+
+   /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Remove a route for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop);
+
+   /**
+    * Add a route to legacy routing table for specific network
+    *
+    * @param netId the network to add the route to
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Remove a route from legacy routing table for specific network
+    *
+    * @param netId the network to remove the route from
+    * @param ifName the name of interface of the route.
+    *               This interface should be assigned to the netID.
+    * @param destination the destination of the route
+    * @param nextHop The route's next hop address,
+    *                or it could be either NEXTHOP_NONE, NEXTHOP_UNREACHABLE, NEXTHOP_THROW.
+    * @param uid uid of the user
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveLegacyRoute(
+            int netId,
+            in @utf8InCpp String ifName,
+            in @utf8InCpp String destination,
+            in @utf8InCpp String nextHop,
+            int uid);
+
+   /**
+    * Get default network
+    *
+    * @return netId of default network
+    */
+    int networkGetDefault();
+
+   /**
+    * Set network as default network
+    *
+    * @param netId the network to set as the default
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetDefault(int netId);
+
+   /**
+    * Clear default network
+    *
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkClearDefault();
+
+   /**
+    * PERMISSION_NONE is used for regular networks and apps. TODO: use PERMISSION_INTERNET
+    * for this instead, and use PERMISSION_NONE to indicate no network permissions at all.
+    */
+    const int PERMISSION_NONE = 0;
+
+   /**
+    * PERMISSION_NETWORK represents the CHANGE_NETWORK_STATE permission.
+    */
+    const int PERMISSION_NETWORK = 1;
+
+   /**
+    * PERMISSION_SYSTEM represents the ability to use restricted networks. This is mostly
+    * equivalent to the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+    */
+    const int PERMISSION_SYSTEM = 2;
+
+   /**
+    * NO_PERMISSIONS indicates that this app is installed and doesn't have either
+    * PERMISSION_INTERNET or PERMISSION_UPDATE_DEVICE_STATS.
+    * TODO: use PERMISSION_NONE to represent this case
+    */
+    const int NO_PERMISSIONS = 0;
+
+   /**
+    * PERMISSION_INTERNET indicates that the app can create AF_INET and AF_INET6 sockets
+    */
+    const int PERMISSION_INTERNET = 4;
+
+   /**
+    * PERMISSION_UPDATE_DEVICE_STATS is used for system UIDs and privileged apps
+    * that have the UPDATE_DEVICE_STATS permission
+    */
+    const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+
+   /**
+    * PERMISSION_UNINSTALLED is used when an app is uninstalled from the device. All internet
+    * related permissions need to be cleaned
+    */
+    const int PERMISSION_UNINSTALLED = -1;
+
+
+   /**
+    * Sets the permission required to access a specific network.
+    *
+    * @param netId the network to set
+    * @param permission network permission to use
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkSetPermissionForNetwork(int netId, int permission);
+
+   /**
+    * Assigns network access permissions to the specified users.
+    *
+    * @param permission network permission to use
+    * @param uids uid of users to set permission
+    */
+    void networkSetPermissionForUser(int permission, in int[] uids);
+
+   /**
+    * Clears network access permissions for the specified users.
+    *
+    * @param uids uid of users to clear permission
+    */
+    void networkClearPermissionForUser(in int[] uids);
+
+   /**
+    * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+    * specified. Or remove all permissions from the uids.
+    *
+    * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
+    *                   PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+    *                   revoke all permissions for the uids.
+    * @param uids uid of users to grant permission
+    */
+    void trafficSetNetPermForUids(int permission, in int[] uids);
+
+   /**
+    * Gives the specified user permission to protect sockets from VPNs.
+    * Typically used by VPN apps themselves, to ensure that the sockets
+    * they use to communicate with the VPN server aren't routed through
+    * the VPN network.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectAllow(int uid);
+
+   /**
+    * Removes the permission to protect sockets from VPN.
+    *
+    * @param uid uid of user to set
+    */
+    void networkSetProtectDeny(int uid);
+
+   /**
+    * Get the status of network protect for user
+    *
+    * @param uids uid of user
+    * @return true if the user can protect sockets from VPN, false otherwise.
+    */
+    boolean networkCanProtect(int uid);
+
+    /** Only allows packets from specific UID/Interface.
+        @deprecated use FIREWALL_ALLOWLIST. */
+    const int FIREWALL_WHITELIST = 0;
+
+    /** Only allows packets from specific UID/Interface. */
+    const int FIREWALL_ALLOWLIST = 0;
+
+    /** Blocks packets from specific UID/Interface.
+        @deprecated use FIREWALL_DENYLIST. */
+    const int FIREWALL_BLACKLIST = 1;
+
+    /** Blocks packets from specific UID/Interface. */
+    const int FIREWALL_DENYLIST = 1;
+
+   /**
+    * Set type of firewall
+    * Type allowlist only allows packets from specific UID/Interface
+    * Type denylist blocks packets from specific UID/Interface
+    *
+    * @param firewalltype type of firewall, either FIREWALL_ALLOWLIST or FIREWALL_DENYLIST
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetFirewallType(int firewalltype);
+
+    // Specify allow Rule which allows packets
+    const int FIREWALL_RULE_ALLOW = 1;
+    // Specify deny Rule which drops packets
+    const int FIREWALL_RULE_DENY = 2;
+
+    // No specific chain is chosen, use general firewall chain(fw_input, fw_output)
+    const int FIREWALL_CHAIN_NONE = 0;
+    // Specify DOZABLE chain(fw_dozable) which is used in dozable mode
+    const int FIREWALL_CHAIN_DOZABLE = 1;
+    // Specify STANDBY chain(fw_standby) which is used in standby mode
+    const int FIREWALL_CHAIN_STANDBY = 2;
+    // Specify POWERSAVE chain(fw_powersave) which is used in power save mode
+    const int FIREWALL_CHAIN_POWERSAVE = 3;
+    // Specify RESTRICTED chain(fw_restricted) which is used in restricted
+    // networking mode
+    const int FIREWALL_CHAIN_RESTRICTED = 4;
+
+   /**
+    * Set firewall rule for interface
+    *
+    * @param ifName the interface to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+
+   /**
+    * Set firewall rule for uid
+    *
+    * @param childChain target chain
+    * @param uid uid to allow/deny
+    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallSetUidRule(int childChain, int uid, int firewallRule);
+
+   /**
+    * Enable/Disable target firewall child chain
+    *
+    * @param childChain target chain to enable
+    * @param enable whether to enable or disable child chain.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void firewallEnableChildChain(int childChain, boolean enable);
+
+   /**
+    * Get interface list
+    *
+    * @return An array of strings containing all the interfaces on the system.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    @utf8InCpp String[] interfaceGetList();
+
+    // Must be kept in sync with constant in InterfaceConfiguration.java
+    const String IF_STATE_UP = "up";
+    const String IF_STATE_DOWN = "down";
+
+    const String IF_FLAG_BROADCAST = "broadcast";
+    const String IF_FLAG_LOOPBACK = "loopback";
+    const String IF_FLAG_POINTOPOINT = "point-to-point";
+    const String IF_FLAG_RUNNING = "running";
+    const String IF_FLAG_MULTICAST = "multicast";
+
+   /**
+    * Get interface configuration
+    *
+    * @param ifName interface name
+    * @return An InterfaceConfigurationParcel for the specified interface.
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+
+   /**
+    * Set interface configuration
+    *
+    * @param cfg Interface configuration to set
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         unix errno.
+    */
+    void interfaceSetCfg(in InterfaceConfigurationParcel cfg);
+
+   /**
+    * Set interface IPv6 privacy extensions
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Clear all IP addresses on the given interface
+    *
+    * @param ifName interface name
+    * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+    *         POSIX errno.
+    */
+    void interfaceClearAddrs(in @utf8InCpp String ifName);
+
+   /**
+    * Enable or disable IPv6 on the given interface
+    *
+    * @param ifName interface name
+    * @param enable whether to enable or disable this setting.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+
+   /**
+    * Set interface MTU
+    *
+    * @param ifName interface name
+    * @param mtu MTU value
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+
+   /**
+    * Add forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Remove forwarding rule/stats on given interface.
+    *
+    * @param intIface downstream interface
+    * @param extIface upstream interface
+    */
+    void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+
+   /**
+    * Set the values of tcp_{rmem,wmem}.
+    *
+    * @param rmemValues the target values of tcp_rmem, each value is separated by spaces
+    * @param wmemValues the target values of tcp_wmem, each value is separated by spaces
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+
+   /**
+    * Register unsolicited event listener
+    * Netd supports multiple unsolicited event listeners.
+    *
+    * @param listener unsolicited event listener to register
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void registerUnsolicitedEventListener(INetdUnsolicitedEventListener listener);
+
+    /**
+     * Add ingress interface filtering rules to a list of UIDs
+     *
+     * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
+     * allowed interface and loopback to be sent to the list of UIDs.
+     *
+     * Calling this method on one or more UIDs with an existing filtering rule but a different
+     * interface name will result in the filtering rule being updated to allow the new interface
+     * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
+     *
+     * @param ifName the name of the interface on which the filtering rules will allow packets to
+              be received.
+     * @param uids an array of UIDs which the filtering rules will be set
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+
+    /**
+     * Remove ingress interface filtering rules from a list of UIDs
+     *
+     * Clear the ingress interface filtering rules from the list of UIDs which were previously set
+     * by firewallAddUidInterfaceRules(). Ignore any uid which does not have filtering rule.
+     *
+     * @param uids an array of UIDs from which the filtering rules will be removed
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    void firewallRemoveUidInterfaceRules(in int[] uids);
+
+   /**
+    * Request netd to change the current active network stats map.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void trafficSwapActiveStatsMap();
+
+   /**
+    * Retrieves OEM netd listener interface
+    *
+    * @return a IBinder object, it could be casted to oem specific interface.
+    */
+    IBinder getOemNetd();
+
+   /**
+    * Start tethering with given configuration
+    *
+    * @param config config to start tethering.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherStartWithConfiguration(in TetherConfigParcel config);
+
+
+    /**
+     * Get the fwmark and its net id mask for the given network id.
+     *
+     * @param netId the network to get the fwmark and mask for.
+     * @return A MarkMaskParcel of the given network id.
+     */
+    MarkMaskParcel getFwmarkForNetwork(int netId);
+
+    /**
+    * Add a route for specific network
+    *
+    * @param netId the network to add the route to
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Update a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+    * Remove a route for specific network
+    *
+    * @param routeInfo parcelable with route information
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+
+    /**
+     * Adds a tethering offload rule, or updates it if it already exists.
+     *
+     * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated
+     * if the input interface and destination prefix match. Otherwise, a new rule will be created.
+     *
+     * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline
+     *             module accesses the BPF map directly starting in S. See BpfCoordinator.
+     * @param rule The rule to add or update.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    void tetherOffloadRuleAdd(in TetherOffloadRuleParcel rule);
+
+    /**
+     * Deletes a tethering offload rule.
+     *
+     * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted
+     * if the destination IP address and the source interface match. It is not an error if there is
+     * no matching rule to delete.
+     *
+     * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline
+     *             module accesses the BPF map directly starting in S. See BpfCoordinator.
+     * @param rule The rule to delete.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    void tetherOffloadRuleRemove(in TetherOffloadRuleParcel rule);
+
+    /**
+     * Return BPF tethering offload statistics.
+     *
+     * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline
+     *             module accesses the BPF map directly starting in S. See BpfCoordinator.
+     * @return an array of TetherStatsParcel's, where each entry contains the upstream interface
+     *         index and its tethering statistics since tethering was first started.
+     *         There will only ever be one entry for a given interface index.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+    TetherStatsParcel[] tetherOffloadGetStats();
+
+   /**
+    * Set a per-interface quota for tethering offload.
+    *
+    * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline
+    *             module accesses the BPF map directly starting in S. See BpfCoordinator.
+    * @param ifIndex Index of upstream interface
+    * @param quotaBytes The quota defined as the number of bytes, starting from zero and counting
+    *       from *now*. A value of QUOTA_UNLIMITED (-1) indicates there is no limit.
+    * @throws ServiceSpecificException in case of failure, with an error code indicating the
+    *         cause of the failure.
+    */
+    void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+
+    /**
+     * Return BPF tethering offload statistics and clear the stats for a given upstream.
+     *
+     * Must only be called once all offload rules have already been deleted for the given upstream
+     * interface. The existing stats will be fetched and returned. The stats and the limit for the
+     * given upstream interface will be deleted as well.
+     *
+     * The stats and limit for a given upstream interface must be initialized (using
+     * tetherOffloadSetInterfaceQuota) before any offload will occur on that interface.
+     *
+     * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline
+     *             module accesses the BPF map directly starting in S. See BpfCoordinator.
+     * @param ifIndex Index of upstream interface.
+     * @return TetherStatsParcel, which contains the given upstream interface index and its
+     *         tethering statistics since tethering was first started on that upstream interface.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+     TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+
+    /**
+     * Creates a network.
+     *
+     * @param config the configuration of network.
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkCreate(in NativeNetworkConfig config);
+
+    /**
+     * Adds the specified UID ranges to the specified network. The network can be physical or
+     * virtual. Traffic from the UID ranges will be routed to the network by default. The possible
+     * value of subsidiary priority for physical and unreachable networks is 0-999. 0 is the highest
+     * priority. 0 is also the default value. Virtual network supports only the default value.
+     *
+     * @param NativeUidRangeConfig a parcel contains netId, UID ranges, subsidiary priority, etc.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkAddUidRangesParcel(in NativeUidRangeConfig uidRangesConfig);
+
+    /**
+     * Removes the specified UID ranges from the specified network. The network can be physical or
+     * virtual. Traffic from the UID ranges will no longer be routed to the network by default. The
+     * possible value of subsidiary priority for physical and unreachable networks is 0-999. 0 is
+     * the highest priority. 0 is also the default value. Virtual network supports only the default
+     * value.
+     *
+     * @param NativeUidRangeConfig a parcel contains netId, UID ranges, subsidiary priority, etc.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void networkRemoveUidRangesParcel(in NativeUidRangeConfig uidRangesConfig);
+
+    /**
+     * Migrate an existing IPsec tunnel mode SA to different addresses.
+     *
+     * If the underlying network also changes, caller must update it by
+     * calling ipSecAddSecurityAssociation.
+     *
+     * @param migrateInfo parcelable with migration info.
+     *
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *         cause of the failure.
+     */
+     void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+
+     /**
+      * IPSEC_DIRECTION_IN is used for IPsec SAs or policies that direct traffic towards the host.
+      */
+     const int IPSEC_DIRECTION_IN = 0;
+
+     /**
+      * IPSEC_DIRECTION_OUT is used for IPsec SAs or policies that direct traffic away from the host.
+      */
+     const int IPSEC_DIRECTION_OUT = 1;
+
+    /**
+    * Set the list of allowed UIDs for all networks with restrictions.
+    *
+    * This list is the entire list of restrictions for all networks known by
+    * netd. Calling this function always defines the entire list of restrictions,
+    * and networks not in the passed list are always reset to having no
+    * restrictions.
+    *
+    * @param NativeUidRangeConfig[] An array of allowlists, one per network. For each allowlist:
+    *                               - netId: the netId on which to set the allowlist
+    *                               - uidRanges: the UIDs allowed to use this network
+    *                               - subPriority: unused
+    */
+    void setNetworkAllowlist(in NativeUidRangeConfig[] allowedNetworks);
+}
diff --git a/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..652a79c
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Unsolicited netd events which are reported by the kernel via netlink.
+ * This one-way interface groups asynchronous notifications sent
+ * by netd to any process that registered itself via INetd.registerUnsolEventListener.
+ *
+ * {@hide}
+ */
+oneway interface INetdUnsolicitedEventListener {
+
+    /**
+     * Notifies that an interface has been idle/active for a certain period of time.
+     * It is the event for idletimer.
+     *
+     * @param isActive true for active status, false for idle
+     * @param timerLabel unique identifier of the idletimer.
+     *              Since NMS only set the identifier as int, only report event with int label.
+     * @param timestampNs kernel timestamp of this event, 0 for no timestamp
+     * @param uid uid of this event, -1 for no uid.
+     *            It represents the uid that was responsible for waking the radio.
+     */
+    void onInterfaceClassActivityChanged(
+            boolean isActive,
+            int timerLabel,
+            long timestampNs,
+            int uid);
+
+    /**
+     * Notifies that a specific interface reached its quota limit.
+     *
+     * @param alertName alert name of the quota limit
+     * @param ifName interface which reached the limit
+     */
+    void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+
+    /**
+     * Provides information on IPv6 DNS servers on a specific interface.
+     *
+     * @param ifName interface name
+     * @param lifetimeS lifetime for the DNS servers in seconds
+     * @param servers the address of servers.
+     *                  e.g. IpV6: "2001:4860:4860::6464"
+     *
+     */
+    void onInterfaceDnsServerInfo(
+            @utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+
+    /**
+     * Notifies that an address has updated on a specific interface.
+     *
+     * @param addr address that is being updated
+     * @param ifName the name of the interface on which the address is configured
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope current scope of the address
+     */
+    void onInterfaceAddressUpdated(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an address has been removed on a specific interface.
+     *
+     * @param addr address of this change
+     * @param ifName the name of the interface that changed addresses
+     * @param flags address flags, see ifa_flags in if_addr.h
+     * @param scope address address scope
+     */
+    void onInterfaceAddressRemoved(
+            @utf8InCpp String addr,
+            @utf8InCpp String ifName,
+            int flags,
+            int scope);
+
+    /**
+     * Notifies that an interface has been added.
+     *
+     * @param ifName the name of the added interface
+     */
+    void onInterfaceAdded(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that an interface has been removed.
+     *
+     * @param ifName the name of the removed interface
+     */
+    void onInterfaceRemoved(@utf8InCpp String ifName);
+
+    /**
+     * Notifies that the status of the specific interface has changed.
+     *
+     * @param ifName the name of the interface that changed status
+     * @param up true for interface up, false for down
+     */
+    void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that the link state of the specific interface has changed.
+     *
+     * @param ifName the name of the interface whose link state has changed
+     * @param up true for interface link state up, false for link state down
+     */
+    void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+
+    /**
+     * Notifies that an IP route has changed.
+     *
+     * @param updated true for update, false for remove
+     * @param route destination prefix of this route, e.g., "2001:db8::/64"
+     * @param gateway address of gateway, empty string for no gateway
+     * @param ifName interface name of this route, empty string for no interface
+     */
+    void onRouteChanged(
+            boolean updated,
+            @utf8InCpp String route,
+            @utf8InCpp String gateway,
+            @utf8InCpp String ifName);
+
+    /**
+     * Notifies that kernel has detected a socket sending data not wrapped
+     * inside a layer of SSL/TLS encryption.
+     *
+     * @param uid uid of this event
+     * @param hex packet content in hex format
+     */
+    void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..c20792c
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Configuration details for a network interface.
+ *
+ * {@hide}
+ */
+parcelable InterfaceConfigurationParcel {
+    @utf8InCpp String ifName;
+    @utf8InCpp String hwAddr;
+    @utf8InCpp String ipv4Addr;
+    int prefixLength;
+    /**
+    * Interface flags, String versions of IFF_* defined in netd/if.h
+    */
+    @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..e192d66
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,50 @@
+/**
+ * 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 android.net;
+
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+  /** The unique identifier for allocated resources. */
+  int requestId;
+  /**
+   * The address family identifier for the new selector. Can be AF_INET
+   * or AF_INET6.
+   */
+  int selAddrFamily;
+  /** IPSEC_DIRECTION_IN or IPSEC_DIRECTION_OUT. */
+  int direction;
+  /**
+   * The IP address for the current sending endpoint.
+   *
+   * The local address for an outbound SA and the remote address for an
+   * inbound SA.
+   */
+  @utf8InCpp String oldSourceAddress;
+  /**
+   * The IP address for the current receiving endpoint.
+   *
+   * The remote address for an outbound SA and the local address for an
+   * inbound SA.
+   */
+  @utf8InCpp String oldDestinationAddress;
+  /** The IP address for the new sending endpoint. */
+  @utf8InCpp String newSourceAddress;
+  /** The IP address for the new receiving endpoint. */
+  @utf8InCpp String newDestinationAddress;
+  /** The identifier for the XFRM interface. */
+  int interfaceId;
+}
diff --git a/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl b/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..932b7bf
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Structure that stores a firewall mark and its mask.
+ *
+ * {@hide}
+ */
+parcelable MarkMaskParcel {
+    // The fwmark.
+    int mark;
+    // Net id mask of fwmark.
+    int mask;
+}
diff --git a/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..96eccc7
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NativeNetworkType;
+import android.net.NativeVpnType;
+
+/**
+ * The configuration to create a network.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true, equals=true)
+@JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+    /** The networkId to create. */
+    int netId;
+
+    /**
+     * The type of network : virtual, physical or physical local network.
+     */
+    NativeNetworkType networkType = NativeNetworkType.PHYSICAL;
+
+    /**
+     * The permission necessary to use the network. Must be PERMISSION_NONE, PERMISSION_NETWORK
+     * or PERMISSION_SYSTEM. Ignored for virtual network types.
+     */
+    int permission;
+
+    /**
+     *  For virtual networks. Whether unprivileged apps are allowed to bypass the VPN. Ignored for
+     *  all other network types.
+     */
+    boolean secure;
+
+    /** For virtual networks. The type of VPN to create.  Ignored for all other network types. */
+    NativeVpnType vpnType = NativeVpnType.PLATFORM;
+
+    /**
+     * For virtual networks. Whether local traffic is excluded from the VPN.
+     */
+    boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/binder/android/net/NativeNetworkType.aidl b/staticlibs/netd/binder/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..7005535
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/NativeNetworkType.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+@Backing(type="int")
+enum NativeNetworkType {
+  /**
+   * Physical network type.
+   */
+  PHYSICAL = 0,
+
+  /**
+   * Virtual private network type.
+   */
+  VIRTUAL = 1,
+
+  /**
+   * Physical local network, such as a tethering downstream.
+   */
+  PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/binder/android/net/NativeVpnType.aidl b/staticlibs/netd/binder/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..cd1b447
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/NativeVpnType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+@Backing(type="int")
+enum NativeVpnType {
+  /**
+   * A VPN created by an app using the VpnService API.
+   */
+  SERVICE = 1,
+
+  /**
+   * A VPN created using a VpnManager API such as startProvisionedVpnProfile.
+   */
+  PLATFORM = 2,
+
+  /**
+   * An IPsec VPN created by the built-in LegacyVpnRunner.
+   */
+  LEGACY = 3,
+
+  /**
+   * An VPN created by OEM code through other means than VpnService or VpnManager.
+   */
+  OEM = 4,
+}
\ No newline at end of file
diff --git a/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl b/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..fcc86e3
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable RouteInfoParcel {
+  // The destination of the route.
+  @utf8InCpp String destination;
+  // The name of interface of the route. This interface should be assigned to the netID.
+  @utf8InCpp String ifName;
+  // The route's next hop address, or one of the NEXTHOP_* constants defined in INetd.aidl.
+  @utf8InCpp String nextHop;
+  // The MTU of the route.
+  int mtu;
+}
diff --git a/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl b/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..9f371ce
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * The configuration to start tethering.
+ *
+ * {@hide}
+ */
+parcelable TetherConfigParcel {
+    // Whether to enable or disable legacy DNS proxy server.
+    boolean usingLegacyDnsProxy;
+    // DHCP ranges to set.
+    // dhcpRanges might contain many addresss {addr1, addr2, addr3, addr4...}
+    // Netd splits them into ranges: addr1-addr2, addr3-addr4, etc.
+    // An odd number of addrs will fail.
+    @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..c549e61
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Represents a forwarding rule for tethering offload.
+ *
+ * {@hide}
+ */
+parcelable TetherOffloadRuleParcel {
+    /** The interface index of the input interface. */
+    int inputInterfaceIndex;
+
+    /** The interface index of the output interface. */
+    int outputInterfaceIndex;
+
+    /** The base IP address of the destination prefix as a byte array. */
+    byte[] destination;
+
+    /** The destination prefix length. */
+    int prefixLength;
+
+    /** The source link-layer address. Currently, must be a 6-byte MAC address.*/
+    byte[] srcL2Address;
+
+    /** The destination link-layer address. Currently, must be a 6-byte MAC address. */
+    byte[] dstL2Address;
+
+    /** The outbound path mtu. */
+    int pmtu = 1500;
+}
diff --git a/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl b/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..6bf60a8
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * The statistics of tethering interface
+ *
+ * {@hide}
+ */
+parcelable TetherStatsParcel {
+    /**
+     * Parcel representing tethering interface statistics.
+     *
+     * This parcel is used by tetherGetStats, tetherOffloadGetStats and
+     * tetherOffloadGetAndClearStats in INetd.aidl. tetherGetStats uses this parcel to return the
+     * tethering statistics since netd startup and presents the interface via its interface name.
+     * Both tetherOffloadGetStats and tetherOffloadGetAndClearStats use this parcel to return
+     * the tethering statistics since tethering was first started. They present the interface via
+     * its interface index. Note that the interface must be presented by either interface name
+     * |iface| or interface index |ifIndex| in this parcel. The unused interface name is set to
+     * an empty string "" by default and the unused interface index is set to 0 by default.
+     */
+
+    /** The interface name. */
+    @utf8InCpp String iface;
+
+    /** Total number of received bytes. */
+    long rxBytes;
+
+    /** Total number of received packets. */
+    long rxPackets;
+
+    /** Total number of transmitted bytes. */
+    long txBytes;
+
+    /** Total number of transmitted packets. */
+    long txPackets;
+
+    /** The interface index. */
+    int ifIndex = 0;
+}
diff --git a/staticlibs/netd/binder/android/net/UidRangeParcel.aidl b/staticlibs/netd/binder/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..8f1fef6
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/UidRangeParcel.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * {@hide}
+ */
+@JavaOnlyImmutable @JavaDerive(toString=true, equals=true)
+parcelable UidRangeParcel {
+    int start;
+    int stop;
+}
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl
new file mode 100644
index 0000000..f827382
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/DiscoveryInfo.aidl
@@ -0,0 +1,79 @@
+/*
+ * 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 android.net.mdns.aidl;
+
+/**
+ * Discovery service information.
+ * This information combine all arguments that used by both request and callback.
+ * Arguments are used by request:
+ * - id
+ * - registrationType
+ * - interfaceIdx
+ *
+ * Arguments are used by callback:
+ * - id
+ * - serviceName
+ * - registrationType
+ * - domainName
+ * - interfaceIdx
+ * - netId
+ * - result
+ *
+ * {@hide}
+ */
+@JavaOnlyImmutable
+@JavaDerive(equals=true, toString=true)
+parcelable DiscoveryInfo {
+    /**
+     * The operation ID.
+     * Must be unique among all operations (registration/discovery/resolution/getting address) and
+     * can't be reused.
+     * To stop a operation, it needs to use corresponding operation id.
+     */
+    int id;
+
+    /**
+     * The discovery result.
+     */
+    int result;
+
+    /**
+     * The discovered service name.
+     */
+    @utf8InCpp String serviceName;
+
+    /**
+     * The service type being discovered for followed by the protocol, separated by a dot
+     * (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp".
+     */
+    @utf8InCpp String registrationType;
+
+    /**
+     * The domain of the discovered service instance.
+     */
+    @utf8InCpp String domainName;
+
+    /**
+     * The interface index on which to discover services. 0 indicates "all interfaces".
+     */
+    int interfaceIdx;
+
+    /**
+     * The net id on which the service is advertised.
+     */
+    int netId;
+}
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl
new file mode 100644
index 0000000..d53174a
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/GetAddressInfo.aidl
@@ -0,0 +1,69 @@
+/*
+ * 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 android.net.mdns.aidl;
+
+/**
+ * Get service address information.
+ * This information combine all arguments that used by both request and callback.
+ * Arguments are used by request:
+ * - id
+ * - hostname
+ * - interfaceIdx
+ *
+ * Arguments are used by callback:
+ * - id
+ * - hostname
+ * - interfaceIdx
+ * - netId
+ * - address
+ * - result
+ *
+ * {@hide}
+ */
+@JavaOnlyImmutable
+@JavaDerive(equals=true, toString=true)
+parcelable GetAddressInfo {
+    /**
+     * The operation ID.
+     */
+    int id;
+
+    /**
+     * The getting address result.
+     */
+    int result;
+
+    /**
+     * The fully qualified domain name of the host to be queried for.
+     */
+    @utf8InCpp String hostname;
+
+    /**
+     * The service address info, it's IPv4 or IPv6 addres.
+     */
+    @utf8InCpp String address;
+
+    /**
+     * The interface index on which to issue the query. 0 indicates "all interfaces".
+     */
+    int interfaceIdx;
+
+    /**
+     * The net id to which the answers pertain.
+     */
+    int netId;
+}
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl
new file mode 100644
index 0000000..255d70f
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/IMDns.aidl
@@ -0,0 +1,127 @@
+/*
+ * 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 android.net.mdns.aidl;
+
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
+
+/** {@hide} */
+interface IMDns {
+    /**
+     * Start the MDNSResponder daemon.
+     *
+     * @throws ServiceSpecificException with unix errno EALREADY if daemon is already running.
+     */
+    void startDaemon();
+
+    /**
+     * Stop the MDNSResponder daemon.
+     *
+     * @throws ServiceSpecificException with unix errno EBUSY if daemon is still in use.
+     */
+    void stopDaemon();
+
+    /**
+     * Start registering a service.
+     * This operation will send a service registration request to MDNSResponder. Register a listener
+     * via IMDns#registerEventListener to get the registration result SERVICE_REGISTERED/
+     * SERVICE_REGISTRATION_FAILED from callback IMDnsEventListener#onServiceRegistrationStatus.
+     *
+     * @param info The service information to register.
+     *
+     * @throws ServiceSpecificException with one of the following error values:
+     *         - Unix errno EBUSY if request id is already in use.
+     *         - kDNSServiceErr_* list in dns_sd.h if registration fail.
+     */
+    void registerService(in RegistrationInfo info);
+
+    /**
+     * Start discovering services.
+     * This operation will send a request to MDNSResponder to discover services. Register a listener
+     * via IMDns#registerEventListener to get the discovery result SERVICE_FOUND/SERVICE_LOST/
+     * SERVICE_DISCOVERY_FAILED from callback IMDnsEventListener#onServiceDiscoveryStatus.
+     *
+     * @param info The service to discover.
+     *
+     * @throws ServiceSpecificException with one of the following error values:
+     *         - Unix errno EBUSY if request id is already in use.
+     *         - kDNSServiceErr_* list in dns_sd.h if discovery fail.
+     */
+    void discover(in DiscoveryInfo info);
+
+    /**
+     * Start resolving the target service.
+     * This operation will send a request to MDNSResponder to resolve the target service. Register a
+     * listener via IMDns#registerEventListener to get the resolution result SERVICE_RESOLVED/
+     * SERVICE_RESOLUTION_FAILED from callback IMDnsEventListener#onServiceResolutionStatus.
+     *
+     * @param info The service to resolve.
+     *
+     * @throws ServiceSpecificException with one of the following error values:
+     *         - Unix errno EBUSY if request id is already in use.
+     *         - kDNSServiceErr_* list in dns_sd.h if resolution fail.
+     */
+    void resolve(in ResolutionInfo info);
+
+    /**
+     * Start getting the target service address.
+     * This operation will send a request to MDNSResponder to get the target service address.
+     * Register a listener via IMDns#registerEventListener to get the query result
+     * SERVICE_GET_ADDR_SUCCESS/SERVICE_GET_ADDR_FAILED from callback
+     * IMDnsEventListener#onGettingServiceAddressStatus.
+     *
+     * @param info the getting service address information.
+     *
+     * @throws ServiceSpecificException with one of the following error values:
+     *         - Unix errno EBUSY if request id is already in use.
+     *         - kDNSServiceErr_* list in dns_sd.h if getting address fail.
+     */
+    void getServiceAddress(in GetAddressInfo info);
+
+    /**
+     * Stop a operation which's requested before.
+     *
+     * @param id the operation id to be stopped.
+     *
+     * @throws ServiceSpecificException with unix errno ESRCH if request id is not in use.
+     */
+    void stopOperation(int id);
+
+    /**
+     * Register an event listener.
+     *
+     * @param listener The listener to be registered.
+     *
+     * @throws ServiceSpecificException with one of the following error values:
+     *         - Unix errno EINVAL if listener is null.
+     *         - Unix errno EEXIST if register duplicated listener.
+     */
+    void registerEventListener(in IMDnsEventListener listener);
+
+    /**
+     * Unregister an event listener.
+     *
+     * @param listener The listener to be unregistered.
+     *
+     * @throws ServiceSpecificException with unix errno EINVAL if listener is null.
+     */
+    void unregisterEventListener(in IMDnsEventListener listener);
+}
+
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl
new file mode 100644
index 0000000..a202a26
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/IMDnsEventListener.aidl
@@ -0,0 +1,66 @@
+/**
+ * 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 android.net.mdns.aidl;
+
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.RegistrationInfo;
+import android.net.mdns.aidl.ResolutionInfo;
+
+/**
+ * MDNS events which are reported by the MDNSResponder.
+ * This one-way interface defines the asynchronous notifications sent by mdns service to any process
+ * that registered itself via IMDns.registerEventListener.
+ *
+ * {@hide}
+ */
+oneway interface IMDnsEventListener {
+    /**
+     * Types for MDNS operation result.
+     * These are in sync with frameworks/libs/net/common/netd/libnetdutils/include/netdutils/\
+     * ResponseCode.h
+     */
+    const int SERVICE_DISCOVERY_FAILED     = 602;
+    const int SERVICE_FOUND                = 603;
+    const int SERVICE_LOST                 = 604;
+    const int SERVICE_REGISTRATION_FAILED  = 605;
+    const int SERVICE_REGISTERED           = 606;
+    const int SERVICE_RESOLUTION_FAILED    = 607;
+    const int SERVICE_RESOLVED             = 608;
+    const int SERVICE_GET_ADDR_FAILED      = 611;
+    const int SERVICE_GET_ADDR_SUCCESS     = 612;
+
+    /**
+     * Notify service registration status.
+     */
+    void onServiceRegistrationStatus(in RegistrationInfo status);
+
+    /**
+     * Notify service discovery status.
+     */
+    void onServiceDiscoveryStatus(in DiscoveryInfo status);
+
+    /**
+     * Notify service resolution status.
+     */
+    void onServiceResolutionStatus(in ResolutionInfo status);
+
+    /**
+     * Notify getting service address status.
+     */
+    void onGettingServiceAddressStatus(in GetAddressInfo status);
+}
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl
new file mode 100644
index 0000000..5483559
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/RegistrationInfo.aidl
@@ -0,0 +1,78 @@
+/*
+ * 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 android.net.mdns.aidl;
+
+/**
+ * Registration service information.
+ * This information combine all arguments that used by both request and callback.
+ * Arguments are used by request:
+ * - id
+ * - serviceName
+ * - registrationType
+ * - port
+ * - txtRecord
+ * - interfaceIdx
+ *
+ * Arguments are used by callback:
+ * - id
+ * - serviceName
+ * - registrationType
+ * - result
+ *
+ * {@hide}
+ */
+@JavaOnlyImmutable
+@JavaDerive(equals=true, toString=true)
+parcelable RegistrationInfo {
+    /**
+     * The operation ID.
+     */
+    int id;
+
+    /**
+     * The registration result.
+     */
+    int result;
+
+    /**
+     * The service name to be registered.
+     */
+    @utf8InCpp String serviceName;
+
+    /**
+     * The service type followed by the protocol, separated by a dot (e.g. "_ftp._tcp"). The service
+     * type must be an underscore, followed by 1-15 characters, which may be letters, digits, or
+     * hyphens. The transport protocol must be "_tcp" or "_udp". New service types should be
+     * registered at <http://www.dns-sd.org/ServiceTypes.html>.
+     */
+    @utf8InCpp String registrationType;
+
+    /**
+     * The port on which the service accepts connections.
+     */
+    int port;
+
+    /**
+     * The txt record.
+     */
+    byte[] txtRecord;
+
+    /**
+     * The interface index on which to register the service. 0 indicates "all interfaces".
+     */
+    int interfaceIdx;
+}
diff --git a/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl b/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl
new file mode 100644
index 0000000..26e0cee
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/mdns/aidl/ResolutionInfo.aidl
@@ -0,0 +1,92 @@
+/*
+ * 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 android.net.mdns.aidl;
+
+/**
+ * Resolution service information.
+ * This information combine all arguments that used by both request and callback.
+ * Arguments are used by request:
+ * - id
+ * - serviceName
+ * - registrationType
+ * - domain
+ * - interfaceIdx
+ *
+ * Arguments are used by callback:
+ * - id
+ * - port
+ * - serviceFullName
+ * - hostname
+ * - txtRecord
+ * - interfaceIdx
+ * - result
+ *
+ * {@hide}
+ */
+@JavaOnlyImmutable
+@JavaDerive(equals=true, toString=true)
+parcelable ResolutionInfo {
+    /**
+     * The operation ID.
+     */
+    int id;
+
+    /**
+     * The resolution result.
+     */
+    int result;
+
+    /**
+     * The service name to be resolved.
+     */
+    @utf8InCpp String serviceName;
+
+    /**
+     * The service type to be resolved.
+     */
+    @utf8InCpp String registrationType;
+
+    /**
+     * The service domain to be resolved.
+     */
+    @utf8InCpp String domain;
+
+    /**
+     * The resolved full service domain name, in the form <servicename>.<protocol>.<domain>.
+     */
+    @utf8InCpp String serviceFullName;
+
+    /**
+     * The target hostname of the machine providing the service.
+     */
+    @utf8InCpp String hostname;
+
+    /**
+     * The port on which connections are accepted for this service.
+     */
+    int port;
+
+    /**
+     * The service's txt record.
+     */
+    byte[] txtRecord;
+
+    /**
+     * The interface index on which to resolve the service. 0 indicates "all interfaces".
+     */
+    int interfaceIdx;
+}
diff --git a/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..ef1b2cb
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+/**
+ * Logs netd events.
+ *
+ * {@hide}
+ */
+oneway interface INetdEventListener {
+    const int EVENT_GETADDRINFO = 1;
+    const int EVENT_GETHOSTBYNAME = 2;
+    const int EVENT_GETHOSTBYADDR = 3;
+    const int EVENT_RES_NSEND = 4;
+
+    const int REPORTING_LEVEL_NONE = 0;
+    const int REPORTING_LEVEL_METRICS = 1;
+    const int REPORTING_LEVEL_FULL = 2;
+
+    // Maximum number of IP addresses logged for DNS lookups before we truncate the full list.
+    const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+
+    /**
+     * Logs a DNS lookup function call (getaddrinfo and gethostbyname).
+     *
+     * @param netId the ID of the network the lookup was performed on.
+     * @param eventType one of the EVENT_* constants in this interface.
+     * @param returnCode the return value of the function call.
+     * @param latencyMs the latency of the function call.
+     * @param hostname the name that was looked up.
+     * @param ipAddresses (possibly a subset of) the IP addresses returned.
+     *        At most {@link #DNS_REPORTED_IP_ADDRESSES_LIMIT} addresses are logged.
+     * @param ipAddressesCount the number of IP addresses returned. May be different from the length
+     *        of ipAddresses if there were too many addresses to log.
+     * @param uid the UID of the application that performed the query.
+     */
+    void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
+            @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses,
+            int ipAddressesCount, int uid);
+
+    /**
+     * Represents a private DNS validation success or failure.
+     *
+     * @param netId the ID of the network the validation was performed on.
+     * @param ipAddress the IP address for which validation was performed.
+     * @param hostname the hostname for which validation was performed.
+     * @param validated whether or not validation was successful.
+     */
+    void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname,
+            boolean validated);
+
+    /**
+     * Logs a single connect library call.
+     *
+     * @param netId the ID of the network the connect was performed on.
+     * @param error 0 if the connect call succeeded, otherwise errno if it failed.
+     * @param latencyMs the latency of the connect call.
+     * @param ipAddr destination IP address.
+     * @param port destination port number.
+     * @param uid the UID of the application that performed the connection.
+     */
+    void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+
+    /**
+     * Logs a single RX packet which caused the main CPU to exit sleep state.
+     * @param prefix arbitrary string provided via wakeupAddInterface()
+     * @param uid UID of the destination process or -1 if no UID is available.
+     * @param ethertype of the RX packet encoded in an int in native order, or -1 if not available.
+     * @param ipNextHeader ip protocol of the RX packet as IPPROTO_* number,
+              or -1 if the packet was not IPv4 or IPv6.
+     * @param dstHw destination hardware address, or 0 if not available.
+     * @param srcIp source IP address, or null if not available.
+     * @param dstIp destination IP address, or null if not available.
+     * @param srcPort src port of RX packet in native order, or -1 if the packet was not UDP or TCP.
+     * @param dstPort dst port of RX packet in native order, or -1 if the packet was not UDP or TCP.
+     * @param timestampNs receive timestamp for the offending packet. In units of nanoseconds and
+     *        synchronized to CLOCK_MONOTONIC.
+     */
+    void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw,
+            String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+
+    /**
+     * An event sent after every Netlink sock_diag poll performed by Netd. This reported batch
+     * groups TCP socket stats aggregated by network id. Per-network data are stored in a
+     * structure-of-arrays style where networkIds, sentPackets, lostPackets, rttUs, and
+     * sentAckDiffMs have the same length. Stats for the i-th network is spread across all these
+     * arrays at index i.
+     * @param networkIds an array of network ids for which there was tcp socket stats to collect in
+     *        the last sock_diag poll.
+     * @param sentPackets an array of packet sent across all TCP sockets still alive and new
+              TCP sockets since the last sock_diag poll, summed per network id.
+     * @param lostPackets, an array of packet lost across all TCP sockets still alive and new
+              TCP sockets since the last sock_diag poll, summed per network id.
+     * @param rttUs an array of smoothed round trip times in microseconds, averaged across all TCP
+              sockets since the last sock_diag poll for a given network id.
+     * @param sentAckDiffMs an array of milliseconds duration between the last packet sent and the
+              last ack received for a socket, averaged across all TCP sockets for a network id.
+     */
+    void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets,
+            in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+
+    /**
+     * Represents adding or removing a NAT64 prefix.
+     *
+     * @param netId the ID of the network the prefix was discovered on.
+     * @param added true if the NAT64 prefix was added, or false if the NAT64 prefix was removed.
+     *        There is only one prefix at a time for each netId. If a prefix is added, it replaces
+     *        the previous-added prefix.
+     * @param prefixString the detected NAT64 prefix as a string literal.
+     * @param prefixLength the prefix length associated with this NAT64 prefix.
+     */
+    void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString,
+            int prefixLength);
+}
diff --git a/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..99497a8
--- /dev/null
+++ b/staticlibs/netd/binder/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netd.aidl;
+
+import android.net.UidRangeParcel;
+
+/**
+ * The configuration to add or remove UID ranges.
+ *
+ * {@hide}
+ */
+@JavaDerive(toString=true, equals=true)
+@JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+    /** The network ID of the network to add/remove the ranges to/from. */
+    int netId;
+
+    /** A set of non-overlapping ranges of UIDs. */
+    UidRangeParcel[] uidRanges;
+
+    /**
+     * The priority of this UID range config. 0 is the highest priority; 999 is the lowest priority.
+     * The function of this parameter is to adjust the priority when the same UID is set to
+     * different networks for different features.
+     */
+    int subPriority;
+}
\ No newline at end of file
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
new file mode 100644
index 0000000..3169033
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -0,0 +1,73 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libnetdutils",
+    srcs: [
+        "DumpWriter.cpp",
+        "Fd.cpp",
+        "InternetAddresses.cpp",
+        "Log.cpp",
+        "Netfilter.cpp",
+        "Netlink.cpp",
+        "NetlinkListener.cpp",
+        "Slice.cpp",
+        "Socket.cpp",
+        "SocketOption.cpp",
+        "Status.cpp",
+        "Syscalls.cpp",
+        "UniqueFd.cpp",
+        "UniqueFile.cpp",
+        "Utils.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    export_shared_lib_headers: [
+        "libbase",
+    ],
+    export_include_dirs: ["include"],
+    sanitize: {
+        cfi: true,
+    },
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.resolv",
+        "com.android.tethering",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_test {
+    name: "netdutils_test",
+    srcs: [
+        "BackoffSequenceTest.cpp",
+        "FdTest.cpp",
+        "InternetAddressesTest.cpp",
+        "LogTest.cpp",
+        "MemBlockTest.cpp",
+        "SliceTest.cpp",
+        "StatusTest.cpp",
+        "SyscallsTest.cpp",
+        "ThreadUtilTest.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    static_libs: [
+        "libgmock",
+        "libnetdutils",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+cc_library_headers {
+    name: "libnetd_utils_headers",
+    export_include_dirs: ["include"],
+}
diff --git a/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp
new file mode 100644
index 0000000..b6653fe
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "netdutils/BackoffSequence.h"
+
+namespace android {
+namespace netdutils {
+
+TEST(BackoffSequence, defaults) {
+    BackoffSequence<uint32_t> backoff;
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000001U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000002U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000008U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000010U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000020U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000040U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000080U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000100U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000200U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000400U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000800U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00001000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00002000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00004000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00008000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00010000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00020000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00040000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00080000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00100000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00200000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00400000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00800000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x01000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x02000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x04000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x08000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x10000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x20000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x40000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x80000000U, backoff.getNextTimeout());
+    // Maxes out, and stays there, ad infinitum.
+    for (int i = 0; i < 10; i++) {
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(0xffffffffU, backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, backoffToOncePerHour) {
+    auto backoff = BackoffSequence<uint32_t>::Builder()
+            .withInitialRetransmissionTime(1)
+            .withMaximumRetransmissionTime(3600)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000001U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000002U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000008U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000010U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000020U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000040U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000080U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000100U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000200U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000400U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000800U, backoff.getNextTimeout());
+    // Maxes out, and stays there, ad infinitum.
+    for (int i = 0; i < 10; i++) {
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(3600U, backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, simpleMaxRetransCount) {
+    auto backoff = BackoffSequence<uint32_t>::Builder()
+            .withInitialRetransmissionTime(3)
+            .withMaximumRetransmissionCount(7)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000003U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000006U, backoff.getNextTimeout());
+    EXPECT_EQ(0x0000000cU, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000018U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000030U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000060U, backoff.getNextTimeout());
+    EXPECT_EQ(0x000000c0U, backoff.getNextTimeout());
+
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, simpleMaxDuration) {
+    auto backoff = BackoffSequence<int>::Builder()
+            .withInitialRetransmissionTime(3)
+            .withMaximumRetransmissionDuration(7)
+            .withEndOfSequenceIndicator(-1)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000003, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004, backoff.getNextTimeout());
+
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+        EXPECT_EQ(-1, backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, ZeroInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::seconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::seconds(0))
+            .build();
+
+    for (int i = 0; i < 10; i++) {
+        // TODO: Decide whether this needs fixing, and how.
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransDurationGreaterThanInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::milliseconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::milliseconds(5))
+            .withMaximumRetransmissionDuration(std::chrono::milliseconds(3))
+            .build();
+
+    EXPECT_EQ(std::chrono::milliseconds(3), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransDurationEqualsInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::hours>::Builder()
+            .withInitialRetransmissionTime(std::chrono::hours(5))
+            .withMaximumRetransmissionDuration(std::chrono::hours(5))
+            .build();
+
+    EXPECT_EQ(std::chrono::hours(5), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransTimeAndDurationGreaterThanInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::nanoseconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::nanoseconds(7))
+            .withMaximumRetransmissionTime(std::chrono::nanoseconds(3))
+            .withMaximumRetransmissionDuration(std::chrono::nanoseconds(5))
+            .build();
+
+    EXPECT_EQ(std::chrono::nanoseconds(3), backoff.getNextTimeout());
+    EXPECT_EQ(std::chrono::nanoseconds(2), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/DumpWriter.cpp b/staticlibs/netd/libnetdutils/DumpWriter.cpp
new file mode 100644
index 0000000..092ddba
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/DumpWriter.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "netdutils/DumpWriter.h"
+
+#include <unistd.h>
+#include <limits>
+
+#include <android-base/stringprintf.h>
+#include <utils/String8.h>
+
+using android::base::StringAppendV;
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+const char kIndentString[] = "  ";
+const size_t kIndentStringLen = strlen(kIndentString);
+
+}  // namespace
+
+DumpWriter::DumpWriter(int fd) : mIndentLevel(0), mFd(fd) {}
+
+void DumpWriter::incIndent() {
+    if (mIndentLevel < std::numeric_limits<decltype(mIndentLevel)>::max()) {
+        mIndentLevel++;
+    }
+}
+
+void DumpWriter::decIndent() {
+    if (mIndentLevel > std::numeric_limits<decltype(mIndentLevel)>::min()) {
+        mIndentLevel--;
+    }
+}
+
+void DumpWriter::println(const std::string& line) {
+    if (!line.empty()) {
+        for (int i = 0; i < mIndentLevel; i++) {
+            ::write(mFd, kIndentString, kIndentStringLen);
+        }
+        ::write(mFd, line.c_str(), line.size());
+    }
+    ::write(mFd, "\n", 1);
+}
+
+// NOLINTNEXTLINE(cert-dcl50-cpp): Grandfathered C-style variadic function.
+void DumpWriter::println(const char* fmt, ...) {
+    std::string line;
+    va_list ap;
+    va_start(ap, fmt);
+    StringAppendV(&line, fmt, ap);
+    va_end(ap);
+    println(line);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Fd.cpp b/staticlibs/netd/libnetdutils/Fd.cpp
new file mode 100644
index 0000000..2651f90
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Fd.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd) {
+    return os << "Fd[" << fd.get() << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/FdTest.cpp b/staticlibs/netd/libnetdutils/FdTest.cpp
new file mode 100644
index 0000000..7080f83
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/FdTest.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Status.h"
+#include "netdutils/Syscalls.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::StrictMock;
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Force implicit conversion from UniqueFd -> Fd
+inline Fd toFd(const UniqueFd& fd) {
+    return fd;
+}
+
+}  // namespace
+
+TEST(Fd, smoke) {
+    // Expect the following lines to compile
+    Fd fd1(1);
+    Fd fd2(fd1);
+    Fd fd3 = fd2;
+    const Fd fd4(8);
+    const Fd fd5(fd4);
+    const Fd fd6 = fd5;
+    EXPECT_TRUE(isWellFormed(fd3));
+    EXPECT_TRUE(isWellFormed(fd6));
+
+    // Corner case
+    Fd zero(0);
+    EXPECT_TRUE(isWellFormed(zero));
+
+    // Invalid file descriptors
+    Fd bad(-1);
+    Fd weird(-9);
+    EXPECT_FALSE(isWellFormed(bad));
+    EXPECT_FALSE(isWellFormed(weird));
+
+    // Default constructor
+    EXPECT_EQ(Fd(-1), Fd());
+    std::stringstream ss;
+    ss << fd3 << " " << fd6 << " " << bad << " " << weird;
+    EXPECT_EQ("Fd[1] Fd[8] Fd[-1] Fd[-9]", ss.str());
+}
+
+class UniqueFdTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST_F(UniqueFdTest, operatorOstream) {
+    UniqueFd u(97);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    std::stringstream ss;
+    ss << u;
+    EXPECT_EQ("UniqueFd[Fd[97]]", ss.str());
+    u.reset();
+}
+
+TEST_F(UniqueFdTest, destructor) {
+    {
+        UniqueFd u(98);
+        EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, reset) {
+    UniqueFd u(99);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    u.reset();
+
+    // Expectation above should be upon reset
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveConstructor) {
+    constexpr Fd kFd(101);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2(std::move(u1));
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveAssignment) {
+    constexpr Fd kFd(102);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2 = std::move(u1);
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        UniqueFd u3;
+        u3 = std::move(u2);
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u2));
+        EXPECT_TRUE(isWellFormed(u3));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, constConstructor) {
+    constexpr Fd kFd(103);
+    const UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+}
+
+TEST_F(UniqueFdTest, closeFailure) {
+    constexpr Fd kFd(103);
+    UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(statusFromErrno(EINTR, "test")));
+    EXPECT_DEBUG_DEATH(u.reset(), "");
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
new file mode 100644
index 0000000..322f1b1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/InternetAddresses.h"
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+namespace android {
+
+using base::StringPrintf;
+
+namespace netdutils {
+
+std::string IPAddress::toString() const noexcept {
+    char repr[INET6_ADDRSTRLEN] = "\0";
+
+    switch (mData.family) {
+        case AF_UNSPEC:
+            return "<unspecified>";
+        case AF_INET: {
+            const in_addr v4 = mData.ip.v4;
+            inet_ntop(AF_INET, &v4, repr, sizeof(repr));
+            break;
+        }
+        case AF_INET6: {
+            const in6_addr v6 = mData.ip.v6;
+            inet_ntop(AF_INET6, &v6, repr, sizeof(repr));
+            break;
+        }
+        default:
+            return "<unknown_family>";
+    }
+
+    if (mData.family == AF_INET6 && mData.scope_id > 0) {
+        return StringPrintf("%s%%%u", repr, mData.scope_id);
+    }
+
+    return repr;
+}
+
+bool IPAddress::forString(const std::string& repr, IPAddress* ip) {
+    const addrinfo hints = {
+            .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
+    };
+    addrinfo* res;
+    const int ret = getaddrinfo(repr.c_str(), nullptr, &hints, &res);
+    ScopedAddrinfo res_cleanup(res);
+    if (ret != 0) {
+        return false;
+    }
+
+    bool rval = true;
+    switch (res[0].ai_family) {
+        case AF_INET: {
+            sockaddr_in* sin = (sockaddr_in*) res[0].ai_addr;
+            if (ip) *ip = IPAddress(sin->sin_addr);
+            break;
+        }
+        case AF_INET6: {
+            sockaddr_in6* sin6 = (sockaddr_in6*) res[0].ai_addr;
+            if (ip) *ip = IPAddress(sin6->sin6_addr, sin6->sin6_scope_id);
+            break;
+        }
+        default:
+            rval = false;
+            break;
+    }
+
+    return rval;
+}
+
+IPPrefix::IPPrefix(const IPAddress& ip, int length) : IPPrefix(ip) {
+    // Silently treat CIDR lengths like "-1" as meaning the full bit length
+    // appropriate to the address family.
+    if (length < 0) return;
+    if (length >= mData.cidrlen) return;
+
+    switch (mData.family) {
+        case AF_UNSPEC:
+            break;
+        case AF_INET: {
+            const in_addr_t mask = (length > 0) ? (~0U) << (IPV4_ADDR_BITS - length) : 0U;
+            mData.ip.v4.s_addr &= htonl(mask);
+            mData.cidrlen = static_cast<uint8_t>(length);
+            break;
+        }
+        case AF_INET6: {
+            // The byte in which this CIDR length falls.
+            const int which = length / 8;
+            const int mask = (length % 8 == 0) ? 0 : 0xff << (8 - length % 8);
+            mData.ip.v6.s6_addr[which] &= mask;
+            for (int i = which + 1; i < IPV6_ADDR_LEN; i++) {
+                mData.ip.v6.s6_addr[i] = 0U;
+            }
+            mData.cidrlen = static_cast<uint8_t>(length);
+            break;
+        }
+        default:
+            // TODO: Complain bitterly about possible data corruption?
+            return;
+    }
+}
+
+bool IPPrefix::isUninitialized() const noexcept {
+    static const internal_::compact_ipdata empty{};
+    return mData == empty;
+}
+
+bool IPPrefix::forString(const std::string& repr, IPPrefix* prefix) {
+    size_t index = repr.find('/');
+    if (index == std::string::npos) return false;
+
+    // Parse the IP address.
+    IPAddress ip;
+    if (!IPAddress::forString(repr.substr(0, index), &ip)) return false;
+
+    // Parse the prefix length. Can't use base::ParseUint because it accepts non-base 10 input.
+    const char* prefixString = repr.c_str() + index + 1;
+    if (!isdigit(*prefixString)) return false;
+    char* endptr;
+    unsigned long prefixlen = strtoul(prefixString, &endptr, 10);
+    if (*endptr != '\0') return false;
+
+    uint8_t maxlen = (ip.family() == AF_INET) ? 32 : 128;
+    if (prefixlen > maxlen) return false;
+
+    *prefix = IPPrefix(ip, prefixlen);
+    return true;
+}
+
+std::string IPPrefix::toString() const noexcept {
+    return StringPrintf("%s/%d", ip().toString().c_str(), mData.cidrlen);
+}
+
+std::string IPSockAddr::toString() const noexcept {
+    switch (mData.family) {
+        case AF_INET6:
+            return StringPrintf("[%s]:%u", ip().toString().c_str(), mData.port);
+        default:
+            return StringPrintf("%s:%u", ip().toString().c_str(), mData.port);
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
new file mode 100644
index 0000000..9e37d11
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/macros.h>
+#include <fmt/format.h>
+#include <gtest/gtest.h>
+
+#include "netdutils/InternetAddresses.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+enum Relation { EQ, LT };
+
+std::ostream& operator<<(std::ostream& os, Relation relation) {
+    switch (relation) {
+        case EQ: os << "eq"; break;
+        case LT: os << "lt"; break;
+        default: os << "?!"; break;
+    }
+    return os;
+}
+
+template <typename T>
+struct OperatorExpectation {
+    const Relation relation;
+    const T obj1;
+    const T obj2;
+
+    std::string toString() const {
+        std::stringstream output;
+        output << obj1 << " " << relation << " " << obj2;
+        return output.str();
+    }
+};
+
+template <typename T>
+void testGamutOfOperators(const OperatorExpectation<T>& expectation) {
+    switch (expectation.relation) {
+        case EQ:
+            EXPECT_TRUE(expectation.obj1 == expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 <= expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 >= expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 != expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 < expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 > expectation.obj2);
+            break;
+
+        case LT:
+            EXPECT_TRUE(expectation.obj1 < expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 <= expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 != expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 > expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 >= expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 == expectation.obj2);
+            break;
+
+        default:
+            FAIL() << "Unknown relation given in test expectation";
+    }
+}
+
+const in_addr IPV4_ANY{htonl(INADDR_ANY)};
+const in_addr IPV4_LOOPBACK{htonl(INADDR_LOOPBACK)};
+const in_addr IPV4_ONES{~0U};
+const in6_addr IPV6_ANY = IN6ADDR_ANY_INIT;
+const in6_addr IPV6_LOOPBACK = IN6ADDR_LOOPBACK_INIT;
+const in6_addr FE80{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}};
+const in6_addr FE80_1{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}};
+const in6_addr FE80_2{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}};
+const uint8_t ff = std::numeric_limits<uint8_t>::max();
+const in6_addr IPV6_ONES{{{ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff}}};
+
+TEST(IPAddressTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPAddress>> kExpectations{
+            {EQ, IPAddress(), IPAddress()},
+            {EQ, IPAddress(IPV4_ONES), IPAddress(IPV4_ONES)},
+            {EQ, IPAddress(IPV6_ONES), IPAddress(IPV6_ONES)},
+            {EQ, IPAddress(FE80_1), IPAddress(FE80_1)},
+            {EQ, IPAddress(FE80_2), IPAddress(FE80_2)},
+            {LT, IPAddress(), IPAddress(IPV4_ANY)},
+            {LT, IPAddress(), IPAddress(IPV4_ONES)},
+            {LT, IPAddress(), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV4_ANY), IPAddress(IPV4_ONES)},
+            {LT, IPAddress(IPV4_ANY), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_LOOPBACK)},
+            {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV6_LOOPBACK), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(FE80_1), IPAddress(FE80_2)},
+            {LT, IPAddress(FE80_1), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(FE80_2), IPAddress(IPV6_ONES)},
+            // Sort by scoped_id within the same address.
+            {LT, IPAddress(FE80_1), IPAddress(FE80_1, 1)},
+            {LT, IPAddress(FE80_1, 1), IPAddress(FE80_1, 2)},
+            // Sort by address first, scope_id second.
+            {LT, IPAddress(FE80_1, 2), IPAddress(FE80_2, 1)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPAddressTest, ScopeIds) {
+    // Scope IDs ignored for IPv4 addresses.
+    const IPAddress ones(IPV4_ONES);
+    EXPECT_EQ(0U, ones.scope_id());
+    const IPAddress ones22(ones, 22);
+    EXPECT_EQ(0U, ones22.scope_id());
+    EXPECT_EQ(ones, ones22);
+    const IPAddress ones23(ones, 23);
+    EXPECT_EQ(0U, ones23.scope_id());
+    EXPECT_EQ(ones22, ones23);
+
+    EXPECT_EQ("fe80::1%22", IPAddress(FE80_1, 22).toString());
+    EXPECT_EQ("fe80::2%23", IPAddress(FE80_2, 23).toString());
+
+    // Verify that given an IPAddress with a scope_id an address without a
+    // scope_id can be constructed (just in case it's useful).
+    const IPAddress fe80_intf22(FE80_1, 22);
+    EXPECT_EQ(22U, fe80_intf22.scope_id());
+    EXPECT_EQ(fe80_intf22, IPAddress(fe80_intf22));
+    EXPECT_EQ(IPAddress(FE80_1), IPAddress(fe80_intf22, 0));
+}
+
+TEST(IPAddressTest, forString) {
+    IPAddress ip;
+
+    EXPECT_FALSE(IPAddress::forString("not_an_ip", &ip));
+    EXPECT_FALSE(IPAddress::forString("not_an_ip", nullptr));
+    EXPECT_EQ(IPAddress(), IPAddress::forString("not_an_ip"));
+
+    EXPECT_EQ(IPAddress(IPV4_ANY), IPAddress::forString("0.0.0.0"));
+    EXPECT_EQ(IPAddress(IPV4_ONES), IPAddress::forString("255.255.255.255"));
+    EXPECT_EQ(IPAddress(IPV4_LOOPBACK), IPAddress::forString("127.0.0.1"));
+
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::"));
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::0"));
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("0::"));
+    EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("::1"));
+    EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("0::1"));
+    EXPECT_EQ(IPAddress(FE80_1), IPAddress::forString("fe80::1"));
+    EXPECT_EQ(IPAddress(FE80_1, 22), IPAddress::forString("fe80::1%22"));
+    // This relies upon having a loopback interface named "lo" with ifindex 1.
+    EXPECT_EQ(IPAddress(FE80_1, 1), IPAddress::forString("fe80::1%lo"));
+}
+
+TEST(IPPrefixTest, forString) {
+    IPPrefix prefix;
+
+    EXPECT_FALSE(IPPrefix::forString("", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("invalid", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001::db8::", &prefix));
+
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8:://32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32z", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32/", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/0x20", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8:: /32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/ 32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString(" 2001:db8::/32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32 ", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/+32", &prefix));
+
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0/33", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/129", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0/-1", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/-1", &prefix));
+
+    EXPECT_TRUE(IPPrefix::forString("2001:db8::/32", &prefix));
+    EXPECT_EQ("2001:db8::/32", prefix.toString());
+    EXPECT_EQ(IPPrefix(IPAddress::forString("2001:db8::"), 32), prefix);
+
+    EXPECT_EQ(IPPrefix(), IPPrefix::forString("invalid"));
+
+    EXPECT_EQ("0.0.0.0/0", IPPrefix::forString("0.0.0.0/0").toString());
+    EXPECT_EQ("::/0", IPPrefix::forString("::/0").toString());
+    EXPECT_EQ("192.0.2.128/25", IPPrefix::forString("192.0.2.131/25").toString());
+    EXPECT_EQ("2001:db8:1:2:3:4:5:4/126",
+              IPPrefix::forString("2001:db8:1:2:3:4:5:6/126").toString());
+}
+
+TEST(IPPrefixTest, IPv4Truncation) {
+    const auto prefixStr = [](int length) -> std::string {
+        return IPPrefix(IPAddress(IPV4_ONES), length).toString();
+    };
+
+    EXPECT_EQ("0.0.0.0/0", prefixStr(0));
+
+    EXPECT_EQ("128.0.0.0/1", prefixStr(1));
+    EXPECT_EQ("192.0.0.0/2", prefixStr(2));
+    EXPECT_EQ("224.0.0.0/3", prefixStr(3));
+    EXPECT_EQ("240.0.0.0/4", prefixStr(4));
+    EXPECT_EQ("248.0.0.0/5", prefixStr(5));
+    EXPECT_EQ("252.0.0.0/6", prefixStr(6));
+    EXPECT_EQ("254.0.0.0/7", prefixStr(7));
+    EXPECT_EQ("255.0.0.0/8", prefixStr(8));
+
+    EXPECT_EQ("255.128.0.0/9", prefixStr(9));
+    EXPECT_EQ("255.192.0.0/10", prefixStr(10));
+    EXPECT_EQ("255.224.0.0/11", prefixStr(11));
+    EXPECT_EQ("255.240.0.0/12", prefixStr(12));
+    EXPECT_EQ("255.248.0.0/13", prefixStr(13));
+    EXPECT_EQ("255.252.0.0/14", prefixStr(14));
+    EXPECT_EQ("255.254.0.0/15", prefixStr(15));
+    EXPECT_EQ("255.255.0.0/16", prefixStr(16));
+
+    EXPECT_EQ("255.255.128.0/17", prefixStr(17));
+    EXPECT_EQ("255.255.192.0/18", prefixStr(18));
+    EXPECT_EQ("255.255.224.0/19", prefixStr(19));
+    EXPECT_EQ("255.255.240.0/20", prefixStr(20));
+    EXPECT_EQ("255.255.248.0/21", prefixStr(21));
+    EXPECT_EQ("255.255.252.0/22", prefixStr(22));
+    EXPECT_EQ("255.255.254.0/23", prefixStr(23));
+    EXPECT_EQ("255.255.255.0/24", prefixStr(24));
+
+    EXPECT_EQ("255.255.255.128/25", prefixStr(25));
+    EXPECT_EQ("255.255.255.192/26", prefixStr(26));
+    EXPECT_EQ("255.255.255.224/27", prefixStr(27));
+    EXPECT_EQ("255.255.255.240/28", prefixStr(28));
+    EXPECT_EQ("255.255.255.248/29", prefixStr(29));
+    EXPECT_EQ("255.255.255.252/30", prefixStr(30));
+    EXPECT_EQ("255.255.255.254/31", prefixStr(31));
+    EXPECT_EQ("255.255.255.255/32", prefixStr(32));
+}
+
+TEST(IPPrefixTest, IPv6Truncation) {
+    const auto prefixStr = [](int length) -> std::string {
+        return IPPrefix(IPAddress(IPV6_ONES), length).toString();
+    };
+
+    EXPECT_EQ("::/0", prefixStr(0));
+
+    EXPECT_EQ("8000::/1", prefixStr(1));
+    EXPECT_EQ("c000::/2", prefixStr(2));
+    EXPECT_EQ("e000::/3", prefixStr(3));
+    EXPECT_EQ("f000::/4", prefixStr(4));
+    EXPECT_EQ("f800::/5", prefixStr(5));
+    EXPECT_EQ("fc00::/6", prefixStr(6));
+    EXPECT_EQ("fe00::/7", prefixStr(7));
+    EXPECT_EQ("ff00::/8", prefixStr(8));
+
+    EXPECT_EQ("ff80::/9", prefixStr(9));
+    EXPECT_EQ("ffc0::/10", prefixStr(10));
+    EXPECT_EQ("ffe0::/11", prefixStr(11));
+    EXPECT_EQ("fff0::/12", prefixStr(12));
+    EXPECT_EQ("fff8::/13", prefixStr(13));
+    EXPECT_EQ("fffc::/14", prefixStr(14));
+    EXPECT_EQ("fffe::/15", prefixStr(15));
+    EXPECT_EQ("ffff::/16", prefixStr(16));
+
+    EXPECT_EQ("ffff:8000::/17", prefixStr(17));
+    EXPECT_EQ("ffff:c000::/18", prefixStr(18));
+    EXPECT_EQ("ffff:e000::/19", prefixStr(19));
+    EXPECT_EQ("ffff:f000::/20", prefixStr(20));
+    EXPECT_EQ("ffff:f800::/21", prefixStr(21));
+    EXPECT_EQ("ffff:fc00::/22", prefixStr(22));
+    EXPECT_EQ("ffff:fe00::/23", prefixStr(23));
+    EXPECT_EQ("ffff:ff00::/24", prefixStr(24));
+
+    EXPECT_EQ("ffff:ff80::/25", prefixStr(25));
+    EXPECT_EQ("ffff:ffc0::/26", prefixStr(26));
+    EXPECT_EQ("ffff:ffe0::/27", prefixStr(27));
+    EXPECT_EQ("ffff:fff0::/28", prefixStr(28));
+    EXPECT_EQ("ffff:fff8::/29", prefixStr(29));
+    EXPECT_EQ("ffff:fffc::/30", prefixStr(30));
+    EXPECT_EQ("ffff:fffe::/31", prefixStr(31));
+    EXPECT_EQ("ffff:ffff::/32", prefixStr(32));
+
+    EXPECT_EQ("ffff:ffff:8000::/33", prefixStr(33));
+    EXPECT_EQ("ffff:ffff:c000::/34", prefixStr(34));
+    EXPECT_EQ("ffff:ffff:e000::/35", prefixStr(35));
+    EXPECT_EQ("ffff:ffff:f000::/36", prefixStr(36));
+    EXPECT_EQ("ffff:ffff:f800::/37", prefixStr(37));
+    EXPECT_EQ("ffff:ffff:fc00::/38", prefixStr(38));
+    EXPECT_EQ("ffff:ffff:fe00::/39", prefixStr(39));
+    EXPECT_EQ("ffff:ffff:ff00::/40", prefixStr(40));
+
+    EXPECT_EQ("ffff:ffff:ff80::/41", prefixStr(41));
+    EXPECT_EQ("ffff:ffff:ffc0::/42", prefixStr(42));
+    EXPECT_EQ("ffff:ffff:ffe0::/43", prefixStr(43));
+    EXPECT_EQ("ffff:ffff:fff0::/44", prefixStr(44));
+    EXPECT_EQ("ffff:ffff:fff8::/45", prefixStr(45));
+    EXPECT_EQ("ffff:ffff:fffc::/46", prefixStr(46));
+    EXPECT_EQ("ffff:ffff:fffe::/47", prefixStr(47));
+    EXPECT_EQ("ffff:ffff:ffff::/48", prefixStr(48));
+
+    EXPECT_EQ("ffff:ffff:ffff:8000::/49", prefixStr(49));
+    EXPECT_EQ("ffff:ffff:ffff:c000::/50", prefixStr(50));
+    EXPECT_EQ("ffff:ffff:ffff:e000::/51", prefixStr(51));
+    EXPECT_EQ("ffff:ffff:ffff:f000::/52", prefixStr(52));
+    EXPECT_EQ("ffff:ffff:ffff:f800::/53", prefixStr(53));
+    EXPECT_EQ("ffff:ffff:ffff:fc00::/54", prefixStr(54));
+    EXPECT_EQ("ffff:ffff:ffff:fe00::/55", prefixStr(55));
+    EXPECT_EQ("ffff:ffff:ffff:ff00::/56", prefixStr(56));
+
+    EXPECT_EQ("ffff:ffff:ffff:ff80::/57", prefixStr(57));
+    EXPECT_EQ("ffff:ffff:ffff:ffc0::/58", prefixStr(58));
+    EXPECT_EQ("ffff:ffff:ffff:ffe0::/59", prefixStr(59));
+    EXPECT_EQ("ffff:ffff:ffff:fff0::/60", prefixStr(60));
+    EXPECT_EQ("ffff:ffff:ffff:fff8::/61", prefixStr(61));
+    EXPECT_EQ("ffff:ffff:ffff:fffc::/62", prefixStr(62));
+    EXPECT_EQ("ffff:ffff:ffff:fffe::/63", prefixStr(63));
+    EXPECT_EQ("ffff:ffff:ffff:ffff::/64", prefixStr(64));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:8000::/65", prefixStr(65));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:c000::/66", prefixStr(66));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:e000::/67", prefixStr(67));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:f000::/68", prefixStr(68));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:f800::/69", prefixStr(69));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fc00::/70", prefixStr(70));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fe00::/71", prefixStr(71));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ff00::/72", prefixStr(72));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ff80::/73", prefixStr(73));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffc0::/74", prefixStr(74));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffe0::/75", prefixStr(75));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fff0::/76", prefixStr(76));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fff8::/77", prefixStr(77));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fffc::/78", prefixStr(78));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fffe::/79", prefixStr(79));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff::/80", prefixStr(80));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:8000::/81", prefixStr(81));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:c000::/82", prefixStr(82));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:e000::/83", prefixStr(83));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f000::/84", prefixStr(84));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f800::/85", prefixStr(85));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fc00::/86", prefixStr(86));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fe00::/87", prefixStr(87));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff00::/88", prefixStr(88));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff80::/89", prefixStr(89));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffc0::/90", prefixStr(90));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffe0::/91", prefixStr(91));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff0::/92", prefixStr(92));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff8::/93", prefixStr(93));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffc::/94", prefixStr(94));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffe::/95", prefixStr(95));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff::/96", prefixStr(96));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:8000:0/97", prefixStr(97));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:c000:0/98", prefixStr(98));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:e000:0/99", prefixStr(99));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f000:0/100", prefixStr(100));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f800:0/101", prefixStr(101));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/102", prefixStr(102));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/103", prefixStr(103));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/104", prefixStr(104));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/105", prefixStr(105));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/106", prefixStr(106));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/107", prefixStr(107));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/108", prefixStr(108));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/109", prefixStr(109));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/110", prefixStr(110));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/111", prefixStr(111));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112", prefixStr(112));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/113", prefixStr(113));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/114", prefixStr(114));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/115", prefixStr(115));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/116", prefixStr(116));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/117", prefixStr(117));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/118", prefixStr(118));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/119", prefixStr(119));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120", prefixStr(120));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/121", prefixStr(121));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/122", prefixStr(122));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/123", prefixStr(123));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/124", prefixStr(124));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/125", prefixStr(125));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/126", prefixStr(126));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/127", prefixStr(127));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", prefixStr(128));
+}
+
+TEST(IPPrefixTest, TruncationOther) {
+    const struct {
+        const char* ip;
+        const int cidrLen;
+        const char* ipTruncated;
+    } testExpectations[] = {
+            {"192.0.2.0", 24, "192.0.2.0"},
+            {"192.0.2.0", 23, "192.0.2.0"},
+            {"192.0.2.0", 22, "192.0.0.0"},
+            {"192.0.2.0", 1, "128.0.0.0"},
+            {"2001:db8:cafe:d00d::", 56, "2001:db8:cafe:d000::"},
+            {"2001:db8:cafe:d00d::", 48, "2001:db8:cafe::"},
+            {"2001:db8:cafe:d00d::", 47, "2001:db8:cafe::"},
+            {"2001:db8:cafe:d00d::", 46, "2001:db8:cafc::"},
+    };
+
+    for (const auto& expectation : testExpectations) {
+        IPAddress ip;
+        EXPECT_TRUE(IPAddress::forString(expectation.ip, &ip))
+                << "Failed to parse IP address " << expectation.ip;
+
+        IPAddress ipTruncated;
+        EXPECT_TRUE(IPAddress::forString(expectation.ipTruncated, &ipTruncated))
+                << "Failed to parse IP address " << expectation.ipTruncated;
+
+        IPPrefix prefix(ip, expectation.cidrLen);
+
+        EXPECT_EQ(expectation.cidrLen, prefix.length())
+                << "Unexpected cidrLen " << expectation.cidrLen;
+        EXPECT_EQ(ipTruncated, prefix.ip())
+                << "Unexpected IP truncation: " << prefix.ip() << ", expected: " << ipTruncated;
+    }
+}
+
+TEST(IPPrefixTest, containsPrefix) {
+    const struct {
+        const char* prefix;
+        const char* otherPrefix;
+        const bool expected;
+        std::string asParameters() const {
+            return fmt::format("prefix={}, other={}, expect={}", prefix, otherPrefix, expected);
+        }
+    } testExpectations[] = {
+            {"192.0.0.0/8", "192.0.0.0/8", true},
+            {"192.1.0.0/16", "192.1.0.0/16", true},
+            {"192.1.2.0/24", "192.1.2.0/24", true},
+            {"192.1.2.3/32", "192.1.2.3/32", true},
+            {"0.0.0.0/0", "192.0.0.0/8", true},
+            {"0.0.0.0/0", "192.1.0.0/16", true},
+            {"0.0.0.0/0", "192.1.2.0/24", true},
+            {"0.0.0.0/0", "192.1.2.3/32", true},
+            {"192.0.0.0/8", "192.1.0.0/16", true},
+            {"192.0.0.0/8", "192.1.2.0/24", true},
+            {"192.0.0.0/8", "192.1.2.5/32", true},
+            {"192.1.0.0/16", "192.1.2.0/24", true},
+            {"192.1.0.0/16", "192.1.3.6/32", true},
+            {"192.5.6.0/24", "192.5.6.7/32", true},
+            {"192.1.2.3/32", "192.1.2.0/24", false},
+            {"192.1.2.3/32", "192.1.0.0/16", false},
+            {"192.1.2.3/32", "192.0.0.0/8", false},
+            {"192.1.2.3/32", "0.0.0.0/0", false},
+            {"192.1.2.0/24", "192.1.0.0/16", false},
+            {"192.1.2.0/24", "192.0.0.0/8", false},
+            {"192.1.2.0/24", "0.0.0.0/0", false},
+            {"192.9.0.0/16", "192.0.0.0/8", false},
+            {"192.9.0.0/16", "0.0.0.0/0", false},
+            {"192.0.0.0/8", "0.0.0.0/0", false},
+            {"192.0.0.0/8", "191.0.0.0/8", false},
+            {"191.0.0.0/8", "192.0.0.0/8", false},
+            {"192.8.0.0/16", "192.7.0.0/16", false},
+            {"192.7.0.0/16", "192.8.0.0/16", false},
+            {"192.8.6.0/24", "192.7.5.0/24", false},
+            {"192.7.5.0/24", "192.8.6.0/24", false},
+            {"192.8.6.100/32", "192.8.6.200/32", false},
+            {"192.8.6.200/32", "192.8.6.100/32", false},
+            {"192.0.0.0/8", "192.0.0.0/12", true},
+            {"192.0.0.0/12", "192.0.0.0/8", false},
+            {"2001::/16", "2001::/16", true},
+            {"2001:db8::/32", "2001:db8::/32", true},
+            {"2001:db8:cafe::/48", "2001:db8:cafe::/48", true},
+            {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:d00d::/64", true},
+            {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec0::/80", true},
+            {"2001:db8:cafe:d00d:fec0:de::/96", "2001:db8:cafe:d00d:fec0:de::/96", true},
+            {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+            {"2001:db8::cafe:0:1/128", "2001:db8::cafe:0:1/128", true},
+            {"2001::/16", "2001:db8::/32", true},
+            {"2001::/16", "2001:db8:cafe::/48", true},
+            {"2001::/16", "2001:db8:cafe:d00d::/64", true},
+            {"2001::/16", "2001:db8:cafe:d00d:fec0::/80", true},
+            {"2001::/16", "2001:db8:cafe:d00d:fec0:de::/96", true},
+            {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+            {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true},
+            {"::/0", "2001::/16", true},
+            {"::/0", "2001:db8::/32", true},
+            {"::/0", "2001:db8:cafe::/48", true},
+            {"::/0", "2001:db8:cafe:d00d::/64", true},
+            {"::/0", "2001:db8:cafe:d00d:fec0::/80", true},
+            {"::/0", "2001:db8:cafe:d00d:fec0:de::/96", true},
+            {"::/0", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+            {"::/0", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true},
+            {"2001:db8::dd/128", "2001::/16", false},
+            {"2001:db8::dd/128", "2001:db8::/32", false},
+            {"2001:db8::dd/128", "2001:db8:cafe::/48", false},
+            {"2001:db8::dd/128", "2001:db8:cafe:d00d::/64", false},
+            {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0::/80", false},
+            {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de::/96", false},
+            {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de:ac::/112", false},
+            {"2001:db7::/32", "2001:db8::/32", false},
+            {"2001:db8::/32", "2001:db7::/32", false},
+            {"2001:db8:caff::/48", "2001:db8:cafe::/48", false},
+            {"2001:db8:cafe::/48", "2001:db8:caff::/48", false},
+            {"2001:db8:cafe:a00d::/64", "2001:db8:cafe:d00d::/64", false},
+            {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:a00d::/64", false},
+            {"2001:db8:cafe:d00d:fec1::/80", "2001:db8:cafe:d00d:fec0::/80", false},
+            {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec1::/80", false},
+            {"2001:db8:cafe:d00d:fec0:dd::/96", "2001:db8:cafe:d00d:fec0:ae::/96", false},
+            {"2001:db8:cafe:d00d:fec0:ae::/96", "2001:db8:cafe:d00d:fec0:dd::/96", false},
+            {"2001:db8:cafe:d00d:fec0:de:aa::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", false},
+            {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+            {"2001:db8::cafe:0:123/128", "2001:db8::cafe:0:456/128", false},
+            {"2001:db8::cafe:0:456/128", "2001:db8::cafe:0:123/128", false},
+            {"2001:db8::/32", "2001:db8::/64", true},
+            {"2001:db8::/64", "2001:db8::/32", false},
+            {"::/0", "0.0.0.0/0", false},
+            {"::/0", "1.0.0.0/8", false},
+            {"::/0", "1.2.0.0/16", false},
+            {"::/0", "1.2.3.0/24", false},
+            {"::/0", "1.2.3.4/32", false},
+            {"2001::/16", "1.2.3.4/32", false},
+            {"2001::db8::/32", "1.2.3.4/32", false},
+            {"2001:db8:cafe::/48", "1.2.3.4/32", false},
+            {"2001:db8:cafe:d00d::/64", "1.2.3.4/32", false},
+            {"2001:db8:cafe:d00d:fec0::/80", "1.2.3.4/32", false},
+            {"2001:db8:cafe:d00d:fec0:ae::/96", "1.2.3.4/32", false},
+            {"2001:db8:cafe:d00d:fec0:de:aa::/112", "1.2.3.4/32", false},
+            {"0.0.0.0/0", "::/0", false},
+            {"0.0.0.0/0", "2001::/16", false},
+            {"0.0.0.0/0", "2001::db8::/32", false},
+            {"0.0.0.0/0", "2001:db8:cafe::/48", false},
+            {"0.0.0.0/0", "2001:db8:cafe:d00d::/64", false},
+            {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0::/80", false},
+            {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:ae::/96", false},
+            {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+            {"1.2.3.4/32", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+            {"1.2.3.0/24", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+            {"1.2.0.0/16", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+            {"1.0.0.0/8", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+    };
+
+    for (const auto& expectation : testExpectations) {
+        SCOPED_TRACE(expectation.asParameters());
+        IPPrefix a = IPPrefix::forString(expectation.prefix);
+        IPPrefix b = IPPrefix::forString(expectation.otherPrefix);
+        EXPECT_EQ(expectation.expected, a.contains(b));
+    }
+}
+
+TEST(IPPrefixTest, containsAddress) {
+    const struct {
+        const char* prefix;
+        const char* address;
+        const bool expected;
+        std::string asParameters() const {
+            return fmt::format("prefix={}, address={}, expect={}", prefix, address, expected);
+        }
+    } testExpectations[] = {
+        {"0.0.0.0/0", "255.255.255.255", true},
+        {"0.0.0.0/0", "1.2.3.4", true},
+        {"0.0.0.0/0", "1.2.3.0", true},
+        {"0.0.0.0/0", "1.2.0.0", true},
+        {"0.0.0.0/0", "1.0.0.0", true},
+        {"0.0.0.0/0", "0.0.0.0", true},
+        {"0.0.0.0/0", "2001:4868:4860::8888", false},
+        {"0.0.0.0/0", "::/0", false},
+        {"192.0.2.0/23", "192.0.2.0", true},
+        {"192.0.2.0/23", "192.0.2.43", true},
+        {"192.0.2.0/23", "192.0.3.21", true},
+        {"192.0.2.0/23", "192.0.0.21", false},
+        {"192.0.2.0/23", "8.8.8.8", false},
+        {"192.0.2.0/23", "2001:4868:4860::8888", false},
+        {"192.0.2.0/23", "::/0", false},
+        {"1.2.3.4/32", "1.2.3.4", true},
+        {"1.2.3.4/32", "1.2.3.5", false},
+        {"10.0.0.0/8", "10.2.0.0", true},
+        {"10.0.0.0/8", "10.2.3.5", true},
+        {"10.0.0.0/8", "10.0.0.0", true},
+        {"10.0.0.0/8", "10.255.255.254", true},
+        {"10.0.0.0/8", "11.0.0.0", false},
+        {"::/0", "2001:db8:f000::ace:d00c", true},
+        {"::/0", "2002:db8:f00::ace:d00d", true},
+        {"::/0", "2001:db7:f00::ace:d00e", true},
+        {"::/0", "2001:db8:f01::bad:d00d", true},
+        {"::/0", "::", true},
+        {"::/0", "0.0.0.0", false},
+        {"::/0", "1.2.3.4", false},
+        {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00c", true},
+        {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00d", true},
+        {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00e", false},
+        {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::bad:d00d", false},
+        {"2001:db8:f00::ace:d00d/127", "2001:4868:4860::8888", false},
+        {"2001:db8:f00::ace:d00d/127", "8.8.8.8", false},
+        {"2001:db8:f00::ace:d00d/127", "0.0.0.0", false},
+        {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00d", true},
+        {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00c", false},
+        {"2001::/16", "2001::", true},
+        {"2001::/16", "2001:db8:f00::ace:d00d", true},
+        {"2001::/16", "2001:db8:f00::bad:d00d", true},
+        {"2001::/16", "2001::abc", true},
+        {"2001::/16", "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
+        {"2001::/16", "2000::", false},
+    };
+
+    for (const auto& expectation : testExpectations) {
+        SCOPED_TRACE(expectation.asParameters());
+        IPPrefix a = IPPrefix::forString(expectation.prefix);
+        IPAddress b = IPAddress::forString(expectation.address);
+        EXPECT_EQ(expectation.expected, a.contains(b));
+    }
+}
+
+TEST(IPPrefixTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPPrefix>> kExpectations{
+            {EQ, IPPrefix(), IPPrefix()},
+            {EQ, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 0)},
+            {EQ, IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS), IPPrefix(IPAddress(IPV4_ANY))},
+            {EQ, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            {EQ, IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS), IPPrefix(IPAddress(IPV6_ANY))},
+            // Needlessly fully-specified IPv6 link-local address.
+            {EQ, IPPrefix(IPAddress(FE80_1)), IPPrefix(IPAddress(FE80_1, 0), IPV6_ADDR_BITS)},
+            // Different IPv6 link-local addresses within the same /64, no scoped_id: same /64.
+            {EQ, IPPrefix(IPAddress(FE80_1), 64), IPPrefix(IPAddress(FE80_2), 64)},
+            // Different IPv6 link-local address within the same /64, same scoped_id: same /64.
+            {EQ, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 17), 64)},
+            // Unspecified < IPv4.
+            {LT, IPPrefix(), IPPrefix(IPAddress(IPV4_ANY), 0)},
+            // Same IPv4 base address sorts by prefix length.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 1)},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS)},
+            // Truncation means each base IPv4 address is different.
+            {LT, IPPrefix(IPAddress(IPV4_ONES), 0), IPPrefix(IPAddress(IPV4_ONES), 1)},
+            {LT, IPPrefix(IPAddress(IPV4_ONES), 1), IPPrefix(IPAddress(IPV4_ONES), IPV4_ADDR_BITS)},
+            // Sort by base IPv4 addresses first.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 24), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            // IPv4 < IPv6.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            {LT, IPPrefix(IPAddress(IPV4_ONES)), IPPrefix(IPAddress(IPV6_ANY))},
+            // Unspecified < IPv6.
+            {LT, IPPrefix(), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            // Same IPv6 base address sorts by prefix length.
+            {LT, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 1)},
+            {LT, IPPrefix(IPAddress(IPV6_ANY), 1), IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS)},
+            // Truncation means each base IPv6 address is different.
+            {LT, IPPrefix(IPAddress(IPV6_ONES), 0), IPPrefix(IPAddress(IPV6_ONES), 1)},
+            {LT, IPPrefix(IPAddress(IPV6_ONES), 1), IPPrefix(IPAddress(IPV6_ONES), IPV6_ADDR_BITS)},
+            // Different IPv6 link-local address in same /64, different scoped_id: different /64.
+            {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 22), 64)},
+            {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_1, 18), 64)},
+            {LT, IPPrefix(IPAddress(FE80_1, 18), 64), IPPrefix(IPAddress(FE80_1, 19), 64)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPSockAddrTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPSockAddr>> kExpectations{
+            {EQ, IPSockAddr(), IPSockAddr()},
+            {EQ, IPSockAddr(IPAddress(IPV4_ANY)), IPSockAddr(IPAddress(IPV4_ANY), 0)},
+            {EQ, IPSockAddr(IPAddress(IPV6_ANY)), IPSockAddr(IPAddress(IPV6_ANY), 0)},
+            {EQ, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1), 80)},
+            {EQ, IPSockAddr(IPAddress(FE80_1, 17)), IPSockAddr(IPAddress(FE80_1, 17), 0)},
+            {LT, IPSockAddr(IPAddress(IPV4_ANY), 0), IPSockAddr(IPAddress(IPV4_ANY), 1)},
+            {LT, IPSockAddr(IPAddress(IPV4_ANY), 53), IPSockAddr(IPAddress(IPV4_ANY), 123)},
+            {LT, IPSockAddr(IPAddress(IPV4_ONES), 123), IPSockAddr(IPAddress(IPV6_ANY), 53)},
+            {LT, IPSockAddr(IPAddress(IPV6_ANY), 0), IPSockAddr(IPAddress(IPV6_ANY), 1)},
+            {LT, IPSockAddr(IPAddress(IPV6_ANY), 53), IPSockAddr(IPAddress(IPV6_ANY), 123)},
+            {LT, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1, 17), 80)},
+            {LT, IPSockAddr(IPAddress(FE80_1, 17), 80), IPSockAddr(IPAddress(FE80_1, 22), 80)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPSockAddrTest, toString) {
+    EXPECT_EQ("<unspecified>:0", IPSockAddr().toString());
+    EXPECT_EQ("0.0.0.0:0", IPSockAddr(IPAddress(IPV4_ANY)).toString());
+    EXPECT_EQ("255.255.255.255:67", IPSockAddr(IPAddress(IPV4_ONES), 67).toString());
+    EXPECT_EQ("[::]:0", IPSockAddr(IPAddress(IPV6_ANY)).toString());
+    EXPECT_EQ("[::1]:53", IPSockAddr(IPAddress(IPV6_LOOPBACK), 53).toString());
+    EXPECT_EQ("[fe80::1]:0", IPSockAddr(IPAddress(FE80_1)).toString());
+    EXPECT_EQ("[fe80::2%17]:123", IPSockAddr(IPAddress(FE80_2, 17), 123).toString());
+}
+
+TEST(CompatIPDataTest, ConversionsClearUnneededValues) {
+    const uint32_t idx = 17;
+    const IPSockAddr linkLocalNtpSockaddr(IPAddress(FE80_2, idx), 123);
+    EXPECT_EQ(IPAddress(FE80_2, idx), linkLocalNtpSockaddr.ip());
+    // IPSockAddr(IPSockaddr.ip()) see the port cleared.
+    EXPECT_EQ(0, IPSockAddr(linkLocalNtpSockaddr.ip()).port());
+    const IPPrefix linkLocalPrefix(linkLocalNtpSockaddr.ip(), 64);
+    EXPECT_EQ(IPAddress(FE80, idx), linkLocalPrefix.ip());
+    // IPPrefix(IPPrefix.ip()) see the CIDR length cleared.
+    EXPECT_EQ(IPV6_ADDR_BITS, IPPrefix(linkLocalPrefix.ip()).length());
+}
+
+}  // namespace
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Log.cpp b/staticlibs/netd/libnetdutils/Log.cpp
new file mode 100644
index 0000000..d2ce98f
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Log.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/Log.h"
+#include "netdutils/Slice.h"
+
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <mutex>
+#include <sstream>
+
+#include <android-base/strings.h>
+#include <log/log.h>
+
+using ::android::base::Join;
+using ::android::base::StringPrintf;
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+std::string makeTimestampedEntry(const std::string& entry) {
+    using ::std::chrono::duration_cast;
+    using ::std::chrono::milliseconds;
+    using ::std::chrono::system_clock;
+
+    std::stringstream tsEntry;
+    const auto now = system_clock::now();
+    const auto time_sec = system_clock::to_time_t(now);
+    tsEntry << std::put_time(std::localtime(&time_sec), "%m-%d %H:%M:%S.") << std::setw(3)
+            << std::setfill('0')
+            << duration_cast<milliseconds>(now - system_clock::from_time_t(time_sec)).count() << " "
+            << entry;
+
+    return tsEntry.str();
+}
+
+}  // namespace
+
+std::string LogEntry::toString() const {
+    std::vector<std::string> text;
+
+    if (!mMsg.empty()) text.push_back(mMsg);
+    if (!mFunc.empty()) {
+        text.push_back(StringPrintf("%s(%s)", mFunc.c_str(), Join(mArgs, ", ").c_str()));
+    }
+    if (!mReturns.empty()) {
+        text.push_back("->");
+        text.push_back(StringPrintf("(%s)", Join(mReturns, ", ").c_str()));
+    }
+    if (!mUid.empty()) text.push_back(mUid);
+    if (!mDuration.empty()) text.push_back(StringPrintf("(%s)", mDuration.c_str()));
+
+    return Join(text, " ");
+}
+
+LogEntry& LogEntry::message(const std::string& message) {
+    mMsg = message;
+    return *this;
+}
+
+LogEntry& LogEntry::function(const std::string& function_name) {
+    mFunc = function_name;
+    return *this;
+}
+
+LogEntry& LogEntry::prettyFunction(const std::string& pretty_function) {
+    // __PRETTY_FUNCTION__ generally seems to be of the form:
+    //
+    //     qualifed::returnType qualified::function(args...)
+    //
+    // where the qualified forms include "(anonymous namespace)" in the
+    // "::"-delimited list and keywords like "virtual" (where applicable).
+    //
+    // Here we try to convert strings like:
+    //
+    //     virtual binder::Status android::net::NetdNativeService::isAlive(bool *)
+    //     netdutils::LogEntry android::netd::(anonymous namespace)::AAA::BBB::function()
+    //
+    // into just "NetdNativeService::isAlive" or "BBB::function". Note that
+    // without imposing convention, how to easily identify any namespace/class
+    // name boundary is not obvious.
+    const size_t endFuncName = pretty_function.rfind('(');
+    const size_t precedingSpace = pretty_function.rfind(' ', endFuncName);
+    size_t substrStart = (precedingSpace != std::string::npos) ? precedingSpace + 1 : 0;
+
+    const size_t beginFuncName = pretty_function.rfind("::", endFuncName);
+    if (beginFuncName != std::string::npos && substrStart < beginFuncName) {
+        const size_t previousNameBoundary = pretty_function.rfind("::", beginFuncName - 1);
+        if (previousNameBoundary < beginFuncName && substrStart < previousNameBoundary) {
+            substrStart = previousNameBoundary + 2;
+        } else {
+            substrStart = beginFuncName + 2;
+        }
+    }
+
+    mFunc = pretty_function.substr(substrStart, endFuncName - substrStart);
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::string& val) {
+    mArgs.push_back(val.empty() ? "\"\"" : val);
+    return *this;
+}
+
+template <>
+LogEntry& LogEntry::arg<>(bool val) {
+    mArgs.push_back(val ? "true" : "false");
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<int32_t>& val) {
+    mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str()));
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<uint8_t>& val) {
+    mArgs.push_back('{' + toHex(makeSlice(val)) + '}');
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<std::string>& val) {
+    mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str()));
+    return *this;
+}
+
+LogEntry& LogEntry::returns(const std::string& rval) {
+    mReturns.push_back(rval);
+    return *this;
+}
+
+LogEntry& LogEntry::returns(bool rval) {
+    mReturns.push_back(rval ? "true" : "false");
+    return *this;
+}
+
+LogEntry& LogEntry::returns(const Status& status) {
+    mReturns.push_back(status.msg());
+    return *this;
+}
+
+LogEntry& LogEntry::withUid(uid_t uid) {
+    mUid = StringPrintf("(uid=%d)", uid);
+    return *this;
+}
+
+LogEntry& LogEntry::withAutomaticDuration() {
+    using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
+
+    const std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
+    std::stringstream duration;
+    duration << std::setprecision(1) << std::chrono::duration_cast<ms>(end - mStart).count()
+             << "ms";
+    mDuration = duration.str();
+    return *this;
+}
+
+LogEntry& LogEntry::withDuration(const std::string& duration) {
+    mDuration = duration;
+    return *this;
+}
+
+Log::~Log() {
+    // TODO: dump the last N entries to the android log for possible posterity.
+    info(LogEntry().function(__FUNCTION__));
+}
+
+void Log::forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const {
+    // We make a (potentially expensive) copy of the log buffer (including
+    // all strings), in case the |perEntryFn| takes its sweet time.
+    std::deque<std::string> entries;
+    {
+        std::shared_lock<std::shared_mutex> guard(mLock);
+        entries.assign(mEntries.cbegin(), mEntries.cend());
+    }
+
+    for (const std::string& entry : entries) perEntryFn(entry);
+}
+
+void Log::record(Log::Level lvl, const std::string& entry) {
+    switch (lvl) {
+        case Level::LOG:
+            break;
+        case Level::INFO:
+            ALOG(LOG_INFO, mTag.c_str(), "%s", entry.c_str());
+            break;
+        case Level::WARN:
+            ALOG(LOG_WARN, mTag.c_str(), "%s", entry.c_str());
+            break;
+        case Level::ERROR:
+            ALOG(LOG_ERROR, mTag.c_str(), "%s", entry.c_str());
+            break;
+    }
+
+    std::lock_guard guard(mLock);
+    mEntries.push_back(makeTimestampedEntry(entry));
+    while (mEntries.size() > mMaxEntries) mEntries.pop_front();
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/LogTest.cpp b/staticlibs/netd/libnetdutils/LogTest.cpp
new file mode 100644
index 0000000..1270560
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/LogTest.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Log.h"
+
+android::netdutils::LogEntry globalFunctionName() {
+    return android::netdutils::LogEntry().function(__FUNCTION__);
+}
+
+android::netdutils::LogEntry globalPrettyFunctionName() {
+    return android::netdutils::LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+}
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+LogEntry functionName() {
+    return LogEntry().function(__FUNCTION__);
+}
+
+LogEntry prettyFunctionName() {
+    return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+}
+
+}  // namespace
+
+class AAA {
+  public:
+    AAA() = default;
+
+    LogEntry functionName() {
+        return LogEntry().function(__FUNCTION__);
+    }
+
+    LogEntry prettyFunctionName() {
+        return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+    }
+
+    class BBB {
+      public:
+        BBB() = default;
+
+        LogEntry functionName() {
+            return LogEntry().function(__FUNCTION__);
+        }
+
+        LogEntry prettyFunctionName() {
+            return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+        }
+    };
+};
+
+TEST(LogEntryTest, Empty) {
+    LogEntry empty;
+    EXPECT_EQ("", empty.toString());
+}
+
+TEST(LogEntryTest, GlobalFunction) {
+    EXPECT_EQ("globalFunctionName()", ::globalFunctionName().toString());
+}
+
+TEST(LogEntryTest, GlobalPrettyFunction) {
+    EXPECT_EQ("globalPrettyFunctionName()", ::globalPrettyFunctionName().toString());
+}
+
+TEST(LogEntryTest, UnnamedNamespaceFunction) {
+    const LogEntry entry = functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, UnnamedNamespacePrettyFunction) {
+    const LogEntry entry = prettyFunctionName();
+    EXPECT_EQ("prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, ClassFunction) {
+    const LogEntry entry = AAA().functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, ClassPrettyFunction) {
+    const LogEntry entry = AAA().prettyFunctionName();
+    EXPECT_EQ("AAA::prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, InnerClassFunction) {
+    const LogEntry entry = AAA::BBB().functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, InnerClassPrettyFunction) {
+    const LogEntry entry = AAA::BBB().prettyFunctionName();
+    EXPECT_EQ("BBB::prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, PrintChainedArguments) {
+    const LogEntry entry = LogEntry()
+            .function("testFunc")
+            .arg("hello")
+            .arg(42)
+            .arg(true);
+    EXPECT_EQ("testFunc(hello, 42, true)", entry.toString());
+}
+
+TEST(LogEntryTest, PrintIntegralTypes) {
+    const LogEntry entry = LogEntry()
+            .function("testFunc")
+            .arg('A')
+            .arg(100U)
+            .arg(-1000LL);
+    EXPECT_EQ("testFunc(65, 100, -1000)", entry.toString());
+}
+
+TEST(LogEntryTest, PrintHex) {
+    const std::vector<uint8_t> buf{0xDE, 0xAD, 0xBE, 0xEF};
+    const LogEntry entry = LogEntry().function("testFunc").arg(buf);
+    EXPECT_EQ("testFunc({deadbeef})", entry.toString());
+}
+
+TEST(LogEntryTest, PrintArgumentPack) {
+    const LogEntry entry = LogEntry().function("testFunc").args("hello", 42, false);
+    EXPECT_EQ("testFunc(hello, 42, false)", entry.toString());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/MemBlockTest.cpp b/staticlibs/netd/libnetdutils/MemBlockTest.cpp
new file mode 100644
index 0000000..6455a7e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/MemBlockTest.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <algorithm>
+#include <cstdint>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/MemBlock.h"
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+constexpr unsigned DNS_PACKET_SIZE = 512;
+constexpr int ARBITRARY_VALUE = 0x55;
+
+MemBlock makeArbitraryMemBlock(size_t len) {
+    MemBlock result(len);
+    // Do some fictional work before returning.
+    for (Slice slice = result.get(); !slice.empty(); slice = drop(slice, 1)) {
+        slice.base()[0] = ARBITRARY_VALUE;
+    }
+    return result;
+}
+
+void checkAllZeros(Slice slice) {
+    for (; !slice.empty(); slice = drop(slice, 1)) {
+        EXPECT_EQ(0U, slice.base()[0]);
+    }
+}
+
+void checkArbitraryMemBlock(const MemBlock& block, size_t expectedSize) {
+    Slice slice = block.get();
+    EXPECT_EQ(expectedSize, slice.size());
+    EXPECT_NE(nullptr, slice.base());
+    for (; !slice.empty(); slice = drop(slice, 1)) {
+        EXPECT_EQ(ARBITRARY_VALUE, slice.base()[0]);
+    }
+}
+
+void checkHelloMello(Slice dest, Slice src) {
+    EXPECT_EQ('h', dest.base()[0]);
+    EXPECT_EQ('e', dest.base()[1]);
+    EXPECT_EQ('l', dest.base()[2]);
+    EXPECT_EQ('l', dest.base()[3]);
+    EXPECT_EQ('o', dest.base()[4]);
+
+    src.base()[0] = 'm';
+    EXPECT_EQ('h', dest.base()[0]);
+}
+
+}  // namespace
+
+TEST(MemBlockTest, Empty) {
+    MemBlock empty;
+    EXPECT_TRUE(empty.get().empty());
+    EXPECT_EQ(nullptr, empty.get().base());
+}
+
+TEST(MemBlockTest, ExplicitZero) {
+    MemBlock zero(0);
+    EXPECT_TRUE(zero.get().empty());
+    EXPECT_EQ(nullptr, zero.get().base());
+}
+
+TEST(MemBlockTest, BasicAllocation) {
+    MemBlock dnsPacket(DNS_PACKET_SIZE);
+    Slice slice = dnsPacket.get();
+    EXPECT_EQ(DNS_PACKET_SIZE, slice.size());
+    // Verify the space is '\0'-initialized.
+    ASSERT_NO_FATAL_FAILURE(checkAllZeros(slice));
+    EXPECT_NE(nullptr, slice.base());
+}
+
+TEST(MemBlockTest, MoveConstruction) {
+    MemBlock block(makeArbitraryMemBlock(DNS_PACKET_SIZE));
+    ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE));
+}
+
+TEST(MemBlockTest, MoveAssignmentOrConstruction) {
+    MemBlock block = makeArbitraryMemBlock(DNS_PACKET_SIZE);
+    ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE));
+}
+
+TEST(MemBlockTest, StdMoveAssignment) {
+    constexpr unsigned SIZE = 10;
+
+    MemBlock block;
+    EXPECT_TRUE(block.get().empty());
+    EXPECT_EQ(nullptr, block.get().base());
+
+    {
+        MemBlock block2 = makeArbitraryMemBlock(SIZE);
+        EXPECT_EQ(SIZE, block2.get().size());
+        // More fictional work.
+        for (unsigned i = 0; i < SIZE; i++) {
+            block2.get().base()[i] = i;
+        }
+        block = std::move(block2);
+    }
+
+    EXPECT_EQ(SIZE, block.get().size());
+    for (unsigned i = 0; i < SIZE; i++) {
+        EXPECT_EQ(i, block.get().base()[i]);
+    }
+}
+
+TEST(MemBlockTest, ConstructionFromSlice) {
+    uint8_t data[] = {'h', 'e', 'l', 'l', 'o'};
+    Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0])));
+
+    MemBlock dataCopy(dataSlice);
+    ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy.get(), dataSlice));
+}
+
+TEST(MemBlockTest, ImplicitCastToSlice) {
+    uint8_t data[] = {'h', 'e', 'l', 'l', 'o'};
+    Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0])));
+
+    MemBlock dataCopy(dataSlice.size());
+    // NOTE: no explicit MemBlock::get().
+    // Verify the space is '\0'-initialized.
+    ASSERT_NO_FATAL_FAILURE(checkAllZeros(dataCopy));
+    copy(dataCopy, dataSlice);
+    ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy, dataSlice));
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Netfilter.cpp b/staticlibs/netd/libnetdutils/Netfilter.cpp
new file mode 100644
index 0000000..bb43de0
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Netfilter.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <arpa/inet.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <ios>
+
+#include "netdutils/Netfilter.h"
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg) {
+    return os << std::hex << "nfgenmsg["
+              << "family: 0x" << static_cast<int>(msg.nfgen_family) << ", version: 0x"
+              << static_cast<int>(msg.version) << ", res_id: 0x" << ntohs(msg.res_id) << "]"
+              << std::dec;
+}
diff --git a/staticlibs/netd/libnetdutils/Netlink.cpp b/staticlibs/netd/libnetdutils/Netlink.cpp
new file mode 100644
index 0000000..824c0f2
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Netlink.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <ios>
+#include <linux/netlink.h>
+
+#include "netdutils/Math.h"
+#include "netdutils/Netlink.h"
+
+namespace android {
+namespace netdutils {
+
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlmsghdr)) {
+        nlmsghdr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nlmsg_len, sizeof(hdr));
+        onMsg(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlattr)) {
+        nlattr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nla_len, sizeof(hdr));
+        onAttr(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return (lhs.nl_family == rhs.nl_family) && (lhs.nl_pid == rhs.nl_pid) &&
+           (lhs.nl_groups == rhs.nl_groups);
+}
+
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr) {
+    return os << std::hex << "nlmsghdr["
+              << "len: 0x" << hdr.nlmsg_len << ", type: 0x" << hdr.nlmsg_type << ", flags: 0x"
+              << hdr.nlmsg_flags << ", seq: 0x" << hdr.nlmsg_seq << ", pid: 0x" << hdr.nlmsg_pid
+              << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const nlattr& attr) {
+    return os << std::hex << "nlattr["
+              << "len: 0x" << attr.nla_len << ", type: 0x" << attr.nla_type << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr) {
+    return os << std::hex << "sockaddr_nl["
+              << "family: " << addr.nl_family << ", pid: " << addr.nl_pid
+              << ", groups: " << addr.nl_groups << "]" << std::dec;
+}
diff --git a/staticlibs/netd/libnetdutils/NetlinkListener.cpp b/staticlibs/netd/libnetdutils/NetlinkListener.cpp
new file mode 100644
index 0000000..decaa9c
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/NetlinkListener.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "NetlinkListener"
+
+#include <sstream>
+#include <vector>
+
+#include <linux/netfilter/nfnetlink.h>
+
+#include <log/log.h>
+#include <netdutils/Misc.h>
+#include <netdutils/NetlinkListener.h>
+#include <netdutils/Syscalls.h>
+
+namespace android {
+namespace netdutils {
+
+using netdutils::Fd;
+using netdutils::Slice;
+using netdutils::Status;
+using netdutils::UniqueFd;
+using netdutils::findWithDefault;
+using netdutils::forEachNetlinkMessage;
+using netdutils::makeSlice;
+using netdutils::sSyscalls;
+using netdutils::status::ok;
+using netdutils::statusFromErrno;
+
+namespace {
+
+constexpr int kNetlinkMsgErrorType = (NFNL_SUBSYS_NONE << 8) | NLMSG_ERROR;
+
+constexpr sockaddr_nl kKernelAddr = {
+    .nl_family = AF_NETLINK, .nl_pad = 0, .nl_pid = 0, .nl_groups = 0,
+};
+
+const NetlinkListener::DispatchFn kDefaultDispatchFn = [](const nlmsghdr& nlmsg, const Slice) {
+    std::stringstream ss;
+    ss << nlmsg;
+    ALOGE("unhandled netlink message: %s", ss.str().c_str());
+};
+
+}  // namespace
+
+NetlinkListener::NetlinkListener(UniqueFd event, UniqueFd sock, const std::string& name)
+    : mEvent(std::move(event)), mSock(std::move(sock)), mThreadName(name) {
+    const auto rxErrorHandler = [](const nlmsghdr& nlmsg, const Slice msg) {
+        std::stringstream ss;
+        ss << nlmsg << " " << msg << " " << netdutils::toHex(msg, 32);
+        ALOGE("unhandled netlink message: %s", ss.str().c_str());
+    };
+    expectOk(NetlinkListener::subscribe(kNetlinkMsgErrorType, rxErrorHandler));
+
+    mErrorHandler = [& name = mThreadName](const int fd, const int err) {
+        ALOGE("Error on NetlinkListener(%s) fd=%d: %s", name.c_str(), fd, strerror(err));
+    };
+
+    // Start the thread
+    mWorker = std::thread([this]() { run().ignoreError(); });
+}
+
+NetlinkListener::~NetlinkListener() {
+    const auto& sys = sSyscalls.get();
+    const uint64_t data = 1;
+    // eventfd should never enter an error state unexpectedly
+    expectOk(sys.write(mEvent, makeSlice(data)).status());
+    mWorker.join();
+}
+
+Status NetlinkListener::send(const Slice msg) {
+    const auto& sys = sSyscalls.get();
+    ASSIGN_OR_RETURN(auto sent, sys.sendto(mSock, msg, 0, kKernelAddr));
+    if (sent != msg.size()) {
+        return statusFromErrno(EMSGSIZE, "unexpect message size");
+    }
+    return ok;
+}
+
+Status NetlinkListener::subscribe(uint16_t type, const DispatchFn& fn) {
+    std::lock_guard guard(mMutex);
+    mDispatchMap[type] = fn;
+    return ok;
+}
+
+Status NetlinkListener::unsubscribe(uint16_t type) {
+    std::lock_guard guard(mMutex);
+    mDispatchMap.erase(type);
+    return ok;
+}
+
+void NetlinkListener::registerSkErrorHandler(const SkErrorHandler& handler) {
+    mErrorHandler = handler;
+}
+
+Status NetlinkListener::run() {
+    std::vector<char> rxbuf(4096);
+
+    const auto rxHandler = [this](const nlmsghdr& nlmsg, const Slice& buf) {
+        std::lock_guard guard(mMutex);
+        const auto& fn = findWithDefault(mDispatchMap, nlmsg.nlmsg_type, kDefaultDispatchFn);
+        fn(nlmsg, buf);
+    };
+
+    if (mThreadName.length() > 0) {
+        int ret = pthread_setname_np(pthread_self(), mThreadName.c_str());
+        if (ret) {
+            ALOGE("thread name set failed, name: %s, ret: %s", mThreadName.c_str(), strerror(ret));
+        }
+    }
+    const auto& sys = sSyscalls.get();
+    const std::array<Fd, 2> fds{{{mEvent}, {mSock}}};
+    const int events = POLLIN;
+    const double timeout = 3600;
+    while (true) {
+        ASSIGN_OR_RETURN(auto revents, sys.ppoll(fds, events, timeout));
+        // After mEvent becomes readable, we should stop servicing mSock and return
+        if (revents[0] & POLLIN) {
+            break;
+        }
+        if (revents[1] & (POLLIN|POLLERR)) {
+            auto rx = sys.recvfrom(mSock, makeSlice(rxbuf), 0);
+            int err = rx.status().code();
+            if (err) {
+                // Ignore errors. The only error we expect to see here is ENOBUFS, and there's
+                // nothing we can do about that. The recvfrom above will already have cleared the
+                // error indication and ensured we won't get EPOLLERR again.
+                // TODO: Consider using NETLINK_NO_ENOBUFS.
+                mErrorHandler(((Fd) mSock).get(), err);
+                continue;
+            }
+            forEachNetlinkMessage(rx.value(), rxHandler);
+        }
+    }
+    return ok;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Slice.cpp b/staticlibs/netd/libnetdutils/Slice.cpp
new file mode 100644
index 0000000..7a07d47
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Slice.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <sstream>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Convert one byte to a two character hexadecimal string
+const std::string toHex(uint8_t byte) {
+    const std::array<char, 16> kLookup = {
+        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
+    return {kLookup[byte >> 4], kLookup[byte & 0xf]};
+}
+
+}  // namespace
+
+std::string toString(const Slice s) {
+    return std::string(reinterpret_cast<char*>(s.base()), s.size());
+}
+
+std::string toHex(const Slice s, int wrap) {
+    Slice tail = s;
+    int count = 0;
+    std::stringstream ss;
+    while (!tail.empty()) {
+        uint8_t byte = 0;
+        extract(tail, byte);
+        ss << toHex(byte);
+        if ((++count % wrap) == 0) {
+            ss << "\n";
+        }
+        tail = drop(tail, 1);
+    }
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice) {
+    return os << std::hex << "Slice[base: " << reinterpret_cast<void*>(slice.base())
+              << ", limit: " << reinterpret_cast<void*>(slice.limit()) << ", size: 0x"
+              << slice.size() << "]" << std::dec;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SliceTest.cpp b/staticlibs/netd/libnetdutils/SliceTest.cpp
new file mode 100644
index 0000000..a496933
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SliceTest.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <array>
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+class SliceTest : public testing::Test {
+  protected:
+    std::array<char, 256> mRaw = {};
+};
+
+TEST_F(SliceTest, smoke) {
+    Slice s1 = makeSlice(mRaw);
+    Slice s2 = makeSlice(mRaw);
+    auto p = split(s1, 14);
+    s2 = p.first; // avoid warn-unused error
+    std::stringstream ss;
+    ss << Slice();
+    EXPECT_EQ("Slice[base: 0x0, limit: 0x0, size: 0x0]", ss.str());
+    constexpr size_t kBytes = 14;
+    EXPECT_EQ(s1.base(), take(s1, kBytes).base());
+    EXPECT_EQ(kBytes, take(s1, kBytes).size());
+    EXPECT_EQ(s1.base() + kBytes, drop(s1, kBytes).base());
+    EXPECT_EQ(s1.size() - kBytes, drop(s1, kBytes).size());
+    double a = 0;
+    double b = 0;
+    int c = 0;
+    EXPECT_EQ(sizeof(a), extract(s1, a));
+    EXPECT_EQ(sizeof(a) + sizeof(b), extract(s1, a, b));
+    EXPECT_EQ(sizeof(a) + sizeof(b) + sizeof(c), extract(s1, a, b, c));
+}
+
+TEST_F(SliceTest, constructor) {
+    // Expect the following lines to compile
+    Slice s1 = makeSlice(mRaw);
+    Slice s2(s1);
+    Slice s3 = s2;
+    const Slice s4(s3);
+    const Slice s5 = s4;
+    s3 = s5;
+    Slice s6(mRaw.data(), mRaw.size());
+    Slice s7(mRaw.data(), mRaw.data() + mRaw.size());
+    struct {
+      int a;
+      double b;
+      float c;
+    } anon;
+    makeSlice(anon);
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()), s1.base());
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()) + mRaw.size(), s1.limit());
+    EXPECT_EQ(mRaw.size(), s1.size());
+    EXPECT_FALSE(mRaw.empty());
+    EXPECT_TRUE(Slice().empty());
+    EXPECT_TRUE(Slice(nullptr, static_cast<size_t>(0)).empty());
+    EXPECT_TRUE(Slice(nullptr, nullptr).empty());
+}
+
+TEST_F(SliceTest, extract) {
+    struct A {
+        int a, b;
+        bool operator==(const A& other) const { return a == other.a && b == other.b; }
+    };
+    struct B {
+        char str[12];
+        bool b;
+        int i;
+        bool operator==(const B& other) const {
+            return b == other.b && i == other.i && 0 == strncmp(str, other.str, 12);
+        }
+    };
+
+    A origA1 = {1, 2};
+    A origA2 = {3, 4};
+    B origB = {"hello world", true, 1234};
+
+    // Populate buffer for extracting.
+    Slice buffer = makeSlice(mRaw);
+    copy(buffer, makeSlice(origA1));
+    copy(drop(buffer, sizeof(origA1)), makeSlice(origB));
+    copy(drop(buffer, sizeof(origA1) + sizeof(origB)), makeSlice(origA2));
+
+    {
+        // Non-variadic extract
+        A a1{};
+        size_t len = extract(buffer, a1);
+        EXPECT_EQ(sizeof(A), len);
+        EXPECT_EQ(origA1, a1);
+    }
+
+    {
+        // Variadic extract, 2 destinations
+        A a1{};
+        B b{};
+        size_t len = extract(buffer, a1, b);
+        EXPECT_EQ(sizeof(A) + sizeof(B), len);
+        EXPECT_EQ(origA1, a1);
+        EXPECT_EQ(origB, b);
+    }
+
+    {
+        // Variadic extract, 3 destinations
+        A a1{}, a2{};
+        B b{};
+        size_t len = extract(buffer, a1, b, a2);
+        EXPECT_EQ(2 * sizeof(A) + sizeof(B), len);
+        EXPECT_EQ(origA1, a1);
+        EXPECT_EQ(origB, b);
+        EXPECT_EQ(origA2, a2);
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Socket.cpp b/staticlibs/netd/libnetdutils/Socket.cpp
new file mode 100644
index 0000000..e962b6e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Socket.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <arpa/inet.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::string> toString(const in6_addr& addr) {
+    std::array<char, INET6_ADDRSTRLEN> out = {};
+    auto* rv = inet_ntop(AF_INET6, &addr, out.data(), out.size());
+    if (rv == nullptr) {
+        return statusFromErrno(errno, "inet_ntop() failed");
+    }
+    return std::string(out.data());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SocketOption.cpp b/staticlibs/netd/libnetdutils/SocketOption.cpp
new file mode 100644
index 0000000..023df6e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SocketOption.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/SocketOption.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <utility>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+Status enableSockopt(Fd sock, int level, int optname) {
+    auto& sys = sSyscalls.get();
+    const int on = 1;
+    return sys.setsockopt(sock, level, optname, &on, sizeof(on));
+}
+
+Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval) {
+    RETURN_IF_NOT_OK(enableSockopt(sock, SOL_SOCKET, SO_KEEPALIVE));
+
+    auto& sys = sSyscalls.get();
+    if (idleTime != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &idleTime, sizeof(idleTime)));
+    }
+    if (numProbes != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &numProbes, sizeof(numProbes)));
+    }
+    if (probeInterval != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &probeInterval,
+                sizeof(probeInterval)));
+    }
+
+    return status::ok;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Status.cpp b/staticlibs/netd/libnetdutils/Status.cpp
new file mode 100644
index 0000000..acd8f11
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Status.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Status.h"
+
+#include <sstream>
+
+#include "android-base/stringprintf.h"
+
+namespace android {
+namespace netdutils {
+
+Status statusFromErrno(int err, const std::string& msg) {
+    return Status(err, base::StringPrintf("[%s] : %s", strerror(err), msg.c_str()));
+}
+
+bool equalToErrno(const Status& status, int err) {
+    return status.code() == err;
+}
+
+std::string toString(const Status& status) {
+    std::stringstream ss;
+    ss << status;
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Status& s) {
+    return os << "Status[code: " << s.code() << ", msg: \"" << s.msg() << "\"]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/StatusTest.cpp b/staticlibs/netd/libnetdutils/StatusTest.cpp
new file mode 100644
index 0000000..4cfc3bb
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/StatusTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace netdutils {
+namespace {
+
+TEST(StatusTest, valueSemantics) {
+    // Default constructor
+    EXPECT_EQ(status::ok, Status());
+
+    // Copy constructor
+    Status status1(1);
+    Status status2(status1);  // NOLINT(performance-unnecessary-copy-initialization)
+    EXPECT_EQ(1, status2.code());
+
+    // Copy assignment
+    Status status3;
+    status3 = status2;
+    EXPECT_EQ(1, status3.code());
+
+    // Same with const objects
+    const Status status4(4);
+    const Status status5(status4);  // NOLINT(performance-unnecessary-copy-initialization)
+    Status status6;
+    status6 = status5;
+    EXPECT_EQ(4, status6.code());
+}
+
+TEST(StatusTest, errorMessages) {
+    Status s(42, "for tea too");
+    EXPECT_EQ(42, s.code());
+    EXPECT_FALSE(s.ok());
+    EXPECT_EQ(s.msg(), "for tea too");
+}
+
+TEST(StatusOrTest, moveSemantics) {
+    // Status objects should be cheaply movable.
+    EXPECT_TRUE(std::is_nothrow_move_constructible<Status>::value);
+    EXPECT_TRUE(std::is_nothrow_move_assignable<Status>::value);
+
+    // Should move from a temporary Status (twice)
+    Status s(Status(Status(42, "move me")));
+    EXPECT_EQ(42, s.code());
+    EXPECT_EQ(s.msg(), "move me");
+
+    Status s2(666, "EDAEMON");
+    EXPECT_NE(s, s2);
+    s = s2;  // Invokes the move-assignment operator.
+    EXPECT_EQ(666, s.code());
+    EXPECT_EQ(s.msg(), "EDAEMON");
+    EXPECT_EQ(s, s2);
+
+    // A moved-from Status can be re-used.
+    s2 = s;
+
+    // Now both objects are valid.
+    EXPECT_EQ(666, s.code());
+    EXPECT_EQ(s.msg(), "EDAEMON");
+    EXPECT_EQ(s, s2);
+}
+
+TEST(StatusTest, ignoredStatus) {
+    statusFromErrno(ENOTTY, "Not a typewriter, what did you expect?").ignoreError();
+}
+
+TEST(StatusOrTest, ostream) {
+    {
+      StatusOr<int> so(11);
+      std::stringstream ss;
+      ss << so;
+      // TODO: Fix StatusOr to optionally output "value:".
+      EXPECT_EQ("StatusOr[status: Status[code: 0, msg: \"\"]]", ss.str());
+    }
+    {
+      StatusOr<int> err(status::undefined);
+      std::stringstream ss;
+      ss << err;
+      EXPECT_EQ("StatusOr[status: Status[code: 2147483647, msg: \"undefined\"]]", ss.str());
+    }
+}
+
+}  // namespace
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Syscalls.cpp b/staticlibs/netd/libnetdutils/Syscalls.cpp
new file mode 100644
index 0000000..7e1a242
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Syscalls.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Syscalls.h"
+
+#include <atomic>
+#include <type_traits>
+#include <utility>
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Retry syscall fn as long as it returns -1 with errno == EINTR
+template <typename FnT, typename... Params>
+typename std::invoke_result<FnT, Params...>::type syscallRetry(FnT fn, Params&&... params) {
+    auto rv = fn(std::forward<Params>(params)...);
+    while ((rv == -1) && (errno == EINTR)) {
+        rv = fn(std::forward<Params>(params)...);
+    }
+    return rv;
+}
+
+}  // namespace
+
+// Production implementation of Syscalls that forwards to libc syscalls.
+class RealSyscalls final : public Syscalls {
+  public:
+    ~RealSyscalls() override = default;
+
+    StatusOr<UniqueFd> open(const std::string& pathname, int flags, mode_t mode) const override {
+        UniqueFd fd(::open(pathname.c_str(), flags, mode));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "open(\"" + pathname + "\"...) failed");
+        }
+        return fd;
+    }
+
+    StatusOr<UniqueFd> socket(int domain, int type, int protocol) const override {
+        UniqueFd sock(::socket(domain, type, protocol));
+        if (!isWellFormed(sock)) {
+            return statusFromErrno(errno, "socket() failed");
+        }
+        return sock;
+    }
+
+    Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const override {
+        auto rv = ::getsockname(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "getsockname() failed");
+        }
+        return status::ok;
+    }
+
+    Status getsockopt(Fd sock, int level, int optname, void* optval,
+                      socklen_t* optlen) const override {
+        auto rv = ::getsockopt(sock.get(), level, optname, optval, optlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "getsockopt() failed");
+        }
+        return status::ok;
+    }
+
+    Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                      socklen_t optlen) const override {
+        auto rv = ::setsockopt(sock.get(), level, optname, optval, optlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "setsockopt() failed");
+        }
+        return status::ok;
+    }
+
+    Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = ::bind(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "bind() failed");
+        }
+        return status::ok;
+    }
+
+    Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = syscallRetry(::connect, sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "connect() failed");
+        }
+        return status::ok;
+    }
+
+    StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const override {
+        auto rv = ::ioctl(sock.get(), request, ifr);
+        if (rv == -1) {
+            return statusFromErrno(errno, "ioctl() failed");
+        }
+        return *ifr;
+    }
+
+    StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const override {
+        UniqueFd fd(::eventfd(initval, flags));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "eventfd() failed");
+        }
+        return fd;
+    }
+
+    StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const override {
+        timespec ts = {};
+        ts.tv_sec = timeout;
+        ts.tv_nsec = (timeout - ts.tv_sec) * 1e9;
+        auto rv = syscallRetry(::ppoll, fds, nfds, &ts, nullptr);
+        if (rv == -1) {
+            return statusFromErrno(errno, "ppoll() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const override {
+        auto rv = syscallRetry(::writev, fd.get(), iov.data(), iov.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "writev() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<size_t> write(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::write, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "write() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> read(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::read, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "read() failed");
+        }
+        return Slice(buf.base(), rv);
+    }
+
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                            socklen_t dstlen) const override {
+        auto rv = syscallRetry(::sendto, sock.get(), buf.base(), buf.size(), flags, dst, dstlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "sendto() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                             socklen_t* srclen) const override {
+        auto rv = syscallRetry(::recvfrom, sock.get(), dst.base(), dst.size(), flags, src, srclen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "recvfrom() failed");
+        }
+        if (rv == 0) {
+            return status::eof;
+        }
+        return take(dst, rv);
+    }
+
+    Status shutdown(Fd fd, int how) const override {
+        auto rv = ::shutdown(fd.get(), how);
+        if (rv == -1) {
+            return statusFromErrno(errno, "shutdown() failed");
+        }
+        return status::ok;
+    }
+
+    Status close(Fd fd) const override {
+        auto rv = ::close(fd.get());
+        if (rv == -1) {
+            return statusFromErrno(errno, "close() failed");
+        }
+        return status::ok;
+    }
+
+    StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const override {
+        UniqueFile file(::fopen(path.c_str(), mode.c_str()));
+        if (file == nullptr) {
+            return statusFromErrno(errno, "fopen(\"" + path + "\", \"" + mode + "\") failed");
+        }
+        return file;
+    }
+
+    StatusOr<pid_t> fork() const override {
+        pid_t rv = ::fork();
+        if (rv == -1) {
+            return statusFromErrno(errno, "fork() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const override {
+        auto rv = ::vfprintf(file, format, ap);
+        if (rv == -1) {
+            return statusFromErrno(errno, "vfprintf() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const override {
+        auto rv = ::vfscanf(file, format, ap);
+        if (rv == -1) {
+            return statusFromErrno(errno, "vfscanf() failed");
+        }
+        return rv;
+    }
+
+    Status fclose(FILE* file) const override {
+        auto rv = ::fclose(file);
+        if (rv == -1) {
+            return statusFromErrno(errno, "fclose() failed");
+        }
+        return status::ok;
+    }
+};
+
+SyscallsHolder::~SyscallsHolder() {
+    delete &get();
+}
+
+Syscalls& SyscallsHolder::get() {
+    while (true) {
+        // memory_order_relaxed gives the compiler and hardware more
+        // freedom. If we get a stale value (this should only happen
+        // early in the execution of a program) the exchange code below
+        // will loop until we get the most current value.
+        auto* syscalls = mSyscalls.load(std::memory_order_relaxed);
+        // Common case returns existing syscalls
+        if (syscalls) {
+            return *syscalls;
+        }
+
+        // This code will execute on first get()
+        std::unique_ptr<Syscalls> tmp(new RealSyscalls());
+        Syscalls* expected = nullptr;
+        bool success = mSyscalls.compare_exchange_strong(expected, tmp.get());
+        if (success) {
+            // Ownership was transferred to mSyscalls already, must release()
+            return *tmp.release();
+        }
+    }
+}
+
+Syscalls& SyscallsHolder::swap(Syscalls& syscalls) {
+    return *mSyscalls.exchange(&syscalls);
+}
+
+SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SyscallsTest.cpp b/staticlibs/netd/libnetdutils/SyscallsTest.cpp
new file mode 100644
index 0000000..78ffab5
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SyscallsTest.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Handle.h"
+#include "netdutils/Math.h"
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Netfilter.h"
+#include "netdutils/Netlink.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/Syscalls.h"
+
+using testing::_;
+using testing::ByMove;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace android {
+namespace netdutils {
+
+class SyscallsTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST(syscalls, scopedMock) {
+    auto& old = sSyscalls.get();
+    {
+        StrictMock<ScopedMockSyscalls> s;
+        EXPECT_EQ(&s, &sSyscalls.get());
+    }
+    EXPECT_EQ(&old, &sSyscalls.get());
+}
+
+TEST_F(SyscallsTest, open) {
+    const char kPath[] = "/test/path/please/ignore";
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 883;
+    constexpr mode_t kMode = 37373;
+    const auto& sys = sSyscalls.get();
+    EXPECT_CALL(mSyscalls, open(kPath, kFlags, kMode)).WillOnce(Return(ByMove(UniqueFd(kFd))));
+    EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    auto result = sys.open(kPath, kFlags, kMode);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(kFd, result.value());
+}
+
+TEST_F(SyscallsTest, getsockname) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _))
+        .WillOnce(Invoke([expected](Fd, sockaddr* addr, socklen_t* addrlen) {
+            memcpy(addr, &expected, sizeof(expected));
+            EXPECT_EQ(*addrlen, static_cast<socklen_t>(sizeof(expected)));
+            return status::ok;
+        }));
+    const auto result = sys.getsockname<sockaddr_nl>(kFd);
+    EXPECT_TRUE(isOk(result));
+    EXPECT_EQ(expected, result.value());
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _)).WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.getsockname<sockaddr_nl>(kFd).status());
+}
+
+TEST_F(SyscallsTest, setsockopt) {
+    constexpr Fd kFd(40);
+    constexpr int kLevel = 50;
+    constexpr int kOptname = 70;
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.setsockopt(kFd, kLevel, kOptname, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.setsockopt(kFd, kLevel, kOptname, expected));
+}
+
+TEST_F(SyscallsTest, getsockopt) {
+    constexpr Fd kFd(40);
+    constexpr int kLevel = 50;
+    constexpr int kOptname = 70;
+    sockaddr_nl expected = {};
+    socklen_t optLen = 0;
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen));
+}
+
+TEST_F(SyscallsTest, bind) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.bind(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.bind(kFd, expected));
+}
+
+TEST_F(SyscallsTest, connect) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.connect(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.connect(kFd, expected));
+}
+
+TEST_F(SyscallsTest, sendto) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto slice = makeSlice(payload);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, sendto(kFd, slice, kFlags, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(slice.size()));
+    EXPECT_EQ(status::ok, sys.sendto(kFd, slice, kFlags, expected));
+}
+
+TEST_F(SyscallsTest, recvfrom) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto dst = makeSlice(payload);
+    const auto used = take(dst, 8);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, recvfrom(kFd, dst, kFlags, _, _))
+            .WillOnce(Invoke(
+                    [expected, used](Fd, const Slice, int, sockaddr* src, socklen_t* srclen) {
+                        *srclen = sizeof(expected);
+                        memcpy(src, &expected, *srclen);
+                        return used;
+                    }));
+    auto result = sys.recvfrom<sockaddr_nl>(kFd, dst, kFlags);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(used, result.value().first);
+    EXPECT_EQ(expected, result.value().second);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp
new file mode 100644
index 0000000..8fad8b8
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <string>
+
+#include <android-base/expected.h>
+#include <gtest/gtest.h>
+#include <netdutils/ThreadUtil.h>
+
+namespace android::netdutils {
+
+namespace {
+
+android::base::expected<std::string, int> getThreadName() {
+    char name[16] = {};
+    if (const int ret = pthread_getname_np(pthread_self(), name, sizeof(name)); ret != 0) {
+        return android::base::unexpected(ret);
+    }
+    return std::string(name);
+}
+
+class NoopRun {
+  public:
+    explicit NoopRun(const std::string& name = "") : mName(name) { instanceNum++; }
+
+    // Destructor happens in the thread.
+    ~NoopRun() {
+        if (checkName) {
+            auto expected = getThreadName();
+            EXPECT_TRUE(expected.has_value());
+            EXPECT_EQ(mExpectedName, expected.value());
+        }
+        instanceNum--;
+    }
+
+    void run() {}
+
+    std::string threadName() { return mName; }
+
+    // Set the expected thread name which will be used to check if it matches the actual thread
+    // name which is returned from the system call. The check will happen in the destructor.
+    void setExpectedName(const std::string& expectedName) {
+        checkName = true;
+        mExpectedName = expectedName;
+    }
+
+    static bool waitForAllReleased(int timeoutMs) {
+        constexpr int intervalMs = 20;
+        int limit = timeoutMs / intervalMs;
+        for (int i = 1; i < limit; i++) {
+            if (instanceNum == 0) {
+                return true;
+            }
+            usleep(intervalMs * 1000);
+        }
+        return false;
+    }
+
+    // To track how many instances are alive.
+    static std::atomic<int> instanceNum;
+
+  private:
+    std::string mName;
+    std::string mExpectedName;
+    bool checkName = false;
+};
+
+std::atomic<int> NoopRun::instanceNum;
+
+}  // namespace
+
+TEST(ThreadUtilTest, objectReleased) {
+    NoopRun::instanceNum = 0;
+    NoopRun* obj = new NoopRun();
+    EXPECT_EQ(1, NoopRun::instanceNum);
+    threadLaunch(obj);
+
+    // Wait for the object released along with the thread exited.
+    EXPECT_TRUE(NoopRun::waitForAllReleased(1000));
+    EXPECT_EQ(0, NoopRun::instanceNum);
+}
+
+TEST(ThreadUtilTest, SetThreadName) {
+    NoopRun::instanceNum = 0;
+
+    // Test thread name empty.
+    NoopRun* obj1 = new NoopRun();
+    obj1->setExpectedName("");
+
+    // Test normal case.
+    NoopRun* obj2 = new NoopRun("TestName");
+    obj2->setExpectedName("TestName");
+
+    // Test thread name too long.
+    std::string name("TestNameTooooLong");
+    NoopRun* obj3 = new NoopRun(name);
+    obj3->setExpectedName(name.substr(0, 15));
+
+    // Thread names are examined in their destructors.
+    EXPECT_EQ(3, NoopRun::instanceNum);
+    threadLaunch(obj1);
+    threadLaunch(obj2);
+    threadLaunch(obj3);
+
+    EXPECT_TRUE(NoopRun::waitForAllReleased(1000));
+    EXPECT_EQ(0, NoopRun::instanceNum);
+}
+
+}  // namespace android::netdutils
diff --git a/staticlibs/netd/libnetdutils/UniqueFd.cpp b/staticlibs/netd/libnetdutils/UniqueFd.cpp
new file mode 100644
index 0000000..1cb30ed
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/UniqueFd.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <algorithm>
+
+#include "netdutils/UniqueFd.h"
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+void UniqueFd::reset(Fd fd) {
+    auto& sys = sSyscalls.get();
+    std::swap(fd, mFd);
+    if (isWellFormed(fd)) {
+        expectOk(sys.close(fd));
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd) {
+    return os << "UniqueFd[" << static_cast<Fd>(fd) << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/UniqueFile.cpp b/staticlibs/netd/libnetdutils/UniqueFile.cpp
new file mode 100644
index 0000000..21e8779
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/UniqueFile.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <algorithm>
+
+#include "netdutils/Syscalls.h"
+#include "netdutils/UniqueFile.h"
+
+namespace android {
+namespace netdutils {
+
+void UniqueFileDtor::operator()(FILE* file) const {
+    const auto& sys = sSyscalls.get();
+    sys.fclose(file).ignoreError();
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
new file mode 100644
index 0000000..16ec882
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <map>
+
+#include <net/if.h>
+
+#include "dirent.h"
+#include "netdutils/Status.h"
+#include "netdutils/Utils.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames() {
+    std::vector<std::string> ifaceNames;
+    DIR* d;
+    struct dirent* de;
+
+    if (!(d = opendir("/sys/class/net"))) {
+        return statusFromErrno(errno, "Cannot open iface directory");
+    }
+    while ((de = readdir(d))) {
+        if ((de->d_type != DT_DIR) && (de->d_type != DT_LNK)) continue;
+        if (de->d_name[0] == '.') continue;
+        ifaceNames.push_back(std::string(de->d_name));
+    }
+    closedir(d);
+    return ifaceNames;
+}
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList() {
+    std::map<std::string, uint32_t> ifacePairs;
+
+    ASSIGN_OR_RETURN(auto ifaceNames, getIfaceNames());
+
+    for (const auto& name : ifaceNames) {
+        uint32_t ifaceIndex = if_nametoindex(name.c_str());
+        if (ifaceIndex) {
+            ifacePairs.insert(std::pair<std::string, uint32_t>(name, ifaceIndex));
+        }
+    }
+    return ifacePairs;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h
new file mode 100644
index 0000000..a52e72d
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETDUTILS_BACKOFFSEQUENCE_H
+#define NETDUTILS_BACKOFFSEQUENCE_H
+
+#include <stdint.h>
+#include <algorithm>
+#include <chrono>
+#include <limits>
+
+namespace android {
+namespace netdutils {
+
+// Encapsulate some RFC 3315 section 14 -style backoff mechanics.
+//
+//     https://tools.ietf.org/html/rfc3315#section-14
+template<typename time_type = std::chrono::seconds, typename counter_type = uint32_t>
+class BackoffSequence {
+  public:
+    struct Parameters {
+        time_type initialRetransTime{TIME_UNITY};
+        counter_type maxRetransCount{0U};
+        time_type maxRetransTime{TIME_ZERO};
+        time_type maxRetransDuration{TIME_ZERO};
+        time_type endOfSequenceIndicator{TIME_ZERO};
+    };
+
+    BackoffSequence() : BackoffSequence(Parameters{}) {}
+    BackoffSequence(const BackoffSequence &) = default;
+    BackoffSequence(BackoffSequence &&) = default;
+    BackoffSequence& operator=(const BackoffSequence &) = default;
+    BackoffSequence& operator=(BackoffSequence &&) = default;
+
+    bool hasNextTimeout() const noexcept {
+        return !maxRetransCountExceed() && !maxRetransDurationExceeded();
+    }
+
+    // Returns 0 when the sequence is exhausted.
+    time_type getNextTimeout() {
+        if (!hasNextTimeout()) return getEndOfSequenceIndicator();
+
+        mRetransTime = getNextTimeoutAfter(mRetransTime);
+
+        mRetransCount++;
+        mTotalRetransDuration += mRetransTime;
+        return mRetransTime;
+    }
+
+    time_type getEndOfSequenceIndicator() const noexcept {
+        return mParams.endOfSequenceIndicator;
+    }
+
+    class Builder {
+      public:
+        Builder() {}
+
+        constexpr Builder& withInitialRetransmissionTime(time_type irt) {
+            mParams.initialRetransTime = irt;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionCount(counter_type mrc) {
+            mParams.maxRetransCount = mrc;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionTime(time_type mrt) {
+            mParams.maxRetransTime = mrt;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionDuration(time_type mrd) {
+            mParams.maxRetransDuration = mrd;
+            return *this;
+        }
+        constexpr Builder& withEndOfSequenceIndicator(time_type eos) {
+            mParams.endOfSequenceIndicator = eos;
+            return *this;
+        }
+
+        constexpr BackoffSequence build() const {
+            return BackoffSequence(mParams);
+        }
+
+      private:
+        Parameters mParams;
+    };
+
+  private:
+    static constexpr int PER_ITERATION_SCALING_FACTOR = 2;
+    static constexpr time_type TIME_ZERO = time_type();
+    static constexpr time_type TIME_UNITY = time_type(1);
+
+    constexpr BackoffSequence(const struct Parameters &params)
+            : mParams(params),
+              mRetransCount(0),
+              mRetransTime(TIME_ZERO),
+              mTotalRetransDuration(TIME_ZERO) {}
+
+    constexpr bool maxRetransCountExceed() const {
+        return (mParams.maxRetransCount > 0) && (mRetransCount >= mParams.maxRetransCount);
+    }
+
+    constexpr bool maxRetransDurationExceeded() const {
+        return (mParams.maxRetransDuration > TIME_ZERO) &&
+               (mTotalRetransDuration >= mParams.maxRetransDuration);
+    }
+
+    time_type getNextTimeoutAfter(time_type lastTimeout) const {
+        // TODO: Support proper random jitter. Also, consider supporting some
+        // per-iteration scaling factor other than doubling.
+        time_type nextTimeout = (lastTimeout > TIME_ZERO)
+                ? PER_ITERATION_SCALING_FACTOR * lastTimeout
+                : mParams.initialRetransTime;
+
+        // Check if overflow occurred.
+        if (nextTimeout < lastTimeout) {
+            nextTimeout = std::numeric_limits<time_type>::max();
+        }
+
+        // Cap to maximum allowed, if necessary.
+        if (mParams.maxRetransTime > TIME_ZERO) {
+            nextTimeout = std::min(nextTimeout, mParams.maxRetransTime);
+        }
+
+        // Don't overflow the maximum total duration.
+        if (mParams.maxRetransDuration > TIME_ZERO) {
+            nextTimeout = std::min(nextTimeout, mParams.maxRetransDuration - lastTimeout);
+        }
+        return nextTimeout;
+    }
+
+    const Parameters mParams;
+    counter_type mRetransCount;
+    time_type mRetransTime;
+    time_type mTotalRetransDuration;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_BACKOFFSEQUENCE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h
new file mode 100644
index 0000000..a50b5e6
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef NETDUTILS_DUMPWRITER_H_
+#define NETDUTILS_DUMPWRITER_H_
+
+#include <string>
+
+namespace android {
+namespace netdutils {
+
+class DumpWriter {
+  public:
+    DumpWriter(int fd);
+
+    void incIndent();
+    void decIndent();
+
+    void println(const std::string& line);
+    template <size_t n>
+    void println(const char line[n]) {
+        println(std::string(line));
+    }
+    // Hint to the compiler that it should apply printf validation of
+    // arguments (beginning at position 3) of the format (specified in
+    // position 2). Note that position 1 is the implicit "this" argument.
+    void println(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
+    void blankline() { println(""); }
+
+  private:
+    uint8_t mIndentLevel;
+    int mFd;
+};
+
+class ScopedIndent {
+  public:
+    ScopedIndent() = delete;
+    ScopedIndent(const ScopedIndent&) = delete;
+    ScopedIndent(ScopedIndent&&) = delete;
+    explicit ScopedIndent(DumpWriter& dw) : mDw(dw) { mDw.incIndent(); }
+    ~ScopedIndent() { mDw.decIndent(); }
+    ScopedIndent& operator=(const ScopedIndent&) = delete;
+    ScopedIndent& operator=(ScopedIndent&&) = delete;
+
+    // TODO: consider additional {inc,dec}Indent methods and a counter that
+    // can be used to unwind all pending increments on exit.
+
+  private:
+    DumpWriter& mDw;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_DUMPWRITER_H_
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Fd.h b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h
new file mode 100644
index 0000000..7db4087
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_FD_H
+#define NETUTILS_FD_H
+
+#include <ostream>
+
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Strongly typed wrapper for file descriptors with value semantics.
+// This class should typically hold unowned file descriptors.
+class Fd {
+  public:
+    constexpr Fd() = default;
+
+    constexpr Fd(int fd) : mFd(fd) {}
+
+    int get() const { return mFd; }
+
+    bool operator==(const Fd& other) const { return get() == other.get(); }
+    bool operator!=(const Fd& other) const { return get() != other.get(); }
+
+  private:
+    int mFd = -1;
+};
+
+// Return true if fd appears valid (non-negative)
+inline bool isWellFormed(const Fd fd) {
+    return fd.get() >= 0;
+}
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_FD_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Handle.h b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h
new file mode 100644
index 0000000..82083d4
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_HANDLE_H
+#define NETUTILS_HANDLE_H
+
+#include <ostream>
+
+namespace android {
+namespace netdutils {
+
+// Opaque, strongly typed wrapper for integer-like handles.
+// Explicitly avoids implementing arithmetic operations.
+//
+// This class is intended to avoid common errors when reordering
+// arguments to functions, typos and other cases where plain integer
+// types would silently cover up the mistake.
+//
+// usage:
+// DEFINE_HANDLE(ProductId, uint64_t);
+// DEFINE_HANDLE(ThumbnailHash, uint64_t);
+// void foo(ProductId p, ThumbnailHash th) {...}
+//
+// void test() {
+//     ProductId p(88);
+//     ThumbnailHash th1(100), th2(200);
+//
+//     foo(p, th1);        <- ok!
+//     foo(th1, p);        <- disallowed!
+//     th1 += 10;          <- disallowed!
+//     p = th2;            <- disallowed!
+//     assert(th1 != th2); <- ok!
+// }
+template <typename T, typename TagT>
+class Handle {
+  public:
+    constexpr Handle() = default;
+    constexpr Handle(const T& value) : mValue(value) {}
+
+    const T get() const { return mValue; }
+
+    bool operator==(const Handle& that) const { return get() == that.get(); }
+    bool operator!=(const Handle& that) const { return get() != that.get(); }
+
+  private:
+    T mValue;
+};
+
+#define DEFINE_HANDLE(name, type) \
+    struct _##name##Tag {};       \
+    using name = ::android::netdutils::Handle<type, _##name##Tag>;
+
+template <typename T, typename TagT>
+inline std::ostream& operator<<(std::ostream& os, const Handle<T, TagT>& handle) {
+    return os << handle.get();
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_HANDLE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
new file mode 100644
index 0000000..d10cec7
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <cstring>
+#include <limits>
+#include <string>
+
+#include "netdutils/NetworkConstants.h"
+
+namespace android {
+namespace netdutils {
+
+namespace internal_ {
+
+// A structure to hold data for dealing with Internet addresses (IPAddress) and
+// related types such as IPSockAddr and IPPrefix.
+struct compact_ipdata {
+    uint8_t family{AF_UNSPEC};
+    uint8_t cidrlen{0U};  // written and read in host-byte order
+    in_port_t port{0U};   // written and read in host-byte order
+    uint32_t scope_id{0U};
+    union {
+        in_addr v4;
+        in6_addr v6;
+    } ip{.v6 = IN6ADDR_ANY_INIT};  // written and read in network-byte order
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator==(const compact_ipdata& a, const compact_ipdata& b) {
+        if ((a.family != b.family) || (a.cidrlen != b.cidrlen) || (a.port != b.port) ||
+            (a.scope_id != b.scope_id)) {
+            return false;
+        }
+        switch (a.family) {
+            case AF_UNSPEC:
+                // After the above checks, two AF_UNSPEC objects can be
+                // considered equal, for convenience.
+                return true;
+            case AF_INET: {
+                const in_addr v4a = a.ip.v4;
+                const in_addr v4b = b.ip.v4;
+                return (v4a.s_addr == v4b.s_addr);
+            }
+            case AF_INET6: {
+                const in6_addr v6a = a.ip.v6;
+                const in6_addr v6b = b.ip.v6;
+                return IN6_ARE_ADDR_EQUAL(&v6a, &v6b);
+            }
+        }
+        return false;
+    }
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator!=(const compact_ipdata& a, const compact_ipdata& b) { return !(a == b); }
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator<(const compact_ipdata& a, const compact_ipdata& b) {
+        if (a.family != b.family) return (a.family < b.family);
+        switch (a.family) {
+            case AF_INET: {
+                const in_addr v4a = a.ip.v4;
+                const in_addr v4b = b.ip.v4;
+                if (v4a.s_addr != v4b.s_addr) return (ntohl(v4a.s_addr) < ntohl(v4b.s_addr));
+                break;
+            }
+            case AF_INET6: {
+                const in6_addr v6a = a.ip.v6;
+                const in6_addr v6b = b.ip.v6;
+                const int cmp = std::memcmp(v6a.s6_addr, v6b.s6_addr, IPV6_ADDR_LEN);
+                if (cmp != 0) return cmp < 0;
+                break;
+            }
+        }
+        if (a.cidrlen != b.cidrlen) return (a.cidrlen < b.cidrlen);
+        if (a.port != b.port) return (a.port < b.port);
+        return (a.scope_id < b.scope_id);
+    }
+};
+
+static_assert(AF_UNSPEC <= std::numeric_limits<uint8_t>::max(), "AF_UNSPEC value too large");
+static_assert(AF_INET <= std::numeric_limits<uint8_t>::max(), "AF_INET value too large");
+static_assert(AF_INET6 <= std::numeric_limits<uint8_t>::max(), "AF_INET6 value too large");
+static_assert(sizeof(compact_ipdata) == 24U, "compact_ipdata unexpectedly large");
+
+}  // namespace internal_
+
+struct AddrinfoDeleter {
+    void operator()(struct addrinfo* p) const {
+        if (p != nullptr) {
+            freeaddrinfo(p);
+        }
+    }
+};
+
+typedef std::unique_ptr<struct addrinfo, struct AddrinfoDeleter> ScopedAddrinfo;
+
+inline bool usesScopedIds(const in6_addr& ipv6) {
+    return (IN6_IS_ADDR_LINKLOCAL(&ipv6) || IN6_IS_ADDR_MC_LINKLOCAL(&ipv6));
+}
+
+class IPPrefix;
+class IPSockAddr;
+
+class IPAddress {
+  public:
+    static bool forString(const std::string& repr, IPAddress* ip);
+    static IPAddress forString(const std::string& repr) {
+        IPAddress ip;
+        if (!forString(repr, &ip)) return IPAddress();
+        return ip;
+    }
+
+    IPAddress() = default;
+    IPAddress(const IPAddress&) = default;
+    IPAddress(IPAddress&&) = default;
+
+    explicit IPAddress(const in_addr& ipv4)
+        : mData({AF_INET, IPV4_ADDR_BITS, 0U, 0U, {.v4 = ipv4}}) {}
+    explicit IPAddress(const in6_addr& ipv6)
+        : mData({AF_INET6, IPV6_ADDR_BITS, 0U, 0U, {.v6 = ipv6}}) {}
+    IPAddress(const in6_addr& ipv6, uint32_t scope_id)
+        : mData({AF_INET6,
+                 IPV6_ADDR_BITS,
+                 0U,
+                 // Sanity check: scoped_ids only for link-local addresses.
+                 usesScopedIds(ipv6) ? scope_id : 0U,
+                 {.v6 = ipv6}}) {}
+    IPAddress(const IPAddress& ip, uint32_t scope_id) : IPAddress(ip) {
+        mData.scope_id = (family() == AF_INET6 && usesScopedIds(mData.ip.v6)) ? scope_id : 0U;
+    }
+
+    IPAddress& operator=(const IPAddress&) = default;
+    IPAddress& operator=(IPAddress&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    constexpr uint32_t scope_id() const noexcept { return mData.scope_id; }
+
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPAddress& ip) {
+        os << ip.toString();
+        return os;
+    }
+    friend bool operator==(const IPAddress& a, const IPAddress& b) { return (a.mData == b.mData); }
+    friend bool operator!=(const IPAddress& a, const IPAddress& b) { return (a.mData != b.mData); }
+    friend bool operator<(const IPAddress& a, const IPAddress& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPAddress& a, const IPAddress& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPAddress& a, const IPAddress& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPAddress& a, const IPAddress& b) { return (b < a) || (a == b); }
+
+  private:
+    friend class IPPrefix;
+    friend class IPSockAddr;
+
+    explicit IPAddress(const internal_::compact_ipdata& ipdata) : mData(ipdata) {
+        mData.port = 0U;
+        switch (mData.family) {
+            case AF_INET:
+                mData.cidrlen = IPV4_ADDR_BITS;
+                mData.scope_id = 0U;
+                break;
+            case AF_INET6:
+                mData.cidrlen = IPV6_ADDR_BITS;
+                if (usesScopedIds(ipdata.ip.v6)) mData.scope_id = ipdata.scope_id;
+                break;
+            default:
+                mData.cidrlen = 0U;
+                mData.scope_id = 0U;
+                break;
+        }
+    }
+
+    internal_::compact_ipdata mData{};
+};
+
+class IPPrefix {
+  public:
+    static bool forString(const std::string& repr, IPPrefix* prefix);
+    static IPPrefix forString(const std::string& repr) {
+        IPPrefix prefix;
+        if (!forString(repr, &prefix)) return IPPrefix();
+        return prefix;
+    }
+
+    IPPrefix() = default;
+    IPPrefix(const IPPrefix&) = default;
+    IPPrefix(IPPrefix&&) = default;
+
+    explicit IPPrefix(const IPAddress& ip) : mData(ip.mData) {}
+
+    // Truncate the IP address |ip| at length |length|. Lengths greater than
+    // the address-family-relevant maximum, along with negative values, are
+    // interpreted as if the address-family-relevant maximum had been given.
+    IPPrefix(const IPAddress& ip, int length);
+
+    IPPrefix& operator=(const IPPrefix&) = default;
+    IPPrefix& operator=(IPPrefix&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    IPAddress ip() const noexcept { return IPAddress(mData); }
+    in_addr addr4() const noexcept { return mData.ip.v4; }
+    in6_addr addr6() const noexcept { return mData.ip.v6; }
+    constexpr int length() const noexcept { return mData.cidrlen; }
+    bool contains(const IPPrefix& other) {
+        return length() <= other.length() && IPPrefix(other.ip(), length()).ip() == ip();
+    }
+    bool contains(const IPAddress& other) {
+        return IPPrefix(other, length()).ip() == ip();
+    }
+
+    bool isUninitialized() const noexcept;
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPPrefix& prefix) {
+        os << prefix.toString();
+        return os;
+    }
+    friend bool operator==(const IPPrefix& a, const IPPrefix& b) { return (a.mData == b.mData); }
+    friend bool operator!=(const IPPrefix& a, const IPPrefix& b) { return (a.mData != b.mData); }
+    friend bool operator<(const IPPrefix& a, const IPPrefix& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPPrefix& a, const IPPrefix& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPPrefix& a, const IPPrefix& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPPrefix& a, const IPPrefix& b) { return (b < a) || (a == b); }
+
+  private:
+    internal_::compact_ipdata mData{};
+};
+
+// An Internet socket address.
+//
+// Cannot represent other types of socket addresses (e.g. UNIX socket address, et cetera).
+class IPSockAddr {
+  public:
+    // TODO: static forString
+
+    static IPSockAddr toIPSockAddr(const std::string& repr, in_port_t port) {
+        return IPSockAddr(IPAddress::forString(repr), port);
+    }
+    static IPSockAddr toIPSockAddr(const sockaddr& sa) {
+        switch (sa.sa_family) {
+            case AF_INET:
+                return IPSockAddr(*reinterpret_cast<const sockaddr_in*>(&sa));
+            case AF_INET6:
+                return IPSockAddr(*reinterpret_cast<const sockaddr_in6*>(&sa));
+            default:
+                return IPSockAddr();
+        }
+    }
+    static IPSockAddr toIPSockAddr(const sockaddr_storage& ss) {
+        return toIPSockAddr(*reinterpret_cast<const sockaddr*>(&ss));
+    }
+
+    IPSockAddr() = default;
+    IPSockAddr(const IPSockAddr&) = default;
+    IPSockAddr(IPSockAddr&&) = default;
+
+    explicit IPSockAddr(const IPAddress& ip) : mData(ip.mData) {}
+    IPSockAddr(const IPAddress& ip, in_port_t port) : mData(ip.mData) { mData.port = port; }
+    explicit IPSockAddr(const sockaddr_in& ipv4sa)
+        : IPSockAddr(IPAddress(ipv4sa.sin_addr), ntohs(ipv4sa.sin_port)) {}
+    explicit IPSockAddr(const sockaddr_in6& ipv6sa)
+        : IPSockAddr(IPAddress(ipv6sa.sin6_addr, ipv6sa.sin6_scope_id), ntohs(ipv6sa.sin6_port)) {}
+
+    IPSockAddr& operator=(const IPSockAddr&) = default;
+    IPSockAddr& operator=(IPSockAddr&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    IPAddress ip() const noexcept { return IPAddress(mData); }
+    constexpr in_port_t port() const noexcept { return mData.port; }
+
+    // Implicit conversion to sockaddr_storage.
+    operator sockaddr_storage() const noexcept {
+        sockaddr_storage ss;
+        ss.ss_family = mData.family;
+        switch (mData.family) {
+            case AF_INET:
+                reinterpret_cast<sockaddr_in*>(&ss)->sin_addr = mData.ip.v4;
+                reinterpret_cast<sockaddr_in*>(&ss)->sin_port = htons(mData.port);
+                break;
+            case AF_INET6:
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr = mData.ip.v6;
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port = htons(mData.port);
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_scope_id = mData.scope_id;
+                break;
+        }
+        return ss;
+    }
+
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPSockAddr& prefix) {
+        os << prefix.toString();
+        return os;
+    }
+    friend bool operator==(const IPSockAddr& a, const IPSockAddr& b) {
+        return (a.mData == b.mData);
+    }
+    friend bool operator!=(const IPSockAddr& a, const IPSockAddr& b) {
+        return (a.mData != b.mData);
+    }
+    friend bool operator<(const IPSockAddr& a, const IPSockAddr& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPSockAddr& a, const IPSockAddr& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPSockAddr& a, const IPSockAddr& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPSockAddr& a, const IPSockAddr& b) { return (b < a) || (a == b); }
+
+  private:
+    internal_::compact_ipdata mData{};
+};
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
new file mode 100644
index 0000000..77ae649
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETUTILS_LOG_H
+#define NETUTILS_LOG_H
+
+#include <chrono>
+#include <deque>
+#include <shared_mutex>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
+
+#include <netdutils/Status.h>
+
+namespace android {
+namespace netdutils {
+
+class LogEntry {
+  public:
+    LogEntry() = default;
+    LogEntry(const LogEntry&) = default;
+    LogEntry(LogEntry&&) = default;
+    ~LogEntry() = default;
+    LogEntry& operator=(const LogEntry&) = default;
+    LogEntry& operator=(LogEntry&&) = default;
+
+    std::string toString() const;
+
+    ///
+    // Helper methods that make it easy to build up a LogEntry message.
+    // If performance becomes a factor the implementations could be inlined.
+    ///
+    LogEntry& message(const std::string& message);
+
+    // For calling with __FUNCTION__.
+    LogEntry& function(const std::string& function_name);
+    // For calling with __PRETTY_FUNCTION__.
+    LogEntry& prettyFunction(const std::string& pretty_function);
+
+    // Convenience methods for each of the common types of function arguments.
+    LogEntry& arg(const std::string& val);
+    // Intended for binary buffers, formats as hex
+    LogEntry& arg(const std::vector<uint8_t>& val);
+    LogEntry& arg(const std::vector<int32_t>& val);
+    LogEntry& arg(const std::vector<std::string>& val);
+    template <typename IntT, typename = std::enable_if_t<std::is_arithmetic_v<IntT>>>
+    LogEntry& arg(IntT val) {
+        mArgs.push_back(std::to_string(val));
+        return *this;
+    }
+    // Not using a plain overload here to avoid the implicit conversion from
+    // any pointer to bool, which causes string literals to print as 'true'.
+    template <>
+    LogEntry& arg<>(bool val);
+
+    template <typename... Args>
+    LogEntry& args(const Args&... a) {
+        // Cleverness ahead: we throw away the initializer_list filled with
+        // zeroes, all we care about is calling arg() for each argument.
+        (void) std::initializer_list<int>{(arg(a), 0)...};
+        return *this;
+    }
+
+    // Some things can return more than one value, or have multiple output
+    // parameters, so each of these adds to the mReturns vector.
+    LogEntry& returns(const std::string& rval);
+    LogEntry& returns(const Status& status);
+    LogEntry& returns(bool rval);
+    template <class T>
+    LogEntry& returns(T val) {
+        mReturns.push_back(std::to_string(val));
+        return *this;
+    }
+
+    LogEntry& withUid(uid_t uid);
+
+    // Append the duration computed since the creation of this instance.
+    LogEntry& withAutomaticDuration();
+    // Append the string-ified duration computed by some other means.
+    LogEntry& withDuration(const std::string& duration);
+
+  private:
+    std::chrono::steady_clock::time_point mStart = std::chrono::steady_clock::now();
+    std::string mMsg{};
+    std::string mFunc{};
+    std::vector<std::string> mArgs{};
+    std::vector<std::string> mReturns{};
+    std::string mUid{};
+    std::string mDuration{};
+};
+
+class Log {
+  public:
+    Log() = delete;
+    Log(const std::string& tag) : Log(tag, MAX_ENTRIES) {}
+    Log(const std::string& tag, size_t maxEntries) : mTag(tag), mMaxEntries(maxEntries) {}
+    Log(const Log&) = delete;
+    Log(Log&&) = delete;
+    ~Log();
+    Log& operator=(const Log&) = delete;
+    Log& operator=(Log&&) = delete;
+
+    LogEntry newEntry() const { return LogEntry(); }
+
+    // Record a log entry in internal storage only.
+    void log(const std::string& entry) { record(Level::LOG, entry); }
+    template <size_t n>
+    void log(const char entry[n]) { log(std::string(entry)); }
+    void log(const LogEntry& entry) { log(entry.toString()); }
+    void log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        log(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGI as well.
+    void info(const std::string& entry) { record(Level::INFO, entry); }
+    template <size_t n>
+    void info(const char entry[n]) { info(std::string(entry)); }
+    void info(const LogEntry& entry) { info(entry.toString()); }
+    void info(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        info(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGW as well.
+    void warn(const std::string& entry) { record(Level::WARN, entry); }
+    template <size_t n>
+    void warn(const char entry[n]) { warn(std::string(entry)); }
+    void warn(const LogEntry& entry) { warn(entry.toString()); }
+    void warn(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        warn(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGE as well.
+    void error(const std::string& entry) { record(Level::ERROR, entry); }
+    template <size_t n>
+    void error(const char entry[n]) { error(std::string(entry)); }
+    void error(const LogEntry& entry) { error(entry.toString()); }
+    void error(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        error(result);
+    }
+
+    // Iterates over every entry in the log in chronological order. Operates
+    // on a copy of the log entries, and so perEntryFn may itself call one of
+    // the logging functions if needed.
+    void forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const;
+
+  private:
+    static constexpr const size_t MAX_ENTRIES = 750U;
+    const std::string mTag;
+    const size_t mMaxEntries;
+
+    // The LOG level adds an entry to mEntries but does not output the message
+    // to the system log. All other levels append to mEntries and output to the
+    // the system log.
+    enum class Level {
+        LOG,
+        INFO,
+        WARN,
+        ERROR,
+    };
+
+    void record(Level lvl, const std::string& entry);
+
+    mutable std::shared_mutex mLock;
+    std::deque<const std::string> mEntries;  // GUARDED_BY(mLock), when supported
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_LOG_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Math.h b/staticlibs/netd/libnetdutils/include/netdutils/Math.h
new file mode 100644
index 0000000..c41fbf5
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Math.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MATH_H
+#define NETUTILS_MATH_H
+
+#include <algorithm>
+#include <cstdint>
+
+namespace android {
+namespace netdutils {
+
+template <class T>
+inline constexpr const T mask(const int shift) {
+    return (1 << shift) - 1;
+}
+
+// Align x up to the nearest integer multiple of 2^shift
+template <class T>
+inline constexpr const T align(const T& x, const int shift) {
+    return (x + mask<T>(shift)) & ~mask<T>(shift);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MATH_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h
new file mode 100644
index 0000000..fd4d612
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETUTILS_MEMBLOCK_H
+#define NETUTILS_MEMBLOCK_H
+
+#include <memory>
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+// A class to encapsulate self-deleting byte arrays while preserving access
+// to the underlying length (without the length being part of the type, e.g.
+// std::array<>). By design, the only interface to the underlying bytes is
+// via Slice, to encourage safer memory access usage.
+//
+// No thread-safety guarantees whatsoever.
+class MemBlock {
+  public:
+    MemBlock() : MemBlock(0U) {}
+    explicit MemBlock(size_t len)
+            : mData((len > 0U) ? new uint8_t[len]{} : nullptr),
+              mLen(len) {}
+    // Allocate memory of size src.size() and copy src into this MemBlock.
+    explicit MemBlock(Slice src) : MemBlock(src.size()) {
+        copy(get(), src);
+    }
+
+    // No copy construction or assignment.
+    MemBlock(const MemBlock&) = delete;
+    MemBlock& operator=(const MemBlock&) = delete;
+
+    // Move construction and assignment are okay.
+    MemBlock(MemBlock&&) = default;
+    MemBlock& operator=(MemBlock&&) = default;
+
+    // Even though this method is const, the memory wrapped by the
+    // returned Slice is mutable.
+    Slice get() const noexcept { return Slice(mData.get(), mLen); }
+
+    // Implicit cast to Slice.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    operator const Slice() const noexcept { return get(); }
+
+  private:
+    std::unique_ptr<uint8_t[]> mData;
+    size_t mLen;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MEMBLOCK_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Misc.h b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h
new file mode 100644
index 0000000..d344f81
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MISC_H
+#define NETUTILS_MISC_H
+
+#include <map>
+
+namespace android {
+namespace netdutils {
+
+// Lookup key in map, returing a default value if key is not found
+template <typename U, typename V>
+inline const V& findWithDefault(const std::map<U, V>& map, const U& key, const V& dflt) {
+    auto it = map.find(key);
+    return (it == map.end()) ? dflt : it->second;
+}
+
+// Movable, copiable, scoped lambda (or std::function) runner. Useful
+// for running arbitrary cleanup or logging code when exiting a scope.
+//
+// Compare to defer in golang.
+template <typename FnT>
+class Cleanup {
+  public:
+    Cleanup() = delete;
+    explicit Cleanup(FnT fn) : mFn(fn) {}
+    ~Cleanup() { if (!mReleased) mFn(); }
+
+    void release() { mReleased = true; }
+
+  private:
+    bool mReleased{false};
+    FnT mFn;
+};
+
+// Helper to make a new Cleanup. Avoids complex or impossible syntax
+// when wrapping lambdas.
+//
+// Usage:
+// auto cleanup = makeCleanup([](){ your_code_here; });
+template <typename FnT>
+Cleanup<FnT> makeCleanup(FnT fn) {
+    return Cleanup<FnT>(fn);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MISC_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h
new file mode 100644
index 0000000..f57b55c
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MOCK_SYSCALLS_H
+#define NETUTILS_MOCK_SYSCALLS_H
+
+#include <atomic>
+#include <cassert>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+class MockSyscalls : public Syscalls {
+  public:
+    virtual ~MockSyscalls() = default;
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD3(open,
+                       StatusOr<UniqueFd>(const std::string& pathname, int flags, mode_t mode));
+    MOCK_CONST_METHOD3(socket, StatusOr<UniqueFd>(int domain, int type, int protocol));
+    MOCK_CONST_METHOD3(getsockname, Status(Fd sock, sockaddr* addr, socklen_t* addrlen));
+    MOCK_CONST_METHOD5(getsockopt, Status(Fd sock, int level, int optname, void* optval,
+                                          socklen_t *optlen));
+    MOCK_CONST_METHOD5(setsockopt, Status(Fd sock, int level, int optname, const void* optval,
+                                          socklen_t optlen));
+
+    MOCK_CONST_METHOD3(bind, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+    MOCK_CONST_METHOD3(connect, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+    MOCK_CONST_METHOD3(ioctl, StatusOr<ifreq>(Fd sock, unsigned long request, ifreq* ifr));
+
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD2(eventfd, StatusOr<UniqueFd>(unsigned int initval, int flags));
+    MOCK_CONST_METHOD3(ppoll, StatusOr<int>(pollfd* fds, nfds_t nfds, double timeout));
+
+    MOCK_CONST_METHOD2(writev, StatusOr<size_t>(Fd fd, const std::vector<iovec>& iov));
+    MOCK_CONST_METHOD2(write, StatusOr<size_t>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD2(read, StatusOr<Slice>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD5(sendto, StatusOr<size_t>(Fd sock, const Slice buf, int flags,
+                                                const sockaddr* dst, socklen_t dstlen));
+    MOCK_CONST_METHOD5(recvfrom, StatusOr<Slice>(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                                 socklen_t* srclen));
+    MOCK_CONST_METHOD2(shutdown, Status(Fd fd, int how));
+    MOCK_CONST_METHOD1(close, Status(Fd fd));
+
+    MOCK_CONST_METHOD2(fopen,
+                       StatusOr<UniqueFile>(const std::string& path, const std::string& mode));
+    MOCK_CONST_METHOD3(vfprintf, StatusOr<int>(FILE* file, const char* format, va_list ap));
+    MOCK_CONST_METHOD3(vfscanf, StatusOr<int>(FILE* file, const char* format, va_list ap));
+    MOCK_CONST_METHOD1(fclose, Status(FILE* file));
+    MOCK_CONST_METHOD0(fork, StatusOr<pid_t>());
+};
+
+// For the lifetime of this mock, replace the contents of sSyscalls
+// with a pointer to this mock. Behavior is undefined if multiple
+// ScopedMockSyscalls instances exist concurrently.
+class ScopedMockSyscalls : public MockSyscalls {
+  public:
+    ScopedMockSyscalls() : mOld(sSyscalls.swap(*this)) { assert((mRefcount++) == 1); }
+    virtual ~ScopedMockSyscalls() {
+        sSyscalls.swap(mOld);
+        assert((mRefcount--) == 0);
+    }
+
+  private:
+    std::atomic<int> mRefcount{0};
+    Syscalls& mOld;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MOCK_SYSCALLS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h
new file mode 100644
index 0000000..c8b30c6
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *
+ */
+#pragma once
+
+#include <android-base/format.h>
+#include <android-base/logging.h>
+#include "gtest/gtest.h"
+
+using ::testing::TestInfo;
+using ::testing::UnitTest;
+
+#define DBG 1
+
+/*
+ * Test base class for net native tests to support common usage.
+ */
+class NetNativeTestBase : public ::testing::Test {
+  public:
+    // TODO: update the logging when gtest supports logging the life cycle on each test.
+    NetNativeTestBase() {
+        if (DBG) LOG(INFO) << getTestCaseLog(true);
+    }
+    ~NetNativeTestBase() {
+        if (DBG) LOG(INFO) << getTestCaseLog(false);
+    }
+
+    std::string getTestCaseLog(bool running) {
+        const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+        return fmt::format("{}: {}#{}", (running ? "started" : "finished"),
+                           test_info->test_suite_name(), test_info->name());
+    }
+};
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h
new file mode 100644
index 0000000..22736f1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_NETFILTER_H
+#define NETUTILS_NETFILTER_H
+
+#include <ostream>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg);
+
+#endif /* NETUTILS_NETFILTER_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h
new file mode 100644
index 0000000..ee5183a
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_NETLINK_H
+#define NETUTILS_NETLINK_H
+
+#include <functional>
+#include <ostream>
+#include <linux/netlink.h>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+// Invoke onMsg once for each netlink message in buf. onMsg will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the message payload.
+//
+// Assume that the first message begins at offset zero within buf.
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg);
+
+// Invoke onAttr once for each netlink attribute in buf. onAttr will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the attribute payload.
+//
+// Assume that the first attribute begins at offset zero within buf.
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr);
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr);
+std::ostream& operator<<(std::ostream& os, const nlattr& attr);
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr);
+
+#endif /* NETUTILS_NETLINK_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h b/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h
new file mode 100644
index 0000000..97f7bb2
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/NetlinkListener.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETLINK_LISTENER_H
+#define NETLINK_LISTENER_H
+
+#include <functional>
+#include <map>
+#include <mutex>
+#include <thread>
+
+#include <android-base/thread_annotations.h>
+#include <netdutils/Netlink.h>
+#include <netdutils/Slice.h>
+#include <netdutils/Status.h>
+#include <netdutils/UniqueFd.h>
+
+namespace android {
+namespace netdutils {
+
+class NetlinkListenerInterface {
+  public:
+    using DispatchFn = std::function<void(const nlmsghdr& nlmsg, const netdutils::Slice msg)>;
+
+    using SkErrorHandler = std::function<void(const int fd, const int err)>;
+
+    virtual ~NetlinkListenerInterface() = default;
+
+    // Send message to the kernel using the underlying netlink socket
+    virtual netdutils::Status send(const netdutils::Slice msg) = 0;
+
+    // Deliver future messages with nlmsghdr.nlmsg_type == type to fn.
+    //
+    // Threadsafe.
+    // All dispatch functions invoked on a single service thread.
+    // subscribe() and join() must not be called from the stack of fn().
+    virtual netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) = 0;
+
+    // Halt delivery of future messages with nlmsghdr.nlmsg_type == type.
+    // Threadsafe.
+    virtual netdutils::Status unsubscribe(uint16_t type) = 0;
+
+    virtual void registerSkErrorHandler(const SkErrorHandler& handler) = 0;
+};
+
+// NetlinkListener manages a netlink socket and associated blocking
+// service thread.
+//
+// This class is written in a generic way to allow multiple different
+// netlink subsystems to share this common infrastructure. If multiple
+// subsystems share the same message delivery requirements (drops ok,
+// no drops) they may share a single listener by calling subscribe()
+// with multiple types.
+//
+// This class is suitable for moderate performance message
+// processing. In particular it avoids extra copies of received
+// message data and allows client code to control which message
+// attributes are processed.
+//
+// Note that NetlinkListener is capable of processing multiple batched
+// netlink messages in a single system call. This is useful to
+// netfilter extensions that allow batching of events like NFLOG.
+class NetlinkListener : public NetlinkListenerInterface {
+  public:
+    NetlinkListener(netdutils::UniqueFd event, netdutils::UniqueFd sock, const std::string& name);
+
+    ~NetlinkListener() override;
+
+    netdutils::Status send(const netdutils::Slice msg) override;
+
+    netdutils::Status subscribe(uint16_t type, const DispatchFn& fn) override EXCLUDES(mMutex);
+
+    netdutils::Status unsubscribe(uint16_t type) override EXCLUDES(mMutex);
+
+    void registerSkErrorHandler(const SkErrorHandler& handler) override;
+
+  private:
+    netdutils::Status run();
+
+    const netdutils::UniqueFd mEvent;
+    const netdutils::UniqueFd mSock;
+    const std::string mThreadName;
+    std::mutex mMutex;
+    std::map<uint16_t, DispatchFn> mDispatchMap GUARDED_BY(mMutex);
+    std::thread mWorker;
+    SkErrorHandler mErrorHandler;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETLINK_LISTENER_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h
new file mode 100644
index 0000000..dead9a1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+namespace android {
+namespace netdutils {
+
+// See also NetworkConstants.java in frameworks/base.
+constexpr int IPV4_ADDR_LEN = 4;
+constexpr int IPV4_ADDR_BITS = 32;
+constexpr int IPV6_ADDR_LEN = 16;
+constexpr int IPV6_ADDR_BITS = 128;
+
+// Referred from SHA256_DIGEST_LENGTH in boringssl
+constexpr size_t SHA256_SIZE = 32;
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h
new file mode 100644
index 0000000..c170684
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef NETDUTILS_RESPONSECODE_H
+#define NETDUTILS_RESPONSECODE_H
+
+namespace android {
+namespace netdutils {
+
+class ResponseCode {
+    // Keep in sync with
+    // frameworks/base/services/java/com/android/server/NetworkManagementService.java
+  public:
+    // 100 series - Requestion action was initiated; expect another reply
+    // before proceeding with a new command.
+    // clang-format off
+    static constexpr int ActionInitiated                = 100;
+    static constexpr int InterfaceListResult            = 110;
+    static constexpr int TetherInterfaceListResult      = 111;
+    static constexpr int TetherDnsFwdTgtListResult      = 112;
+    static constexpr int TtyListResult                  = 113;
+    static constexpr int TetheringStatsListResult       = 114;
+    static constexpr int TetherDnsFwdNetIdResult        = 115;
+
+    // 200 series - Requested action has been successfully completed
+    static constexpr int CommandOkay                    = 200;
+    static constexpr int TetherStatusResult             = 210;
+    static constexpr int IpFwdStatusResult              = 211;
+    static constexpr int InterfaceGetCfgResult          = 213;
+    // Formerly: int SoftapStatusResult                 = 214;
+    static constexpr int UsbRNDISStatusResult           = 215;
+    static constexpr int InterfaceRxCounterResult       = 216;
+    static constexpr int InterfaceTxCounterResult       = 217;
+    static constexpr int InterfaceRxThrottleResult      = 218;
+    static constexpr int InterfaceTxThrottleResult      = 219;
+    static constexpr int QuotaCounterResult             = 220;
+    static constexpr int TetheringStatsResult           = 221;
+    // NOTE: keep synced with bionic/libc/dns/net/gethnamaddr.c
+    static constexpr int DnsProxyQueryResult            = 222;
+    static constexpr int ClatdStatusResult              = 223;
+
+    // 400 series - The command was accepted but the requested action
+    // did not take place.
+    static constexpr int OperationFailed                = 400;
+    static constexpr int DnsProxyOperationFailed        = 401;
+    static constexpr int ServiceStartFailed             = 402;
+    static constexpr int ServiceStopFailed              = 403;
+
+    // 500 series - The command was not accepted and the requested
+    // action did not take place.
+    static constexpr int CommandSyntaxError             = 500;
+    static constexpr int CommandParameterError          = 501;
+
+    // 600 series - Unsolicited broadcasts
+    static constexpr int InterfaceChange                = 600;
+    static constexpr int BandwidthControl               = 601;
+    static constexpr int ServiceDiscoveryFailed         = 602;
+    static constexpr int ServiceDiscoveryServiceAdded   = 603;
+    static constexpr int ServiceDiscoveryServiceRemoved = 604;
+    static constexpr int ServiceRegistrationFailed      = 605;
+    static constexpr int ServiceRegistrationSucceeded   = 606;
+    static constexpr int ServiceResolveFailed           = 607;
+    static constexpr int ServiceResolveSuccess          = 608;
+    static constexpr int ServiceSetHostnameFailed       = 609;
+    static constexpr int ServiceSetHostnameSuccess      = 610;
+    static constexpr int ServiceGetAddrInfoFailed       = 611;
+    static constexpr int ServiceGetAddrInfoSuccess      = 612;
+    static constexpr int InterfaceClassActivity         = 613;
+    static constexpr int InterfaceAddressChange         = 614;
+    static constexpr int InterfaceDnsInfo               = 615;
+    static constexpr int RouteChange                    = 616;
+    static constexpr int StrictCleartext                = 617;
+    // clang-format on
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_RESPONSECODE_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Slice.h b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h
new file mode 100644
index 0000000..717fbd1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_SLICE_H
+#define NETUTILS_SLICE_H
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <ostream>
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace netdutils {
+
+// Immutable wrapper for a linear region of unowned bytes.
+// Slice represents memory as a half-closed interval [base, limit).
+//
+// Note that without manually invoking the Slice() constructor, it is
+// impossible to increase the size of a slice. This guarantees that
+// applications that properly use the slice API will never access
+// memory outside of a slice.
+//
+// Note that const Slice still wraps mutable memory, however copy
+// assignment and move assignment to slice are disabled.
+class Slice {
+  public:
+    Slice() = default;
+
+    // Create a slice beginning at base and continuing to but not including limit
+    Slice(void* base, void* limit) : mBase(toUint8(base)), mLimit(toUint8(limit)) {}
+
+    // Create a slice beginning at base and continuing for size bytes
+    Slice(void* base, size_t size) : Slice(base, toUint8(base) + size) {}
+
+    // Return the address of the first byte in this slice
+    uint8_t* base() const { return mBase; }
+
+    // Return the address of the first byte following this slice
+    uint8_t* limit() const { return mLimit; }
+
+    // Return the size of this slice in bytes
+    size_t size() const { return limit() - base(); }
+
+    // Return true if size() == 0
+    bool empty() const { return base() == limit(); }
+
+  private:
+    static uint8_t* toUint8(void* ptr) { return reinterpret_cast<uint8_t*>(ptr); }
+
+    uint8_t* mBase = nullptr;
+    uint8_t* mLimit = nullptr;
+};
+
+// Return slice representation of ref which must be a POD type
+template <typename T>
+inline const Slice makeSlice(const T& ref) {
+    static_assert(std::is_pod<T>::value, "value must be a POD type");
+    static_assert(!std::is_pointer<T>::value, "value must not be a pointer type");
+    return {const_cast<T*>(&ref), sizeof(ref)};
+}
+
+// Return slice representation of string data()
+inline const Slice makeSlice(const std::string& s) {
+    using ValueT = std::string::value_type;
+    return {const_cast<ValueT*>(s.data()), s.size() * sizeof(ValueT)};
+}
+
+// Return slice representation of vector data()
+template <typename T>
+inline const Slice makeSlice(const std::vector<T>& v) {
+    return {const_cast<T*>(v.data()), v.size() * sizeof(T)};
+}
+
+// Return slice representation of array data()
+template <typename U, size_t V>
+inline const Slice makeSlice(const std::array<U, V>& a) {
+    return {const_cast<U*>(a.data()), a.size() * sizeof(U)};
+}
+
+// Return prefix and suffix of Slice s ending and starting at position cut
+inline std::pair<const Slice, const Slice> split(const Slice s, size_t cut) {
+    const size_t tmp = std::min(cut, s.size());
+    return {{s.base(), s.base() + tmp}, {s.base() + tmp, s.limit()}};
+}
+
+// Return prefix of Slice s ending at position cut
+inline const Slice take(const Slice s, size_t cut) {
+    return std::get<0>(split(s, cut));
+}
+
+// Return suffix of Slice s starting at position cut
+inline const Slice drop(const Slice s, size_t cut) {
+    return std::get<1>(split(s, cut));
+}
+
+// Copy from src into dst. Bytes copied is the lesser of dst.size() and src.size()
+inline size_t copy(const Slice dst, const Slice src) {
+    const auto min = std::min(dst.size(), src.size());
+    memcpy(dst.base(), src.base(), min);
+    return min;
+}
+
+// Base case for variadic extract below
+template <typename Head>
+inline size_t extract(const Slice src, Head& head) {
+    return copy(makeSlice(head), src);
+}
+
+// Copy from src into one or more pointers to POD data.  If src.size()
+// is less than the sum of all data pointers a suffix of data will be
+// left unmodified. Return the number of bytes copied.
+template <typename Head, typename... Tail>
+inline size_t extract(const Slice src, Head& head, Tail&... tail) {
+    const auto extracted = extract(src, head);
+    return extracted + extract(drop(src, extracted), tail...);
+}
+
+// Return a string containing a copy of the contents of s
+std::string toString(const Slice s);
+
+// Return a string containing a hexadecimal representation of the contents of s.
+// This function inserts a newline into its output every wrap bytes.
+std::string toHex(const Slice s, int wrap = INT_MAX);
+
+inline bool operator==(const Slice& lhs, const Slice& rhs) {
+    return (lhs.base() == rhs.base()) && (lhs.limit() == rhs.limit());
+}
+
+inline bool operator!=(const Slice& lhs, const Slice& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice);
+
+// Return suffix of Slice s starting at the first match of byte c. If no matched
+// byte, return an empty Slice.
+inline const Slice findFirstMatching(const Slice s, uint8_t c) {
+    uint8_t* match = (uint8_t*)memchr(s.base(), c, s.size());
+    if (!match) return Slice();
+    return drop(s, match - s.base());
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_SLICE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Socket.h b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h
new file mode 100644
index 0000000..e5aaab9
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_SOCKET_H
+#define NETDUTILS_SOCKET_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+inline sockaddr* asSockaddrPtr(void* addr) {
+    return reinterpret_cast<sockaddr*>(addr);
+}
+
+inline const sockaddr* asSockaddrPtr(const void* addr) {
+    return reinterpret_cast<const sockaddr*>(addr);
+}
+
+// Return a string representation of addr or Status if there was a
+// failure during conversion.
+StatusOr<std::string> toString(const in6_addr& addr);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SOCKET_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h
new file mode 100644
index 0000000..3b0aab7
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETDUTILS_SOCKETOPTION_H
+#define NETDUTILS_SOCKETOPTION_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "netdutils/Fd.h"
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Turn on simple "boolean" socket options.
+//
+// This is simple wrapper for options that are enabled via code of the form:
+//
+//     int on = 1;
+//     setsockopt(..., &on, sizeof(on));
+Status enableSockopt(Fd sock, int level, int optname);
+
+// Turn on TCP keepalives, and set keepalive parameters for this socket.
+//
+// A parameter value of zero does not set that parameter.
+//
+// Typical system defaults are:
+//
+//     idleTime (in seconds)
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_time
+//     7200
+//
+//     numProbes
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_probes
+//     9
+//
+//     probeInterval (in seconds)
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
+//     75
+Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SOCKETOPTION_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Status.h b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
new file mode 100644
index 0000000..7b0bd47
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_STATUS_H
+#define NETUTILS_STATUS_H
+
+#include <cassert>
+#include <limits>
+#include <ostream>
+
+#include <android-base/result.h>
+
+namespace android {
+namespace netdutils {
+
+// Simple status implementation suitable for use on the stack in low
+// or moderate performance code. This can definitely be improved but
+// for now short string optimization is expected to keep the common
+// success case fast.
+//
+// Status is implicitly movable via the default noexcept move constructor
+// and noexcept move-assignment operator.
+class [[nodiscard]] Status {
+  public:
+    Status() = default;
+    explicit Status(int code) : mCode(code) {}
+
+    // Constructs an error Status, |code| must be non-zero.
+    Status(int code, std::string msg) : mCode(code), mMsg(std::move(msg)) { assert(!ok()); }
+
+    Status(android::base::Result<void> result)
+        : mCode(result.ok() ? 0 : static_cast<int>(result.error().code())),
+          mMsg(result.ok() ? "" : result.error().message()) {}
+
+    int code() const { return mCode; }
+
+    bool ok() const { return code() == 0; }
+
+    const std::string& msg() const { return mMsg; }
+
+    // Explicitly ignores the Status without triggering [[nodiscard]] errors.
+    void ignoreError() const {}
+
+    bool operator==(const Status& other) const { return code() == other.code(); }
+    bool operator!=(const Status& other) const { return !(*this == other); }
+
+  private:
+    int mCode = 0;
+    std::string mMsg;
+};
+
+namespace status {
+
+const Status ok{0};
+// EOF is not part of errno space, we'll place it far above the
+// highest existing value.
+const Status eof{0x10001, "end of file"};
+const Status undefined{std::numeric_limits<int>::max(), "undefined"};
+
+}  // namespace status
+
+// Return true if status is "OK". This is sometimes preferable to
+// status.ok() when we want to check the state of Status-like objects
+// that implicitly cast to Status.
+inline bool isOk(const Status& status) {
+    return status.ok();
+}
+
+// For use only in tests. Used for both Status and Status-like objects. See also isOk().
+#define EXPECT_OK(status) EXPECT_TRUE(isOk(status))
+#define ASSERT_OK(status) ASSERT_TRUE(isOk(status))
+
+// Documents that status is expected to be ok. This function may log
+// (or assert when running in debug mode) if status has an unexpected value.
+inline void expectOk(const Status& /*status*/) {
+    // TODO: put something here, for now this function serves solely as documentation.
+}
+
+// Convert POSIX errno to a Status object.
+// If Status is extended to have more features, this mapping may
+// become more complex.
+Status statusFromErrno(int err, const std::string& msg);
+
+// Helper that checks Status-like object (notably StatusOr) against a
+// value in the errno space.
+bool equalToErrno(const Status& status, int err);
+
+// Helper that converts Status-like object (notably StatusOr) to a
+// message.
+std::string toString(const Status& status);
+
+std::ostream& operator<<(std::ostream& os, const Status& s);
+
+// Evaluate 'stmt' to a Status object and if it results in an error, return that
+// error.  Use 'tmp' as a variable name to avoid shadowing any variables named
+// tmp.
+#define RETURN_IF_NOT_OK_IMPL(tmp, stmt)           \
+    do {                                           \
+        ::android::netdutils::Status tmp = (stmt); \
+        if (!isOk(tmp)) {                          \
+            return tmp;                            \
+        }                                          \
+    } while (false)
+
+// Create a unique variable name to avoid shadowing local variables.
+#define RETURN_IF_NOT_OK_CONCAT(line, stmt) RETURN_IF_NOT_OK_IMPL(__CONCAT(_status_, line), stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from current function.
+//
+// Example usage:
+// Status bar() { ... }
+//
+// RETURN_IF_NOT_OK(status);
+// RETURN_IF_NOT_OK(bar());
+#define RETURN_IF_NOT_OK(stmt) RETURN_IF_NOT_OK_CONCAT(__LINE__, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h
new file mode 100644
index 0000000..c7aa4e4
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_STATUSOR_H
+#define NETUTILS_STATUSOR_H
+
+#include <cassert>
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Wrapper around a combination of Status and application value type.
+// T may be any copyable or movable type.
+template <typename T>
+class [[nodiscard]] StatusOr {
+  public:
+    // Constructs a new StatusOr with status::undefined status.
+    // This is marked 'explicit' to try to catch cases like 'return {};',
+    // where people think StatusOr<std::vector<int>> will be initialized
+    // with an empty vector, instead of a status::undefined.
+    explicit StatusOr() = default;
+
+    // Implicit copy constructor and construction from T.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(Status status) : mStatus(std::move(status)) { assert(!isOk(mStatus)); }
+
+    // Implicit construction from T. It is convenient and sensible to be able
+    // to do 'return T()' when the return type is StatusOr<T>.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(const T& value) : mStatus(status::ok), mValue(value) {}
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(T&& value) : mStatus(status::ok), mValue(std::move(value)) {}
+
+    // Move constructor ok (if T supports move)
+    StatusOr(StatusOr&&) noexcept = default;
+    // Move assignment ok (if T supports move)
+    StatusOr& operator=(StatusOr&&) noexcept = default;
+    // Copy constructor ok (if T supports copy)
+    StatusOr(const StatusOr&) = default;
+    // Copy assignment ok (if T supports copy)
+    StatusOr& operator=(const StatusOr&) = default;
+
+    // Returns a const reference to wrapped type.
+    // It is an error to call value() when !isOk(status())
+    const T& value() const & { return mValue; }
+    const T&& value() const && { return mValue; }
+
+    // Returns an rvalue reference to wrapped type
+    // It is an error to call value() when !isOk(status())
+    //
+    // If T is expensive to copy but supports efficient move, it can be moved
+    // out of a StatusOr as follows:
+    //   T value = std::move(statusor).value();
+    T& value() & { return mValue; }
+    T&& value() && { return mValue; }
+
+    // Returns the Status object assigned at construction time.
+    const Status status() const { return mStatus; }
+
+    // Explicitly ignores the Status without triggering [[nodiscard]] errors.
+    void ignoreError() const {}
+
+    // Implicit cast to Status.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    operator Status() const { return status(); }
+
+  private:
+    Status mStatus = status::undefined;
+    T mValue;
+};
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& os, const StatusOr<T>& s) {
+    return os << "StatusOr[status: " << s.status() << "]";
+}
+
+#define ASSIGN_OR_RETURN_IMPL(tmp, lhs, stmt) \
+    auto tmp = (stmt);                        \
+    RETURN_IF_NOT_OK(tmp);                    \
+    lhs = std::move(tmp.value());
+
+#define ASSIGN_OR_RETURN_CONCAT(line, lhs, stmt) \
+    ASSIGN_OR_RETURN_IMPL(__CONCAT(_status_or_, line), lhs, stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from the current function. Otherwise, assign the result to lhs.
+//
+// This macro supports both move and copy assignment operators. lhs
+// may be either a new local variable or an existing non-const
+// variable accessible in the current scope.
+//
+// Example usage:
+// StatusOr<MyType> foo() { ... }
+//
+// ASSIGN_OR_RETURN(auto myVar, foo());
+// ASSIGN_OR_RETURN(myExistingVar, foo());
+// ASSIGN_OR_RETURN(myMemberVar, foo());
+#define ASSIGN_OR_RETURN(lhs, stmt) ASSIGN_OR_RETURN_CONCAT(__LINE__, lhs, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUSOR_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h
new file mode 100644
index 0000000..e7b4326
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef NETDUTILS_STOPWATCH_H
+#define NETDUTILS_STOPWATCH_H
+
+#include <chrono>
+
+namespace android {
+namespace netdutils {
+
+class Stopwatch {
+  private:
+    using clock = std::chrono::steady_clock;
+    using time_point = std::chrono::time_point<clock>;
+
+  public:
+    Stopwatch() : mStart(clock::now()) {}
+    virtual ~Stopwatch() = default;
+
+    int64_t timeTakenUs() const { return getElapsedUs(clock::now()); }
+    int64_t getTimeAndResetUs() {
+        const auto& now = clock::now();
+        int64_t elapsed = getElapsedUs(now);
+        mStart = now;
+        return elapsed;
+    }
+
+  private:
+    time_point mStart;
+
+    int64_t getElapsedUs(const time_point& now) const {
+        return (std::chrono::duration_cast<std::chrono::microseconds>(now - mStart)).count();
+    }
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_STOPWATCH_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h
new file mode 100644
index 0000000..36fcd85
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_SYSCALLS_H
+#define NETDUTILS_SYSCALLS_H
+
+#include <memory>
+
+#include <net/if.h>
+#include <poll.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "netdutils/Fd.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/UniqueFd.h"
+#include "netdutils/UniqueFile.h"
+
+namespace android {
+namespace netdutils {
+
+class Syscalls {
+  public:
+    virtual ~Syscalls() = default;
+
+    virtual StatusOr<UniqueFd> open(const std::string& pathname, int flags,
+                                    mode_t mode = 0) const = 0;
+
+    virtual StatusOr<UniqueFd> socket(int domain, int type, int protocol) const = 0;
+
+    virtual Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const = 0;
+
+    virtual Status getsockopt(Fd sock, int level, int optname, void *optval,
+                              socklen_t *optlen) const = 0;
+
+    virtual Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                              socklen_t optlen) const = 0;
+
+    virtual Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const = 0;
+
+    virtual StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const = 0;
+
+    virtual StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const = 0;
+
+    virtual StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const = 0;
+
+    virtual StatusOr<size_t> write(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<Slice> read(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                                    socklen_t dstlen) const = 0;
+
+    virtual StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                     socklen_t* srclen) const = 0;
+
+    virtual Status shutdown(Fd fd, int how) const = 0;
+
+    virtual Status close(Fd fd) const = 0;
+
+    virtual StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const = 0;
+
+    virtual StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const = 0;
+
+    virtual StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const = 0;
+
+    virtual Status fclose(FILE* file) const = 0;
+
+    virtual StatusOr<pid_t> fork() const = 0;
+
+    // va_args helpers
+    // va_start doesn't work when the preceding argument is a reference
+    // type so we're forced to use const char*.
+    StatusOr<int> fprintf(FILE* file, const char* format, ...) const {
+        va_list ap;
+        va_start(ap, format);
+        auto result = vfprintf(file, format, ap);
+        va_end(ap);
+        return result;
+    }
+
+    // va_start doesn't work when the preceding argument is a reference
+    // type so we're forced to use const char*.
+    StatusOr<int> fscanf(FILE* file, const char* format, ...) const {
+        va_list ap;
+        va_start(ap, format);
+        auto result = vfscanf(file, format, ap);
+        va_end(ap);
+        return result;
+    }
+
+    // Templated helpers that forward directly to methods declared above
+    template <typename SockaddrT>
+    StatusOr<SockaddrT> getsockname(Fd sock) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        RETURN_IF_NOT_OK(getsockname(sock, asSockaddrPtr(&addr), &addrlen));
+        return addr;
+    }
+
+    template <typename SockoptT>
+    Status getsockopt(Fd sock, int level, int optname, void* optval, socklen_t* optlen) const {
+        return getsockopt(sock, level, optname, optval, optlen);
+    }
+
+    template <typename SockoptT>
+    Status setsockopt(Fd sock, int level, int optname, const SockoptT& opt) const {
+        return setsockopt(sock, level, optname, &opt, sizeof(opt));
+    }
+
+    template <typename SockaddrT>
+    Status bind(Fd sock, const SockaddrT& addr) const {
+        return bind(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <typename SockaddrT>
+    Status connect(Fd sock, const SockaddrT& addr) const {
+        return connect(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <size_t size>
+    StatusOr<std::array<uint16_t, size>> ppoll(const std::array<Fd, size>& fds, uint16_t events,
+                                               double timeout) const {
+        std::array<pollfd, size> tmp;
+        for (size_t i = 0; i < size; ++i) {
+            tmp[i].fd = fds[i].get();
+            tmp[i].events = events;
+            tmp[i].revents = 0;
+        }
+        RETURN_IF_NOT_OK(ppoll(tmp.data(), tmp.size(), timeout).status());
+        std::array<uint16_t, size> out;
+        for (size_t i = 0; i < size; ++i) {
+            out[i] = tmp[i].revents;
+        }
+        return out;
+    }
+
+    template <typename SockaddrT>
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const SockaddrT& dst) const {
+        return sendto(sock, buf, flags, asSockaddrPtr(&dst), sizeof(dst));
+    }
+
+    // Ignore src sockaddr
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags) const {
+        return recvfrom(sock, dst, flags, nullptr, nullptr);
+    }
+
+    template <typename SockaddrT>
+    StatusOr<std::pair<Slice, SockaddrT>> recvfrom(Fd sock, const Slice dst, int flags) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        ASSIGN_OR_RETURN(auto used, recvfrom(sock, dst, flags, asSockaddrPtr(&addr), &addrlen));
+        return std::make_pair(used, addr);
+    }
+};
+
+// Specialized singleton that supports zero initialization and runtime
+// override of contained pointer.
+class SyscallsHolder {
+  public:
+    ~SyscallsHolder();
+
+    // Return a pointer to an unowned instance of Syscalls.
+    Syscalls& get();
+
+    // Testing only: set the value returned by getSyscalls. Return the old value.
+    // Callers are responsible for restoring the previous value returned
+    // by getSyscalls to avoid leaks.
+    Syscalls& swap(Syscalls& syscalls);
+
+  private:
+    std::atomic<Syscalls*> mSyscalls{nullptr};
+};
+
+// Syscalls instance used throughout netdutils
+extern SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SYSCALLS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h
new file mode 100644
index 0000000..62e6f70
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_THREADUTIL_H
+#define NETDUTILS_THREADUTIL_H
+
+#include <pthread.h>
+#include <memory>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace netdutils {
+
+struct scoped_pthread_attr {
+    scoped_pthread_attr() { pthread_attr_init(&attr); }
+    ~scoped_pthread_attr() { pthread_attr_destroy(&attr); }
+
+    int detach() { return -pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); }
+
+    pthread_attr_t attr;
+};
+
+inline void setThreadName(std::string name) {
+    // MAX_TASK_COMM_LEN=16 is not exported by bionic.
+    const size_t MAX_TASK_COMM_LEN = 16;
+
+    // Crop name to 16 bytes including the NUL byte, as required by pthread_setname_np()
+    if (name.size() >= MAX_TASK_COMM_LEN) name.resize(MAX_TASK_COMM_LEN - 1);
+
+    if (int ret = pthread_setname_np(pthread_self(), name.c_str()); ret != 0) {
+        LOG(WARNING) << "Unable to set thread name to " << name << ": " << strerror(ret);
+    }
+}
+
+template <typename T>
+inline void* runAndDelete(void* obj) {
+    std::unique_ptr<T> handler(reinterpret_cast<T*>(obj));
+    setThreadName(handler->threadName().c_str());
+    handler->run();
+    return nullptr;
+}
+
+template <typename T>
+inline int threadLaunch(T* obj) {
+    if (obj == nullptr) {
+        return -EINVAL;
+    }
+
+    scoped_pthread_attr scoped_attr;
+
+    int rval = scoped_attr.detach();
+    if (rval != 0) {
+        return rval;
+    }
+
+    pthread_t thread;
+    rval = pthread_create(&thread, &scoped_attr.attr, &runAndDelete<T>, obj);
+    if (rval != 0) {
+        LOG(WARNING) << __func__ << ": pthread_create failed: " << rval;
+        return -rval;
+    }
+
+    return rval;
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_THREADUTIL_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h
new file mode 100644
index 0000000..42c1090
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef NETDUTILS_UID_CONSTANTS_H
+#define NETDUTILS_UID_CONSTANTS_H
+
+// These are used by both eBPF kernel programs and netd, we cannot put them in NetdConstant.h since
+// we have to minimize the number of headers included by the BPF kernel program.
+#define MIN_SYSTEM_UID 0
+#define MAX_SYSTEM_UID 9999
+
+#define PER_USER_RANGE 100000
+
+#endif  // NETDUTILS_UID_CONSTANTS_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h
new file mode 100644
index 0000000..61101f9
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_UNIQUEFD_H
+#define NETUTILS_UNIQUEFD_H
+
+#include <unistd.h>
+#include <ostream>
+
+#include "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+// Stricter unique_fd implementation that:
+// *) Does not implement release()
+// *) Does not implicitly cast to int
+// *) Uses a strongly typed wrapper (Fd) for the underlying file descriptor
+//
+// Users of UniqueFd should endeavor to treat this as a completely
+// opaque object. The only code that should interpret the wrapped
+// value is in Syscalls.h
+class UniqueFd {
+  public:
+    UniqueFd() = default;
+
+    UniqueFd(Fd fd) : mFd(fd) {}
+
+    ~UniqueFd() { reset(); }
+
+    // Disallow copy
+    UniqueFd(const UniqueFd&) = delete;
+    UniqueFd& operator=(const UniqueFd&) = delete;
+
+    // Allow move
+    UniqueFd(UniqueFd&& other) { std::swap(mFd, other.mFd); }
+    UniqueFd& operator=(UniqueFd&& other) {
+        std::swap(mFd, other.mFd);
+        return *this;
+    }
+
+    // Cleanup any currently owned Fd, replacing it with the optional
+    // parameter fd
+    void reset(Fd fd = Fd());
+
+    // Implict cast to Fd
+    operator const Fd &() const { return mFd; }
+
+  private:
+    Fd mFd;
+};
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_UNIQUEFD_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h
new file mode 100644
index 0000000..6dd6d67
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_UNIQUEFILE_H
+#define NETDUTILS_UNIQUEFILE_H
+
+#include <stdio.h>
+#include <memory>
+
+namespace android {
+namespace netdutils {
+
+struct UniqueFileDtor {
+    void operator()(FILE* file) const;
+};
+
+using UniqueFile = std::unique_ptr<FILE, UniqueFileDtor>;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_UNIQUEFILE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Utils.h b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
new file mode 100644
index 0000000..83c583b
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef NETUTILS_UTILS_H
+#define NETUTILS_UTILS_H
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames();
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList();
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_UTILS_H */
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
new file mode 100644
index 0000000..40371e6
--- /dev/null
+++ b/staticlibs/tests/unit/Android.bp
@@ -0,0 +1,53 @@
+//########################################################################
+// Build NetworkStaticLibTests package
+//########################################################################
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "NetworkStaticLibTestsLib",
+    srcs: ["src/**/*.java","src/**/*.kt"],
+    min_sdk_version: "29",
+    defaults: ["framework-connectivity-test-defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-extended-minus-junit4",
+        "netd-client",
+        "net-tests-utils",
+        "net-utils-framework-common",
+        "net-utils-device-common",
+        "net-utils-device-common-async",
+        "net-utils-device-common-bpf",
+        "net-utils-device-common-ip",
+        "net-utils-device-common-wear",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    visibility: [
+        "//frameworks/base/packages/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+        "//packages/modules/NetworkStack/tests/integration",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+android_test {
+    name: "NetworkStaticLibTests",
+    certificate: "platform",
+    static_libs: [
+        "NetworkStaticLibTestsLib",
+    ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    test_suites: ["device-tests"],
+    lint: { strict_updatability_linting: true },
+}
diff --git a/staticlibs/tests/unit/AndroidManifest.xml b/staticlibs/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..84a20a2
--- /dev/null
+++ b/staticlibs/tests/unit/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.libnet.tests">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.libnet.tests"
+        android:label="Network Static Library Tests" />
+</manifest>
diff --git a/staticlibs/tests/unit/jarjar-rules.txt b/staticlibs/tests/unit/jarjar-rules.txt
new file mode 100644
index 0000000..e032ae5
--- /dev/null
+++ b/staticlibs/tests/unit/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.android.net.module.util.** com.android.net.moduletests.util.@1
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
new file mode 100644
index 0000000..e25d554
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.arp.ArpPacket;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class ArpPacketTest {
+
+    private static final Inet4Address TEST_IPV4_ADDR =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2");
+    private static final Inet4Address INADDR_ANY =
+            (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0");
+    private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] {
+            0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 };
+    private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+    private static final byte[] TEST_ARP_PROBE = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // sender IP address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target mac address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+    };
+
+    private static final byte[] TEST_ARP_ANNOUNCE = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // sender IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+        // target mac address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+    };
+
+    private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00,
+    };
+
+    private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] {
+         // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+    };
+
+    @Test
+    public void testBuildArpProbePacket() throws Exception {
+        final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+                TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+                INADDR_ANY.getAddress(), (short) ARP_REQUEST);
+        assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE);
+    }
+
+    @Test
+    public void testBuildArpAnnouncePacket() throws Exception {
+        final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+                TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+                TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST);
+        assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE);
+    }
+
+    @Test
+    public void testParseArpProbePacket() throws Exception {
+        final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length);
+        assertEquals(packet.opCode, ARP_REQUEST);
+        assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+        assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+        assertEquals(packet.senderIp, INADDR_ANY);
+        assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+    }
+
+    @Test
+    public void testParseArpAnnouncePacket() throws Exception {
+        final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE,
+                TEST_ARP_ANNOUNCE.length);
+        assertEquals(packet.opCode, ARP_REQUEST);
+        assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+        assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+        assertEquals(packet.senderIp, TEST_IPV4_ADDR);
+        assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+    }
+
+    @Test
+    public void testParseArpPacket_invalidByteBufferParameters() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE, 0));
+    }
+
+    @Test
+    public void testParseArpPacket_truncatedPacket() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length));
+    }
+
+    @Test
+    public void testParseArpPacket_truncatedMacAddress() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
new file mode 100644
index 0000000..49940ea
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.net.module.util
+
+import com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder
+import com.android.net.module.util.BitUtils.describeDifferences
+import com.android.net.module.util.BitUtils.packBits
+import com.android.net.module.util.BitUtils.unpackBits
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Test
+
+class BitUtilsTests {
+    @Test
+    fun testBitPackingTestCase() {
+        runBitPackingTestCase(0, intArrayOf())
+        runBitPackingTestCase(1, intArrayOf(0))
+        runBitPackingTestCase(3, intArrayOf(0, 1))
+        runBitPackingTestCase(4, intArrayOf(2))
+        runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5))
+        runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63))
+    }
+
+    fun runBitPackingTestCase(packedBits: Long, bits: IntArray) {
+        assertEquals(packedBits, packBits(bits))
+        assertTrue(bits contentEquals unpackBits(packedBits))
+    }
+
+    @Test
+    fun testAppendStringRepresentationOfBitMaskToStringBuilder() {
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("", 0)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT0", 0b1)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder("BIT1&BIT2&BIT4", 0b10110)
+        runTestAppendStringRepresentationOfBitMaskToStringBuilder(
+                "BIT0&BIT60&BIT61&BIT62&BIT63",
+                (0b11110000_00000000_00000000_00000000 shl 32) +
+                        0b00000000_00000000_00000000_00000001)
+    }
+
+    fun runTestAppendStringRepresentationOfBitMaskToStringBuilder(expected: String, bitMask: Long) {
+        StringBuilder().let {
+            appendStringRepresentationOfBitMaskToStringBuilder(it, bitMask, { i -> "BIT$i" }, "&")
+            assertEquals(expected, it.toString())
+        }
+    }
+
+    @Test
+    fun testDescribeDifferences() {
+        fun describe(a: Long, b: Long) = describeDifferences(a, b, Integer::toString)
+        assertNull(describe(0, 0))
+        assertNull(describe(5, 5))
+        assertNull(describe(Long.MAX_VALUE, Long.MAX_VALUE))
+
+        assertEquals("+0", describe(0, 1))
+        assertEquals("-0", describe(1, 0))
+
+        assertEquals("+0+2", describe(0, 5))
+        assertEquals("+2", describe(1, 5))
+        assertEquals("-0+2", describe(1, 4))
+
+        fun makeField(vararg i: Int) = i.sumOf { 1L shl it }
+        assertEquals("-0-4-6-9+1+3+11", describe(makeField(0, 4, 6, 9), makeField(1, 3, 11)))
+        assertEquals("-1-5-9+6+8", describe(makeField(0, 1, 3, 4, 5, 9), makeField(0, 3, 4, 6, 8)))
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
new file mode 100644
index 0000000..a66dacd
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.net.module.util;
+
+import static android.system.OsConstants.EPERM;
+import static android.system.OsConstants.R_OK;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.TestBpfMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BpfDumpTest {
+    private static final int TEST_KEY = 123;
+    private static final String TEST_KEY_BASE64 = "ewAAAA==";
+    private static final int TEST_VAL = 456;
+    private static final String TEST_VAL_BASE64 = "yAEAAA==";
+    private static final String BASE64_DELIMITER = ",";
+    private static final String TEST_KEY_VAL_BASE64 =
+            TEST_KEY_BASE64 + BASE64_DELIMITER + TEST_VAL_BASE64;
+    private static final String INVALID_BASE64_STRING = "Map is null";
+
+    @Test
+    public void testToBase64EncodedString() {
+        final Struct.S32 key = new Struct.S32(TEST_KEY);
+        final Struct.S32 value = new Struct.S32(TEST_VAL);
+
+        // Verified in python:
+        //   import base64
+        //   print(base64.b64encode(b'\x7b\x00\x00\x00')) # key: ewAAAA== (TEST_KEY_BASE64)
+        //   print(base64.b64encode(b'\xc8\x01\x00\x00')) # value: yAEAAA== (TEST_VAL_BASE64)
+        assertEquals("7B000000", HexDump.toHexString(key.writeToBytes()));
+        assertEquals("C8010000", HexDump.toHexString(value.writeToBytes()));
+        assertEquals(TEST_KEY_VAL_BASE64, BpfDump.toBase64EncodedString(key, value));
+    }
+
+    @Test
+    public void testFromBase64EncodedString() {
+        Pair<Struct.S32, Struct.S32> decodedKeyValue = BpfDump.fromBase64EncodedString(
+                Struct.S32.class, Struct.S32.class, TEST_KEY_VAL_BASE64);
+        assertEquals(TEST_KEY, decodedKeyValue.first.val);
+        assertEquals(TEST_VAL, decodedKeyValue.second.val);
+    }
+
+    private void assertThrowsIllegalArgumentException(final String testStr) {
+        assertThrows(IllegalArgumentException.class,
+                () -> BpfDump.fromBase64EncodedString(Struct.S32.class, Struct.S32.class, testStr));
+    }
+
+    @Test
+    public void testFromBase64EncodedStringInvalidString() {
+        assertThrowsIllegalArgumentException(INVALID_BASE64_STRING);
+        assertThrowsIllegalArgumentException(TEST_KEY_BASE64);
+        assertThrowsIllegalArgumentException(
+                TEST_KEY_BASE64 + BASE64_DELIMITER + INVALID_BASE64_STRING);
+        assertThrowsIllegalArgumentException(
+                INVALID_BASE64_STRING + BASE64_DELIMITER + TEST_VAL_BASE64);
+        assertThrowsIllegalArgumentException(
+                INVALID_BASE64_STRING + BASE64_DELIMITER + INVALID_BASE64_STRING);
+        assertThrowsIllegalArgumentException(
+                TEST_KEY_VAL_BASE64 + BASE64_DELIMITER + TEST_KEY_BASE64);
+    }
+
+    private String getDumpMap(final IBpfMap<Struct.S32, Struct.S32> map) {
+        final StringWriter sw = new StringWriter();
+        BpfDump.dumpMap(map, new PrintWriter(sw), "mapName", "header",
+                (key, val) -> "key=" + key.val + ", val=" + val.val);
+        return sw.toString();
+    }
+
+    @Test
+    public void testDumpMap() throws Exception {
+        final IBpfMap<Struct.S32, Struct.S32> map =
+                new TestBpfMap<>(Struct.S32.class, Struct.S32.class);
+        map.updateEntry(new Struct.S32(123), new Struct.S32(456));
+
+        final String dump = getDumpMap(map);
+        assertEquals(dump, "mapName:\n"
+                + "  header\n"
+                + "  key=123, val=456\n");
+    }
+
+    @Test
+    public void testDumpMapMultipleEntries() throws Exception {
+        final IBpfMap<Struct.S32, Struct.S32> map =
+                new TestBpfMap<>(Struct.S32.class, Struct.S32.class);
+        map.updateEntry(new Struct.S32(123), new Struct.S32(456));
+        map.updateEntry(new Struct.S32(789), new Struct.S32(123));
+
+        final String dump = getDumpMap(map);
+        assertTrue(dump.contains("mapName:"));
+        assertTrue(dump.contains("header"));
+        assertTrue(dump.contains("key=123, val=456"));
+        assertTrue(dump.contains("key=789, val=123"));
+    }
+
+    private String getDumpMapStatus(final IBpfMap<Struct.S32, Struct.S32> map) {
+        final StringWriter sw = new StringWriter();
+        BpfDump.dumpMapStatus(map, new PrintWriter(sw), "mapName", "mapPath");
+        return sw.toString();
+    }
+
+    @Test
+    public void testGetMapStatus() {
+        final IBpfMap<Struct.S32, Struct.S32> map =
+                new TestBpfMap<>(Struct.S32.class, Struct.S32.class);
+        assertEquals("mapName: OK\n", getDumpMapStatus(map));
+    }
+
+    @Test
+    public void testGetMapStatusNull() {
+        final MockitoSession session = mockitoSession()
+                .spyStatic(Os.class)
+                .startMocking();
+        try {
+            // Os.access succeeds
+            doReturn(true).when(() -> Os.access("mapPath", R_OK));
+            assertEquals("mapName: NULL(map is pinned to mapPath)\n", getDumpMapStatus(null));
+
+            // Os.access throws EPERM
+            doThrow(new ErrnoException("", EPERM)).when(() -> Os.access("mapPath", R_OK));
+            assertEquals("mapName: NULL(map is not pinned to mapPath: Operation not permitted)\n",
+                    getDumpMapStatus(null));
+        } finally {
+            session.finishMocking();
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt
new file mode 100644
index 0000000..e58adad
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ByteUtilsTests.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.net.module.util
+
+import com.android.net.module.util.ByteUtils.indexOf
+import com.android.net.module.util.ByteUtils.concat
+import org.junit.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertNotSame
+
+class ByteUtilsTests {
+    private val EMPTY = byteArrayOf()
+    private val ARRAY1 = byteArrayOf(1)
+    private val ARRAY234 = byteArrayOf(2, 3, 4)
+
+    @Test
+    fun testIndexOf() {
+        assertEquals(-1, indexOf(EMPTY, 1))
+        assertEquals(-1, indexOf(ARRAY1, 2))
+        assertEquals(-1, indexOf(ARRAY234, 1))
+        assertEquals(0, indexOf(byteArrayOf(-1), -1))
+        assertEquals(0, indexOf(ARRAY234, 2))
+        assertEquals(1, indexOf(ARRAY234, 3))
+        assertEquals(2, indexOf(ARRAY234, 4))
+        assertEquals(1, indexOf(byteArrayOf(2, 3, 2, 3), 3))
+    }
+
+    @Test
+    fun testConcat() {
+        assertContentEquals(EMPTY, concat())
+        assertContentEquals(EMPTY, concat(EMPTY))
+        assertContentEquals(EMPTY, concat(EMPTY, EMPTY, EMPTY))
+        assertContentEquals(ARRAY1, concat(ARRAY1))
+        assertNotSame(ARRAY1, concat(ARRAY1))
+        assertContentEquals(ARRAY1, concat(EMPTY, ARRAY1, EMPTY))
+        assertContentEquals(byteArrayOf(1, 1, 1), concat(ARRAY1, ARRAY1, ARRAY1))
+        assertContentEquals(byteArrayOf(1, 2, 3, 4), concat(ARRAY1, ARRAY234))
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
new file mode 100644
index 0000000..851d09a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import android.util.Log
+import com.android.testutils.tryTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private val TAG = CleanupTest::class.simpleName
+
+@RunWith(JUnit4::class)
+class CleanupTest {
+    class TestException1 : Exception()
+    class TestException2 : Exception()
+    class TestException3 : Exception()
+
+    @Test
+    fun testNotThrow() {
+        var x = 1
+        val result = tryTest {
+            x = 2
+            Log.e(TAG, "Do nothing")
+            6
+        } cleanup {
+            assertTrue(x == 2)
+            x = 3
+            Log.e(TAG, "Do nothing")
+        }
+        assertTrue(x == 3)
+        assertTrue(result == 6)
+    }
+
+    @Test
+    fun testThrowTry() {
+        var x = 1
+        val thrown = assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+                x = 4
+            } cleanup {
+                assertTrue(x == 2)
+                x = 3
+                Log.e(TAG, "Do nothing")
+            }
+        }
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+        assertTrue(x == 3)
+    }
+
+    @Test
+    fun testThrowCleanup() {
+        var x = 1
+        val thrown = assertFailsWith<TestException2> {
+            tryTest {
+                x = 2
+                Log.e(TAG, "Do nothing")
+            } cleanup {
+                assertTrue(x == 2)
+                x = 3
+                throw TestException2()
+                x = 4
+            }
+        }
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+        assertTrue(x == 3)
+    }
+
+    @Test
+    fun testThrowBoth() {
+        var x = 1
+        val thrown = assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+                x = 3
+            } cleanup {
+                assertTrue(x == 2)
+                x = 4
+                throw TestException2()
+                x = 5
+            }
+        }
+        assertTrue(thrown.suppressedExceptions[0] is TestException2)
+        assertTrue(x == 4)
+    }
+
+    @Test
+    fun testReturn() {
+        val resultIfSuccess = 11
+        val resultIfException = 12
+        fun doTestReturn(crash: Boolean) = tryTest {
+            if (crash) throw RuntimeException() else resultIfSuccess
+        }.catch<RuntimeException> {
+            resultIfException
+        } cleanup {}
+
+        assertTrue(6 == tryTest { 6 } cleanup { Log.e(TAG, "tested") })
+        assertEquals(resultIfSuccess, doTestReturn(crash = false))
+        assertEquals(resultIfException, doTestReturn(crash = true))
+    }
+
+    @Test
+    fun testCatch() {
+        var x = 1
+        tryTest {
+            x = 2
+            throw TestException1()
+            x = 3
+        }.catch<TestException1> {
+            x = 4
+        }.catch<TestException2> {
+            x = 5
+        } cleanup {
+            assertTrue(x == 4)
+            x = 6
+        }
+        assertTrue(x == 6)
+    }
+
+    @Test
+    fun testNotCatch() {
+        var x = 1
+        assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException2> {
+                fail("Caught TestException2 instead of TestException1")
+            } cleanup {
+                assertTrue(x == 2)
+                x = 3
+            }
+        }
+        assertTrue(x == 3)
+    }
+
+    @Test
+    fun testThrowInCatch() {
+        var x = 1
+        val thrown = assertFailsWith<TestException2> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException1> {
+                x = 3
+                throw TestException2()
+            } cleanup {
+                assertTrue(x == 3)
+                x = 4
+            }
+        }
+        assertTrue(x == 4)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+    }
+
+    @Test
+    fun testAssertionErrorInCatch() {
+        var x = 1
+        val thrown = assertFailsWith<AssertionError> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException1> {
+                x = 3
+                fail("Test failure in catch")
+            } cleanup {
+                assertTrue(x == 3)
+                x = 4
+            }
+        }
+        assertTrue(x == 4)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+    }
+
+    @Test
+    fun testMultipleCleanups() {
+        var x = 1
+        val thrown = assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            } cleanupStep {
+                assertTrue(x == 2)
+                x = 3
+                throw TestException2()
+                x = 4
+            } cleanupStep {
+                assertTrue(x == 3)
+                x = 5
+                throw TestException3()
+                x = 6
+            } cleanup {
+                assertTrue(x == 5)
+                x = 7
+            }
+        }
+        assertEquals(2, thrown.suppressedExceptions.size)
+        assertTrue(thrown.suppressedExceptions[0] is TestException2)
+        assertTrue(thrown.suppressedExceptions[1] is TestException3)
+        assert(x == 7)
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
new file mode 100644
index 0000000..8a13397
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CleanupTestJava {
+    private static final String TAG = CleanupTestJava.class.getSimpleName();
+    private static final class TestException1 extends Exception {}
+    private static final class TestException2 extends Exception {}
+    private static final class TestException3 extends Exception {}
+
+    @Test
+    public void testNotThrow() {
+        final AtomicInteger x = new AtomicInteger(1);
+        final int a = testAndCleanup(() -> {
+            x.compareAndSet(1, 2);
+            Log.e(TAG, "Do nothing");
+            return 6;
+        }, () -> {
+                x.compareAndSet(2, 3);
+                Log.e(TAG, "Do nothing");
+            });
+        assertEquals(3, x.get());
+        assertEquals(6, a);
+    }
+
+    @Test
+    public void testThrowTry() {
+        final AtomicInteger x = new AtomicInteger(1);
+        assertThrows(TestException1.class, () ->
+                testAndCleanup(() -> {
+                    x.compareAndSet(1, 2);
+                    throw new TestException1();
+                    // Java refuses to call x.set(3) here because this line is unreachable
+                }, () -> {
+                        x.compareAndSet(2, 3);
+                        Log.e(TAG, "Do nothing");
+                    })
+        );
+        assertEquals(3, x.get());
+    }
+
+    @Test
+    public void testThrowCleanup() {
+        final AtomicInteger x = new AtomicInteger(1);
+        assertThrows(TestException2.class, () ->
+                testAndCleanup(() -> {
+                    x.compareAndSet(1, 2);
+                    Log.e(TAG, "Do nothing");
+                }, () -> {
+                        x.compareAndSet(2, 3);
+                        throw new TestException2();
+                        // Java refuses to call x.set(4) here because this line is unreachable
+                    })
+        );
+        assertEquals(3, x.get());
+    }
+
+    @Test
+    public void testThrowBoth() {
+        final AtomicInteger x = new AtomicInteger(1);
+        assertThrows(TestException1.class, () ->
+                testAndCleanup(() -> {
+                    x.compareAndSet(1, 2);
+                    throw new TestException1();
+                }, () -> {
+                        x.compareAndSet(2, 3);
+                        throw new TestException2();
+                    })
+        );
+        assertEquals(3, x.get());
+    }
+
+    @Test
+    public void testMultipleCleanups() {
+        final AtomicInteger x = new AtomicInteger(1);
+        final TestException1 exception = assertThrows(TestException1.class, () ->
+                testAndCleanup(() -> {
+                    x.compareAndSet(1, 2);
+                    throw new TestException1();
+                }, () -> {
+                        x.compareAndSet(2, 3);
+                        throw new TestException2();
+                    }, () -> {
+                        x.compareAndSet(3, 4);
+                        throw new TestException3();
+                    }, () -> {
+                        x.compareAndSet(4, 5);
+                    })
+        );
+        assertEquals(2, exception.getSuppressed().length);
+        assertTrue(exception.getSuppressed()[0] instanceof TestException2);
+        assertTrue(exception.getSuppressed()[1] instanceof TestException3);
+        assertEquals(5, x.get());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
new file mode 100644
index 0000000..e23f999
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CollectionUtilsTest {
+    @Test
+    fun testAny() {
+        assertTrue(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "E" })
+        assertFalse(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "F" })
+        assertTrue(CollectionUtils.any(listOf("AA", "BBB")) { it.length >= 3 })
+        assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length >= 4 })
+        assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length < 0 })
+        assertFalse(CollectionUtils.any(listOf<String>()) { true })
+        assertFalse(CollectionUtils.any(listOf<String>()) { false })
+        assertTrue(CollectionUtils.any(listOf("A")) { true })
+        assertFalse(CollectionUtils.any(listOf("A")) { false })
+    }
+
+    @Test
+    fun testIndexOf() {
+        assertEquals(4, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "E" })
+        assertEquals(0, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "A" })
+        assertEquals(1, CollectionUtils.indexOf(listOf("AA", "BBB", "CCCC")) { it.length >= 3 })
+        assertEquals(1, CollectionUtils.indexOf(listOf("AA", null, "CCCC")) { it == null })
+        assertEquals(1, CollectionUtils.indexOf(listOf(null, "CCCC")) { it != null })
+    }
+
+    @Test
+    fun testIndexOfSubArray() {
+        val haystack = byteArrayOf(1, 2, 3, 4, 5)
+        assertEquals(2, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(3, 4)))
+        assertEquals(3, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(4, 5)))
+        assertEquals(4, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(5)))
+        assertEquals(-1, CollectionUtils.indexOfSubArray(haystack, byteArrayOf(3, 2)))
+        assertEquals(0, CollectionUtils.indexOfSubArray(haystack, byteArrayOf()))
+        assertEquals(-1, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf(3, 2)))
+        assertEquals(0, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf()))
+    }
+
+    @Test
+    fun testAll() {
+        assertFalse(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "E" })
+        assertTrue(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "F" })
+        assertFalse(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length > 2 })
+        assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length >= 1 })
+        assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length < 4 })
+        assertTrue(CollectionUtils.all(listOf<String>()) { true })
+        assertTrue(CollectionUtils.all(listOf<String>()) { false })
+        assertTrue(CollectionUtils.all(listOf(1)) { true })
+        assertFalse(CollectionUtils.all(listOf(1)) { false })
+    }
+
+    @Test
+    fun testContains() {
+        assertTrue(CollectionUtils.contains(shortArrayOf(10, 20, 30), 10))
+        assertTrue(CollectionUtils.contains(shortArrayOf(10, 20, 30), 30))
+        assertFalse(CollectionUtils.contains(shortArrayOf(10, 20, 30), 40))
+        assertFalse(CollectionUtils.contains(null, 10.toShort()))
+        assertTrue(CollectionUtils.contains(intArrayOf(10, 20, 30), 10))
+        assertTrue(CollectionUtils.contains(intArrayOf(10, 20, 30), 30))
+        assertFalse(CollectionUtils.contains(intArrayOf(10, 20, 30), 40))
+        assertFalse(CollectionUtils.contains(null, 10.toInt()))
+        assertTrue(CollectionUtils.contains(arrayOf("A", "B", "C"), "A"))
+        assertTrue(CollectionUtils.contains(arrayOf("A", "B", "C"), "C"))
+        assertFalse(CollectionUtils.contains(arrayOf("A", "B", "C"), "D"))
+        assertFalse(CollectionUtils.contains(null, "A"))
+
+        val list = listOf("A", "B", "Ab", "C", "D", "E", "A", "E")
+        assertTrue(CollectionUtils.contains(list) { it.length == 2 })
+        assertFalse(CollectionUtils.contains(list) { it.length < 1 })
+        assertTrue(CollectionUtils.contains(list) { it > "A" })
+        assertFalse(CollectionUtils.contains(list) { it > "F" })
+    }
+
+    @Test
+    fun testTotal() {
+        assertEquals(10, CollectionUtils.total(longArrayOf(3, 6, 1)))
+        assertEquals(10, CollectionUtils.total(longArrayOf(6, 1, 3)))
+        assertEquals(10, CollectionUtils.total(longArrayOf(1, 3, 6)))
+        assertEquals(3, CollectionUtils.total(longArrayOf(1, 1, 1)))
+        assertEquals(0, CollectionUtils.total(null))
+    }
+
+    @Test
+    fun testFindFirstFindLast() {
+        val listAE = listOf("A", "B", "C", "D", "E")
+        assertSame(CollectionUtils.findFirst(listAE) { it == "A" }, listAE[0])
+        assertSame(CollectionUtils.findFirst(listAE) { it == "B" }, listAE[1])
+        assertSame(CollectionUtils.findFirst(listAE) { it == "E" }, listAE[4])
+        assertNull(CollectionUtils.findFirst(listAE) { it == "F" })
+        assertSame(CollectionUtils.findLast(listAE) { it == "A" }, listAE[0])
+        assertSame(CollectionUtils.findLast(listAE) { it == "B" }, listAE[1])
+        assertSame(CollectionUtils.findLast(listAE) { it == "E" }, listAE[4])
+        assertNull(CollectionUtils.findLast(listAE) { it == "F" })
+
+        val listMulti = listOf("A", "B", "A", "C", "D", "E", "A", "E")
+        assertSame(CollectionUtils.findFirst(listMulti) { it == "A" }, listMulti[0])
+        assertSame(CollectionUtils.findFirst(listMulti) { it == "B" }, listMulti[1])
+        assertSame(CollectionUtils.findFirst(listMulti) { it == "E" }, listMulti[5])
+        assertNull(CollectionUtils.findFirst(listMulti) { it == "F" })
+        assertSame(CollectionUtils.findLast(listMulti) { it == "A" }, listMulti[6])
+        assertSame(CollectionUtils.findLast(listMulti) { it == "B" }, listMulti[1])
+        assertSame(CollectionUtils.findLast(listMulti) { it == "E" }, listMulti[7])
+        assertNull(CollectionUtils.findLast(listMulti) { it == "F" })
+    }
+
+    @Test
+    fun testMap() {
+        val listAE = listOf("A", "B", "C", "D", "E", null)
+        assertEquals(listAE.map { "-$it-" }, CollectionUtils.map(listAE) { "-$it-" })
+    }
+
+    @Test
+    fun testZip() {
+        val listAE = listOf("A", "B", "C", "D", "E")
+        val list15 = listOf(1, 2, 3, 4, 5)
+        // Normal #zip returns kotlin.Pair, not android.util.Pair
+        assertEquals(list15.zip(listAE).map { android.util.Pair(it.first, it.second) },
+                CollectionUtils.zip(list15, listAE))
+        val listNull = listOf("A", null, "B", "C", "D")
+        assertEquals(list15.zip(listNull).map { android.util.Pair(it.first, it.second) },
+                CollectionUtils.zip(list15, listNull))
+        assertEquals(emptyList<android.util.Pair<Int, Int>>(),
+                CollectionUtils.zip(emptyList<Int>(), emptyList<Int>()))
+        assertFailsWith<IllegalArgumentException> {
+            // Different size
+            CollectionUtils.zip(listOf(1, 2), list15)
+        }
+    }
+
+    @Test
+    fun testAssoc() {
+        val listADA = listOf("A", "B", "C", "D", "A")
+        val list15 = listOf(1, 2, 3, 4, 5)
+        assertEquals(list15.zip(listADA).toMap(), CollectionUtils.assoc(list15, listADA))
+
+        // Null key is fine
+        val assoc = CollectionUtils.assoc(listOf(1, 2, null), listOf("A", "B", "C"))
+        assertEquals("C", assoc[null])
+
+        assertFailsWith<IllegalArgumentException> {
+            // Same key multiple times
+            CollectionUtils.assoc(listOf("A", "B", "A"), listOf(1, 2, 3))
+        }
+        assertFailsWith<IllegalArgumentException> {
+            // Same key multiple times, but it's null
+            CollectionUtils.assoc(listOf(null, "B", null), listOf(1, 2, 3))
+        }
+        assertFailsWith<IllegalArgumentException> {
+            // Different size
+            CollectionUtils.assoc(listOf(1, 2), list15)
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java
new file mode 100644
index 0000000..8af0196
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ConnectivityUtilsTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.net.InetAddresses.parseNumericAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for ConnectivityUtils */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityUtilsTest {
+    @Test
+    public void testIsIPv6ULA() {
+        assertTrue(isIPv6ULA(parseNumericAddress("fc00::")));
+        assertTrue(isIPv6ULA(parseNumericAddress("fc00::1")));
+        assertTrue(isIPv6ULA(parseNumericAddress("fc00:1234::5678")));
+        assertTrue(isIPv6ULA(parseNumericAddress("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+        assertFalse(isIPv6ULA(parseNumericAddress("fe00::")));
+        assertFalse(isIPv6ULA(parseNumericAddress("2480:1248::123:456")));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
new file mode 100644
index 0000000..5a96bcb
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+import java.util.Arrays;
+
+
+/**
+ * Tests for DeviceConfigUtils.
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DeviceConfigUtilsTest {
+    private static final String TEST_NAME_SPACE = "connectivity";
+    private static final String TEST_EXPERIMENT_FLAG = "experiment_flag";
+    private static final int TEST_FLAG_VALUE = 28;
+    private static final String TEST_FLAG_VALUE_STRING = "28";
+    private static final int TEST_DEFAULT_FLAG_VALUE = 0;
+    private static final int TEST_MAX_FLAG_VALUE = 1000;
+    private static final int TEST_MIN_FLAG_VALUE = 100;
+    private static final long TEST_PACKAGE_VERSION = 290000000;
+    private static final String TEST_PACKAGE_NAME = "test.package.name";
+    // The APEX name is the name of the APEX module, as in android.content.pm.ModuleInfo, and is
+    // used for its mount point in /apex. APEX packages are actually APKs with a different
+    // file extension, so they have an AndroidManifest: the APEX package name is the package name in
+    // that manifest, and is reflected in android.content.pm.ApplicationInfo. Contrary to the APEX
+    // (module) name, different package names are typically used to identify the organization that
+    // built and signed the APEX modules.
+    private static final String TEST_APEX_PACKAGE_NAME = "com.prefix.android.tethering";
+    private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
+    private static final String TEST_CONNRES_PACKAGE_NAME =
+            "com.prefix.android.connectivity.resources";
+    private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack";
+    private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack";
+    private final PackageInfo mPackageInfo = new PackageInfo();
+    private final PackageInfo mApexPackageInfo = new PackageInfo();
+    private MockitoSession mSession;
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPm;
+    @Mock private Resources mResources;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking();
+
+        mPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        mApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+
+        doReturn(mPm).when(mContext).getPackageManager();
+        doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+        doReturn(mPackageInfo).when(mPm).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
+        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_APEX_PACKAGE_NAME), anyInt());
+
+        doReturn(mResources).when(mContext).getResources();
+
+        final ResolveInfo ri = new ResolveInfo();
+        ri.activityInfo = new ActivityInfo();
+        ri.activityInfo.applicationInfo = new ApplicationInfo();
+        ri.activityInfo.applicationInfo.packageName = TEST_CONNRES_PACKAGE_NAME;
+        ri.activityInfo.applicationInfo.sourceDir =
+                "/apex/com.android.tethering/priv-app/ServiceConnectivityResources@version";
+        doReturn(Arrays.asList(ri)).when(mPm).queryIntentActivities(argThat(
+                intent -> intent.getAction().equals(DeviceConfigUtils.RESOURCES_APK_INTENT)),
+                eq(MATCH_SYSTEM_ONLY));
+    }
+
+    @After
+    public void tearDown() {
+        mSession.finishMocking();
+        DeviceConfigUtils.resetPackageVersionCacheForTest();
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_Null() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_NotNull() {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_NormalValue() {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_NullValue() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, 0 /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_OverMaximumValue() {
+        doReturn(Integer.toString(TEST_MAX_FLAG_VALUE + 10)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_EqualsMaximumValue() {
+        doReturn(Integer.toString(TEST_MAX_FLAG_VALUE)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_MAX_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_BelowMinimumValue() {
+        doReturn(Integer.toString(TEST_MIN_FLAG_VALUE - 10)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_DEFAULT_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyInt_EqualsMinimumValue() {
+        doReturn(Integer.toString(TEST_MIN_FLAG_VALUE)).when(() -> DeviceConfig.getProperty(
+                eq(TEST_NAME_SPACE), eq(TEST_EXPERIMENT_FLAG)));
+        assertEquals(TEST_MIN_FLAG_VALUE, DeviceConfigUtils.getDeviceConfigPropertyInt(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG, TEST_MIN_FLAG_VALUE /* minimum value */,
+                TEST_MAX_FLAG_VALUE /* maximum value */,
+                TEST_DEFAULT_FLAG_VALUE /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyBoolean_Null() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                false /* default value */));
+    }
+
+    @Test
+    public void testGetDeviceConfigPropertyBoolean_NotNull() {
+        doReturn("true").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+                TEST_NAME_SPACE, TEST_EXPERIMENT_FLAG,
+                false /* default value */));
+    }
+
+    @Test
+    public void testIsNetworkStackFeatureEnabled() {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testIsTetheringFeatureEnabled() {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testFeatureDefaultEnabled() {
+        doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testFeatureIsEnabledWithException() throws Exception {
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+
+        // Feature should be enabled by flag value "1".
+        doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        doReturn("1").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+
+        // Feature should be disabled by flag value "999999999".
+        doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        doReturn("999999999").when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+
+        // Follow defaultEnabled if the flag is not set
+        doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        doReturn(null).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG,
+                false /* defaultEnabled */));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG,
+                true /* defaultEnabled */));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testFeatureIsEnabledOnGo() throws Exception {
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(
+                eq(TEST_APEX_PACKAGE_NAME), anyInt());
+        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_GO_APEX_PACKAGE_NAME), anyInt());
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                NAMESPACE_CONNECTIVITY, TEST_EXPERIMENT_FLAG));
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                NAMESPACE_TETHERING, TEST_EXPERIMENT_FLAG));
+
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testIsNetworkStackFeatureEnabledCaching() throws Exception {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+
+        // Package info is only queried once
+        verify(mContext, times(1)).getPackageManager();
+        verify(mContext, times(1)).getPackageName();
+        verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+    }
+
+    @Test
+    public void testIsTetheringFeatureEnabledCaching() throws Exception {
+        doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(NAMESPACE_TETHERING,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_EXPERIMENT_FLAG));
+
+        // Package info is only queried once
+        verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+        verify(mContext, never()).getPackageName();
+    }
+
+    @Test
+    public void testGetResBooleanConfig() {
+        final int someResId = 1234;
+        doReturn(true).when(mResources).getBoolean(someResId);
+        assertTrue(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false));
+        doReturn(false).when(mResources).getBoolean(someResId);
+        assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false));
+        doThrow(new Resources.NotFoundException()).when(mResources).getBoolean(someResId);
+        assertFalse(DeviceConfigUtils.getResBooleanConfig(mContext, someResId, false));
+    }
+
+    @Test
+    public void testGetResIntegerConfig() {
+        final int someResId = 1234;
+        doReturn(2097).when(mResources).getInteger(someResId);
+        assertEquals(2097, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
+        doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId);
+        assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
+    }
+
+    @Test
+    public void testGetNetworkStackModuleVersionCaching() throws Exception {
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+        assertEquals(TEST_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+
+        assertEquals(TEST_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+        // Package info is only queried once
+        verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+        verify(mContext, never()).getPackageName();
+    }
+
+    @Test
+    public void testGetNetworkStackModuleVersionOnNonMainline() {
+        assertEquals(DeviceConfigUtils.DEFAULT_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+    }
+
+    @Test
+    public void testGetNetworkStackModuleVersion() throws Exception {
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        final PackageInfo goNetworkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        goNetworkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION + 1);
+        doReturn(goNetworkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+        // Verify the returned value is go module version.
+        assertEquals(TEST_PACKAGE_VERSION + 1,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+    }
+
+    @Test
+    public void testIsFeatureSupported_networkStackFeature() throws Exception {
+        // Supported for DEFAULT_PACKAGE_VERSION
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID + 1));
+    }
+
+    @Test
+    public void testIsFeatureSupported_tetheringFeature() throws Exception {
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + CONNECTIVITY_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + CONNECTIVITY_MODULE_ID));
+    }
+
+    @Test
+    public void testIsFeatureSupported_illegalModule() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigUtils.isFeatureSupported(mContext, TEST_PACKAGE_VERSION));
+    }
+
+    @Test
+    public void testIsTetheringFeatureNotChickenedOut() throws Exception {
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+
+        doReturn(TEST_FLAG_VALUE_STRING).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testIsNetworkStackFeatureNotChickenedOut() throws Exception {
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                eq(NAMESPACE_CONNECTIVITY), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+
+        doReturn(TEST_FLAG_VALUE_STRING).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+                                               eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
new file mode 100644
index 0000000..28e183a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static android.net.DnsResolver.CLASS_IN;
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketTest {
+    private static final int TEST_DNS_PACKET_ID = 0x7722;
+    private static final int TEST_DNS_PACKET_FLAGS = 0x8180;
+
+    private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
+            int qCount, int aCount, int nsCount, int arCount) {
+        assertEquals(header.getId(), id);
+        assertEquals(header.getFlags(), flag);
+        assertEquals(header.getRecordCount(DnsPacket.QDSECTION), qCount);
+        assertEquals(header.getRecordCount(DnsPacket.ANSECTION), aCount);
+        assertEquals(header.getRecordCount(DnsPacket.NSSECTION), nsCount);
+        assertEquals(header.getRecordCount(DnsPacket.ARSECTION), arCount);
+    }
+
+    private void assertRecordParses(DnsPacket.DnsRecord record, String dname,
+            int dtype, int dclass, int ttl, byte[] rr) {
+        assertEquals(record.dName, dname);
+        assertEquals(record.nsType, dtype);
+        assertEquals(record.nsClass, dclass);
+        assertEquals(record.ttl, ttl);
+        assertTrue(Arrays.equals(record.getRR(), rr));
+    }
+
+    static class TestDnsPacket extends DnsPacket {
+        TestDnsPacket(byte[] data) throws DnsPacket.ParseException {
+            super(data);
+        }
+
+        TestDnsPacket(@NonNull DnsHeader header, @Nullable ArrayList<DnsRecord> qd,
+                @Nullable ArrayList<DnsRecord> an) {
+            super(header, qd, an);
+        }
+
+        public DnsHeader getHeader() {
+            return mHeader;
+        }
+        public List<DnsRecord> getRecordList(int secType) {
+            return mRecords[secType];
+        }
+    }
+
+    @Test
+    public void testNullDisallowed() {
+        try {
+            new TestDnsPacket(null);
+            fail("Exception not thrown for null byte array");
+        } catch (DnsPacket.ParseException e) {
+        }
+    }
+
+    @Test
+    public void testV4Answer() throws Exception {
+        final byte[] v4blob = new byte[] {
+            /* Header */
+            0x55, 0x66, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x01, 0x2b, /* TTL */
+            0x00, 0x04, /* Data length */
+            (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v4blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
+
+        // Record part
+        List<DnsPacket.DnsRecord> qdRecordList =
+                packet.getRecordList(DnsPacket.QDSECTION);
+        assertEquals(qdRecordList.size(), 1);
+        assertRecordParses(qdRecordList.get(0), "www.google.com", 1, 1, 0, null);
+
+        List<DnsPacket.DnsRecord> anRecordList =
+                packet.getRecordList(DnsPacket.ANSECTION);
+        assertEquals(anRecordList.size(), 1);
+        assertRecordParses(anRecordList.get(0), "www.google.com", 1, 1, 0x12b,
+                new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
+    }
+
+    @Test
+    public void testV6Answer() throws Exception {
+        final byte[] v6blob = new byte[] {
+            /* Header */
+            0x77, 0x22, /* Transaction ID */
+            (byte) 0x81, (byte) 0x80, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x01, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            /* Answers */
+            (byte) 0xc0, 0x0c, /* Name */
+            0x00, 0x1c, /* Type */
+            0x00, 0x01, /* Class */
+            0x00, 0x00, 0x00, 0x37, /* TTL */
+            0x00, 0x10, /* Data length */
+            0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
+        };
+        TestDnsPacket packet = new TestDnsPacket(v6blob);
+
+        // Header part
+        assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
+
+        // Record part
+        List<DnsPacket.DnsRecord> qdRecordList =
+                packet.getRecordList(DnsPacket.QDSECTION);
+        assertEquals(qdRecordList.size(), 1);
+        assertRecordParses(qdRecordList.get(0), "www.google.com", 28, 1, 0, null);
+
+        List<DnsPacket.DnsRecord> anRecordList =
+                packet.getRecordList(DnsPacket.ANSECTION);
+        assertEquals(anRecordList.size(), 1);
+        assertRecordParses(anRecordList.get(0), "www.google.com", 28, 1, 0x37,
+                new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
+    }
+
+    /** Verifies that the synthesized {@link DnsPacket.DnsHeader} can be parsed correctly. */
+    @Test
+    public void testDnsHeaderSynthesize() {
+        final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID,
+                TEST_DNS_PACKET_FLAGS, 3 /* qcount */, 5 /* ancount */);
+        final DnsPacket.DnsHeader actualHeader = new DnsPacket.DnsHeader(
+                ByteBuffer.wrap(testHeader.getBytes()));
+        assertEquals(testHeader, actualHeader);
+    }
+
+    /** Verifies that the synthesized {@link DnsPacket.DnsRecord} can be parsed correctly. */
+    @Test
+    public void testDnsRecordSynthesize() throws IOException {
+        assertDnsRecordRoundTrip(
+                DnsPacket.DnsRecord.makeAOrAAAARecord(DnsPacket.ANSECTION,
+                        "test.com", CLASS_IN, 5 /* ttl */,
+                        InetAddressUtils.parseNumericAddress("abcd::fedc")));
+        assertDnsRecordRoundTrip(DnsPacket.DnsRecord.makeQuestion("test.com", TYPE_AAAA, CLASS_IN));
+        assertDnsRecordRoundTrip(DnsPacket.DnsRecord.makeCNameRecord(DnsPacket.ANSECTION,
+                "test.com", CLASS_IN, 0 /* ttl */, "example.com"));
+    }
+
+    /**
+     * Verifies ttl/rData error handling when parsing
+     * {@link DnsPacket.DnsRecord} from bytes.
+     */
+    @Test
+    public void testDnsRecordTTLRDataErrorHandling() throws IOException {
+        // Verify the constructor ignore ttl/rData of questions even if they are supplied.
+        final byte[] qdWithTTLRData = new byte[]{
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x00, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */};
+        final DnsPacket.DnsRecord questionsFromBytes =
+                DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData));
+        assertEquals(0, questionsFromBytes.ttl);
+        assertNull(questionsFromBytes.getRR());
+
+        // Verify ANSECTION must have rData when constructing.
+        final byte[] anWithoutTTLRData = new byte[]{
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */};
+        assertThrows(BufferUnderflowException.class, () ->
+                DnsPacket.DnsRecord.parse(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData)));
+    }
+
+    private void assertDnsRecordRoundTrip(DnsPacket.DnsRecord before)
+            throws IOException {
+        final DnsPacket.DnsRecord after = DnsPacket.DnsRecord.parse(before.rType,
+                ByteBuffer.wrap(before.getBytes()));
+        assertEquals(after, before);
+    }
+
+    /** Verifies that the synthesized {@link DnsPacket} can be parsed correctly. */
+    @Test
+    public void testDnsPacketSynthesize() throws IOException {
+        // Ipv4 dns response packet generated by scapy:
+        //   dns_r = scapy.DNS(
+        //      id=0xbeef,
+        //      qr=1,
+        //      qd=scapy.DNSQR(qname="hello.example.com"),
+        //      an=scapy.DNSRR(rrname="hello.example.com", type="CNAME", rdata='test.com') /
+        //      scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
+        //   scapy.hexdump(dns_r)
+        //   dns_r.show2()
+        // Note that since the synthesizing does not support name compression yet, the domain
+        // name of the sample need to be uncompressed when generating.
+        final byte[] v4BlobUncompressed = new byte[]{
+                /* Header */
+                (byte) 0xbe, (byte) 0xef, /* Transaction ID */
+                (byte) 0x81, 0x00, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x02, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */
+                0x00, 0x05, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x00, 0x00, /* TTL */
+                0x00, 0x0A, /* Data length */
+                0x04, 0x74, 0x65, 0x73, 0x74, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Alias: test.com */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name: hello.example.com */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x00, 0x00, /* TTL */
+                0x00, 0x04, /* Data length */
+                0x01, 0x02, 0x03, 0x04, /* Address: 1.2.3.4 */
+        };
+
+        // Forge one via constructors.
+        final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(0xbeef,
+                0x8100, 1 /* qcount */, 2 /* ancount */);
+        final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>();
+        final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>();
+        qlist.add(DnsPacket.DnsRecord.makeQuestion(
+                "hello.example.com", TYPE_A, CLASS_IN));
+        alist.add(DnsPacket.DnsRecord.makeCNameRecord(
+                DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, "test.com"));
+        alist.add(DnsPacket.DnsRecord.makeAOrAAAARecord(
+                DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */,
+                InetAddressUtils.parseNumericAddress("1.2.3.4")));
+        final TestDnsPacket testPacket = new TestDnsPacket(testHeader, qlist, alist);
+
+        // Assert content equals in both ways.
+        assertTrue(Arrays.equals(v4BlobUncompressed, testPacket.getBytes()));
+        assertEquals(new TestDnsPacket(v4BlobUncompressed), testPacket);
+    }
+
+    @Test
+    public void testDnsPacketSynthesize_recordCountMismatch() throws IOException {
+        final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(0xbeef,
+                0x8100, 1 /* qcount */, 1 /* ancount */);
+        final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>();
+        final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>();
+        qlist.add(DnsPacket.DnsRecord.makeQuestion(
+                "hello.example.com", TYPE_A, CLASS_IN));
+
+        // Assert throws if the supplied answer records fewer than the declared count.
+        assertThrows(IllegalArgumentException.class, () ->
+                new TestDnsPacket(testHeader, qlist, alist));
+
+        // Assert throws if the supplied answer records more than the declared count.
+        alist.add(DnsPacket.DnsRecord.makeCNameRecord(
+                DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */, "test.com"));
+        alist.add(DnsPacket.DnsRecord.makeAOrAAAARecord(
+                DnsPacket.ANSECTION, "hello.example.com", CLASS_IN, 0 /* ttl */,
+                InetAddressUtils.parseNumericAddress("1.2.3.4")));
+        assertThrows(IllegalArgumentException.class, () ->
+                new TestDnsPacket(testHeader, qlist, alist));
+
+        // Assert counts matched if the byte buffer still has data when parsing ended.
+        final byte[] blobTooMuchData = new byte[]{
+                /* Header */
+                (byte) 0xbe, (byte) 0xef, /* Transaction ID */
+                (byte) 0x81, 0x00, /* Flags */
+                0x00, 0x00, /* Questions */
+                0x00, 0x00, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+        };
+        final TestDnsPacket packetFromTooMuchData = new TestDnsPacket(blobTooMuchData);
+        for (int i = 0; i < DnsPacket.NUM_SECTIONS; i++) {
+            assertEquals(0, packetFromTooMuchData.getRecordList(i).size());
+            assertEquals(0, packetFromTooMuchData.getHeader().getRecordCount(i));
+        }
+
+        // Assert throws if the byte buffer ended when expecting more records.
+        final byte[] blobNotEnoughData = new byte[]{
+                /* Header */
+                (byte) 0xbe, (byte) 0xef, /* Transaction ID */
+                (byte) 0x81, 0x00, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x02, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x07, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x00, 0x00, /* TTL */
+                0x00, 0x04, /* Data length */
+                0x01, 0x02, 0x03, 0x04, /* Address */
+        };
+        assertThrows(DnsPacket.ParseException.class, () -> new TestDnsPacket(blobNotEnoughData));
+    }
+
+    @Test
+    public void testEqualsAndHashCode() throws IOException {
+        // Verify DnsHeader equals and hashCode.
+        final DnsPacket.DnsHeader testHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID,
+                TEST_DNS_PACKET_FLAGS, 1 /* qcount */, 1 /* ancount */);
+        final DnsPacket.DnsHeader emptyHeader = new DnsPacket.DnsHeader(TEST_DNS_PACKET_ID + 1,
+                TEST_DNS_PACKET_FLAGS + 0x08, 0 /* qcount */, 0 /* ancount */);
+        final DnsPacket.DnsHeader headerFromBytes =
+                new DnsPacket.DnsHeader(ByteBuffer.wrap(testHeader.getBytes()));
+        assertEquals(testHeader, headerFromBytes);
+        assertEquals(testHeader.hashCode(), headerFromBytes.hashCode());
+        assertNotEquals(testHeader, emptyHeader);
+        assertNotEquals(testHeader.hashCode(), emptyHeader.hashCode());
+        assertNotEquals(headerFromBytes, emptyHeader);
+        assertNotEquals(headerFromBytes.hashCode(), emptyHeader.hashCode());
+
+        // Verify DnsRecord equals and hashCode.
+        final DnsPacket.DnsRecord testQuestion = DnsPacket.DnsRecord.makeQuestion(
+                "test.com", TYPE_AAAA, CLASS_IN);
+        final DnsPacket.DnsRecord testAnswer = DnsPacket.DnsRecord.makeCNameRecord(
+                DnsPacket.ANSECTION, "test.com", CLASS_IN, 9, "www.test.com");
+        final DnsPacket.DnsRecord questionFromBytes = DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION,
+                ByteBuffer.wrap(testQuestion.getBytes()));
+        assertEquals(testQuestion, questionFromBytes);
+        assertEquals(testQuestion.hashCode(), questionFromBytes.hashCode());
+        assertNotEquals(testQuestion, testAnswer);
+        assertNotEquals(testQuestion.hashCode(), testAnswer.hashCode());
+        assertNotEquals(questionFromBytes, testAnswer);
+        assertNotEquals(questionFromBytes.hashCode(), testAnswer.hashCode());
+
+        // Verify DnsPacket equals and hashCode.
+        final ArrayList<DnsPacket.DnsRecord> qlist = new ArrayList<>();
+        final ArrayList<DnsPacket.DnsRecord> alist = new ArrayList<>();
+        qlist.add(testQuestion);
+        alist.add(testAnswer);
+        final TestDnsPacket testPacket = new TestDnsPacket(testHeader, qlist, alist);
+        final TestDnsPacket emptyPacket = new TestDnsPacket(
+                emptyHeader, new ArrayList<>(), new ArrayList<>());
+        final TestDnsPacket packetFromBytes = new TestDnsPacket(testPacket.getBytes());
+        assertEquals(testPacket, packetFromBytes);
+        assertEquals(testPacket.hashCode(), packetFromBytes.hashCode());
+        assertNotEquals(testPacket, emptyPacket);
+        assertNotEquals(testPacket.hashCode(), emptyPacket.hashCode());
+        assertNotEquals(packetFromBytes, emptyPacket);
+        assertNotEquals(packetFromBytes.hashCode(), emptyPacket.hashCode());
+
+        // Verify DnsPacket with empty list.
+        final TestDnsPacket emptyPacketFromBytes = new TestDnsPacket(emptyPacket.getBytes());
+        assertEquals(emptyPacket, emptyPacketFromBytes);
+        assertEquals(emptyPacket.hashCode(), emptyPacketFromBytes.hashCode());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
new file mode 100644
index 0000000..9e1ab82
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.net.ParseException;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketUtilsTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+    /**
+     * Verifies that the compressed NAME field in the answer section of the DNS message is parsed
+     * successfully when name compression is permitted. Additionally, verifies that a
+     * {@link DnsPacket.ParseException} is thrown in a hypothetical scenario where name compression
+     * is not expected.
+     */
+    @Test
+    public void testParsingAnswerSectionNameCompressed() throws Exception {
+        final byte[] v4blobNameCompressedAnswer = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                (byte) 0xc0, 0x0c, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer nameCompressedBuf = ByteBuffer.wrap(v4blobNameCompressedAnswer);
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        assertThrows(DnsPacket.ParseException.class, () -> DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */false));
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */true);
+        assertEquals(domainName, "www.google.com");
+    }
+
+    /**
+     * Verifies that an uncompressed NAME field in the answer section of the DNS message is parsed
+     * successfully irrespective of whether name compression is permitted.
+     */
+    @Test
+    public void testParsingAnswerSectionNoNameCompression() throws Exception {
+        final byte[] v4blobNoNameCompression = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer notNameCompressedBuf = ByteBuffer.wrap(v4blobNoNameCompression);
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ true);
+        assertEquals(domainName, "www.google.com");
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ false);
+        assertEquals(domainName, "www.google.com");
+    }
+
+    // Skip test on R- devices since ParseException only available on S+ devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testDomainNameToLabels() throws Exception {
+        assertArrayEquals(
+                new byte[]{3, 'w', 'w', 'w', 6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm', 0},
+                DnsRecordParser.domainNameToLabels("www.google.com"));
+        assertThrows(ParseException.class, () ->
+                DnsRecordParser.domainNameToLabels("aaa."));
+        assertThrows(ParseException.class, () ->
+                DnsRecordParser.domainNameToLabels("aaa"));
+        assertThrows(ParseException.class, () ->
+                DnsRecordParser.domainNameToLabels("."));
+        assertThrows(ParseException.class, () ->
+                DnsRecordParser.domainNameToLabels(""));
+    }
+
+    @Test
+    public void testIsHostName() {
+        Assert.assertTrue(DnsRecordParser.isHostName("www.google.com"));
+        Assert.assertFalse(DnsRecordParser.isHostName("com"));
+        Assert.assertFalse(DnsRecordParser.isHostName("1.2.3.4"));
+        Assert.assertFalse(DnsRecordParser.isHostName("1234::5678"));
+        Assert.assertFalse(DnsRecordParser.isHostName(null));
+        Assert.assertFalse(DnsRecordParser.isHostName(""));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
new file mode 100644
index 0000000..5eaf2ad
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DomainUtilsTest {
+    @Test
+    public void testEncodeInvalidDomain() {
+        byte[] buffer = DomainUtils.encode(".google.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.com.");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("-google.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.com-");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google..com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google!.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.o");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google,com");
+        assertNull(buffer);
+    }
+
+    @Test
+    public void testEncodeValidDomainNamesWithoutCompression() {
+        // Single domain: "google.com"
+        String suffix = "06676F6F676C6503636F6D00";
+        byte[] buffer = DomainUtils.encode("google.com");
+        //assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // Single domain: "google-guest.com"
+        suffix = "0C676F6F676C652D677565737403636F6D00";
+        buffer = DomainUtils.encode("google-guest.com");
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "04636F727006676F6F676C6503636F6D00"                // corp.google.com
+                + "06676F6F676C6503636F6D00";                         // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp.google.com", "google.com"},
+                false /* compression */);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+
+        // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain),
+        // "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "06676F6F676C6503636F6D00";                         // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp..google.com", "google.com"},
+                false /* compression */);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // Invalid domain search list: "corp..google.com", "..google.com"
+        buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"},
+                false /* compression */);
+        assertEquals(0, buffer.length);
+    }
+
+    @Test
+    public void testEncodeValidDomainNamesWithCompression() {
+        // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+        String suffix =
+                "076578616D706C6504636F727006676F6F676C6503636F6D00"  // example.corp.google.com
+                + "C008"                                              // corp.google.com
+                + "C00D";                                             // google.com
+        byte[] buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp.google.com", "google.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "0161C000"                                          // a.example.corp.google.com
+                + "C00D";                                             // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "google.com", "gle.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "C00D"                                              // google.com
+                + "03676C65C014";                                     // gle.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "google.com", "gle.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "google.com", "google"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "C00D";                                              // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "google.com", "google"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "..google.com", "google"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "06737566666978076578616D706C650365647502636E00"    // suffix.example.edu.cn
+                + "C028";                                             // edu.cn
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "google.com", "example.com", "sub.example.com"
+        suffix = "06676F6F676C6503636F6D00"                           // google.com
+                + "076578616D706C65C007"                              // example.com
+                + "03737562C00C";                                     // sub.example.com
+        buffer = DomainUtils.encode(new String[] {
+                "google.com", "example.com", "sub.example.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+    }
+
+    @Test
+    public void testDecodeDomainNames() {
+        ArrayList<String> suffixStringList;
+        String suffixes = "06676F6F676C6503636F6D00" // google.com
+                + "076578616D706C6503636F6D00"       // example.com
+                + "06676F6F676C6500";                // google
+        List<String> expected = Arrays.asList("google.com", "example.com");
+        ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        // include suffix with invalid length: 64
+        suffixes = "06676F6F676C6503636F6D00"        // google.com
+                + "406578616D706C6503636F6D00"       // example.com(length=64)
+                + "06676F6F676C6500";                // google
+        expected = Arrays.asList("google.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        // include suffix with invalid length: 0
+        suffixes = "06676F6F676C6503636F6D00"         // google.com
+                + "076578616D706C6503636F6D00"        // example.com
+                + "00676F6F676C6500";                 // google(length=0)
+        expected = Arrays.asList("google.com", "example.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        suffixes =
+                "076578616D706C6504636F727006676F6F676C6503636F6D00"  // example.corp.google.com
+                + "C008"                                              // corp.google.com
+                + "C00D";                                             // google.com
+        expected = Arrays.asList("example.corp.google.com", "corp.google.com", "google.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, true /* compression */);
+        assertEquals(expected, suffixStringList);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
new file mode 100644
index 0000000..5a15585
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HexDumpTest {
+    @Test
+    public void testBytesToHexString() {
+        assertEquals("abcdef", HexDump.toHexString(
+                new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, false));
+        assertEquals("ABCDEF", HexDump.toHexString(
+                new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, true));
+    }
+
+    @Test
+    public void testNullArray() {
+        assertEquals("(null)", HexDump.dumpHexString(null));
+    }
+
+    @Test
+    public void testHexStringToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef},
+                HexDump.hexStringToByteArray("abcdef"));
+        assertArrayEquals(new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF},
+                HexDump.hexStringToByteArray("ABCDEF"));
+    }
+
+    @Test
+    public void testIntegerToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x04},
+                HexDump.toByteArray((int) 0xff000004));
+    }
+
+    @Test
+    public void testByteToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0x7f}, HexDump.toByteArray((byte) 0x7f));
+    }
+
+    @Test
+    public void testIntegerToHexString() {
+        assertEquals("FF000004", HexDump.toHexString((int) 0xff000004));
+    }
+
+    @Test
+    public void testByteToHexString() {
+        assertEquals("7F", HexDump.toHexString((byte) 0x7f));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java
new file mode 100644
index 0000000..702bdaf
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getImplicitNetmask;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTL;
+import static com.android.net.module.util.Inet4AddressUtils.netmaskToPrefixLength;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
+import static com.android.net.module.util.Inet4AddressUtils.trimAddressZeros;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Inet4AddressUtilsTest {
+
+    @Test
+    public void testInet4AddressToIntHTL() {
+        assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0")));
+        assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0")));
+        assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0")));
+        assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0")));
+        assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254")));
+        assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255")));
+    }
+
+    @Test
+    public void testIntToInet4AddressHTL() {
+        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0));
+        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
+        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
+        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
+        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
+        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
+    }
+
+    @Test
+    public void testInet4AddressToIntHTH() {
+        assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0")));
+        assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0")));
+        assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0")));
+        assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0")));
+        assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254")));
+        assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255")));
+    }
+
+    @Test
+    public void testIntToInet4AddressHTH() {
+        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0));
+        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
+        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
+        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
+        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
+        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
+    }
+
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTL() {
+        assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
+        assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
+        assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
+        assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
+        assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
+        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
+    }
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTH() {
+        assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
+        assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
+        assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
+        assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
+        assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
+        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
+        prefixLengthToV4NetmaskIntHTH(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
+        prefixLengthToV4NetmaskIntHTH(33);
+    }
+
+    private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
+        final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
+        final int addrInt = inet4AddressToIntHTH(ipv4Address(addr));
+        assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
+    }
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
+        checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
+        checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
+        checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
+        checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
+    }
+
+    @Test
+    public void testGetImplicitNetmask() {
+        assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2")));
+        assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7")));
+        assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105")));
+        assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145")));
+        assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1")));
+        assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1")));
+        assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1")));
+        assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8")));
+    }
+
+    private void assertInvalidNetworkMask(Inet4Address addr) {
+        try {
+            netmaskToPrefixLength(addr);
+            fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testNetmaskToPrefixLength() {
+        assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0")));
+        assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0")));
+        assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0")));
+        assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0")));
+        assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254")));
+        assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255")));
+
+        assertInvalidNetworkMask(ipv4Address("0.0.0.1"));
+        assertInvalidNetworkMask(ipv4Address("255.255.255.253"));
+        assertInvalidNetworkMask(ipv4Address("255.255.0.255"));
+    }
+
+    @Test
+    public void testGetPrefixMaskAsAddress() {
+        assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
+        assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
+        assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
+        assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
+    }
+
+    @Test
+    public void testGetBroadcastAddress() {
+        assertEquals("192.168.15.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress());
+        assertEquals("192.255.255.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress());
+        assertEquals("192.168.0.123",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress());
+        assertEquals("255.255.255.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBroadcastAddress_PrefixTooLarge() {
+        getBroadcastAddress(ipv4Address("192.168.0.123"), 33);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBroadcastAddress_NegativePrefix() {
+        getBroadcastAddress(ipv4Address("192.168.0.123"), -1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
+        getPrefixMaskAsInet4Address(33);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPrefixMaskAsAddress_NegativePrefix() {
+        getPrefixMaskAsInet4Address(-1);
+    }
+
+    @Test
+    public void testTrimAddressZeros() {
+        assertNull(trimAddressZeros(null));
+        assertEquals("$invalid&", trimAddressZeros("$invalid&"));
+        assertEquals("example.com", trimAddressZeros("example.com"));
+        assertEquals("a.b.c.d", trimAddressZeros("a.b.c.d"));
+
+        assertEquals("192.0.2.2", trimAddressZeros("192.000.02.2"));
+        assertEquals("192.0.2.2", trimAddressZeros("192.0.2.2"));
+    }
+
+    private Inet4Address ipv4Address(String addr) {
+        return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
new file mode 100644
index 0000000..bb2b933
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InetAddressUtilsTest {
+
+    private InetAddress parcelUnparcelAddress(InetAddress addr) {
+        Parcel p = Parcel.obtain();
+        InetAddressUtils.parcelInetAddress(p, addr, 0 /* flags */);
+        p.setDataPosition(0);
+        byte[] marshalled = p.marshall();
+        p.recycle();
+        p = Parcel.obtain();
+        p.unmarshall(marshalled, 0, marshalled.length);
+        p.setDataPosition(0);
+        InetAddress out = InetAddressUtils.unparcelInetAddress(p);
+        p.recycle();
+        return out;
+    }
+
+    @Test
+    public void testParcelUnparcelIpv4Address() throws Exception {
+        InetAddress ipv4 = InetAddress.getByName("192.0.2.1");
+        assertEquals(ipv4, parcelUnparcelAddress(ipv4));
+    }
+
+    @Test
+    public void testParcelUnparcelIpv6Address() throws Exception {
+        InetAddress ipv6 = InetAddress.getByName("2001:db8::1");
+        assertEquals(ipv6, parcelUnparcelAddress(ipv6));
+    }
+
+    @Test
+    public void testParcelUnparcelScopedIpv6Address() throws Exception {
+        InetAddress ipv6 = InetAddress.getByName("fe80::1%42");
+        assertEquals(42, ((Inet6Address) ipv6).getScopeId());
+        Inet6Address out = (Inet6Address) parcelUnparcelAddress(ipv6);
+        assertEquals(ipv6, out);
+        assertEquals(42, out.getScopeId());
+    }
+
+    @Test
+    public void testWithScopeId() {
+        final int scopeId = 999;
+
+        final String globalAddrStr = "2401:fa00:49c:484:dc41:e6ff:fefd:f180";
+        final Inet6Address globalAddr = (Inet6Address) InetAddresses
+                .parseNumericAddress(globalAddrStr);
+        final Inet6Address updatedGlobalAddr = InetAddressUtils.withScopeId(globalAddr, scopeId);
+        assertFalse(updatedGlobalAddr.isLinkLocalAddress());
+        assertEquals(globalAddrStr, updatedGlobalAddr.getHostAddress());
+        assertEquals(0, updatedGlobalAddr.getScopeId());
+
+        final String localAddrStr = "fe80::4735:9628:d038:2087";
+        final Inet6Address localAddr = (Inet6Address) InetAddresses
+                .parseNumericAddress(localAddrStr);
+        final Inet6Address updatedLocalAddr = InetAddressUtils.withScopeId(localAddr, scopeId);
+        assertTrue(updatedLocalAddr.isLinkLocalAddress());
+        assertEquals(localAddrStr + "%" + scopeId, updatedLocalAddr.getHostAddress());
+        assertEquals(scopeId, updatedLocalAddr.getScopeId());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java
new file mode 100644
index 0000000..a1d8c10
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/InterfaceParamsTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.net.module.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceParamsTest {
+    @Test
+    public void testNullInterfaceReturnsNull() {
+        assertNull(InterfaceParams.getByName(null));
+    }
+
+    @Test
+    public void testNonExistentInterfaceReturnsNull() {
+        assertNull(InterfaceParams.getByName("doesnotexist0"));
+    }
+
+    @Test
+    public void testLoopback() {
+        final InterfaceParams ifParams = InterfaceParams.getByName("lo");
+        assertNotNull(ifParams);
+        assertEquals("lo", ifParams.name);
+        assertTrue(ifParams.index > 0);
+        assertNotNull(ifParams.macAddr);
+        assertFalse(ifParams.hasMacAddress);
+        assertTrue(ifParams.defaultMtu >= NetworkStackConstants.ETHER_MTU);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java
new file mode 100644
index 0000000..20bbd4a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.annotation.SuppressLint;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpRangeTest {
+
+    private static InetAddress address(String addr) {
+        return InetAddresses.parseNumericAddress(addr);
+    }
+
+    private static final Inet4Address IPV4_ADDR = (Inet4Address) address("192.0.2.4");
+    private static final Inet4Address IPV4_RANGE_END = (Inet4Address) address("192.0.3.1");
+    private static final Inet6Address IPV6_ADDR = (Inet6Address) address("2001:db8::");
+    private static final Inet6Address IPV6_RANGE_END = (Inet6Address) address("2001:db9:010f::");
+
+    private static final byte[] IPV4_BYTES = IPV4_ADDR.getAddress();
+    private static final byte[] IPV6_BYTES = IPV6_ADDR.getAddress();
+
+    @Test
+    public void testConstructorBadArguments() {
+        try {
+            new IpRange(null, IPV6_ADDR);
+            fail("Expected NullPointerException: null start address");
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            new IpRange(IPV6_ADDR, null);
+            fail("Expected NullPointerException: null end address");
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            new IpRange(null, null);
+            fail("Expected NullPointerException: null addresses");
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            new IpRange(null);
+            fail("Expected NullPointerException: null start address");
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            new IpRange(address("10.10.10.10"), address("1.2.3.4"));
+            fail("Expected IllegalArgumentException: start address after end address");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            new IpRange(address("ffff::"), address("abcd::"));
+            fail("Expected IllegalArgumentException: start address after end address");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testConstructor() {
+        IpRange r = new IpRange(new IpPrefix(IPV4_ADDR, 32));
+        assertEquals(IPV4_ADDR, r.getStartAddr());
+        assertEquals(IPV4_ADDR, r.getEndAddr());
+
+        r = new IpRange(new IpPrefix(IPV4_ADDR, 16));
+        assertEquals(address("192.0.0.0"), r.getStartAddr());
+        assertEquals(address("192.0.255.255"), r.getEndAddr());
+
+        r = new IpRange(IPV4_ADDR, IPV4_RANGE_END);
+        assertEquals(IPV4_ADDR, r.getStartAddr());
+        assertEquals(IPV4_RANGE_END, r.getEndAddr());
+
+        r = new IpRange(new IpPrefix(IPV6_ADDR, 128));
+        assertEquals(IPV6_ADDR, r.getStartAddr());
+        assertEquals(IPV6_ADDR, r.getEndAddr());
+
+        r = new IpRange(new IpPrefix(IPV6_ADDR, 16));
+        assertEquals(address("2001::"), r.getStartAddr());
+        assertEquals(address("2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), r.getEndAddr());
+
+        r = new IpRange(IPV6_ADDR, IPV6_RANGE_END);
+        assertEquals(IPV6_ADDR, r.getStartAddr());
+        assertEquals(IPV6_RANGE_END, r.getEndAddr());
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testContainsRangeEqualRanges() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+        final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+
+        assertTrue(r1.containsRange(r2));
+        assertTrue(r2.containsRange(r1));
+        assertEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testContainsRangeSubset() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 64));
+        final IpRange r2 = new IpRange(new IpPrefix(address("2001:db8::0101"), 128));
+
+        assertTrue(r1.containsRange(r2));
+        assertFalse(r2.containsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testContainsRangeTruncatesLowerOrderBits() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 100));
+        final IpRange r2 = new IpRange(new IpPrefix(address("2001:db8::0101"), 100));
+
+        assertTrue(r1.containsRange(r2));
+        assertTrue(r2.containsRange(r1));
+        assertEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testContainsRangeSubsetSameStartAddr() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+        final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 39));
+
+        assertTrue(r1.containsRange(r2));
+        assertFalse(r2.containsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testContainsRangeOverlapping() {
+        final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32));
+        final IpRange r2 = new IpRange(address("2001:db8::"), address("2001:db9::1"));
+
+        assertFalse(r1.containsRange(r2));
+        assertFalse(r2.containsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testOverlapsRangeEqualRanges() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+        final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+
+        assertTrue(r1.overlapsRange(r2));
+        assertTrue(r2.overlapsRange(r1));
+        assertEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testOverlapsRangeSubset() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
+        final IpRange r2 = new IpRange(new IpPrefix(IPV6_ADDR, 39));
+
+        assertTrue(r1.overlapsRange(r2));
+        assertTrue(r2.overlapsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testOverlapsRangeDisjoint() {
+        final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 32));
+        final IpRange r2 = new IpRange(new IpPrefix(address("2001:db9::"), 32));
+
+        assertFalse(r1.overlapsRange(r2));
+        assertFalse(r2.overlapsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testOverlapsRangePartialOverlapLow() {
+        final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32));
+        final IpRange r2 = new IpRange(address("2001:db8::"), address("2001:db9::1"));
+
+        assertTrue(r1.overlapsRange(r2));
+        assertTrue(r2.overlapsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @SuppressLint("NewApi")
+    @Test
+    public void testOverlapsRangePartialOverlapHigh() {
+        final IpRange r1 = new IpRange(new IpPrefix(address("2001:db7::"), 32));
+        final IpRange r2 = new IpRange(address("2001:db7::ffff"), address("2001:db8::"));
+
+        assertTrue(r1.overlapsRange(r2));
+        assertTrue(r2.overlapsRange(r1));
+        assertNotEquals(r1, r2);
+    }
+
+    @Test
+    public void testIpRangeToPrefixesIpv4FullRange() throws Exception {
+        final IpRange range = new IpRange(address("0.0.0.0"), address("255.255.255.255"));
+        final List<IpPrefix> prefixes = new ArrayList<>();
+        prefixes.add(new IpPrefix("0.0.0.0/0"));
+
+        assertEquals(prefixes, range.asIpPrefixes());
+    }
+
+    @Test
+    public void testIpRangeToPrefixesIpv4() throws Exception {
+        final IpRange range = new IpRange(IPV4_ADDR, IPV4_RANGE_END);
+        final List<IpPrefix> prefixes = new ArrayList<>();
+        prefixes.add(new IpPrefix("192.0.2.128/25"));
+        prefixes.add(new IpPrefix("192.0.2.64/26"));
+        prefixes.add(new IpPrefix("192.0.2.32/27"));
+        prefixes.add(new IpPrefix("192.0.2.16/28"));
+        prefixes.add(new IpPrefix("192.0.2.8/29"));
+        prefixes.add(new IpPrefix("192.0.2.4/30"));
+        prefixes.add(new IpPrefix("192.0.3.0/31"));
+
+        assertEquals(prefixes, range.asIpPrefixes());
+    }
+
+    @Test
+    public void testIpRangeToPrefixesIpv6FullRange() throws Exception {
+        final IpRange range =
+                new IpRange(address("::"), address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+        final List<IpPrefix> prefixes = new ArrayList<>();
+        prefixes.add(new IpPrefix("::/0"));
+
+        assertEquals(prefixes, range.asIpPrefixes());
+    }
+
+    @Test
+    public void testIpRangeToPrefixesIpv6() throws Exception {
+        final IpRange range = new IpRange(IPV6_ADDR, IPV6_RANGE_END);
+        final List<IpPrefix> prefixes = new ArrayList<>();
+        prefixes.add(new IpPrefix("2001:db8::/32"));
+        prefixes.add(new IpPrefix("2001:db9::/40"));
+        prefixes.add(new IpPrefix("2001:db9:100::/45"));
+        prefixes.add(new IpPrefix("2001:db9:108::/46"));
+        prefixes.add(new IpPrefix("2001:db9:10c::/47"));
+        prefixes.add(new IpPrefix("2001:db9:10e::/48"));
+        prefixes.add(new IpPrefix("2001:db9:10f::/128"));
+
+        assertEquals(prefixes, range.asIpPrefixes());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
new file mode 100644
index 0000000..d57023c
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpUtilsTest {
+
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
+    private static final int TCP_HEADER_LENGTH = 20;
+    private static final int UDP_HEADER_LENGTH = 8;
+    private static final int TCP_CHECKSUM_OFFSET = 16;
+    private static final int UDP_CHECKSUM_OFFSET = 6;
+
+    private int getUnsignedByte(ByteBuffer buf, int offset) {
+        return buf.get(offset) & 0xff;
+    }
+
+    private int getChecksum(ByteBuffer buf, int offset) {
+        return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1);
+    }
+
+    private void assertChecksumEquals(int expected, short actual) {
+        assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff));
+    }
+
+    // Generate test packets using Python code like this::
+    //
+    // from scapy import all as scapy
+    //
+    // def JavaPacketDefinition(bytes):
+    //   out = "        ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n            "
+    //   for i in xrange(len(bytes)):
+    //     out += "(byte) 0x%02x" % ord(bytes[i])
+    //     if i < len(bytes) - 1:
+    //       if i % 4 == 3:
+    //         out += ",\n            "
+    //       else:
+    //         out += ", "
+    //   out += "\n        });"
+    //   return out
+    //
+    // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") /
+    //           scapy.UDP(sport=12345, dport=7) /
+    //           "hello")
+    // print JavaPacketDefinition(str(packet))
+
+    @Test
+    public void testEmptyAndZeroBufferChecksum() throws Exception {
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] { (byte) 0x00, (byte) 0x00, });
+        // the following should *not* return 0xFFFF
+        assertEquals(0, IpUtils.checksum(packet, 0, 0, 0));
+        assertEquals(0, IpUtils.checksum(packet, 0, 0, 1));
+        assertEquals(0, IpUtils.checksum(packet, 0, 0, 2));
+        assertEquals(0, IpUtils.checksum(packet, 0, 1, 2));
+        assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 0));
+        assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 1));
+        assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 2));
+        assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 1, 2));
+    }
+
+    @Test
+    public void testIpv6TcpChecksum() throws Exception {
+        // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
+        //           scapy.TCP(sport=12345, dport=7,
+        //                     seq=1692871236, ack=128376451, flags=16,
+        //                     window=32768) /
+        //           "hello, world")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+            (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07,
+            (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44,
+            (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83,
+            (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00,
+            (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00,
+            (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c,
+            (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77,
+            (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64
+        });
+
+        // Check that a valid packet has checksum 0.
+        int transportLen = packet.limit() - IPV6_HEADER_LENGTH;
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that we can calculate the checksum from scratch.
+        int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET;
+        int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1);
+        assertEquals(0xee71, sum);
+
+        packet.put(sumOffset, (byte) 0);
+        packet.put(sumOffset + 1, (byte) 0);
+        assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that writing the checksum back into the packet results in a valid packet.
+        packet.putShort(
+            sumOffset,
+            IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+    }
+
+    @Test
+    public void testIpv4UdpChecksum() {
+        // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
+        //           scapy.UDP(sport=32012, dport=4500) /
+        //           "\xff")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d,
+            (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+            (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+            (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94,
+            (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36,
+            (byte) 0xff
+        });
+
+        // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid
+        // UDP checksum, so the udpChecksum rewrites 0 to 0xffff).
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that we can calculate the checksums from scratch.
+        final int ipSumOffset = IPV4_CHECKSUM_OFFSET;
+        final int ipSum = getChecksum(packet, ipSumOffset);
+        assertEquals(0xf68b, ipSum);
+
+        packet.put(ipSumOffset, (byte) 0);
+        packet.put(ipSumOffset + 1, (byte) 0);
+        assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0));
+
+        final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET;
+        final int udpSum = getChecksum(packet, udpSumOffset);
+        assertEquals(0xee36, udpSum);
+
+        packet.put(udpSumOffset, (byte) 0);
+        packet.put(udpSumOffset + 1, (byte) 0);
+        assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that writing the checksums back into the packet results in a valid packet.
+        packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0));
+        packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+    }
+
+    @Test
+    public void testIpv4IcmpChecksum() throws Exception {
+        // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
+        //           scapy.ICMP(type=0x8, id=0x1234, seq=0x5678) /
+        //           "hello, world")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            /* IPv4 */
+            (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x28,
+            (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+            (byte) 0x40, (byte) 0x01, (byte) 0xf6, (byte) 0x90,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+            /* ICMP */
+            (byte) 0x08,                                         /* type: echo-request */
+            (byte) 0x00,                                         /* code: 0 */
+            (byte) 0x4f, (byte) 0x07,                            /* chksum: 0x4f07 */
+            (byte) 0x12, (byte) 0x34,                            /* id: 0x1234 */
+            (byte) 0x56, (byte) 0x78,                            /* seq: 0x5678 */
+            (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c,  /* data: hello, world */
+            (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77,
+            (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64
+        });
+
+        // Check that a valid packet has checksum 0.
+        int transportLen = packet.limit() - IPV4_HEADER_LENGTH;
+        assertEquals(0, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen));
+
+        // Check that we can calculate the checksum from scratch.
+        int sumOffset = IPV4_HEADER_LENGTH + ICMP_CHECKSUM_OFFSET;
+        int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1);
+        assertEquals(0x4f07, sum);
+
+        packet.put(sumOffset, (byte) 0);
+        packet.put(sumOffset + 1, (byte) 0);
+        assertChecksumEquals(sum, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen));
+
+        // Check that writing the checksum back into the packet results in a valid packet.
+        packet.putShort(
+            sumOffset,
+            IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen));
+        assertEquals(0, IpUtils.icmpChecksum(packet, IPV4_HEADER_LENGTH, transportLen));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
new file mode 100644
index 0000000..4866a48
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Ipv6UtilsTest {
+
+    private static final MacAddress MAC1 = MacAddress.fromString("11:22:33:44:55:66");
+    private static final MacAddress MAC2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final Inet6Address LINK_LOCAL = addr("fe80::1");
+    private static final Inet6Address ROUTER_LINK_LOCAL = addr("fe80::cafe:d00d");
+    private static final Inet6Address ALL_ROUTERS =
+            NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+    private static final Inet6Address ALL_NODES =
+            NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+
+    @Test
+    public void testBuildRsPacket() {
+        ByteBuffer b = Ipv6Utils.buildRsPacket(MAC1, MAC2, LINK_LOCAL, ALL_ROUTERS /* no opts */);
+
+        EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+        assertEquals(MAC1, eth.srcMac);
+        assertEquals(MAC2, eth.dstMac);
+
+        Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+        assertEquals(255, ipv6.hopLimit);
+        assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+        assertEquals(LINK_LOCAL, ipv6.srcIp);
+        assertEquals(ALL_ROUTERS, ipv6.dstIp);
+
+        Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+        assertEquals(NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION, icmpv6.type);
+        assertEquals(0, icmpv6.code);
+    }
+
+    @Test
+    public void testBuildRaPacket() {
+        final byte pioFlags =
+                NetworkStackConstants.PIO_FLAG_AUTONOMOUS | NetworkStackConstants.PIO_FLAG_ON_LINK;
+        ByteBuffer pio1 = PrefixInformationOption.build(new IpPrefix("2001:db8:1::/64"),
+                pioFlags, 3600 /* validLifetime */, 1800 /* preferredLifetime */);
+        ByteBuffer pio2 = PrefixInformationOption.build(new IpPrefix("fdcd:a17f:6502:1::/64"),
+                pioFlags, 86400 /* validLifetime */, 86400 /* preferredLifetime */);
+
+        ByteBuffer b = Ipv6Utils.buildRaPacket(MAC2, MAC1, ROUTER_LINK_LOCAL, ALL_NODES,
+                (byte) 0 /* flags */, 7200 /* lifetime */,
+                30_000 /* reachableTime */, 750 /* retransTimer */,
+                pio1, pio2);
+
+        EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+        assertEquals(MAC2, eth.srcMac);
+        assertEquals(MAC1, eth.dstMac);
+
+        Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+        assertEquals(255, ipv6.hopLimit);
+        assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+        assertEquals(ROUTER_LINK_LOCAL, ipv6.srcIp);
+        assertEquals(ALL_NODES, ipv6.dstIp);
+
+        Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+        assertEquals(NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT, icmpv6.type);
+        assertEquals(0, icmpv6.code);
+
+        RaHeader ra = Struct.parse(RaHeader.class, b);
+        assertEquals(0, ra.hopLimit);  // Hop limit: unspecified.
+        assertEquals(0, ra.flags);
+        assertEquals(7200, ra.lifetime);
+        assertEquals(30_000, ra.reachableTime);
+        assertEquals(750, ra.retransTimer);
+
+        PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, b);
+        assertPioEquals(pio, "2001:db8:1::/64", pioFlags, 3600, 1800);
+        pio = Struct.parse(PrefixInformationOption.class, b);
+        assertPioEquals(pio, "fdcd:a17f:6502:1::/64", pioFlags, 86400, 86400);
+    }
+
+    @Test
+    public void testBuildEchoRequestPacket() {
+        final ByteBuffer b = Ipv6Utils.buildEchoRequestPacket(MAC2, MAC1, LINK_LOCAL, ALL_NODES);
+
+        EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+        assertEquals(MAC2, eth.srcMac);
+        assertEquals(MAC1, eth.dstMac);
+
+        Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+        assertEquals(255, ipv6.hopLimit);
+        assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+        assertEquals(LINK_LOCAL, ipv6.srcIp);
+        assertEquals(ALL_NODES, ipv6.dstIp);
+
+        Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+        assertEquals(NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE, icmpv6.type);
+        assertEquals(0, icmpv6.code);
+    }
+
+    @Test
+    public void testBuildEchoReplyPacket() {
+        final ByteBuffer b = Ipv6Utils.buildEchoReplyPacket(LINK_LOCAL, ALL_NODES);
+
+        Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+        assertEquals(255, ipv6.hopLimit);
+        assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+        assertEquals(LINK_LOCAL, ipv6.srcIp);
+        assertEquals(ALL_NODES, ipv6.dstIp);
+
+        Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+        assertEquals(NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE, icmpv6.type);
+        assertEquals(0, icmpv6.code);
+    }
+
+    private void assertPioEquals(PrefixInformationOption pio, String prefix, byte flags,
+            long valid, long preferred) {
+        assertEquals(NetworkStackConstants.ICMPV6_ND_OPTION_PIO, pio.type);
+        assertEquals(4, pio.length);
+        assertEquals(flags, pio.flags);
+        assertEquals(valid, pio.validLifetime);
+        assertEquals(preferred, pio.preferredLifetime);
+        IpPrefix expected = new IpPrefix(prefix);
+        IpPrefix actual = new IpPrefix(pio.prefix, pio.prefixLen);
+        assertEquals(expected, actual);
+    }
+
+    private static Inet6Address addr(String addr) {
+        return (Inet6Address) InetAddresses.parseNumericAddress(addr);
+    }
+
+    private byte[] slice(byte[] array, int length) {
+        return Arrays.copyOf(array, length);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt
new file mode 100644
index 0000000..7574087
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/JniUtilTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+public final class JniUtilTest {
+    private val TEST_JAVA_UTIL_NAME = "java_util_jni"
+    private val TEST_ORG_JUNIT_NAME = "org_junit_jni"
+
+    @Test
+    fun testGetJniLibraryName() {
+        assertEquals(TEST_JAVA_UTIL_NAME,
+                JniUtil.getJniLibraryName(java.util.Set::class.java.getPackage()))
+        assertEquals(TEST_ORG_JUNIT_NAME,
+                JniUtil.getJniLibraryName(org.junit.Before::class.java.getPackage()))
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
new file mode 100644
index 0000000..80ab618
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static com.android.testutils.MiscAsserts.assertSameElements;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.SuppressLint;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public final class LinkPropertiesUtilsTest {
+    @SuppressLint("NewApi")
+    private static final IpPrefix PREFIX = new IpPrefix(toInetAddress("75.208.6.0"), 24);
+    private static final InetAddress V4_ADDR = toInetAddress("75.208.6.1");
+    private static final InetAddress V6_ADDR  = toInetAddress(
+            "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+    private static final InetAddress DNS1 = toInetAddress("75.208.7.1");
+    private static final InetAddress DNS2 = toInetAddress("69.78.7.1");
+
+    private static final InetAddress GATEWAY1 = toInetAddress("75.208.8.1");
+    private static final InetAddress GATEWAY2 = toInetAddress("69.78.8.1");
+
+    private static final String IF_NAME = "wlan0";
+    private static final LinkAddress V4_LINKADDR = new LinkAddress(V4_ADDR, 32);
+    private static final LinkAddress V6_LINKADDR = new LinkAddress(V6_ADDR, 128);
+    private static final RouteInfo RT_INFO1 = new RouteInfo(PREFIX, GATEWAY1, IF_NAME);
+    private static final RouteInfo RT_INFO2 = new RouteInfo(PREFIX, GATEWAY2, IF_NAME);
+    private static final String TEST_DOMAIN = "link.properties.com";
+
+    private static InetAddress toInetAddress(String addrString) {
+        return InetAddresses.parseNumericAddress(addrString);
+    }
+
+    private LinkProperties createTestObject() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(IF_NAME);
+        lp.addLinkAddress(V4_LINKADDR);
+        lp.addLinkAddress(V6_LINKADDR);
+        lp.addDnsServer(DNS1);
+        lp.addDnsServer(DNS2);
+        lp.setDomains(TEST_DOMAIN);
+        lp.addRoute(RT_INFO1);
+        lp.addRoute(RT_INFO2);
+        lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
+        return lp;
+    }
+
+    @Test
+    public void testLinkPropertiesIdenticalEqual() {
+        final LinkProperties source = createTestObject();
+        final LinkProperties target = new LinkProperties(source);
+
+        assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalInterfaceName(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+
+        assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertTrue(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+        // Test different interface name.
+        target.setInterfaceName("lo");
+        assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalInterfaceName(target, source));
+        // Restore interface name
+        target.setInterfaceName(IF_NAME);
+
+        // Compare addresses.size() not equals.
+        final LinkAddress testLinkAddr = new LinkAddress(toInetAddress("75.208.6.2"), 32);
+        target.addLinkAddress(testLinkAddr);
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+
+        // Currently, target contains V4_LINKADDR, V6_LINKADDR and testLinkAddr.
+        // Compare addresses.size() equals but contains different address.
+        target.removeLinkAddress(V4_LINKADDR);
+        assertEquals(source.getAddresses().size(), target.getAddresses().size());
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+        // Restore link address
+        target.addLinkAddress(V4_LINKADDR);
+        target.removeLinkAddress(testLinkAddr);
+
+        // Compare size not equals.
+        target.addDnsServer(toInetAddress("75.208.10.1"));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        // Compare the same servers but target has different domains.
+        target.removeDnsServer(toInetAddress("75.208.10.1"));
+        target.setDomains("test.com");
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+
+        // Test null domain.
+        target.setDomains(null);
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalDnses(target, source));
+        // Restore domain
+        target.setDomains(TEST_DOMAIN);
+
+        // Compare size not equals.
+        final RouteInfo testRoute = new RouteInfo(toInetAddress("75.208.7.1"));
+        target.addRoute(testRoute);
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+
+        // Currently, target contains RT_INFO1, RT_INFO2 and testRoute.
+        // Compare size equals but different routes.
+        target.removeRoute(RT_INFO1);
+        assertEquals(source.getRoutes().size(), target.getRoutes().size());
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalRoutes(target, source));
+        // Restore route
+        target.addRoute(RT_INFO1);
+        target.removeRoute(testRoute);
+
+        // Test different proxy.
+        target.setHttpProxy(ProxyInfo.buildDirectProxy("hello", 8888));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+        // Test null proxy.
+        target.setHttpProxy(null);
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+        final LinkProperties stacked = new LinkProperties();
+        stacked.setInterfaceName("v4-" + target.getInterfaceName());
+        stacked.addLinkAddress(testLinkAddr);
+        target.addStackedLink(stacked);
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+        assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+    }
+
+    private <T> void compareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
+            List<T> expectAdded) {
+        CompareResult<T> result = new CompareResult<>(oldItems, newItems);
+        assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
+        assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
+    }
+
+    @Test
+    public void testCompareResult() {
+        // Either adding or removing items
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
+                Arrays.asList(2, 3, 4), new ArrayList<>());
+        compareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4),
+                new ArrayList<>(), Arrays.asList(3, 4));
+
+
+        // adding and removing items at the same time
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5),
+                Arrays.asList(1), Arrays.asList(5));
+        compareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6),
+                Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
+
+        // null cases
+        compareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>());
+        compareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3));
+        compareResult(null, null, new ArrayList<>(), new ArrayList<>());
+
+        // Some more tests with strings
+        final ArrayList<String> list1 = new ArrayList<>();
+        list1.add("string1");
+
+        final ArrayList<String> list2 = new ArrayList<>(list1);
+        final CompareResult<String> cr1 = new CompareResult<>(list1, list2);
+        assertTrue(cr1.added.isEmpty());
+        assertTrue(cr1.removed.isEmpty());
+
+        list2.add("string2");
+        final CompareResult<String> cr2 = new CompareResult<>(list1, list2);
+        assertEquals(Arrays.asList("string2"), cr2.added);
+        assertTrue(cr2.removed.isEmpty());
+
+        list2.remove("string1");
+        final CompareResult<String> cr3 = new CompareResult<>(list1, list2);
+        assertEquals(Arrays.asList("string2"), cr3.added);
+        assertEquals(Arrays.asList("string1"), cr3.removed);
+
+        list1.add("string2");
+        final CompareResult<String> cr4 = new CompareResult<>(list1, list2);
+        assertTrue(cr4.added.isEmpty());
+        assertEquals(Arrays.asList("string1"), cr3.removed);
+    }
+
+    @Test
+    public void testCompareAddresses() {
+        final LinkProperties source = createTestObject();
+        final LinkProperties target = new LinkProperties(source);
+        final InetAddress addr1 = toInetAddress("75.208.6.2");
+        final LinkAddress linkAddr1 = new LinkAddress(addr1, 32);
+
+        CompareResult<LinkAddress> results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(0, results.removed.size());
+        assertEquals(0, results.added.size());
+
+        source.addLinkAddress(linkAddr1);
+        results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(1, results.removed.size());
+        assertEquals(linkAddr1, results.removed.get(0));
+        assertEquals(0, results.added.size());
+
+        final InetAddress addr2 = toInetAddress("75.208.6.3");
+        final LinkAddress linkAddr2 = new LinkAddress(addr2, 32);
+
+        target.addLinkAddress(linkAddr2);
+        results = LinkPropertiesUtils.compareAddresses(source, target);
+        assertEquals(linkAddr1, results.removed.get(0));
+        assertEquals(linkAddr2, results.added.get(0));
+    }
+
+    private void assertCompareOrUpdateResult(CompareOrUpdateResult result,
+            List<String> expectedAdded, List<String> expectedRemoved,
+            List<String> expectedUpdated) {
+        assertSameElements(expectedAdded, result.added);
+        assertSameElements(expectedRemoved, result.removed);
+        assertSameElements(expectedUpdated, result.updated);
+    }
+
+    private List<String> strArray(String... strs) {
+        return Arrays.asList(strs);
+    }
+
+    @Test
+    public void testCompareOrUpdateResult() {
+        // As the item type, use a simple string. An item is defined to be an update of another item
+        // if the string starts with the same alphabetical characters.
+        // Extracting the key from the object is just a regexp.
+        Function<String, String> extractPrefix = (s) -> s.replaceFirst("^([a-z]+).*", "$1");
+        assertEquals("goodbye", extractPrefix.apply("goodbye1234"));
+
+        List<String> oldItems = strArray("hello123", "goodbye5678", "howareyou669");
+        List<String> newItems = strArray("hello123", "goodbye000", "verywell");
+
+        final List<String> emptyList = new ArrayList<>();
+
+        // Items -> empty: everything removed.
+        CompareOrUpdateResult<String, String> result =
+                new CompareOrUpdateResult<String, String>(oldItems, emptyList, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                emptyList, strArray("hello123", "howareyou669", "goodbye5678"), emptyList);
+
+        // Empty -> items: everything added.
+        result = new CompareOrUpdateResult<String, String>(emptyList, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                strArray("hello123", "goodbye000", "verywell"), emptyList,  emptyList);
+
+        // Empty -> empty: no change.
+        result = new CompareOrUpdateResult<String, String>(newItems, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,  emptyList,  emptyList, emptyList);
+
+        // Added, removed, updated at the same time.
+        result =  new CompareOrUpdateResult<>(oldItems, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                strArray("verywell"), strArray("howareyou669"), strArray("goodbye000"));
+
+        // Null -> items: everything added.
+        result = new CompareOrUpdateResult<String, String>(null, newItems, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                strArray("hello123", "goodbye000", "verywell"), emptyList,  emptyList);
+
+        // Items -> null: everything removed.
+        result = new CompareOrUpdateResult<String, String>(oldItems, null, extractPrefix);
+        assertCompareOrUpdateResult(result,
+                emptyList, strArray("hello123", "howareyou669", "goodbye5678"), emptyList);
+
+        // Null -> null: all lists empty.
+        result = new CompareOrUpdateResult<String, String>(null, null, extractPrefix);
+        assertCompareOrUpdateResult(result, emptyList, emptyList, emptyList);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
new file mode 100644
index 0000000..84018a5
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link LocationPermissionChecker}. */
+@RequiresApi(Build.VERSION_CODES.R)
+public class LocationPermissionCheckerTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
+            Build.VERSION_CODES.Q /* ignoreClassUpTo */);
+
+    // Mock objects for testing
+    @Mock private Context mMockContext;
+    @Mock private PackageManager mMockPkgMgr;
+    @Mock private ApplicationInfo mMockApplInfo;
+    @Mock private AppOpsManager mMockAppOps;
+    @Mock private UserManager mMockUserManager;
+    @Mock private LocationManager mLocationManager;
+
+    private static final String TEST_PKG_NAME = "com.google.somePackage";
+    private static final String TEST_FEATURE_ID = "com.google.someFeature";
+    private static final int MANAGED_PROFILE_UID = 1100000;
+    private static final int OTHER_USER_UID = 1200000;
+
+    private final String mInteractAcrossUsersFullPermission =
+            "android.permission.INTERACT_ACROSS_USERS_FULL";
+    private final String mManifestStringCoarse =
+            Manifest.permission.ACCESS_COARSE_LOCATION;
+    private final String mManifestStringFine =
+            Manifest.permission.ACCESS_FINE_LOCATION;
+
+    // Test variables
+    private int mWifiScanAllowApps;
+    private int mUid;
+    private int mCoarseLocationPermission;
+    private int mAllowCoarseLocationApps;
+    private int mFineLocationPermission;
+    private int mAllowFineLocationApps;
+    private int mNetworkSettingsPermission;
+    private int mCurrentUid;
+    private boolean mIsLocationEnabled;
+    private boolean mThrowSecurityException;
+    private Answer<Integer> mReturnPermission;
+    private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+    private LocationPermissionChecker mChecker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initTestVars();
+    }
+
+    private void setupMocks() throws Exception {
+        when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
+                .thenReturn(mMockApplInfo);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+        when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
+                TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
+                eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+                .thenReturn(mAllowCoarseLocationApps);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
+                eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+                .thenReturn(mAllowFineLocationApps);
+        if (mThrowSecurityException) {
+            doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
+                    + " to application bound to user " + mUid))
+                    .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
+        }
+        mockSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class, mMockAppOps);
+        mockSystemService(Context.USER_SERVICE, UserManager.class, mMockUserManager);
+        mockSystemService(Context.LOCATION_SERVICE, LocationManager.class, mLocationManager);
+    }
+
+    private <T> void mockSystemService(String name, Class<T> clazz, T service) {
+        when(mMockContext.getSystemService(name)).thenReturn(service);
+        when(mMockContext.getSystemServiceName(clazz)).thenReturn(name);
+        // Do not use mockito extended final method mocking
+        when(mMockContext.getSystemService(clazz)).thenCallRealMethod();
+    }
+
+    private void setupTestCase() throws Exception {
+        setupMocks();
+        setupMockInterface();
+        mChecker = new LocationPermissionChecker(mMockContext) {
+            @Override
+            protected int getCurrentUser() {
+                // Get the user ID of the process running the test rather than the foreground user
+                // id: ActivityManager.getCurrentUser() requires privileged permissions.
+                return UserHandle.getUserHandleForUid(Process.myUid()).getIdentifier();
+            }
+        };
+    }
+
+    private void initTestVars() {
+        mPermissionsList.clear();
+        mReturnPermission = createPermissionAnswer();
+        mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+        mUid = OTHER_USER_UID;
+        mThrowSecurityException = true;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+        mIsLocationEnabled = false;
+        mCurrentUid = Process.myUid();
+        mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+        mFineLocationPermission = PackageManager.PERMISSION_DENIED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+        mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED;
+    }
+
+    private void setupMockInterface() {
+        Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
+        doAnswer(mReturnPermission).when(mMockContext).checkPermission(
+                anyString(), anyInt(), anyInt());
+        when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
+                UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
+                .thenReturn(true);
+        when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
+                .thenReturn(mCoarseLocationPermission);
+        when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
+                .thenReturn(mFineLocationPermission);
+        when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid))
+                .thenReturn(mNetworkSettingsPermission);
+        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
+    }
+
+    private Answer<Integer> createPermissionAnswer() {
+        return new Answer<Integer>() {
+            @Override
+            public Integer answer(InvocationOnMock invocation) {
+                int myUid = (int) invocation.getArguments()[1];
+                String myPermission = (String) invocation.getArguments()[0];
+                mPermissionsList.get(myPermission);
+                if (mPermissionsList.containsKey(myPermission)) {
+                    int uid = mPermissionsList.get(myPermission);
+                    if (myUid == uid) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        };
+    }
+
+    @Test
+    public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
+        mIsLocationEnabled = true;
+        mThrowSecurityException = false;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mUid = mCurrentUid;
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+    }
+
+    @Test
+    public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        mIsLocationEnabled = true;
+        mThrowSecurityException = false;
+        mUid = mCurrentUid;
+        mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+    }
+
+    @Test
+    public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
+        mThrowSecurityException = true;
+        mIsLocationEnabled = true;
+        mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+
+        assertThrows(SecurityException.class,
+                () -> mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
+        mThrowSecurityException = false;
+        mIsLocationEnabled = true;
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        mIsLocationEnabled = true;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+        mUid = MANAGED_PROFILE_UID;
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
+        verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mIsLocationEnabled = false;
+
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result);
+    }
+
+    @Test
+    public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings()
+            throws Exception {
+        mThrowSecurityException = false;
+        mIsLocationEnabled = false;
+        mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED;
+        setupTestCase();
+
+        final int result =
+                mChecker.checkLocationPermissionWithDetailInfo(
+                        TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+        assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+    }
+
+
+    private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
+        try {
+            r.run();
+            Assert.fail("Expected " + exceptionClass + " to be thrown.");
+        } catch (Exception exception) {
+            assertTrue(exceptionClass.isInstance(exception));
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java
new file mode 100644
index 0000000..2550756
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class MacAddressUtilsTest {
+
+    // Matches WifiInfo.DEFAULT_MAC_ADDRESS
+    private static final MacAddress DEFAULT_MAC_ADDRESS =
+            MacAddress.fromString("02:00:00:00:00:00");
+
+    @Test
+    public void testIsMulticastAddress() {
+        MacAddress[] multicastAddresses = {
+            // broadcast address
+            MacAddress.fromString("ff:ff:ff:ff:ff:ff"),
+            MacAddress.fromString("07:00:d3:56:8a:c4"),
+            MacAddress.fromString("33:33:aa:bb:cc:dd"),
+        };
+        MacAddress[] unicastAddresses = {
+            // all zero address
+            MacAddress.fromString("00:00:00:00:00:00"),
+            MacAddress.fromString("00:01:44:55:66:77"),
+            MacAddress.fromString("08:00:22:33:44:55"),
+            MacAddress.fromString("06:00:00:00:00:00"),
+        };
+
+        for (MacAddress mac : multicastAddresses) {
+            String msg = mac.toString() + " expected to be a multicast address";
+            assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
+        }
+        for (MacAddress mac : unicastAddresses) {
+            String msg = mac.toString() + " expected not to be a multicast address";
+            assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
+        }
+    }
+
+    @Test
+    public void testMacAddressRandomGeneration() {
+        final int iterations = 1000;
+
+        for (int i = 0; i < iterations; i++) {
+            MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
+            String stringRepr = mac.toString();
+
+            assertTrue(stringRepr + " expected to be a locally assigned address",
+                    mac.isLocallyAssigned());
+            assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
+            assertFalse(mac.equals(DEFAULT_MAC_ADDRESS));
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java
new file mode 100644
index 0000000..9e635c2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 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.net.module.util;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public final class NetUtilsTest {
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+    private static final InetAddress V4_ADDR1 = toInetAddress("75.208.7.1");
+    private static final InetAddress V4_ADDR2 = toInetAddress("75.208.7.2");
+    private static final InetAddress V6_ADDR1 = toInetAddress("2001:0db8:85a3::8a2e:0370:7334");
+    private static final InetAddress V6_ADDR2 = toInetAddress("2001:0db8:85a3::8a2e:0370:7335");
+
+    private static final InetAddress V4_GATEWAY = toInetAddress("75.208.8.1");
+    private static final InetAddress V6_GATEWAY = toInetAddress("fe80::6:0000:613");
+
+    private static final InetAddress V4_DEST = toInetAddress("75.208.8.15");
+    private static final InetAddress V6_DEST = toInetAddress("2001:db8:cafe::123");
+
+    private static final RouteInfo V4_EXPECTED = new RouteInfo(new IpPrefix("75.208.8.0/24"),
+            V4_GATEWAY, "wlan0");
+    private static final RouteInfo V6_EXPECTED = new RouteInfo(new IpPrefix("2001:db8:cafe::/64"),
+            V6_GATEWAY, "wlan0");
+
+    private static InetAddress toInetAddress(String addr) {
+        return InetAddresses.parseNumericAddress(addr);
+    }
+
+    @Test
+    public void testAddressTypeMatches() {
+        assertTrue(NetUtils.addressTypeMatches(V4_ADDR1, V4_ADDR2));
+        assertTrue(NetUtils.addressTypeMatches(V6_ADDR1, V6_ADDR2));
+        assertFalse(NetUtils.addressTypeMatches(V4_ADDR1, V6_ADDR1));
+        assertFalse(NetUtils.addressTypeMatches(V6_ADDR1, V4_ADDR1));
+    }
+
+    @Test
+    public void testSelectBestRoute() {
+        final List<RouteInfo> routes = new ArrayList<>();
+
+        RouteInfo route = NetUtils.selectBestRoute(null, V4_DEST);
+        assertNull(route);
+        route = NetUtils.selectBestRoute(routes, null);
+        assertNull(route);
+
+        route = NetUtils.selectBestRoute(routes, V4_DEST);
+        assertNull(route);
+
+        routes.add(V4_EXPECTED);
+        // "75.208.0.0/16" is not an expected result since it is not the longest prefix.
+        routes.add(new RouteInfo(new IpPrefix("75.208.0.0/16"), V4_GATEWAY, "wlan0"));
+        routes.add(new RouteInfo(new IpPrefix("75.208.7.0/24"), V4_GATEWAY, "wlan0"));
+
+        routes.add(V6_EXPECTED);
+        // "2001:db8::/32" is not an expected result since it is not the longest prefix.
+        routes.add(new RouteInfo(new IpPrefix("2001:db8::/32"), V6_GATEWAY, "wlan0"));
+        routes.add(new RouteInfo(new IpPrefix("2001:db8:beef::/64"), V6_GATEWAY, "wlan0"));
+
+        // Verify expected v4 route is selected
+        route = NetUtils.selectBestRoute(routes, V4_DEST);
+        assertEquals(V4_EXPECTED, route);
+
+        // Verify expected v6 route is selected
+        route = NetUtils.selectBestRoute(routes, V6_DEST);
+        assertEquals(V6_EXPECTED, route);
+
+        // Remove expected v4 route
+        routes.remove(V4_EXPECTED);
+        route = NetUtils.selectBestRoute(routes, V4_DEST);
+        assertNotEquals(V4_EXPECTED, route);
+
+        // Remove expected v6 route
+        routes.remove(V6_EXPECTED);
+        route = NetUtils.selectBestRoute(routes, V4_DEST);
+        assertNotEquals(V6_EXPECTED, route);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSelectBestRouteWithExcludedRoutes() {
+        final List<RouteInfo> routes = new ArrayList<>();
+
+        routes.add(V4_EXPECTED);
+        routes.add(new RouteInfo(new IpPrefix("75.208.0.0/16"), V4_GATEWAY, "wlan0"));
+        routes.add(new RouteInfo(new IpPrefix("75.208.7.0/24"), V4_GATEWAY, "wlan0"));
+
+        routes.add(V6_EXPECTED);
+        routes.add(new RouteInfo(new IpPrefix("2001:db8::/32"), V6_GATEWAY, "wlan0"));
+        routes.add(new RouteInfo(new IpPrefix("2001:db8:beef::/64"), V6_GATEWAY, "wlan0"));
+
+        // After adding excluded v4 route with longer prefix, expected result is null.
+        routes.add(new RouteInfo(new IpPrefix("75.208.8.0/28"), null /* gateway */, "wlan0",
+                RouteInfo.RTN_THROW));
+        RouteInfo route = NetUtils.selectBestRoute(routes, V4_DEST);
+        assertNull(route);
+
+        // After adding excluded v6 route with longer prefix, expected result is null.
+        routes.add(new RouteInfo(new IpPrefix("2001:db8:cafe::/96"), null /* gateway */, "wlan0",
+                RouteInfo.RTN_THROW));
+        route = NetUtils.selectBestRoute(routes, V6_DEST);
+        assertNull(route);
+    }
+}
+
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
new file mode 100644
index 0000000..958f45f
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 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.net.module.util
+
+import android.annotation.TargetApi
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_BIP
+import android.net.NetworkCapabilities.NET_CAPABILITY_CBS
+import android.net.NetworkCapabilities.NET_CAPABILITY_EIMS
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
+import android.os.Build
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
+import com.android.net.module.util.NetworkCapabilitiesUtils.RESTRICTED_CAPABILITIES
+import com.android.net.module.util.NetworkCapabilitiesUtils.UNRESTRICTED_CAPABILITIES
+import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.IllegalArgumentException
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkCapabilitiesUtilsTest {
+
+    @Test
+    fun testGetAccountingTransport() {
+        assertEquals(TRANSPORT_WIFI, getDisplayTransport(intArrayOf(TRANSPORT_WIFI)))
+        assertEquals(TRANSPORT_CELLULAR, getDisplayTransport(intArrayOf(TRANSPORT_CELLULAR)))
+        assertEquals(TRANSPORT_BLUETOOTH, getDisplayTransport(intArrayOf(TRANSPORT_BLUETOOTH)))
+        assertEquals(TRANSPORT_ETHERNET, getDisplayTransport(intArrayOf(TRANSPORT_ETHERNET)))
+        assertEquals(TRANSPORT_WIFI_AWARE, getDisplayTransport(intArrayOf(TRANSPORT_WIFI_AWARE)))
+
+        assertEquals(TRANSPORT_VPN, getDisplayTransport(
+                intArrayOf(TRANSPORT_VPN, TRANSPORT_WIFI)))
+        assertEquals(TRANSPORT_VPN, getDisplayTransport(
+                intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_VPN)))
+
+        assertEquals(TRANSPORT_WIFI, getDisplayTransport(
+                intArrayOf(TRANSPORT_ETHERNET, TRANSPORT_WIFI)))
+        assertEquals(TRANSPORT_ETHERNET, getDisplayTransport(
+                intArrayOf(TRANSPORT_ETHERNET, TRANSPORT_TEST)))
+
+        assertFailsWith(IllegalArgumentException::class) {
+            getDisplayTransport(intArrayOf())
+        }
+    }
+
+    // NetworkCapabilities constructor and Builder are not available until R. Mark TargetApi to
+    // ignore the linter error since it's used in only unit test.
+    @Test @TargetApi(Build.VERSION_CODES.R)
+    fun testInferRestrictedCapability() {
+        val nc = NetworkCapabilities()
+        // Default capabilities don't have restricted capability.
+        assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+        // If there is a force restricted capability, then the network capabilities is restricted.
+        nc.addCapability(NET_CAPABILITY_OEM_PAID)
+        nc.addCapability(NET_CAPABILITY_INTERNET)
+        assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+        // Except for the force restricted capability, if there is any unrestricted capability in
+        // capabilities, then the network capabilities is not restricted.
+        nc.removeCapability(NET_CAPABILITY_OEM_PAID)
+        nc.addCapability(NET_CAPABILITY_CBS)
+        assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+        // Except for the force restricted capability, the network capabilities will only be treated
+        // as restricted when there is no any unrestricted capability.
+        nc.removeCapability(NET_CAPABILITY_INTERNET)
+        assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+        if (!SdkLevel.isAtLeastS()) return
+        // BIP deserves its specific test because it's the first capability over 30, meaning the
+        // shift will overflow
+        nc.removeCapability(NET_CAPABILITY_CBS)
+        nc.addCapability(NET_CAPABILITY_BIP)
+        assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+    }
+
+    @Test
+    fun testRestrictedUnrestrictedCapabilities() {
+        // verify EIMS is restricted
+        assertEquals((1 shl NET_CAPABILITY_EIMS).toLong() and RESTRICTED_CAPABILITIES,
+                (1 shl NET_CAPABILITY_EIMS).toLong())
+
+        // verify CBS is also restricted
+        assertEquals((1 shl NET_CAPABILITY_CBS).toLong() and RESTRICTED_CAPABILITIES,
+                (1 shl NET_CAPABILITY_CBS).toLong())
+
+        // verify BIP is also restricted
+        // BIP is not available in R and before, but the BIP constant is inlined so
+        // this test can still run on R.
+        assertEquals((1L shl NET_CAPABILITY_BIP) and RESTRICTED_CAPABILITIES,
+                (1L shl NET_CAPABILITY_BIP))
+
+        // verify default is not restricted
+        assertEquals((1 shl NET_CAPABILITY_INTERNET).toLong() and RESTRICTED_CAPABILITIES, 0)
+
+        assertTrue(RESTRICTED_CAPABILITIES > 0)
+
+        // just to see
+        assertEquals(RESTRICTED_CAPABILITIES and UNRESTRICTED_CAPABILITIES, 0)
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt
new file mode 100644
index 0000000..2904e12
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberId
+import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberIds
+import com.android.testutils.assertContainsStringsExactly
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkIdentityUtilsTest {
+    @Test
+    fun testScrubSubscriberId() {
+        assertEquals("123456...", scrubSubscriberId("1234567890123"))
+        assertEquals("123456...", scrubSubscriberId("1234567"))
+        assertEquals("123...", scrubSubscriberId("123"))
+        assertEquals("...", scrubSubscriberId(""))
+        assertEquals("null", scrubSubscriberId(null))
+    }
+
+    @Test
+    fun testScrubSubscriberIds() {
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf("1234567", "", null))!!,
+                "123456...", "...", "null")
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf("12345"))!!, "12345...")
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf())!!)
+        assertNull(scrubSubscriberIds(null))
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
new file mode 100644
index 0000000..2785ea9
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import android.net.NetworkStats
+import android.text.TextUtils
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkStatsUtilsTest {
+    @Test
+    fun testMultiplySafeByRational() {
+        // Verify basic cases that the method equals to a * b / c.
+        assertEquals(3 * 5 / 2, NetworkStatsUtils.multiplySafeByRational(3, 5, 2))
+
+        // Verify input with zeros.
+        assertEquals(0 * 7 / 3, NetworkStatsUtils.multiplySafeByRational(0, 7, 3))
+        assertEquals(7 * 0 / 3, NetworkStatsUtils.multiplySafeByRational(7, 0, 3))
+        assertEquals(0 * 0 / 1, NetworkStatsUtils.multiplySafeByRational(0, 0, 1))
+        assertEquals(0, NetworkStatsUtils.multiplySafeByRational(0, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertEquals(0, NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, 0, Long.MAX_VALUE))
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(7, 3, 0)
+        }
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(0, 0, 0)
+        }
+
+        // Verify cases where a * b overflows.
+        assertEquals(101, NetworkStatsUtils.multiplySafeByRational(
+                101, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertEquals(721, NetworkStatsUtils.multiplySafeByRational(
+                Long.MAX_VALUE, 721, Long.MAX_VALUE))
+        assertEquals(Long.MAX_VALUE, NetworkStatsUtils.multiplySafeByRational(
+                Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, Long.MAX_VALUE, 0)
+        }
+    }
+
+    @Test
+    fun testConstrain() {
+        assertFailsWith<IllegalArgumentException> {
+            NetworkStatsUtils.constrain(5, 6, 3) // low > high
+        }
+        assertEquals(3, NetworkStatsUtils.constrain(5, 1, 3))
+        assertEquals(3, NetworkStatsUtils.constrain(3, 1, 3))
+        assertEquals(2, NetworkStatsUtils.constrain(2, 1, 3))
+        assertEquals(1, NetworkStatsUtils.constrain(1, 1, 3))
+        assertEquals(1, NetworkStatsUtils.constrain(0, 1, 3))
+
+        assertEquals(11, NetworkStatsUtils.constrain(15, 11, 11))
+        assertEquals(11, NetworkStatsUtils.constrain(11, 11, 11))
+        assertEquals(11, NetworkStatsUtils.constrain(1, 11, 11))
+    }
+
+    @Test
+    fun testBucketToEntry() {
+        val bucket = makeMockBucket(android.app.usage.NetworkStats.Bucket.UID_ALL,
+                android.app.usage.NetworkStats.Bucket.TAG_NONE,
+                android.app.usage.NetworkStats.Bucket.STATE_DEFAULT,
+                android.app.usage.NetworkStats.Bucket.METERED_YES,
+                android.app.usage.NetworkStats.Bucket.ROAMING_NO,
+                android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12)
+        val entry = NetworkStatsUtils.fromBucket(bucket)
+        val expectedEntry = NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL,
+            NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE, NetworkStats.METERED_YES,
+            NetworkStats.ROAMING_NO, NetworkStats.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12,
+            0 /* operations */)
+
+        // TODO: Use assertEquals once all downstreams accept null iface in
+        // NetworkStats.Entry#equals.
+        assertEntryEquals(expectedEntry, entry)
+    }
+
+    private fun makeMockBucket(
+        uid: Int,
+        tag: Int,
+        state: Int,
+        metered: Int,
+        roaming: Int,
+        defaultNetwork: Int,
+        rxBytes: Long,
+        rxPackets: Long,
+        txBytes: Long,
+        txPackets: Long
+    ): android.app.usage.NetworkStats.Bucket {
+        val ret: android.app.usage.NetworkStats.Bucket =
+                mock(android.app.usage.NetworkStats.Bucket::class.java)
+        doReturn(uid).`when`(ret).getUid()
+        doReturn(tag).`when`(ret).getTag()
+        doReturn(state).`when`(ret).getState()
+        doReturn(metered).`when`(ret).getMetered()
+        doReturn(roaming).`when`(ret).getRoaming()
+        doReturn(defaultNetwork).`when`(ret).getDefaultNetworkStatus()
+        doReturn(rxBytes).`when`(ret).getRxBytes()
+        doReturn(rxPackets).`when`(ret).getRxPackets()
+        doReturn(txBytes).`when`(ret).getTxBytes()
+        doReturn(txPackets).`when`(ret).getTxPackets()
+        return ret
+    }
+
+    /**
+     * Assert that the two {@link NetworkStats.Entry} are equals.
+     */
+    private fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) {
+        TextUtils.equals(left.iface, right.iface)
+        assertEquals(left.uid, right.uid)
+        assertEquals(left.set, right.set)
+        assertEquals(left.tag, right.tag)
+        assertEquals(left.metered, right.metered)
+        assertEquals(left.roaming, right.roaming)
+        assertEquals(left.defaultNetwork, right.defaultNetwork)
+        assertEquals(left.rxBytes, right.rxBytes)
+        assertEquals(left.rxPackets, right.rxPackets)
+        assertEquals(left.txBytes, right.txBytes)
+        assertEquals(left.txPackets, right.txPackets)
+        assertEquals(left.operations, right.operations)
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
new file mode 100644
index 0000000..e40cd6b
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -0,0 +1,935 @@
+/*
+ * Copyright (C) 2021 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PacketBuilderTest {
+    private static final MacAddress SRC_MAC = MacAddress.fromString("11:22:33:44:55:66");
+    private static final MacAddress DST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final Inet4Address IPV4_SRC_ADDR = addr4("192.0.2.1");
+    private static final Inet4Address IPV4_DST_ADDR = addr4("198.51.100.1");
+    private static final Inet6Address IPV6_SRC_ADDR = addr6("2001:db8::1");
+    private static final Inet6Address IPV6_DST_ADDR = addr6("2001:db8::2");
+    private static final short SRC_PORT = 9876;
+    private static final short DST_PORT = 433;
+    private static final short SEQ_NO = 13579;
+    private static final short ACK_NO = 24680;
+    private static final byte TYPE_OF_SERVICE = 0;
+    private static final short ID = 27149;
+    private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
+    private static final byte TIME_TO_LIVE = (byte) 0x40;
+    private static final short WINDOW = (short) 0x2000;
+    private static final short URGENT_POINTER = 0;
+    // version=6, traffic class=0x80, flowlabel=0x515ca;
+    private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x680515ca;
+    private static final short HOP_LIMIT = 0x40;
+    private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] {
+            (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+    });
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                 type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                 type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433))
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                     type='IPv6') /
+                //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.UDP(sport=9876, dport=433))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x86, (byte) 0xdd,
+                // IP header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv6') /
+                //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x86, (byte) 0xdd,
+                // IP header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv6') /
+                //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x86, (byte) 0xdd,
+                // IPv6 header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv6') /
+                //           scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x86, (byte) 0xdd,
+                // IPv6 header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // IPv6 header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // IPv6 header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.UDP(sport=9876, dport=433))
+                // IP header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+            };
+
+    private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+                //                      fl=0x515ca, hlim=0x40) /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // IP header
+                (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    /**
+     * Build a packet which has ether header, IP header, TCP/UDP header and data.
+     * The ethernet header and data are optional. Note that both source mac address and
+     * destination mac address are required for ethernet header.
+     *
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                Layer 2 header (EthernetHeader)                | (optional)
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |           Layer 3 header (Ipv4Header, Ipv6Header)             |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |           Layer 4 header (TcpHeader, UdpHeader)               |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                          Payload                              | (optional)
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *
+     * @param srcMac source MAC address. used by L2 ether header.
+     * @param dstMac destination MAC address. used by L2 ether header.
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+     *        currently supported.
+     * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+     *        currently supported.
+     * @param payload the payload.
+     */
+    @NonNull
+    private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+            @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
+            @Nullable final ByteBuffer payload)
+            throws Exception {
+        if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+            fail("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            fail("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        final boolean hasEther = (srcMac != null && dstMac != null);
+        final int payloadLen = (payload == null) ? 0 : payload.limit();
+        final ByteBuffer buffer = PacketBuilder.allocate(hasEther, l3proto, l4proto,
+                payloadLen);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+        // [1] Build ether header.
+        if (hasEther) {
+            final int etherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+            packetBuilder.writeL2Header(srcMac, dstMac, (short) etherType);
+        }
+
+        // [2] Build IP header.
+        if (l3proto == IPPROTO_IP) {
+            packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                    TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+        } else if (l3proto == IPPROTO_IPV6) {
+            packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL,
+                    (byte) l4proto, HOP_LIMIT, IPV6_SRC_ADDR, IPV6_DST_ADDR);
+        }
+
+        // [3] Build TCP or UDP header.
+        if (l4proto == IPPROTO_TCP) {
+            packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                    TCPHDR_ACK, WINDOW, URGENT_POINTER);
+        } else if (l4proto == IPPROTO_UDP) {
+            packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT);
+        }
+
+        // [4] Build payload.
+        if (payload != null) {
+            buffer.put(payload);
+            // in case data might be reused by caller, restore the position and
+            // limit of bytebuffer.
+            payload.clear();
+        }
+
+        return packetBuilder.finalizePacket();
+    }
+
+    /**
+     * Check ethernet header.
+     *
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+     *        currently supported.
+     * @param actual the packet to check.
+     */
+    private void checkEtherHeader(final int l3proto, final ByteBuffer actual) {
+        if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+            fail("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual);
+        assertEquals(SRC_MAC, eth.srcMac);
+        assertEquals(DST_MAC, eth.dstMac);
+        final int expectedEtherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+        assertEquals(expectedEtherType, eth.etherType);
+    }
+
+    /**
+     * Check IPv4 header.
+     *
+     * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+     *        currently supported.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkIpv4Header(final int l4proto, final boolean hasData,
+            final ByteBuffer actual) {
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            fail("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, actual);
+        assertEquals(Ipv4Header.IPHDR_VERSION_IHL, ipv4Header.vi);
+        assertEquals(TYPE_OF_SERVICE, ipv4Header.tos);
+        assertEquals(ID, ipv4Header.id);
+        assertEquals(FLAGS_AND_FRAGMENT_OFFSET, ipv4Header.flagsAndFragmentOffset);
+        assertEquals(TIME_TO_LIVE, ipv4Header.ttl);
+        assertEquals(IPV4_SRC_ADDR, ipv4Header.srcIp);
+        assertEquals(IPV4_DST_ADDR, ipv4Header.dstIp);
+
+        final int dataLength = hasData ? DATA.limit() : 0;
+        if (l4proto == IPPROTO_TCP) {
+            assertEquals(IPV4_HEADER_MIN_LEN + TCP_HEADER_MIN_LEN + dataLength,
+                    ipv4Header.totalLength);
+            assertEquals((byte) IPPROTO_TCP, ipv4Header.protocol);
+            assertEquals(hasData ? (short) 0xe488 : (short) 0xe48c, ipv4Header.checksum);
+        } else if (l4proto == IPPROTO_UDP) {
+            assertEquals(IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + dataLength,
+                    ipv4Header.totalLength);
+            assertEquals((byte) IPPROTO_UDP, ipv4Header.protocol);
+            assertEquals(hasData ? (short) 0xe489 : (short) 0xe48d, ipv4Header.checksum);
+        }
+    }
+
+    /**
+     * Check IPv6 header.
+     *
+     * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+     *        currently supported.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkIpv6Header(final int l4proto, final boolean hasData,
+            final ByteBuffer actual) {
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            fail("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, actual);
+
+        assertEquals(VERSION_TRAFFICCLASS_FLOWLABEL, ipv6Header.vtf);
+        assertEquals(HOP_LIMIT, ipv6Header.hopLimit);
+        assertEquals(IPV6_SRC_ADDR, ipv6Header.srcIp);
+        assertEquals(IPV6_DST_ADDR, ipv6Header.dstIp);
+
+        final int dataLength = hasData ? DATA.limit() : 0;
+        if (l4proto == IPPROTO_TCP) {
+            assertEquals(TCP_HEADER_MIN_LEN + dataLength, ipv6Header.payloadLength);
+            assertEquals((byte) IPPROTO_TCP, ipv6Header.nextHeader);
+        } else if (l4proto == IPPROTO_UDP) {
+            assertEquals(UDP_HEADER_LEN + dataLength, ipv6Header.payloadLength);
+            assertEquals((byte) IPPROTO_UDP, ipv6Header.nextHeader);
+        }
+    }
+
+    /**
+     * Check TCP packet.
+     *
+     * @param hasEther true if the packet has ether header; false otherwise.
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+     *        currently supported.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkTcpPacket(final boolean hasEther, final int l3proto, final boolean hasData,
+            final ByteBuffer actual) {
+        if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+            fail("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        // [1] Check ether header.
+        if (hasEther) {
+            checkEtherHeader(l3proto, actual);
+        }
+
+        // [2] Check IP header.
+        if (l3proto == IPPROTO_IP) {
+            checkIpv4Header(IPPROTO_TCP, hasData, actual);
+        } else if (l3proto == IPPROTO_IPV6) {
+            checkIpv6Header(IPPROTO_TCP, hasData, actual);
+        }
+
+        // [3] Check TCP header.
+        final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, actual);
+        assertEquals(SRC_PORT, tcpHeader.srcPort);
+        assertEquals(DST_PORT, tcpHeader.dstPort);
+        assertEquals(SEQ_NO, tcpHeader.seq);
+        assertEquals(ACK_NO, tcpHeader.ack);
+        assertEquals((short) 0x5010 /* offset=5(*4bytes), control bits=ACK */,
+                tcpHeader.dataOffsetAndControlBits);
+        assertEquals(WINDOW, tcpHeader.window);
+        assertEquals(URGENT_POINTER, tcpHeader.urgentPointer);
+        if (l3proto == IPPROTO_IP) {
+            assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum);
+        } else if (l3proto == IPPROTO_IPV6) {
+            assertEquals(hasData ? (short) 0xd905 : (short) 0x76a7, tcpHeader.checksum);
+        }
+
+        // [4] Check payload.
+        if (hasData) {
+            assertEquals(0xdeadbeef, actual.getInt());
+        }
+    }
+
+    /**
+     * Check UDP packet.
+     *
+     * @param hasEther true if the packet has ether header; false otherwise.
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+     *        currently supported.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkUdpPacket(final boolean hasEther, final int l3proto, final boolean hasData,
+            final ByteBuffer actual) {
+        if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+            fail("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        // [1] Check ether header.
+        if (hasEther) {
+            checkEtherHeader(l3proto, actual);
+        }
+
+        // [2] Check IP header.
+        if (l3proto == IPPROTO_IP) {
+            checkIpv4Header(IPPROTO_UDP, hasData, actual);
+        } else if (l3proto == IPPROTO_IPV6) {
+            checkIpv6Header(IPPROTO_UDP, hasData, actual);
+        }
+
+        // [3] Check UDP header.
+        final UdpHeader udpHeader = Struct.parse(UdpHeader.class, actual);
+        assertEquals(SRC_PORT, udpHeader.srcPort);
+        assertEquals(DST_PORT, udpHeader.dstPort);
+        final int dataLength = hasData ? DATA.limit() : 0;
+        assertEquals(UDP_HEADER_LEN + dataLength, udpHeader.length);
+        if (l3proto == IPPROTO_IP) {
+            assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+        } else if (l3proto == IPPROTO_IPV6) {
+            assertEquals(hasData ? (short) 0xde7e : (short) 0x7c24, udpHeader.checksum);
+        }
+
+        // [4] Check payload.
+        if (hasData) {
+            assertEquals(0xdeadbeef, actual.getInt());
+        }
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4Tcp() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP,
+                null /* data */);
+        checkTcpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4TcpData() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP, DATA);
+        checkTcpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA,
+                packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4Tcp() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IP, IPPROTO_TCP, null /* data */);
+        checkTcpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4TcpData() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IP, IPPROTO_TCP, DATA);
+        checkTcpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4Udp() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP,
+                null /* data */);
+        checkUdpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4UdpData() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP, DATA);
+        checkUdpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4Udp() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IP, IPPROTO_UDP, null /*data*/);
+        checkUdpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4UdpData() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IP, IPPROTO_UDP, DATA);
+        checkUdpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv6TcpData() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP, DATA);
+        checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA,
+                packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv6Tcp() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP,
+                null /*data*/);
+        checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR,
+                packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv6TcpData() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6,
+                IPPROTO_TCP, DATA);
+        checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv6Tcp() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6,
+                IPPROTO_TCP, null /*data*/);
+        checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv6Udp() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+                null /* data */);
+        checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv6UdpData() throws Exception {
+        final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+                DATA);
+        checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv6Udp() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IPV6, IPPROTO_UDP, null /*data*/);
+        checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv6UdpData() throws Exception {
+        final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+                IPPROTO_IPV6, IPPROTO_UDP, DATA);
+        checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testFinalizePacketWithoutIpv4Header() throws Exception {
+        final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+                IPPROTO_TCP, 0 /* payloadLen */);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+        packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                TCPHDR_ACK, WINDOW, URGENT_POINTER);
+        assertThrows("java.io.IOException: Packet is missing IPv4 header", IOException.class,
+                () -> packetBuilder.finalizePacket());
+    }
+
+    @Test
+    public void testFinalizePacketWithoutL4Header() throws Exception {
+        final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+                IPPROTO_TCP, 0 /* payloadLen */);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+        packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+        assertThrows("java.io.IOException: Packet is missing neither TCP nor UDP header",
+                IOException.class, () -> packetBuilder.finalizePacket());
+    }
+
+    @Test
+    public void testWriteL2HeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeL2Header(SRC_MAC, DST_MAC, (short) ETHER_TYPE_IPV4));
+    }
+
+    @Test
+    public void testWriteIpv4HeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                        TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR));
+    }
+
+    @Test
+    public void testWriteTcpHeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                        TCPHDR_ACK, WINDOW, URGENT_POINTER));
+    }
+
+    @Test
+    public void testWriteUdpHeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT));
+    }
+
+    private static Inet4Address addr4(String addr) {
+        return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+    }
+
+    private static Inet6Address addr6(String addr) {
+        return (Inet6Address) InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java
new file mode 100644
index 0000000..459801c
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketReaderTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 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.net.module.util;
+
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import static com.android.net.module.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for PacketReader.
+ *
+ * @hide
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PacketReaderTest {
+    static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
+    static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
+
+    // TODO : reassigning the latch voids its synchronization properties, which means this
+    // scheme just doesn't work. Latches almost always need to be final to work.
+    protected CountDownLatch mLatch;
+    protected FileDescriptor mLocalSocket;
+    protected InetSocketAddress mLocalSockName;
+    protected byte[] mLastRecvBuf;
+    protected boolean mStopped;
+    protected HandlerThread mHandlerThread;
+    protected PacketReader mReceiver;
+
+    class UdpLoopbackReader extends PacketReader {
+        UdpLoopbackReader(Handler h) {
+            super(h);
+        }
+
+        @Override
+        protected FileDescriptor createFd() {
+            FileDescriptor s = null;
+            try {
+                s = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
+                Os.bind(s, LOOPBACK6, 0);
+                mLocalSockName = (InetSocketAddress) Os.getsockname(s);
+                Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
+            } catch (ErrnoException | SocketException e) {
+                closeFd(s);
+                throw new RuntimeException("Failed to create FD", e);
+            }
+
+            mLocalSocket = s;
+            return s;
+        }
+
+        @Override
+        protected void handlePacket(byte[] recvbuf, int length) {
+            mLastRecvBuf = Arrays.copyOf(recvbuf, length);
+            mLatch.countDown();
+        }
+
+        @Override
+        protected void onStart() {
+            mStopped = false;
+            mLatch.countDown();
+        }
+
+        @Override
+        protected void onStop() {
+            mStopped = true;
+            mLatch.countDown();
+        }
+    };
+
+    @Before
+    public void setUp() {
+        resetLatch();
+        mLocalSocket = null;
+        mLocalSockName = null;
+        mLastRecvBuf = null;
+        mStopped = false;
+
+        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
+        mHandlerThread.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mReceiver != null) {
+            mHandlerThread.getThreadHandler().post(() -> mReceiver.stop());
+            waitForActivity();
+        }
+        mReceiver = null;
+        mHandlerThread.quit();
+        mHandlerThread = null;
+    }
+
+    void resetLatch() {
+        mLatch = new CountDownLatch(1);
+    }
+
+    void waitForActivity() throws Exception {
+        try {
+            mLatch.await(1000, TimeUnit.MILLISECONDS);
+        } finally {
+            resetLatch();
+        }
+    }
+
+    void sendPacket(byte[] contents) throws Exception {
+        final DatagramSocket sender = new DatagramSocket();
+        sender.connect(mLocalSockName);
+        sender.send(new DatagramPacket(contents, contents.length));
+        sender.close();
+    }
+
+    @Test
+    public void testBasicWorking() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+        mReceiver = new UdpLoopbackReader(h);
+
+        h.post(() -> mReceiver.start());
+        waitForActivity();
+        assertTrue(mLocalSockName != null);
+        assertEquals(LOOPBACK6, mLocalSockName.getAddress());
+        assertTrue(0 < mLocalSockName.getPort());
+        assertTrue(mLocalSocket != null);
+        assertFalse(mStopped);
+
+        final byte[] one = "one 1".getBytes("UTF-8");
+        sendPacket(one);
+        waitForActivity();
+        assertEquals(1, mReceiver.numPacketsReceived());
+        assertTrue(Arrays.equals(one, mLastRecvBuf));
+        assertFalse(mStopped);
+
+        final byte[] two = "two 2".getBytes("UTF-8");
+        sendPacket(two);
+        waitForActivity();
+        assertEquals(2, mReceiver.numPacketsReceived());
+        assertTrue(Arrays.equals(two, mLastRecvBuf));
+        assertFalse(mStopped);
+
+        h.post(() -> mReceiver.stop());
+        waitForActivity();
+        assertEquals(2, mReceiver.numPacketsReceived());
+        assertTrue(Arrays.equals(two, mLastRecvBuf));
+        assertTrue(mStopped);
+        mReceiver = null;
+    }
+
+    class NullPacketReader extends PacketReader {
+        NullPacketReader(Handler h, int recvbufsize) {
+            super(h, recvbufsize);
+        }
+
+        @Override
+        public FileDescriptor createFd() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testMinimalRecvBufSize() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+
+        for (int i : new int[] { -1, 0, 1, DEFAULT_RECV_BUF_SIZE - 1 }) {
+            final PacketReader b = new NullPacketReader(h, i);
+            assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
+        }
+    }
+
+    @Test
+    public void testStartingFromWrongThread() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
+        assertThrows(IllegalStateException.class, () -> b.start());
+    }
+
+    @Test
+    public void testStoppingFromWrongThread() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
+        assertThrows(IllegalStateException.class, () -> b.stop());
+    }
+
+    @Test
+    public void testSuccessToCreateSocket() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+        final PacketReader b = new UdpLoopbackReader(h);
+        h.post(() -> assertTrue(b.start()));
+    }
+
+    @Test
+    public void testFailToCreateSocket() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
+        h.post(() -> assertFalse(b.start()));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt
new file mode 100644
index 0000000..321fe59
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.net.module.util
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PerUidCounterTest {
+    private val UID_A = 1000
+    private val UID_B = 1001
+    private val UID_C = 1002
+
+    @Test
+    fun testCounterMaximum() {
+        assertFailsWith<IllegalArgumentException> {
+            PerUidCounter(-1)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            PerUidCounter(0)
+        }
+
+        val testLimit = 1000
+        val testCounter = PerUidCounter(testLimit)
+        assertEquals(0, testCounter[UID_A])
+        repeat(testLimit) {
+            testCounter.incrementCountOrThrow(UID_A)
+        }
+        assertEquals(testLimit, testCounter[UID_A])
+        assertFailsWith<IllegalStateException> {
+            testCounter.incrementCountOrThrow(UID_A)
+        }
+        assertEquals(testLimit, testCounter[UID_A])
+    }
+
+    @Test
+    fun testIncrementCountOrThrow() {
+        val counter = PerUidCounter(3)
+
+        // Verify the counters work independently.
+        counter.incrementCountOrThrow(UID_A)
+        counter.incrementCountOrThrow(UID_B)
+        counter.incrementCountOrThrow(UID_B)
+        counter.incrementCountOrThrow(UID_A)
+        counter.incrementCountOrThrow(UID_A)
+        assertEquals(3, counter[UID_A])
+        assertEquals(2, counter[UID_B])
+        assertFailsWith<IllegalStateException> {
+            counter.incrementCountOrThrow(UID_A)
+        }
+        counter.incrementCountOrThrow(UID_B)
+        assertFailsWith<IllegalStateException> {
+            counter.incrementCountOrThrow(UID_B)
+        }
+
+        // Verify exception can be triggered again.
+        assertFailsWith<IllegalStateException> {
+            counter.incrementCountOrThrow(UID_A)
+        }
+        assertFailsWith<IllegalStateException> {
+            repeat(3) {
+                counter.incrementCountOrThrow(UID_A)
+            }
+        }
+        assertEquals(3, counter[UID_A])
+        assertEquals(3, counter[UID_B])
+        assertEquals(0, counter[UID_C])
+    }
+
+    @Test
+    fun testDecrementCountOrThrow() {
+        val counter = PerUidCounter(3)
+
+        // Verify the count cannot go below zero.
+        assertFailsWith<IllegalStateException> {
+            counter.decrementCountOrThrow(UID_A)
+        }
+        assertFailsWith<IllegalStateException> {
+            repeat(5) {
+                counter.decrementCountOrThrow(UID_A)
+            }
+        }
+
+        // Verify the counters work independently.
+        counter.incrementCountOrThrow(UID_A)
+        counter.incrementCountOrThrow(UID_B)
+        assertEquals(1, counter[UID_A])
+        assertEquals(1, counter[UID_B])
+        assertFailsWith<IllegalStateException> {
+            repeat(3) {
+                counter.decrementCountOrThrow(UID_A)
+            }
+        }
+        assertFailsWith<IllegalStateException> {
+            counter.decrementCountOrThrow(UID_A)
+        }
+        assertEquals(0, counter[UID_A])
+        assertEquals(1, counter[UID_B])
+
+        // Verify mixing increment and decrement.
+        val largeCounter = PerUidCounter(100)
+        repeat(90) {
+            largeCounter.incrementCountOrThrow(UID_A)
+        }
+        repeat(70) {
+            largeCounter.decrementCountOrThrow(UID_A)
+        }
+        repeat(80) {
+            largeCounter.incrementCountOrThrow(UID_A)
+        }
+        assertFailsWith<IllegalStateException> {
+            largeCounter.incrementCountOrThrow(UID_A)
+        }
+        assertEquals(100, largeCounter[UID_A])
+        repeat(100) {
+            largeCounter.decrementCountOrThrow(UID_A)
+        }
+        assertFailsWith<IllegalStateException> {
+            largeCounter.decrementCountOrThrow(UID_A)
+        }
+        assertEquals(0, largeCounter[UID_A])
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
new file mode 100644
index 0000000..028308b
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2021 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.net.module.util
+
+import android.Manifest.permission.INTERNET
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.Manifest.permission.NETWORK_STACK
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_DENIED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+import android.os.Build
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
+import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
+import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission
+import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr
+import com.android.net.module.util.PermissionUtils.enforceSystemFeature
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+/** Tests for PermissionUtils */
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+class PermissionUtilsTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
+    private val TEST_PERMISSION1 = "android.permission.TEST_PERMISSION1"
+    private val TEST_PERMISSION2 = "android.permission.TEST_PERMISSION2"
+    private val mockContext = mock(Context::class.java)
+    private val mockPackageManager = mock(PackageManager::class.java)
+
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+
+    @Before
+    fun setup() {
+        doReturn(mockPackageManager).`when`(mockContext).packageManager
+    }
+
+    @Test
+    fun testEnforceAnyPermissionOf() {
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION2)
+        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
+
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION2)
+        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
+
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
+        assertFalse(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        assertFailsWith<SecurityException>("Expect fail but permission granted.") {
+            enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
+        }
+    }
+
+    @Test
+    fun testEnforceNetworkStackPermissionOr() {
+        doReturn(PERMISSION_GRANTED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        enforceNetworkStackPermission(mockContext)
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1)
+
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        enforceNetworkStackPermission(mockContext)
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2)
+
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
+        assertFailsWith<SecurityException>("Expect fail but permission granted.") {
+            enforceNetworkStackPermission(mockContext)
+        }
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1)
+
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
+        assertFailsWith<SecurityException>("Expect fail but permission granted.") {
+            enforceNetworkStackPermission(mockContext)
+        }
+        assertFailsWith<SecurityException>("Expect fail but permission granted.") {
+            enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2)
+        }
+    }
+
+    private fun mockHasSystemFeature(featureName: String, hasFeature: Boolean) {
+        doReturn(hasFeature).`when`(mockPackageManager)
+            .hasSystemFeature(ArgumentMatchers.eq(featureName))
+    }
+
+    @Test
+    fun testEnforceSystemFeature() {
+        val systemFeature = "test.system.feature"
+        val exceptionMessage = "test exception message"
+        mockHasSystemFeature(featureName = systemFeature, hasFeature = false)
+        val e = assertFailsWith<UnsupportedOperationException>("Should fail without feature") {
+            enforceSystemFeature(mockContext, systemFeature, exceptionMessage)
+        }
+        assertEquals(exceptionMessage, e.message)
+
+        mockHasSystemFeature(featureName = systemFeature, hasFeature = true)
+        try {
+            enforceSystemFeature(mockContext, systemFeature, "")
+        } catch (e: UnsupportedOperationException) {
+            Assert.fail("Exception should have not been thrown with system feature enabled")
+        }
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    fun testIsSystemSignaturePermission() {
+        assertTrue(
+            PermissionUtils.isSystemSignaturePermission(
+                context,
+                NETWORK_SETTINGS
+            )
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, PERMISSION_MAINLINE_NETWORK_STACK)
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, "test_permission")
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, INTERNET)
+        )
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
new file mode 100644
index 0000000..aa1bfee
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.net.module.util;
+
+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 androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SharedLogTest {
+    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
+    private static final String TIMESTAMP = "HH:MM:SS";
+    private static final String TAG = "top";
+
+    @Test
+    public void testBasicOperation() {
+        final SharedLog logTop = new SharedLog(TAG);
+        assertTrue(TAG.equals(logTop.getTag()));
+
+        logTop.mark("first post!");
+
+        final SharedLog logLevel2a = logTop.forSubComponent("twoA");
+        final SharedLog logLevel2b = logTop.forSubComponent("twoB");
+        logLevel2b.e("2b or not 2b");
+        logLevel2b.e("No exception", null);
+        logLevel2b.e("Wait, here's one", new Exception("Test"));
+        logLevel2a.w("second post?");
+
+        final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
+        logTop.log("still logging");
+        logLevel2b.e(new Exception("Got another exception"));
+        logLevel3.i("3 >> 2");
+        logLevel2a.mark("ok: last post");
+        logTop.logf("finished!");
+
+        final String[] expected = {
+            " - MARK first post!",
+            " - [twoB] ERROR 2b or not 2b",
+            " - [twoB] ERROR No exception",
+            // No stacktrace in shared log, only in logcat
+            " - [twoB] ERROR Wait, here's one: Test",
+            " - [twoA] WARN second post?",
+            " - still logging",
+            " - [twoB] ERROR java.lang.Exception: Got another exception",
+            " - [twoA.three] 3 >> 2",
+            " - [twoA] MARK ok: last post",
+            " - finished!",
+        };
+        // Verify the logs are all there and in the correct order.
+        assertDumpLogs(expected, logTop);
+
+        // In fact, because they all share the same underlying LocalLog,
+        // every subcomponent SharedLog's dump() is identical.
+        assertDumpLogs(expected, logLevel2a);
+        assertDumpLogs(expected, logLevel2b);
+        assertDumpLogs(expected, logLevel3);
+    }
+
+    private static void assertDumpLogs(String[] expected, SharedLog log) {
+        verifyLogLines(expected, dump(log));
+        verifyLogLines(reverse(expected), reverseDump(log));
+    }
+
+    private static String dump(SharedLog log) {
+        return getSharedLogString(pw -> log.dump(null /* fd */, pw, null /* args */));
+    }
+
+    private static String reverseDump(SharedLog log) {
+        return getSharedLogString(pw -> log.reverseDump(pw));
+    }
+
+    private static String[] reverse(String[] ary) {
+        final List<String> ls = new ArrayList<>(Arrays.asList(ary));
+        Collections.reverse(ls);
+        return ls.toArray(new String[ary.length]);
+    }
+
+    private static String getSharedLogString(Consumer<PrintWriter> functor) {
+        final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+        final PrintWriter pw = new PrintWriter(ostream, true);
+        functor.accept(pw);
+
+        final String dumpOutput = ostream.toString();
+        assertNotNull(dumpOutput);
+        assertFalse("".equals(dumpOutput));
+        return dumpOutput;
+    }
+
+    private static void verifyLogLines(String[] expected, String gottenLogs) {
+        final String[] lines = gottenLogs.split("\n");
+        assertEquals(expected.length, lines.length);
+
+        for (int i = 0; i < expected.length; i++) {
+            String got = lines[i];
+            String want = expected[i];
+            assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
+            assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
+                    got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
new file mode 100644
index 0000000..b4da043
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -0,0 +1,1075 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.SuppressLint;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructTest {
+
+    // IPv6, 0 bytes of options, ifindex 15715755: 0x00efcdab, type 134 (RA), code 0, padding.
+    private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
+
+    // UBE16: 0xfeff, UBE32: 0xfeffffff, UBE64: 0xfeffffffffffffff, UBE63: 0x7effffffffffffff
+    private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "feffffffffffffff"
+            + "7effffffffffffff";
+
+    // S8: 0x7f, S16: 0x7fff, S32: 0x7fffffff, S64: 0x7fffffffffffffff
+    private static final String SIGNED_DATA = "7f" + "ff7f" + "ffffff7f" + "ffffffffffffff7f";
+
+    // nS8: 0x81, nS16: 0x8001, nS32: 0x80000001, nS64: 800000000000000001
+    private static final String SIGNED_NEGATIVE_DATA = "81" + "0180" + "01000080"
+            + "0100000000000080";
+
+    // U8: 0xff, U16: 0xffff, U32: 0xffffffff, U64: 0xffffffffffffffff, U63: 0x7fffffffffffffff,
+    // U63: 0xffffffffffffffff(-1L)
+    private static final String UNSIGNED_DATA = "ff" + "ffff" + "ffffffff" + "ffffffffffffffff"
+            + "ffffffffffffff7f" + "ffffffffffffffff";
+
+    // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064
+    private static final String OPT_PREF64 = "2750" + "20010db80003000400050006";
+    private static final byte[] TEST_PREFIX64 = new byte[]{
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, (byte) 0x00, (byte) 0x03,
+            (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x06,
+    };
+
+    private static final Inet4Address TEST_IPV4_ADDRESS =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.168.100.1");
+    private static final Inet6Address TEST_IPV6_ADDRESS =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:3:4:5:6:7:8");
+
+    private <T> T doParsingMessageTest(final String hexString, final Class<T> clazz,
+            final ByteOrder order) {
+        final ByteBuffer buf = toByteBuffer(hexString);
+        buf.order(order);
+        return Struct.parse(clazz, buf);
+    }
+
+    public static class HeaderMsgWithConstructor extends Struct {
+        static int sType;
+        static int sLength;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        public final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        public final int mLen;
+        @Field(order = 2, type = Type.S32)
+        public final int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        public final short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        public final short mIcmpCode;
+
+        HeaderMsgWithConstructor(final short family, final int len, final int ifindex,
+                final short type, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIfindex = ifindex;
+            mIcmpType = type;
+            mIcmpCode = code;
+        }
+    }
+
+    private void verifyHeaderParsing(final HeaderMsgWithConstructor msg) {
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+
+        assertEquals(16, Struct.getSize(HeaderMsgWithConstructor.class));
+        assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    @Test
+    public void testClassWithExplicitConstructor() {
+        final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
+        verifyHeaderParsing(msg);
+    }
+
+    public static class HeaderMsgWithoutConstructor extends Struct {
+        static int sType;
+        static int sLength;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        public short mFamily;
+        @Field(order = 1, type = Type.U16)
+        public int mLen;
+        @Field(order = 2, type = Type.S32)
+        public int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        public short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        public short mIcmpCode;
+    }
+
+    @Test
+    public void testClassWithDefaultConstructor() {
+        final HeaderMsgWithoutConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithoutConstructor.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+
+        assertEquals(16, Struct.getSize(HeaderMsgWithoutConstructor.class));
+        assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class HeaderMessage {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_NotSubClass() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf));
+    }
+
+    public static class HeaderMessageMissingAnnotation extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        int mIfindex;
+        @Field(order = 2, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 3, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_MissingAnnotationField() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageMissingAnnotation.class, buf));
+    }
+
+    public static class NetworkOrderMessage extends Struct {
+        @Field(order = 0, type = Type.UBE16)
+        public final int mUBE16;
+        @Field(order = 1, type = Type.UBE32)
+        public final long mUBE32;
+        @Field(order = 2, type = Type.UBE64)
+        public final BigInteger mUBE64;
+        @Field(order = 3, type = Type.UBE63)
+        public final long mUBE63;
+
+        NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
+                final long be63) {
+            mUBE16 = be16;
+            mUBE32 = be32;
+            mUBE64 = be64;
+            mUBE63 = be63;
+        }
+    }
+
+    @Test
+    public void testNetworkOrder() {
+        final NetworkOrderMessage msg = doParsingMessageTest(NETWORK_ORDER_MSG,
+                NetworkOrderMessage.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(65279, msg.mUBE16);
+        assertEquals(4278190079L, msg.mUBE32);
+        assertEquals(new BigInteger("18374686479671623679"), msg.mUBE64);
+        assertEquals(9151314442816847871L, msg.mUBE63);
+
+        assertEquals(22, Struct.getSize(NetworkOrderMessage.class));
+        assertArrayEquals(toByteBuffer(NETWORK_ORDER_MSG).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class UnsignedDataMessage extends Struct {
+        @Field(order = 0, type = Type.U8)
+        public final short mU8;
+        @Field(order = 1, type = Type.U16)
+        public final int mU16;
+        @Field(order = 2, type = Type.U32)
+        public final long mU32;
+        @Field(order = 3, type = Type.U64)
+        public final BigInteger mU64;
+        @Field(order = 4, type = Type.U63)
+        public final long mU63;
+        @Field(order = 5, type = Type.U63)
+        public final long mLU64; // represent U64 data with U63 type
+
+        UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
+                final long u63, final long lu64) {
+            mU8 = u8;
+            mU16 = u16;
+            mU32 = u32;
+            mU64 = u64;
+            mU63 = u63;
+            mLU64 = lu64;
+        }
+    }
+
+    @Test
+    public void testUnsignedData() {
+        final UnsignedDataMessage msg = doParsingMessageTest(UNSIGNED_DATA,
+                UnsignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(255, msg.mU8);
+        assertEquals(65535, msg.mU16);
+        assertEquals(4294967295L, msg.mU32);
+        assertEquals(new BigInteger("18446744073709551615"), msg.mU64);
+        assertEquals(9223372036854775807L, msg.mU63);
+        assertEquals(-1L, msg.mLU64);
+
+        assertEquals(31, Struct.getSize(UnsignedDataMessage.class));
+        assertArrayEquals(toByteBuffer(UNSIGNED_DATA).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class U64DataMessage extends Struct {
+        @Field(order = 0, type = Type.U64) long mU64;
+    }
+
+    @Test
+    public void testInvalidType_U64WithLongPrimitive() {
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(U64DataMessage.class, toByteBuffer("ffffffffffffffff")));
+    }
+
+    // BigInteger U64: 0x0000000000001234, BigInteger UBE64: 0x0000000000001234, BigInteger U64: 0
+    private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234"
+            + "0000000000000000";
+
+    public static class SmallValueBigInteger extends Struct {
+        @Field(order = 0, type = Type.U64) public final BigInteger mSmallValue;
+        @Field(order = 1, type = Type.UBE64) public final BigInteger mBSmallValue;
+        @Field(order = 2, type = Type.U64) public final BigInteger mZero;
+
+        SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue,
+                final BigInteger zero) {
+            mSmallValue = smallValue;
+            mBSmallValue = bSmallValue;
+            mZero = zero;
+        }
+    }
+
+    @Test
+    public void testBigIntegerSmallValueOrZero() {
+        final SmallValueBigInteger msg = doParsingMessageTest(SMALL_VALUE_BIGINTEGER,
+                SmallValueBigInteger.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(new BigInteger("4660"), msg.mSmallValue);
+        assertEquals(new BigInteger("4660"), msg.mBSmallValue);
+        assertEquals(new BigInteger("0"), msg.mZero);
+
+        assertEquals(24, Struct.getSize(SmallValueBigInteger.class));
+        assertArrayEquals(toByteBuffer(SMALL_VALUE_BIGINTEGER).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class SignedDataMessage extends Struct {
+        @Field(order = 0, type = Type.S8)
+        public final byte mS8;
+        @Field(order = 1, type = Type.S16)
+        public final short mS16;
+        @Field(order = 2, type = Type.S32)
+        public final int mS32;
+        @Field(order = 3, type = Type.S64)
+        public final long mS64;
+
+        SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) {
+            mS8 = s8;
+            mS16 = s16;
+            mS32 = s32;
+            mS64 = s64;
+        }
+    }
+
+    @Test
+    public void testSignedPositiveData() {
+        final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class,
+                ByteOrder.LITTLE_ENDIAN);
+        assertEquals(127, msg.mS8);
+        assertEquals(32767, msg.mS16);
+        assertEquals(2147483647, msg.mS32);
+        assertEquals(9223372036854775807L, msg.mS64);
+
+        assertEquals(15, Struct.getSize(SignedDataMessage.class));
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    @Test
+    public void testSignedNegativeData() {
+        final SignedDataMessage msg = doParsingMessageTest(SIGNED_NEGATIVE_DATA,
+                SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(-127, msg.mS8);
+        assertEquals(-32767, msg.mS16);
+        assertEquals(-2147483647, msg.mS32);
+        assertEquals(-9223372036854775807L, msg.mS64);
+
+        assertEquals(15, Struct.getSize(SignedDataMessage.class));
+        assertArrayEquals(toByteBuffer(SIGNED_NEGATIVE_DATA).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class HeaderMessageWithDuplicateOrder extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 2, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 3, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_DuplicateFieldOrder() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf));
+    }
+
+    public static class HeaderMessageWithNegativeOrder extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = -4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_NegativeFieldOrder() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf));
+    }
+
+    public static class HeaderMessageOutOfIndexBounds extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 5, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_OutOfIndexBounds() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf));
+    }
+
+    public static class HeaderMessageMismatchedPrimitiveType extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        short mLen; // should be integer
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_MismatchedPrimitiveDataType() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf));
+    }
+
+    public static class PrefixMessage extends Struct {
+        @Field(order = 0, type = Type.UBE16)
+        public final int mLifetime;
+        @Field(order = 1, type = Type.ByteArray, arraysize = 12)
+        public final byte[] mPrefix;
+
+        PrefixMessage(final int lifetime, final byte[] prefix) {
+            mLifetime = lifetime;
+            mPrefix = prefix;
+        }
+    }
+
+    @SuppressLint("NewApi")
+    private void verifyPrefixByteArrayParsing(final PrefixMessage msg) throws Exception {
+        // The original PREF64 option message has just 12 bytes for prefix byte array
+        // (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with
+        // prefix and 4-bytes zeros.
+        final InetAddress addr = InetAddress.getByAddress(Arrays.copyOf(msg.mPrefix, 16));
+        final IpPrefix prefix = new IpPrefix(addr, 96);
+        assertEquals(10064, msg.mLifetime);
+        assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96")));
+
+        assertEquals(14, Struct.getSize(PrefixMessage.class));
+        assertArrayEquals(toByteBuffer(OPT_PREF64).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class PrefixMessageWithZeroLengthArray extends Struct {
+        @Field(order = 0, type = Type.UBE16)
+        final int mLifetime;
+        @Field(order = 1, type = Type.ByteArray, arraysize = 0)
+        final byte[] mPrefix;
+
+        PrefixMessageWithZeroLengthArray(final int lifetime, final byte[] prefix) {
+            mLifetime = lifetime;
+            mPrefix = prefix;
+        }
+    }
+
+    @Test
+    public void testInvalidClass_ZeroLengthByteArray() {
+        final ByteBuffer buf = toByteBuffer(OPT_PREF64);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(PrefixMessageWithZeroLengthArray.class, buf));
+    }
+
+    @Test
+    public void testPrefixArrayField() throws Exception {
+        final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
+                ByteOrder.LITTLE_ENDIAN);
+        verifyPrefixByteArrayParsing(msg);
+    }
+
+    public static class HeaderMessageWithMutableField extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        final int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        final short mIcmpCode;
+
+        HeaderMessageWithMutableField(final short family, final int len, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIcmpCode = code;
+        }
+    }
+
+    @Test
+    public void testMixMutableAndImmutableFields() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithMutableField.class, buf));
+    }
+
+    public static class HeaderMsgWithStaticConstant extends Struct {
+        private static final String TAG = "HeaderMessage";
+        private static final int FIELD_COUNT = 5;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        public final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        public final int mLen;
+        @Field(order = 2, type = Type.S32)
+        public final int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        public final short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        public final short mIcmpCode;
+
+        HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex,
+                final short type, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIfindex = ifindex;
+            mIcmpType = type;
+            mIcmpCode = code;
+        }
+    }
+
+    @Test
+    public void testStaticConstantField() {
+        final HeaderMsgWithStaticConstant msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithStaticConstant.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+
+        assertEquals(16, Struct.getSize(HeaderMsgWithStaticConstant.class));
+        assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public static class MismatchedConstructor extends Struct {
+        @Field(order = 0, type = Type.U16) final int mInt1;
+        @Field(order = 1, type = Type.U16) final int mInt2;
+        MismatchedConstructor(String int1, String int2) {
+            mInt1 = Integer.valueOf(int1);
+            mInt2 = Integer.valueOf(int2);
+        }
+    }
+
+    @Test
+    public void testMisMatchedConstructor() {
+        final ByteBuffer buf = toByteBuffer("1234" + "5678");
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(MismatchedConstructor.class, buf));
+    }
+
+    public static class ClassWithTwoConstructors extends Struct {
+        @Field(order = 0, type = Type.U16) public final int mInt1;
+        @Field(order = 1, type = Type.U16) public final int mInt2;
+        ClassWithTwoConstructors(String int1, String int2) {
+            mInt1 = Integer.valueOf(int1);
+            mInt2 = Integer.valueOf(int2);
+        }
+        ClassWithTwoConstructors(int int1, int int2) {
+            mInt1 = int1;
+            mInt2 = int2;
+        }
+    }
+
+    @Test
+    public void testClassWithTwoConstructors() {
+        final ClassWithTwoConstructors msg = doParsingMessageTest("1234" + "5678",
+                ClassWithTwoConstructors.class, ByteOrder.LITTLE_ENDIAN);
+        assertEquals(13330 /* 0x3412 */, msg.mInt1);
+        assertEquals(30806 /* 0x7856 */, msg.mInt2);
+
+        assertEquals(4, Struct.getSize(ClassWithTwoConstructors.class));
+        assertArrayEquals(toByteBuffer("1234" + "5678").array(),
+                msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    @Test
+    public void testInvalidOutputByteBuffer_ZeroCapacity() {
+        final ByteBuffer output = ByteBuffer.allocate(0);
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
+        assertThrows(BufferOverflowException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testConsecutiveWrites() {
+        final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
+        final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
+                ByteOrder.LITTLE_ENDIAN);
+
+        int size = Struct.getSize(HeaderMsgWithConstructor.class)
+                + Struct.getSize(PrefixMessage.class);
+        final ByteBuffer output = ByteBuffer.allocate(size);
+        output.order(ByteOrder.LITTLE_ENDIAN);
+
+        msg1.writeToByteBuffer(output);
+        msg2.writeToByteBuffer(output);
+        output.flip();
+
+        final ByteBuffer concat = ByteBuffer.allocate(size).put(toByteBuffer(HDR_EMPTY))
+                .put(toByteBuffer(OPT_PREF64));
+        assertArrayEquals(output.array(), concat.array());
+    }
+
+    @Test
+    public void testClassesParsedFromCache() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
+                    HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
+            verifyHeaderParsing(msg1);
+
+            final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
+                    ByteOrder.LITTLE_ENDIAN);
+            verifyPrefixByteArrayParsing(msg2);
+        }
+    }
+
+    public static class BigEndianDataMessage extends Struct {
+        @Field(order = 0, type = Type.S32) public int mInt1;
+        @Field(order = 1, type = Type.S32) public int mInt2;
+        @Field(order = 2, type = Type.UBE16) public int mInt3;
+        @Field(order = 3, type = Type.U16) public int mInt4;
+        @Field(order = 4, type = Type.U64) public BigInteger mBigInteger1;
+        @Field(order = 5, type = Type.UBE64) public BigInteger mBigInteger2;
+        @Field(order = 6, type = Type.S64) public long mLong;
+    }
+
+    private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe"
+            + "ff00004500002301" + "ff00004500002301" + "ff00004500002301";
+
+    @Test
+    public void testBigEndianByteBuffer() {
+        final BigEndianDataMessage msg = doParsingMessageTest(BIG_ENDIAN_DATA,
+                BigEndianDataMessage.class, ByteOrder.BIG_ENDIAN);
+
+        assertEquals(1, msg.mInt1);
+        assertEquals(-2, msg.mInt2);
+        assertEquals(65534, msg.mInt3);
+        assertEquals(65534, msg.mInt4);
+        assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger1);
+        assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger2);
+        assertEquals(0xff00004500002301L, msg.mLong);
+
+        assertEquals(36, Struct.getSize(BigEndianDataMessage.class));
+        assertArrayEquals(toByteBuffer(BIG_ENDIAN_DATA).array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    public static class MacAddressMessage extends Struct {
+        public @Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
+        public @Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
+
+        MacAddressMessage(final MacAddress mac1, final MacAddress mac2) {
+            this.mMac1 = mac1;
+            this.mMac2 = mac2;
+        }
+    }
+
+    @Test
+    public void testMacAddressType() {
+        final MacAddressMessage msg = doParsingMessageTest("001122334455" + "ffffffffffff",
+                MacAddressMessage.class, ByteOrder.BIG_ENDIAN);
+
+        assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.mMac1);
+        assertEquals(MacAddress.fromString("ff:ff:ff:ff:ff:ff"), msg.mMac2);
+
+        assertEquals(12, Struct.getSize(MacAddressMessage.class));
+        assertArrayEquals(toByteBuffer("001122334455" + "ffffffffffff").array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+
+    public static class BadMacAddressType extends Struct {
+        @Field(order = 0, type = Type.EUI48) byte[] mMac;
+    }
+
+    @Test
+    public void testIncorrectType_EUI48WithByteArray() {
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(BadMacAddressType.class, toByteBuffer("ffffffffffff")));
+    }
+
+    @Test
+    public void testStructToByteArrayRoundTrip() {
+        final SignedDataMessage littleEndianMsg = doParsingMessageTest(SIGNED_DATA,
+                SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                littleEndianMsg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+
+        final SignedDataMessage bigEndianMsg = doParsingMessageTest(SIGNED_DATA,
+                SignedDataMessage.class, ByteOrder.BIG_ENDIAN);
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                bigEndianMsg.writeToBytes(ByteOrder.BIG_ENDIAN));
+
+        final SignedDataMessage nativeOrderMsg = ByteOrder.nativeOrder().equals(
+                ByteOrder.LITTLE_ENDIAN) ? littleEndianMsg : bigEndianMsg;
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                nativeOrderMsg.writeToBytes());
+    }
+
+    @Test
+    public void testStructToByteArray() {
+        final SignedDataMessage msg = new SignedDataMessage((byte) -5, (short) 42, (int) 0xff000004,
+                (long) 0xff000004ff000005L);
+        final String leHexString = "fb" + "2a00" + "040000ff" + "050000ff040000ff";
+        final String beHexString = "fb" + "002a" + "ff000004" + "ff000004ff000005";
+        final String hexString = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
+                ? leHexString : beHexString;
+        assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes());
+    }
+
+    public static class IpAddressMessage extends Struct {
+        @Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        IpAddressMessage(final Inet4Address ipv4Address, final Inet6Address ipv6Address) {
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    @Test
+    public void testIpAddressType() {
+        final IpAddressMessage msg = doParsingMessageTest("c0a86401"
+                + "20010db8000300040005000600070008", IpAddressMessage.class, ByteOrder.BIG_ENDIAN);
+
+        assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
+        assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
+
+        assertEquals(20, Struct.getSize(IpAddressMessage.class));
+        assertArrayEquals(toByteBuffer("c0a86401" + "20010db8000300040005000600070008").array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+
+    public static class WrongIpAddressType extends Struct {
+        @Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
+        @Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
+    }
+
+    @Test
+    public void testIncorrectType_IpAddressWithByteArray() {
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(WrongIpAddressType.class,
+                                   toByteBuffer("c0a86401" + "20010db8000300040005000600070008")));
+    }
+
+    public static class FullTypeMessage extends Struct {
+        @Field(order = 0, type = Type.U8) public final short u8;
+        @Field(order = 1, type = Type.U16) public final int u16;
+        @Field(order = 2, type = Type.U32) public final long u32;
+        @Field(order = 3, type = Type.U63) public final long u63;
+        @Field(order = 4, type = Type.U64) public final BigInteger u64;
+        @Field(order = 5, type = Type.S8) public final byte s8;
+        @Field(order = 6, type = Type.S16) public final short s16;
+        @Field(order = 7, type = Type.S32) public final int s32;
+        @Field(order = 8, type = Type.S64) public final long s64;
+        @Field(order = 9, type = Type.UBE16) public final int ube16;
+        @Field(order = 10, type = Type.UBE32) public final long ube32;
+        @Field(order = 11, type = Type.UBE63) public final long ube63;
+        @Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
+        @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
+        @Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
+        @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        FullTypeMessage(final short u8, final int u16, final long u32, final long u63,
+                final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
+                final int ube16, final long ube32, final long ube63, final BigInteger ube64,
+                final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
+                final Inet6Address ipv6Address) {
+            this.u8 = u8;
+            this.u16 = u16;
+            this.u32 = u32;
+            this.u63 = u63;
+            this.u64 = u64;
+            this.s8 = s8;
+            this.s16 = s16;
+            this.s32 = s32;
+            this.s64 = s64;
+            this.ube16 = ube16;
+            this.ube32 = ube32;
+            this.ube63 = ube63;
+            this.ube64 = ube64;
+            this.bytes = bytes;
+            this.eui48 = eui48;
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    private static final String FULL_TYPE_DATA = "ff" + "ffff" + "ffffffff" + "7fffffffffffffff"
+            + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + "7fffffffffffffff" + "7fff"
+            + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + "20010db80003000400050006"
+            + "001122334455" + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_MAC = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "112233445566"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_LONG = "ff" + "ffff" + "ffffffff"
+            + "7ffffffffffffffe" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_INTEGER = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_IPV4 = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a81010" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_IPV6 = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db800030004000500060007000a";
+    @Test
+    public void testStructClass_equals() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        assertEquals(255, msg.u8);
+        assertEquals(65535, msg.u16);
+        assertEquals(4294967295L, msg.u32);
+        assertEquals(9223372036854775807L, msg.u63);
+        assertEquals(new BigInteger("18446744073709551615"), msg.u64);
+        assertEquals(127, msg.s8);
+        assertEquals(32767, msg.s16);
+        assertEquals(2147483647, msg.s32);
+        assertEquals(9223372036854775807L, msg.s64);
+        assertEquals(32767, msg.ube16);
+        assertEquals(2147483647, msg.ube32);
+        assertEquals(9223372036854775807L, msg.ube63);
+        assertEquals(new BigInteger("18446744073709551615"), msg.ube64);
+        assertArrayEquals(TEST_PREFIX64, msg.bytes);
+        assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.eui48);
+        assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
+        assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
+
+        assertEquals(98, msg.getSize(FullTypeMessage.class));
+        assertArrayEquals(toByteBuffer(FULL_TYPE_DATA).array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+
+        final FullTypeMessage msg1 = new FullTypeMessage((short) 0xff, (int) 0xffff,
+                (long) 0xffffffffL, (long) 0x7fffffffffffffffL,
+                new BigInteger("18446744073709551615"), (byte) 0x7f, (short) 0x7fff,
+                (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
+                (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"), TEST_PREFIX64,
+                MacAddress.fromString("00:11:22:33:44:55"), TEST_IPV4_ADDRESS, TEST_IPV6_ADDRESS);
+        assertTrue(msg.equals(msg1));
+    }
+
+    public static class FullTypeMessageWithDupType extends Struct {
+        @Field(order = 0, type = Type.U8) public final short u8;
+        @Field(order = 1, type = Type.U16) public final int u16;
+        @Field(order = 2, type = Type.U32) public final long u32;
+        @Field(order = 3, type = Type.S64) public final long u63; // old: U63, new: S64
+        @Field(order = 4, type = Type.UBE64) public final BigInteger u64; // old: U64, new: UBE64
+        @Field(order = 5, type = Type.S8) public final byte s8;
+        @Field(order = 6, type = Type.S16) public final short s16;
+        @Field(order = 7, type = Type.S32) public final int s32;
+        @Field(order = 8, type = Type.S64) public final long s64;
+        @Field(order = 9, type = Type.U16) public final int ube16; // old:UBE16, new: U16
+        @Field(order = 10, type = Type.UBE32) public final long ube32;
+        @Field(order = 11, type = Type.UBE63) public final long ube63;
+        @Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
+        @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
+        @Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
+        @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        FullTypeMessageWithDupType(final short u8, final int u16, final long u32, final long u63,
+                final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
+                final int ube16, final long ube32, final long ube63, final BigInteger ube64,
+                final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
+                final Inet6Address ipv6Address) {
+            this.u8 = u8;
+            this.u16 = u16;
+            this.u32 = u32;
+            this.u63 = u63;
+            this.u64 = u64;
+            this.s8 = s8;
+            this.s16 = s16;
+            this.s32 = s32;
+            this.s64 = s64;
+            this.ube16 = ube16;
+            this.ube32 = ube32;
+            this.ube63 = ube63;
+            this.ube64 = ube64;
+            this.bytes = bytes;
+            this.eui48 = eui48;
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    @Test
+    public void testStructClass_notEqualWithDifferentClass() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        final FullTypeMessageWithDupType msg1 = doParsingMessageTest(FULL_TYPE_DATA,
+                FullTypeMessageWithDupType.class, ByteOrder.BIG_ENDIAN);
+
+        assertFalse(msg.equals(msg1));
+    }
+
+    @Test
+    public void testStructClass_notEqualWithDifferentValue() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        // With different MAC address.
+        final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_MAC,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.eui48, msg1.eui48);
+        assertFalse(msg.equals(msg1));
+
+        // With different byte array.
+        final FullTypeMessage msg2 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        msg2.bytes[5] = (byte) 42; // change one byte in the array.
+        assertFalse(msg.equals(msg2));
+
+        // With different Long primitive.
+        final FullTypeMessage msg3 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_LONG,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.u63, msg3.u63);
+        assertFalse(msg.equals(msg3));
+
+        // With different Integer primitive.
+        final FullTypeMessage msg4 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_INTEGER,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ube32, msg4.ube32);
+        assertFalse(msg.equals(msg4));
+
+        // With different IPv4 address.
+        final FullTypeMessage msg5 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV4,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ipv4Address, msg5.ipv4Address);
+        assertFalse(msg.equals(msg5));
+
+        // With different IPv6 address.
+        final FullTypeMessage msg6 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV6,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ipv6Address, msg6.ipv6Address);
+        assertFalse(msg.equals(msg6));
+    }
+
+    @Test
+    public void testStructClass_toString() {
+        final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+                + " u63: 9223372036854775807, u64: 18446744073709551615, s8: 127, s16: 32767,"
+                + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+                + " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+                + " bytes: 0x20010DB80003000400050006,"
+                + " eui48: 00:11:22:33:44:55,"
+                + " ipv4Address: 192.168.100.1,"
+                + " ipv6Address: 2001:db8:3:4:5:6:7:8";
+
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        assertEquals(expected, msg.toString());
+    }
+
+    @Test
+    public void testStructClass_toStringWithNullMember() {
+        final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+                + " u63: 9223372036854775807, u64: null, s8: 127, s16: 32767,"
+                + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+                + " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+                + " bytes: null, eui48: null, ipv4Address: 192.168.100.1,"
+                + " ipv6Address: null";
+
+        final FullTypeMessage msg = new FullTypeMessage((short) 0xff, (int) 0xffff,
+                (long) 0xffffffffL, (long) 0x7fffffffffffffffL,
+                null /* u64 */, (byte) 0x7f, (short) 0x7fff,
+                (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
+                (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"),
+                null /* bytes */, null /* eui48 */, TEST_IPV4_ADDRESS, null /* ipv6Address */);
+        assertEquals(expected, msg.toString());
+    }
+
+    @Test
+    public void testStructClass_hashcode() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        assertNotEquals(0, msg.hashCode());
+        assertNotEquals(0, msg1.hashCode());
+        assertTrue(msg.equals(msg1));
+        assertEquals(msg.hashCode(), msg1.hashCode());
+    }
+
+    public static class InvalidByteArray extends Struct {
+        @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
+    }
+
+    @Test
+    public void testStructClass_WrongByteArraySize() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        // Actual byte array size doesn't match the size declared in the annotation.
+        msg.bytes = new byte[16];
+        assertThrows(IllegalStateException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_NullByteArray() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        msg.bytes = null;
+        assertThrows(NullPointerException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_ParsingByteArrayAfterInitialization() {
+        InvalidByteArray msg = new InvalidByteArray();
+        msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03};
+
+        // Although bytes member has been initialized with the length different with
+        // annotation size, parsing from ByteBuffer will get bytes member have the
+        // reference to byte array with correct size.
+        msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class,
+                ByteOrder.BIG_ENDIAN);
+        assertArrayEquals(TEST_PREFIX64, msg.bytes);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
new file mode 100644
index 0000000..8e320d0
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2019 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.net.module.util
+
+import com.android.testutils.ConcurrentInterpreter
+import com.android.testutils.INTERPRET_TIME_UNIT
+import com.android.testutils.InterpretException
+import com.android.testutils.InterpretMatcher
+import com.android.testutils.SyntaxException
+import com.android.testutils.__FILE__
+import com.android.testutils.__LINE__
+import com.android.testutils.intArg
+import com.android.testutils.strArg
+import com.android.testutils.timeArg
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CyclicBarrier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.system.measureTimeMillis
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+val TEST_VALUES = listOf(4, 13, 52, 94, 41, 68, 11, 13, 51, 0, 91, 94, 33, 98, 14)
+const val ABSENT_VALUE = 2
+// Caution in changing these : some tests rely on the fact that TEST_TIMEOUT > 2 * SHORT_TIMEOUT
+// and LONG_TIMEOUT > 2 * TEST_TIMEOUT
+const val SHORT_TIMEOUT = 40L // ms
+const val TEST_TIMEOUT = 200L // ms
+const val LONG_TIMEOUT = 5000L // ms
+
+@RunWith(JUnit4::class)
+class TrackRecordTest {
+    @Test
+    fun testAddAndSizeAndGet() {
+        val repeats = 22 // arbitrary
+        val record = ArrayTrackRecord<Int>()
+        assertEquals(0, record.size)
+        repeat(repeats) { i -> record.add(i + 2) }
+        assertEquals(repeats, record.size)
+        record.add(2)
+        assertEquals(repeats + 1, record.size)
+
+        assertEquals(11, record[9])
+        assertEquals(11, record.getOrNull(9))
+        assertEquals(2, record[record.size - 1])
+        assertEquals(2, record.getOrNull(record.size - 1))
+
+        assertFailsWith<IndexOutOfBoundsException> { record[800] }
+        assertFailsWith<IndexOutOfBoundsException> { record[-1] }
+        assertFailsWith<IndexOutOfBoundsException> { record[repeats + 1] }
+        assertNull(record.getOrNull(800))
+        assertNull(record.getOrNull(-1))
+        assertNull(record.getOrNull(repeats + 1))
+        assertNull(record.getOrNull(800) { true })
+        assertNull(record.getOrNull(-1) { true })
+        assertNull(record.getOrNull(repeats + 1) { true })
+    }
+
+    @Test
+    fun testIndexOf() {
+        val record = ArrayTrackRecord<Int>()
+        TEST_VALUES.forEach { record.add(it) }
+        with(record) {
+            assertEquals(9, indexOf(0))
+            assertEquals(9, lastIndexOf(0))
+            assertEquals(1, indexOf(13))
+            assertEquals(7, lastIndexOf(13))
+            assertEquals(3, indexOf(94))
+            assertEquals(11, lastIndexOf(94))
+            assertEquals(-1, indexOf(ABSENT_VALUE))
+            assertEquals(-1, lastIndexOf(ABSENT_VALUE))
+        }
+    }
+
+    @Test
+    fun testContains() {
+        val record = ArrayTrackRecord<Int>()
+        TEST_VALUES.forEach { record.add(it) }
+        TEST_VALUES.forEach { assertTrue(record.contains(it)) }
+        assertFalse(record.contains(ABSENT_VALUE))
+        assertTrue(record.containsAll(TEST_VALUES))
+        assertTrue(record.containsAll(TEST_VALUES.sorted()))
+        assertTrue(record.containsAll(TEST_VALUES.sortedDescending()))
+        assertTrue(record.containsAll(TEST_VALUES.distinct()))
+        assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2)))
+        assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2).sorted()))
+        assertTrue(record.containsAll(listOf()))
+        assertFalse(record.containsAll(listOf(ABSENT_VALUE)))
+        assertFalse(record.containsAll(TEST_VALUES + listOf(ABSENT_VALUE)))
+    }
+
+    @Test
+    fun testEmpty() {
+        val record = ArrayTrackRecord<Int>()
+        assertTrue(record.isEmpty())
+        record.add(1)
+        assertFalse(record.isEmpty())
+    }
+
+    @Test
+    fun testIterate() {
+        val record = ArrayTrackRecord<Int>()
+        record.forEach { fail("Expected nothing to iterate") }
+        TEST_VALUES.forEach { record.add(it) }
+        // zip relies on the iterator (this calls extension function Iterable#zip(Iterable))
+        record.zip(TEST_VALUES).forEach { assertEquals(it.first, it.second) }
+        // Also test reverse iteration (to test hasPrevious() and friends)
+        record.reversed().zip(TEST_VALUES.reversed()).forEach { assertEquals(it.first, it.second) }
+    }
+
+    @Test
+    fun testIteratorIsSnapshot() {
+        val record = ArrayTrackRecord<Int>()
+        TEST_VALUES.forEach { record.add(it) }
+        val iterator = record.iterator()
+        val expectedSize = record.size
+        record.add(ABSENT_VALUE)
+        record.add(ABSENT_VALUE)
+        var measuredSize = 0
+        iterator.forEach {
+            ++measuredSize
+            assertNotEquals(ABSENT_VALUE, it)
+        }
+        assertEquals(expectedSize, measuredSize)
+    }
+
+    @Test
+    fun testSublist() {
+        val record = ArrayTrackRecord<Int>()
+        TEST_VALUES.forEach { record.add(it) }
+        assertEquals(record.subList(3, record.size - 3),
+                TEST_VALUES.subList(3, TEST_VALUES.size - 3))
+    }
+
+    fun testPollReturnsImmediately(record: TrackRecord<Int>) {
+        record.add(4)
+        val elapsed = measureTimeMillis { assertEquals(4, record.poll(LONG_TIMEOUT, 0)) }
+        // Should not have waited at all, in fact.
+        assertTrue(elapsed < LONG_TIMEOUT)
+        record.add(7)
+        record.add(9)
+        // Can poll multiple times for the same position, in whatever order
+        assertEquals(9, record.poll(0, 2))
+        assertEquals(7, record.poll(Long.MAX_VALUE, 1))
+        assertEquals(9, record.poll(0, 2))
+        assertEquals(4, record.poll(0, 0))
+        assertEquals(9, record.poll(0, 2) { it > 5 })
+        assertEquals(7, record.poll(0, 0) { it > 5 })
+    }
+
+    @Test
+    fun testPollReturnsImmediately() {
+        testPollReturnsImmediately(ArrayTrackRecord())
+        testPollReturnsImmediately(ArrayTrackRecord<Int>().newReadHead())
+    }
+
+    @Test
+    fun testPollTimesOut() {
+        val record = ArrayTrackRecord<Int>()
+        var delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0)) }
+        assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
+        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
+        assertTrue(delay >= SHORT_TIMEOUT)
+    }
+
+    @Test
+    fun testConcurrentPollDisallowed() {
+        val failures = AtomicInteger(0)
+        val readHead = ArrayTrackRecord<Int>().newReadHead()
+        val barrier = CyclicBarrier(2)
+        Thread {
+            barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
+            try {
+                readHead.poll(LONG_TIMEOUT)
+            } catch (e: ConcurrentModificationException) {
+                failures.incrementAndGet()
+                // Unblock the other thread
+                readHead.add(0)
+            }
+        }.start()
+        barrier.await() // barrier 1
+        try {
+            readHead.poll(LONG_TIMEOUT)
+        } catch (e: ConcurrentModificationException) {
+            failures.incrementAndGet()
+            // Unblock the other thread
+            readHead.add(0)
+        }
+        // One of the threads must have gotten an exception.
+        assertEquals(failures.get(), 1)
+    }
+
+    @Test
+    fun testPollWakesUp() {
+        val record = ArrayTrackRecord<Int>()
+        val barrier = CyclicBarrier(2)
+        Thread {
+            barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
+            barrier.await() // barrier 2
+            Thread.sleep(SHORT_TIMEOUT * 2)
+            record.add(31)
+        }.start()
+        barrier.await() // barrier 1
+        // Should find the element in more than SHORT_TIMEOUT but less than TEST_TIMEOUT
+        var delay = measureTimeMillis {
+            barrier.await() // barrier 2
+            assertEquals(31, record.poll(TEST_TIMEOUT, 0))
+        }
+        assertTrue(delay in SHORT_TIMEOUT..TEST_TIMEOUT)
+        // Polling for an element already added in anothe thread (pos 0) : should return immediately
+        delay = measureTimeMillis { assertEquals(31, record.poll(TEST_TIMEOUT, 0)) }
+        assertTrue(delay < TEST_TIMEOUT, "Delay $delay > $TEST_TIMEOUT")
+        // Waiting for an element that never comes
+        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 1)) }
+        assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
+        // Polling for an element that doesn't match what is already there
+        delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
+        assertTrue(delay >= SHORT_TIMEOUT)
+    }
+
+    // Just make sure the interpreter actually throws an exception when the spec
+    // does not conform to the behavior. The interpreter is just a tool to test a
+    // tool used for a tool for test, let's not have hundreds of tests for it ;
+    // if it's broken one of the tests using it will break.
+    @Test
+    fun testInterpreter() {
+        val interpretLine = __LINE__ + 2
+        try {
+            TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+                add(4) | poll(1, 0) = 5
+            """)
+            fail("This spec should have thrown")
+        } catch (e: InterpretException) {
+            assertTrue(e.cause is AssertionError)
+            assertEquals(interpretLine + 1, e.stackTrace[0].lineNumber)
+            assertTrue(e.stackTrace[0].fileName.contains(__FILE__))
+            assertTrue(e.stackTrace[0].methodName.contains("testInterpreter"))
+            assertTrue(e.stackTrace[0].methodName.contains("thread1"))
+        }
+    }
+
+    @Test
+    fun testMultipleAdds() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+            add(2)         |                |                |
+                           | add(4)         |                |
+                           |                | add(6)         |
+                           |                |                | add(8)
+            poll(0, 0) = 2 time 0..1 | poll(0, 0) = 2 | poll(0, 0) = 2 | poll(0, 0) = 2
+            poll(0, 1) = 4 time 0..1 | poll(0, 1) = 4 | poll(0, 1) = 4 | poll(0, 1) = 4
+            poll(0, 2) = 6 time 0..1 | poll(0, 2) = 6 | poll(0, 2) = 6 | poll(0, 2) = 6
+            poll(0, 3) = 8 time 0..1 | poll(0, 3) = 8 | poll(0, 3) = 8 | poll(0, 3) = 8
+        """)
+    }
+
+    @Test
+    fun testConcurrentAdds() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+            add(2)             | add(4)             | add(6)             | add(8)
+            add(1)             | add(3)             | add(5)             | add(7)
+            poll(0, 1) is even | poll(0, 0) is even | poll(0, 3) is even | poll(0, 2) is even
+            poll(0, 5) is odd  | poll(0, 4) is odd  | poll(0, 7) is odd  | poll(0, 6) is odd
+        """)
+    }
+
+    @Test
+    fun testMultiplePoll() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+            add(4)         | poll(1, 0) = 4
+                           | poll(0, 1) = null time 0..1
+                           | poll(1, 1) = null time 1..2
+            sleep; add(7)  | poll(2, 1) = 7 time 1..2
+            sleep; add(18) | poll(2, 2) = 18 time 1..2
+        """)
+    }
+
+    @Test
+    fun testMultiplePollWithPredicate() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+                     | poll(1, 0) = null          | poll(1, 0) = null
+            add(6)   | poll(1, 0) = 6             |
+            add(11)  | poll(1, 0) { > 20 } = null | poll(1, 0) { = 11 } = 11
+                     | poll(1, 0) { > 8 } = 11    |
+        """)
+    }
+
+    @Test
+    fun testMultipleReadHeads() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+                   | poll() = null | poll() = null | poll() = null
+            add(5) |               | poll() = 5    |
+                   | poll() = 5    |               |
+            add(8) | poll() = 8    | poll() = 8    |
+                   |               |               | poll() = 5
+                   |               |               | poll() = 8
+                   |               |               | poll() = null
+                   |               | poll() = null |
+        """)
+    }
+
+    @Test
+    fun testReadHeadPollWithPredicate() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+            add(5)  | poll() { < 0 } = null
+                    | poll() { > 5 } = null
+            add(10) |
+                    | poll() { = 5 } = null   // The "5" was skipped in the previous line
+            add(15) | poll() { > 8 } = 15     // The "10" was skipped in the previous line
+                    | poll(1, 0) { > 8 } = 10 // 10 is the first element after pos 0 matching > 8
+        """)
+    }
+
+    @Test
+    fun testPollImmediatelyAdvancesReadhead() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+            add(1)                  | add(2)              | add(3)   | add(4)
+            mark = 0                | poll(0) { > 3 } = 4 |          |
+            poll(0) { > 10 } = null |                     |          |
+            mark = 4                |                     |          |
+            poll() = null           |                     |          |
+        """)
+    }
+
+    @Test
+    fun testParallelReadHeads() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+            mark = 0   | mark = 0   | mark = 0   | mark = 0
+            add(2)     |            |            |
+                       | add(4)     |            |
+                       |            | add(6)     |
+                       |            |            | add(8)
+            poll() = 2 | poll() = 2 | poll() = 2 | poll() = 2
+            poll() = 4 | poll() = 4 | poll() = 4 | poll() = 4
+            poll() = 6 | poll() = 6 | poll() = 6 | mark = 2
+            poll() = 8 | poll() = 8 | mark = 3   | poll() = 6
+            mark = 4   | mark = 4   | poll() = 8 | poll() = 8
+        """)
+    }
+
+    @Test
+    fun testPeek() {
+        TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+            add(2)     |            |               |
+                       | add(4)     |               |
+                       |            | add(6)        |
+                       |            |               | add(8)
+            peek() = 2 | poll() = 2 | poll() = 2    | peek() = 2
+            peek() = 2 | peek() = 4 | poll() = 4    | peek() = 2
+            peek() = 2 | peek() = 4 | peek() = 6    | poll() = 2
+            peek() = 2 | mark = 1   | mark = 2      | poll() = 4
+            mark = 0   | peek() = 4 | peek() = 6    | peek() = 6
+            poll() = 2 | poll() = 4 | poll() = 6    | poll() = 6
+            poll() = 4 | mark = 2   | poll() = 8    | peek() = 8
+            peek() = 6 | peek() = 6 | peek() = null | mark = 3
+        """)
+    }
+}
+
+private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) {
+    fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) {
+        interpretTestSpec(spec, initial = ArrayTrackRecord(),
+                threadTransform = { (it as ArrayTrackRecord).newReadHead() })
+    } else {
+        interpretTestSpec(spec, ArrayTrackRecord())
+    }
+}
+
+/*
+ * Quick ref of supported expressions :
+ * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
+ * add(x) : calls and returns TrackRecord#add.
+ * poll(time, pos) [{ predicate }] : calls and returns TrackRecord#poll(x time units, pos).
+ *   Optionally, a predicate may be specified.
+ * poll() [{ predicate }] : calls and returns ReadHead#poll(1 time unit). Optionally, a predicate
+ *   may be specified.
+ * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
+ *   string "null" or an int. Returns Unit.
+ * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
+ *   y time units.
+ * predicate must be one of "= x", "< x" or "> x".
+ */
+private val interpretTable = listOf<InterpretMatcher<TrackRecord<Int>>>(
+    // Interpret "XXX is odd" : run XXX and assert its return value is odd ("even" works too)
+    Regex("(.*)\\s+is\\s+(even|odd)") to { i, t, r ->
+        i.interpret(r.strArg(1), t).also {
+            assertEquals((it as Int) % 2, if ("even" == r.strArg(2)) 0 else 1)
+        }
+    },
+    // Interpret "add(XXX)" as TrackRecord#add(int)
+    Regex("""add\((\d+)\)""") to { i, t, r ->
+        t.add(r.intArg(1))
+    },
+    // Interpret "poll(x, y)" as TrackRecord#poll(timeout = x * INTERPRET_TIME_UNIT, pos = y)
+    // Accepts an optional {} argument for the predicate (see makePredicate for syntax)
+    Regex("""poll\((\d+),\s*(\d+)\)\s*(\{.*\})?""") to { i, t, r ->
+        t.poll(r.timeArg(1), r.intArg(2), makePredicate(r.strArg(3)))
+    },
+    // ReadHead#poll. If this throws in the cast, the code is malformed and has passed "poll()"
+    // in a test that takes a TrackRecord that is not a ReadHead. It's technically possible to get
+    // the test code to not compile instead of throw, but it's vastly more complex and this will
+    // fail 100% at runtime any test that would not have compiled.
+    Regex("""poll\((\d+)?\)\s*(\{.*\})?""") to { i, t, r ->
+        (if (r.strArg(1).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(1)).let { time ->
+            (t as ArrayTrackRecord<Int>.ReadHead).poll(time, makePredicate(r.strArg(2)))
+        }
+    },
+    // ReadHead#mark. The same remarks apply as with ReadHead#poll.
+    Regex("mark") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).mark },
+    // ReadHead#peek. The same remarks apply as with ReadHead#poll.
+    Regex("peek\\(\\)") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).peek() }
+)
+
+// Parses a { = x } or { < x } or { > x } string and returns the corresponding predicate
+// Returns an always-true predicate for empty and null arguments
+private fun makePredicate(spec: String?): (Int) -> Boolean {
+    if (spec.isNullOrEmpty()) return { true }
+    val match = Regex("""\{\s*([<>=])\s*(\d+)\s*\}""").matchEntire(spec)
+            ?: throw SyntaxException("Predicate \"${spec}\"")
+    val arg = match.intArg(2)
+    return when (match.strArg(1)) {
+        ">" -> { i -> i > arg }
+        "<" -> { i -> i < arg }
+        "=" -> { i -> i == arg }
+        else -> throw RuntimeException("How did \"${spec}\" match this regexp ?")
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java
new file mode 100644
index 0000000..11a74f2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.async;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.ignoreStubs;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.async.ReadableDataAnswer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BufferedFileTest {
+    @Mock EventManager mockEventManager;
+    @Mock BufferedFile.Listener mockFileListener;
+    @Mock AsyncFile mockAsyncFile;
+    @Mock ParcelFileDescriptor mockParcelFileDescriptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager));
+    }
+
+    @Test
+    public void onClosed() throws Exception {
+        final int inboundBufferSize = 1024;
+        final int outboundBufferSize = 768;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        file.onClosed(mockAsyncFile);
+
+        verify(mockFileListener).onBufferedFileClosed();
+    }
+
+    @Test
+    public void continueReadingAndClose() throws Exception {
+        final int inboundBufferSize = 1024;
+        final int outboundBufferSize = 768;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        assertEquals(inboundBufferSize, file.getInboundBufferFreeSizeForTest());
+        assertEquals(outboundBufferSize, file.getOutboundBufferFreeSize());
+
+        file.continueReading();
+        verify(mockAsyncFile).enableReadEvents(true);
+
+        file.close();
+        verify(mockAsyncFile).close();
+    }
+
+    @Test
+    public void enqueueOutboundData() throws Exception {
+        final int inboundBufferSize = 10;
+        final int outboundBufferSize = 250;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data1 = new byte[101];
+        final byte[] data2 = new byte[102];
+        data1[0] = (byte) 1;
+        data2[0] = (byte) 2;
+
+        assertEquals(0, file.getOutboundBufferSize());
+
+        final int totalLen = data1.length + data2.length;
+
+        when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0);
+        assertTrue(file.enqueueOutboundData(data1, 0, data1.length, null, 0, 0));
+        verify(mockAsyncFile).enableWriteEvents(true);
+
+        assertEquals(data1.length, file.getOutboundBufferSize());
+
+        checkAndResetMocks();
+
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+        when(mockAsyncFile.write(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+        assertTrue(file.enqueueOutboundData(data2, 0, data2.length, null, 0, 0));
+
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(0, file.getOutboundBufferSize());
+
+        assertEquals(0, posCaptor.getValue().intValue());
+        assertEquals(totalLen, lenCaptor.getValue().intValue());
+        assertEquals(data1[0], arrayCaptor.getValue()[0]);
+        assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+    }
+
+    @Test
+    public void enqueueOutboundData_combined() throws Exception {
+        final int inboundBufferSize = 10;
+        final int outboundBufferSize = 250;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data1 = new byte[101];
+        final byte[] data2 = new byte[102];
+        data1[0] = (byte) 1;
+        data2[0] = (byte) 2;
+
+        assertEquals(0, file.getOutboundBufferSize());
+
+        final int totalLen = data1.length + data2.length;
+
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+        when(mockAsyncFile.write(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+        assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length));
+
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(0, file.getOutboundBufferSize());
+
+        assertEquals(0, posCaptor.getValue().intValue());
+        assertEquals(totalLen, lenCaptor.getValue().intValue());
+        assertEquals(data1[0], arrayCaptor.getValue()[0]);
+        assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+    }
+
+    @Test
+    public void enableWriteEvents() throws Exception {
+        final int inboundBufferSize = 10;
+        final int outboundBufferSize = 250;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data1 = new byte[101];
+        final byte[] data2 = new byte[102];
+        final byte[] data3 = new byte[103];
+        data1[0] = (byte) 1;
+        data2[0] = (byte) 2;
+        data3[0] = (byte) 3;
+
+        assertEquals(0, file.getOutboundBufferSize());
+
+        // Write first 2 buffers, but fail to flush them, causing async write request.
+        final int data1And2Len = data1.length + data2.length;
+        when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0);
+        assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length));
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(data1And2Len, file.getOutboundBufferSize());
+        verify(mockAsyncFile).enableWriteEvents(true);
+
+        // Try to write 3rd buffers, which won't fit, then fail to flush.
+        when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0);
+        assertFalse(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0));
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(data1And2Len, file.getOutboundBufferSize());
+        verify(mockAsyncFile, times(2)).enableWriteEvents(true);
+
+        checkAndResetMocks();
+
+        // Simulate writeability event, and successfully flush.
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+        when(mockAsyncFile.write(arrayCaptor.capture(),
+                posCaptor.capture(), lenCaptor.capture())).thenReturn(data1And2Len);
+        file.onWriteReady(mockAsyncFile);
+        verify(mockAsyncFile).enableWriteEvents(false);
+        verify(mockFileListener).onBufferedFileOutboundSpace();
+        assertEquals(0, file.getOutboundBufferSize());
+
+        assertEquals(0, posCaptor.getValue().intValue());
+        assertEquals(data1And2Len, lenCaptor.getValue().intValue());
+        assertEquals(data1[0], arrayCaptor.getValue()[0]);
+        assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+
+        checkAndResetMocks();
+
+        // Now write, but fail to flush the third buffer.
+        when(mockAsyncFile.write(arrayCaptor.capture(),
+                posCaptor.capture(), lenCaptor.capture())).thenReturn(0);
+        assertTrue(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0));
+        verify(mockAsyncFile).enableWriteEvents(true);
+        assertEquals(data3.length, file.getOutboundBufferSize());
+
+        assertEquals(data1And2Len, posCaptor.getValue().intValue());
+        assertEquals(outboundBufferSize - data1And2Len, lenCaptor.getValue().intValue());
+        assertEquals(data3[0], arrayCaptor.getValue()[data1And2Len]);
+    }
+
+    @Test
+    public void read() throws Exception {
+        final int inboundBufferSize = 250;
+        final int outboundBufferSize = 10;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data1 = new byte[101];
+        final byte[] data2 = new byte[102];
+        data1[0] = (byte) 1;
+        data2[0] = (byte) 2;
+
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2);
+        final ReadableByteBuffer inboundBuffer = file.getInboundBuffer();
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        file.onReadReady(mockAsyncFile);
+        verify(mockAsyncFile).enableReadEvents(true);
+        verify(mockFileListener).onBufferedFileInboundData(eq(data1.length + data2.length));
+
+        assertEquals(0, file.getOutboundBufferSize());
+        assertEquals(data1.length + data2.length, inboundBuffer.size());
+        assertEquals((byte) 1, inboundBuffer.peek(0));
+        assertEquals((byte) 2, inboundBuffer.peek(data1.length));
+    }
+
+    @Test
+    public void enableReadEvents() throws Exception {
+        final int inboundBufferSize = 250;
+        final int outboundBufferSize = 10;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data1 = new byte[101];
+        final byte[] data2 = new byte[102];
+        final byte[] data3 = new byte[103];
+        data1[0] = (byte) 1;
+        data2[0] = (byte) 2;
+        data3[0] = (byte) 3;
+
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2, data3);
+        final ReadableByteBuffer inboundBuffer = file.getInboundBuffer();
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        file.onReadReady(mockAsyncFile);
+        verify(mockAsyncFile).enableReadEvents(false);
+        verify(mockFileListener).onBufferedFileInboundData(eq(inboundBufferSize));
+
+        assertEquals(0, file.getOutboundBufferSize());
+        assertEquals(inboundBufferSize, inboundBuffer.size());
+        assertEquals((byte) 1, inboundBuffer.peek(0));
+        assertEquals((byte) 2, inboundBuffer.peek(data1.length));
+        assertEquals((byte) 3, inboundBuffer.peek(data1.length + data2.length));
+
+        checkAndResetMocks();
+
+        // Cannot enable read events since the buffer is full.
+        file.continueReading();
+
+        checkAndResetMocks();
+
+        final byte[] tmp = new byte[inboundBufferSize];
+        inboundBuffer.readBytes(tmp, 0, data1.length);
+        assertEquals(inboundBufferSize - data1.length, inboundBuffer.size());
+
+        file.continueReading();
+
+        inboundBuffer.readBytes(tmp, 0, data2.length);
+        assertEquals(inboundBufferSize - data1.length - data2.length, inboundBuffer.size());
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        file.onReadReady(mockAsyncFile);
+        verify(mockAsyncFile, times(2)).enableReadEvents(true);
+        verify(mockFileListener).onBufferedFileInboundData(
+            eq(data1.length + data2.length + data3.length - inboundBufferSize));
+
+        assertEquals(data3.length, inboundBuffer.size());
+        assertEquals((byte) 3, inboundBuffer.peek(0));
+    }
+
+    @Test
+    public void shutdownReading() throws Exception {
+        final int inboundBufferSize = 250;
+        final int outboundBufferSize = 10;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data = new byte[100];
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+        file.shutdownReading();
+        file.onReadReady(mockAsyncFile);
+
+        verify(mockAsyncFile).enableReadEvents(false);
+
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(data.length, dataAnswer.getRemainingSize());
+    }
+
+    @Test
+    public void shutdownReading_inCallback() throws Exception {
+        final int inboundBufferSize = 250;
+        final int outboundBufferSize = 10;
+
+        final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+        final byte[] data = new byte[100];
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+        doAnswer(new Answer() {
+            @Override public Object answer(InvocationOnMock invocation) {
+                file.shutdownReading();
+                return null;
+            }}).when(mockFileListener).onBufferedFileInboundData(anyInt());
+
+        file.onReadReady(mockAsyncFile);
+
+        verify(mockAsyncFile).enableReadEvents(false);
+
+        assertEquals(0, file.getInboundBuffer().size());
+        assertEquals(0, dataAnswer.getRemainingSize());
+    }
+
+    private void checkAndResetMocks() {
+        verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager,
+            mockParcelFileDescriptor));
+        reset(mockFileListener, mockAsyncFile, mockEventManager);
+    }
+
+    private BufferedFile createFile(
+            int inboundBufferSize, int outboundBufferSize) throws Exception {
+        when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile);
+        return BufferedFile.create(
+            mockEventManager,
+            FileHandle.fromFileDescriptor(mockParcelFileDescriptor),
+            mockFileListener,
+            inboundBufferSize,
+            outboundBufferSize);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
new file mode 100644
index 0000000..01abee2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.net.module.util.async;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CircularByteBufferTest {
+    @Test
+    public void writeBytes() {
+        final int capacity = 23;
+        CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+        assertEquals(0, buffer.size());
+        assertEquals(0, buffer.getDirectReadSize());
+        assertEquals(capacity, buffer.freeSize());
+        assertEquals(capacity, buffer.getDirectWriteSize());
+
+        final byte[] writeBuffer = new byte[15];
+        buffer.writeBytes(writeBuffer, 0, writeBuffer.length);
+
+        assertEquals(writeBuffer.length, buffer.size());
+        assertEquals(writeBuffer.length, buffer.getDirectReadSize());
+        assertEquals(capacity - writeBuffer.length, buffer.freeSize());
+        assertEquals(capacity - writeBuffer.length, buffer.getDirectWriteSize());
+
+        buffer.clear();
+        assertEquals(0, buffer.size());
+        assertEquals(0, buffer.getDirectReadSize());
+        assertEquals(capacity, buffer.freeSize());
+        assertEquals(capacity, buffer.getDirectWriteSize());
+    }
+
+    @Test
+    public void writeBytes_withRollover() {
+        doTestReadWriteWithRollover(new BufferAccessor(false, false));
+    }
+
+    @Test
+    public void writeBytes_withFullBuffer() {
+        doTestReadWriteWithFullBuffer(new BufferAccessor(false, false));
+    }
+
+    @Test
+    public void directWriteBytes_withRollover() {
+        doTestReadWriteWithRollover(new BufferAccessor(true, true));
+    }
+
+    @Test
+    public void directWriteBytes_withFullBuffer() {
+        doTestReadWriteWithFullBuffer(new BufferAccessor(true, true));
+    }
+
+    private void doTestReadWriteWithFullBuffer(BufferAccessor accessor) {
+        CircularByteBuffer buffer = doTestReadWrite(accessor, 20, 5, 4);
+
+        assertEquals(0, buffer.size());
+        assertEquals(20, buffer.freeSize());
+    }
+
+    private void doTestReadWriteWithRollover(BufferAccessor accessor) {
+        // All buffer sizes are prime numbers to ensure that some read or write
+        // operations will roll over the end of the internal buffer.
+        CircularByteBuffer buffer = doTestReadWrite(accessor, 31, 13, 7);
+
+        assertNotEquals(0, buffer.size());
+    }
+
+    private CircularByteBuffer doTestReadWrite(BufferAccessor accessor,
+            final int capacity, final int writeLen, final int readLen) {
+        CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+
+        final byte[] writeBuffer = new byte[writeLen + 2];
+        final byte[] peekBuffer = new byte[readLen + 2];
+        final byte[] readBuffer = new byte[readLen + 2];
+
+        final int numIterations = 1011;
+        final int maxRemaining = readLen - 1;
+
+        int currentWriteSymbol = 0;
+        int expectedReadSymbol = 0;
+        int expectedSize = 0;
+        int totalWritten = 0;
+        int totalRead = 0;
+
+        for (int i = 0; i < numIterations; i++) {
+            // Fill in with write buffers as much as possible.
+            while (buffer.freeSize() >= writeLen) {
+                currentWriteSymbol = fillTestBytes(writeBuffer, 1, writeLen, currentWriteSymbol);
+                accessor.writeBytes(buffer, writeBuffer, 1, writeLen);
+
+                expectedSize += writeLen;
+                totalWritten += writeLen;
+                assertEquals(expectedSize, buffer.size());
+                assertEquals(capacity - expectedSize, buffer.freeSize());
+            }
+
+            // Keep reading into read buffers while there's still data.
+            while (buffer.size() >= readLen) {
+                peekBuffer[1] = 0;
+                peekBuffer[2] = 0;
+                buffer.peekBytes(2, peekBuffer, 3, readLen - 2);
+                assertEquals(0, peekBuffer[1]);
+                assertEquals(0, peekBuffer[2]);
+
+                peekBuffer[2] = buffer.peek(1);
+
+                accessor.readBytes(buffer, readBuffer, 1, readLen);
+                peekBuffer[1] = readBuffer[1];
+
+                expectedReadSymbol = checkTestBytes(
+                    readBuffer, 1, readLen, expectedReadSymbol, totalRead);
+
+                assertArrayEquals(peekBuffer, readBuffer);
+
+                expectedSize -= readLen;
+                totalRead += readLen;
+                assertEquals(expectedSize, buffer.size());
+                assertEquals(capacity - expectedSize, buffer.freeSize());
+            }
+
+            if (buffer.size() > maxRemaining) {
+                fail("Too much data remaining: " + buffer.size());
+            }
+        }
+
+        final int maxWritten = capacity * numIterations;
+        final int minWritten = maxWritten / 2;
+        if (totalWritten < minWritten || totalWritten > maxWritten
+                || (totalWritten - totalRead) > maxRemaining) {
+            fail("Unexpected counts: read=" + totalRead + ", written=" + totalWritten
+                    + ", minWritten=" + minWritten + ", maxWritten=" + maxWritten);
+        }
+
+        return buffer;
+    }
+
+    @Test
+    public void readBytes_overflow() {
+        CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+        final byte[] dataBuffer = new byte[15];
+        buffer.writeBytes(dataBuffer, 0, dataBuffer.length - 2);
+
+        try {
+            buffer.readBytes(dataBuffer, 0, dataBuffer.length);
+            assertTrue(false);
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        assertEquals(13, buffer.size());
+        assertEquals(10, buffer.freeSize());
+    }
+
+    @Test
+    public void writeBytes_overflow() {
+        CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+        final byte[] dataBuffer = new byte[15];
+        buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+
+        try {
+            buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+            assertTrue(false);
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        assertEquals(15, buffer.size());
+        assertEquals(8, buffer.freeSize());
+    }
+
+    private static int fillTestBytes(byte[] buffer, int pos, int len, int startValue) {
+        for (int i = 0; i < len; i++) {
+            buffer[pos + i] = (byte) (startValue & 0xFF);
+            startValue = (startValue + 1) % 256;
+        }
+        return startValue;
+    }
+
+    private static int checkTestBytes(
+            byte[] buffer, int pos, int len, int startValue, int totalRead) {
+        for (int i = 0; i < len; i++) {
+            byte expectedValue = (byte) (startValue & 0xFF);
+            if (expectedValue != buffer[pos + i]) {
+                fail("Unexpected byte=" + (((int) buffer[pos + i]) & 0xFF)
+                        + ", expected=" + (((int) expectedValue) & 0xFF)
+                        + ", pos=" + (totalRead + i));
+            }
+            startValue = (startValue + 1) % 256;
+        }
+        return startValue;
+    }
+
+    private static final class BufferAccessor {
+        private final boolean mDirectRead;
+        private final boolean mDirectWrite;
+
+        BufferAccessor(boolean directRead, boolean directWrite) {
+            mDirectRead = directRead;
+            mDirectWrite = directWrite;
+        }
+
+        void writeBytes(CircularByteBuffer buffer, byte[] src, int pos, int len) {
+            if (mDirectWrite) {
+                while (len > 0) {
+                    if (buffer.getDirectWriteSize() == 0) {
+                        fail("Direct write size is zero: free=" + buffer.freeSize()
+                                + ", size=" + buffer.size());
+                    }
+                    int copyLen = Math.min(len, buffer.getDirectWriteSize());
+                    System.arraycopy(src, pos, buffer.getDirectWriteBuffer(),
+                        buffer.getDirectWritePos(), copyLen);
+                    buffer.accountForDirectWrite(copyLen);
+                    len -= copyLen;
+                    pos += copyLen;
+                }
+            } else {
+                buffer.writeBytes(src, pos, len);
+            }
+        }
+
+        void readBytes(CircularByteBuffer buffer, byte[] dst, int pos, int len) {
+            if (mDirectRead) {
+                while (len > 0) {
+                    if (buffer.getDirectReadSize() == 0) {
+                        fail("Direct read size is zero: free=" + buffer.freeSize()
+                                + ", size=" + buffer.size());
+                    }
+                    int copyLen = Math.min(len, buffer.getDirectReadSize());
+                    System.arraycopy(
+                        buffer.getDirectReadBuffer(), buffer.getDirectReadPos(), dst, pos, copyLen);
+                    buffer.accountForDirectRead(copyLen);
+                    len -= copyLen;
+                    pos += copyLen;
+                }
+            } else {
+                buffer.readBytes(dst, pos, len);
+            }
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java
new file mode 100644
index 0000000..7ee376c
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ip/ConntrackMonitorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.ip;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import static com.android.net.module.util.netlink.ConntrackMessage.Tuple;
+import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4;
+import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkUtils;
+
+import libcore.util.HexEncoding;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.net.Inet4Address;
+
+/**
+ * Tests for ConntrackMonitor.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackMonitorTest {
+    private static final long TIMEOUT_MS = 10_000L;
+
+    @Mock private SharedLog mLog;
+    @Mock private ConntrackMonitor.ConntrackEventConsumer mConsumer;
+
+    private final HandlerThread mHandlerThread = new HandlerThread(
+            ConntrackMonitorTest.class.getSimpleName());
+
+    // Late init since the handler thread has been started.
+    private Handler mHandler;
+    private TestConntrackMonitor mConntrackMonitor;
+
+    // A version of [ConntrackMonitor] that reads packets from the socket pair, and instead
+    // allows the test to write test packets to the socket pair via [sendMessage].
+    private class TestConntrackMonitor extends ConntrackMonitor {
+        TestConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
+                @NonNull ConntrackEventConsumer cb) {
+            super(h, log, cb);
+
+            mReadFd = new FileDescriptor();
+            mWriteFd = new FileDescriptor();
+            try {
+                Os.socketpair(AF_UNIX, SOCK_DGRAM, 0, mWriteFd, mReadFd);
+            } catch (ErrnoException e) {
+                fail("Could not create socket pair: " + e);
+            }
+        }
+
+        @Override
+        protected FileDescriptor createFd() {
+            return mReadFd;
+        }
+
+        private void sendMessage(byte[] msg) {
+            mHandler.post(() -> {
+                try {
+                    NetlinkUtils.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
+                                              TIMEOUT_MS);
+                } catch (ErrnoException | InterruptedIOException e) {
+                    fail("Unable to send netfilter message: " + e);
+                }
+            });
+        }
+
+        private final FileDescriptor mReadFd;
+        private final FileDescriptor mWriteFd;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        // ConntrackMonitor needs to be started from the handler thread.
+        final ConditionVariable initDone = new ConditionVariable();
+        mHandler.post(() -> {
+            TestConntrackMonitor m = new TestConntrackMonitor(mHandler, mLog, mConsumer);
+            m.start();
+            mConntrackMonitor = m;
+
+            initDone.open();
+        });
+        if (!initDone.block(TIMEOUT_MS)) {
+            fail("... init monitor timed-out after " + TIMEOUT_MS + "ms");
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quitSafely();
+    }
+
+    public static final String CT_V4NEW_TCP_HEX =
+            // CHECKSTYLE:OFF IndentationCheck
+            // struct nlmsghdr
+            "8C000000" +      // length = 140
+            "0001" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
+            "0006" +          // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
+            "00000000" +      // seqno = 0
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "1234" +          // res_id = 0x1234 (big endian)
+             // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
+                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0300" +          // nla_type = CTA_STATUS
+            "0000019e" +      // nla_value = 0b110011110 (big endian)
+                              // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
+                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "00000078";       // nla_value = 120 (big endian)
+            // CHECKSTYLE:ON IndentationCheck
+    public static final byte[] CT_V4NEW_TCP_BYTES =
+            HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @NonNull
+    private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) {
+        final Inet4Address privateIp =
+                (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
+        final Inet4Address remoteIp =
+                (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
+        final Inet4Address publicIp =
+                (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");
+
+        return new ConntrackEvent(
+                (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
+                new Tuple(new TupleIpv4(privateIp, remoteIp),
+                        new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)),
+                new Tuple(new TupleIpv4(remoteIp, publicIp),
+                        new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)),
+                status,
+                timeoutSec);
+    }
+
+    @Test
+    public void testConntrackEventNew() throws Exception {
+        final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
+                0x19e /* status */, 120 /* timeoutSec */);
+        mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
+        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
+    }
+
+    @Test
+    public void testConntrackEventEquals() {
+        final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+                5678 /* timeoutSec*/);
+        final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+                5678 /* timeoutSec*/);
+        assertEquals(event1, event2);
+    }
+
+    @Test
+    public void testConntrackEventNotEquals() {
+        final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+                5678 /* timeoutSec*/);
+
+        final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */,
+                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec);
+        assertNotEquals(e, typeNotEqual);
+
+        final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType,
+                null /* diff */, e.tupleReply, e.status, e.timeoutSec);
+        assertNotEquals(e, tupleOrigNotEqual);
+
+        final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType,
+                e.tupleOrig, null /* diff */, e.status, e.timeoutSec);
+        assertNotEquals(e, tupleReplyNotEqual);
+
+        final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType,
+                e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec);
+        assertNotEquals(e, statusNotEqual);
+
+        final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType,
+                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */);
+        assertNotEquals(e, timeoutSecNotEqual);
+    }
+
+    @Test
+    public void testToString() {
+        final ConntrackEvent event = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
+                0x198 /* status */, 120 /* timeoutSec */);
+        final String expected = ""
+                + "ConntrackEvent{"
+                + "msg_type{IPCTNL_MSG_CT_NEW}, "
+                + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
+                + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
+                + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, "
+                + "timeout_sec{120}}";
+        assertEquals(expected, event.toString());
+    }
+
+    public static final String CT_V4DELETE_TCP_HEX =
+            // CHECKSTYLE:OFF IndentationCheck
+            // struct nlmsghdr
+            "84000000" +      // length = 132
+            "0201" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2)
+            "0000" +          // flags = 0
+            "00000000" +      // seqno = 0
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family  = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "1234" +          // res_id = 0x1234 (big endian)
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian)
+                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0300" +          // nla_type = CTA_STATUS
+            "0000039E";       // nla_value = 0b1110011110 (big endian)
+                              // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
+                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) |
+                              // IPS_DYING (1 << 9)
+            // CHECKSTYLE:ON IndentationCheck
+    public static final byte[] CT_V4DELETE_TCP_BYTES =
+            HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @Test
+    public void testConntrackEventDelete() throws Exception {
+        final ConntrackEvent expectedEvent =
+                makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */,
+                        0 /* timeoutSec (absent) */);
+        mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES);
+        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java
new file mode 100644
index 0000000..dea667d
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ip/InterfaceControllerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.ip;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
+import android.net.LinkAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceControllerTest {
+    private static final String TEST_IFACE = "testif";
+    private static final String TEST_IPV4_ADDR = "192.168.123.28";
+    private static final int TEST_PREFIXLENGTH = 31;
+
+    @Mock private INetd mNetd;
+    @Mock private SharedLog mLog;
+    @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor;
+
+    private InterfaceController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mController = new InterfaceController(TEST_IFACE, mNetd, mLog);
+
+        doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture());
+    }
+
+    @Test
+    public void testSetIPv4Address() throws Exception {
+        mController.setIPv4Address(
+                new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR),
+                        TEST_PREFIXLENGTH));
+        verify(mNetd, times(1)).interfaceSetCfg(any());
+        final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
+        assertEquals(TEST_IFACE, parcel.ifName);
+        assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr);
+        assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength);
+        assertEquals("", parcel.hwAddr);
+        assertArrayEquals(new String[0], parcel.flags);
+    }
+
+    @Test
+    public void testClearIPv4Address() throws Exception {
+        mController.clearIPv4Address();
+        verify(mNetd, times(1)).interfaceSetCfg(any());
+        final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
+        assertEquals(TEST_IFACE, parcel.ifName);
+        assertEquals("0.0.0.0", parcel.ipv4Addr);
+        assertEquals(0, parcel.prefixLength);
+        assertEquals("", parcel.hwAddr);
+        assertArrayEquals(new String[0], parcel.flags);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java
new file mode 100644
index 0000000..f02b4cb
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2017 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
+import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackMessageTest {
+    private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
+
+    private short makeCtType(short msgType) {
+        return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType);
+    }
+
+    // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443)
+    public static final String CT_V4UPDATE_TCP_HEX =
+            // struct nlmsghdr
+            "50000000" +      // length = 80
+            "0001" +          // type = (1 << 8) | 0
+            "0501" +          // flags
+            "01000000" +      // seqno = 1
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family  = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "0000" +          // res_id
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 C0A82BD1" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209
+                    "0800 0200 17D30D1A" +  // nla_type=CTA_IP_V4_DST, ip=23.211.13.26
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=6
+                    "0600 0200 AD2D 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "00069780";       // nla_value = 432000 (big endian)
+    public static final byte[] CT_V4UPDATE_TCP_BYTES =
+            HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception {
+        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                OsConstants.IPPROTO_TCP,
+                (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
+                (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
+                432000);
+    }
+
+    // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
+    public static final String CT_V4UPDATE_UDP_HEX =
+            // struct nlmsghdr
+            "50000000" +      // length = 80
+            "0001" +          // type = (1 << 8) | 0
+            "0501" +          // flags
+            "01000000" +      // seqno = 1
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family  = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "0000" +          // res_id
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 6460A792" +  // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146
+                    "0800 0200 D83AC50A" +  // nla_type=CTA_IP_V4_DST, ip=216.58.197.10
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 11 000000" +  // nla_type=CTA_PROTO_NUM, proto=17
+                    "0600 0200 90CD 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "000000B4";       // nla_value = 180 (big endian)
+    public static final byte[] CT_V4UPDATE_UDP_BYTES =
+            HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception {
+        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                OsConstants.IPPROTO_UDP,
+                (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
+                (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
+                180);
+    }
+
+    @Test
+    public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
+        assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
+    }
+
+    @Test
+    public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
+        assertNotNull(msg);
+        assertTrue(msg instanceof ConntrackMessage);
+        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
+
+        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
+        assertNotNull(hdr);
+        assertEquals(80, hdr.nlmsg_len);
+        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
+        assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST
+                | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags);
+        assertEquals(1, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
+        assertNotNull(nfmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
+        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
+        assertEquals((short) 0, nfmsgHdr.res_id);
+
+        assertEquals(InetAddress.parseNumericAddress("192.168.43.209"),
+                conntrackMessage.tupleOrig.srcIp);
+        assertEquals(InetAddress.parseNumericAddress("23.211.13.26"),
+                conntrackMessage.tupleOrig.dstIp);
+        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
+        assertEquals((short) 44333, conntrackMessage.tupleOrig.srcPort);
+        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+        assertNull(conntrackMessage.tupleReply);
+
+        assertEquals(0 /* absent */, conntrackMessage.status);
+        assertEquals(432000, conntrackMessage.timeoutSec);
+    }
+
+    @Test
+    public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
+        assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
+    }
+
+    @Test
+    public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(udp);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
+        assertNotNull(msg);
+        assertTrue(msg instanceof ConntrackMessage);
+        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
+
+        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
+        assertNotNull(hdr);
+        assertEquals(80, hdr.nlmsg_len);
+        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
+        assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST
+                | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags);
+        assertEquals(1, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
+        assertNotNull(nfmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
+        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
+        assertEquals((short) 0, nfmsgHdr.res_id);
+
+        assertEquals(InetAddress.parseNumericAddress("100.96.167.146"),
+                conntrackMessage.tupleOrig.srcIp);
+        assertEquals(InetAddress.parseNumericAddress("216.58.197.10"),
+                conntrackMessage.tupleOrig.dstIp);
+        assertEquals((byte) OsConstants.IPPROTO_UDP, conntrackMessage.tupleOrig.protoNum);
+        assertEquals((short) 37069, conntrackMessage.tupleOrig.srcPort);
+        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+        assertNull(conntrackMessage.tupleReply);
+
+        assertEquals(0 /* absent */, conntrackMessage.status);
+        assertEquals(180, conntrackMessage.timeoutSec);
+    }
+
+    public static final String CT_V4NEW_TCP_HEX =
+            // CHECKSTYLE:OFF IndentationCheck
+            // struct nlmsghdr
+            "8C000000" +      // length = 140
+            "0001" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
+            "0006" +          // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
+            "00000000" +      // seqno = 0
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "1234" +          // res_id = 0x1234 (big endian)
+             // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
+                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0300" +          // nla_type = CTA_STATUS
+            "00000198" +      // nla_value = 0b110011000 (big endian)
+                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "00000078";       // nla_value = 120 (big endian)
+            // CHECKSTYLE:ON IndentationCheck
+    public static final byte[] CT_V4NEW_TCP_BYTES =
+            HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @Test
+    public void testParseCtNew() {
+        assumeTrue(USING_LE);
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
+        assertNotNull(msg);
+        assertTrue(msg instanceof ConntrackMessage);
+        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
+
+        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
+        assertNotNull(hdr);
+        assertEquals(140, hdr.nlmsg_len);
+        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
+        assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL),
+                hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
+        assertNotNull(nfmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
+        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
+        assertEquals((short) 0x1234, nfmsgHdr.res_id);
+
+        assertEquals(InetAddress.parseNumericAddress("192.168.80.12"),
+                conntrackMessage.tupleOrig.srcIp);
+        assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
+                conntrackMessage.tupleOrig.dstIp);
+        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
+        assertEquals((short) 62449, conntrackMessage.tupleOrig.srcPort);
+        assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+        assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
+                conntrackMessage.tupleReply.srcIp);
+        assertEquals(InetAddress.parseNumericAddress("100.81.179.1"),
+                conntrackMessage.tupleReply.dstIp);
+        assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleReply.protoNum);
+        assertEquals((short) 443, conntrackMessage.tupleReply.srcPort);
+        assertEquals((short) 62449, conntrackMessage.tupleReply.dstPort);
+
+        assertEquals(0x198, conntrackMessage.status);
+        assertEquals(120, conntrackMessage.timeoutSec);
+    }
+
+    @Test
+    public void testParseTruncation() {
+        assumeTrue(USING_LE);
+
+        // Expect no crash while parsing the truncated message which has been truncated to every
+        // length between 0 and its full length - 1.
+        for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
+            final byte[] truncated = Arrays.copyOfRange(CT_V4NEW_TCP_BYTES, 0, len);
+
+            final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
+            byteBuffer.order(ByteOrder.nativeOrder());
+            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+                    OsConstants.NETLINK_NETFILTER);
+        }
+    }
+
+    @Test
+    public void testParseTruncationWithInvalidByte() {
+        assumeTrue(USING_LE);
+
+        // Expect no crash while parsing the message which is truncated by invalid bytes. The
+        // message has been truncated to every length between 0 and its full length - 1.
+        for (byte invalid : new byte[]{(byte) 0x00, (byte) 0xff}) {
+            for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
+                final byte[] truncated = new byte[CT_V4NEW_TCP_BYTES.length];
+                Arrays.fill(truncated, (byte) invalid);
+                System.arraycopy(CT_V4NEW_TCP_BYTES, 0, truncated, 0, len);
+
+                final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
+                byteBuffer.order(ByteOrder.nativeOrder());
+                final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+                        OsConstants.NETLINK_NETFILTER);
+            }
+        }
+    }
+
+    // Malformed conntrack messages.
+    public static final String CT_MALFORMED_HEX =
+            // CHECKSTYLE:OFF IndentationCheck
+            // <--           nlmsghr           -->|<-nfgenmsg->|<--    CTA_TUPLE_ORIG     -->|
+            // CTA_TUPLE_ORIG has no nla_value.
+            "18000000 0001 0006 00000000 00000000   02 00 0000 0400 0180"
+            // nested CTA_TUPLE_IP has no nla_value.
+            + "1C000000 0001 0006 00000000 00000000 02 00 0000 0800 0180 0400 0180"
+            // nested CTA_IP_V4_SRC has no nla_value.
+            + "20000000 0001 0006 00000000 00000000 02 00 0000 0C00 0180 0800 0180 0400 0100"
+            // nested CTA_TUPLE_PROTO has no nla_value.
+            // <--           nlmsghr           -->|<-nfgenmsg->|<--    CTA_TUPLE_ORIG
+            + "30000000 0001 0006 00000000 00000000 02 00 0000 1C00 0180 1400 0180 0800 0100"
+            //                                  -->|
+            + "C0A8500C 0800 0200 8C700874 0400 0280";
+            // CHECKSTYLE:ON IndentationCheck
+    public static final byte[] CT_MALFORMED_BYTES =
+            HexEncoding.decode(CT_MALFORMED_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @Test
+    public void testParseMalformation() {
+        assumeTrue(USING_LE);
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_MALFORMED_BYTES);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        // Expect no crash while parsing the malformed message.
+        int messageCount = 0;
+        while (byteBuffer.remaining() > 0) {
+            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+                    OsConstants.NETLINK_NETFILTER);
+            messageCount++;
+        }
+        assertEquals(4, messageCount);
+    }
+
+    @Test
+    public void testToString() {
+        assumeTrue(USING_LE);
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
+        assertNotNull(msg);
+        assertTrue(msg instanceof ConntrackMessage);
+        final ConntrackMessage conntrackMessage = (ConntrackMessage) msg;
+
+        // Bug: "nlmsg_flags{1536(NLM_F_MATCH))" is not correct because StructNlMsgHdr
+        // #stringForNlMsgFlags can't convert all flags (ex: NLM_F_CREATE) and can't distinguish
+        // the flags which have the same value (ex: NLM_F_MATCH <0x200> and NLM_F_EXCL <0x200>).
+        // The flags output string should be "NLM_F_CREATE|NLM_F_EXCL" in this case.
+        // TODO: correct the flag converted string once #stringForNlMsgFlags does.
+        final String expected = ""
+                + "ConntrackMessage{"
+                + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{140}, nlmsg_type{256(IPCTNL_MSG_CT_NEW)}, "
+                + "nlmsg_flags{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "nfgenmsg{NfGenMsg{ nfgen_family{AF_INET}, version{0}, res_id{4660} }}, "
+                + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
+                + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
+                + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, "
+                + "timeout_sec{120}}";
+        assertEquals(expected, conntrackMessage.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
new file mode 100644
index 0000000..65e99f8
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2018 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.net.module.util.netlink;
+
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+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 org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.util.ArraySet;
+import android.util.Range;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InetDiagSocketTest {
+    // ::FFFF:192.0.2.1
+    private static final byte[] SRC_V4_MAPPED_V6_ADDRESS_BYTES = {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+    };
+    // ::FFFF:192.0.2.2
+    private static final byte[] DST_V4_MAPPED_V6_ADDRESS_BYTES = {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+    };
+
+    // Hexadecimal representation of InetDiagReqV2 request.
+    private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0103" +         // flags = NLM_F_REQUEST | NLM_F_DUMP
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "02" +           // family = AF_INET
+            "11" +           // protcol = IPPROTO_UDP
+            "00" +           // idiag_ext
+            "00" +           // pad
+            "ffffffff" +     // idiag_states
+            // inet_diag_sockid
+            "a5de" +         // idiag_sport = 42462
+            "b971" +         // idiag_dport = 47473
+            "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
+            "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+            "00000000" +     // idiag_if
+            "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+    private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false);
+
+    @Test
+    public void testInetDiagReqV2UdpInet4() throws Exception {
+        InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"),
+                42462);
+        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+                47473);
+        final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET,
+                (short) (NLM_F_REQUEST | NLM_F_DUMP));
+        assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg);
+    }
+
+    // Hexadecimal representation of InetDiagReqV2 request.
+    private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0100" +         // flags = NLM_F_REQUEST
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "0a" +           // family = AF_INET6
+            "06" +           // protcol = IPPROTO_TCP
+            "00" +           // idiag_ext
+            "00" +           // pad
+            "ffffffff" +     // idiag_states
+                // inet_diag_sockid
+                "a5de" +         // idiag_sport = 42462
+                "b971" +         // idiag_dport = 47473
+                "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b
+                "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+                "00000000" +     // idiag_if
+                "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false);
+
+    @Test
+    public void testInetDiagReqV2TcpInet6() throws Exception {
+        InetSocketAddress local = new InetSocketAddress(
+                InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
+        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+                47473);
+        byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
+                NLM_F_REQUEST);
+
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
+    }
+
+    // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO.
+    private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0100" +         // flags = NLM_F_REQUEST
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "02" +           // family = AF_INET
+            "06" +           // protcol = IPPROTO_TCP
+            "02" +           // idiag_ext = INET_DIAG_INFO
+            "00" +           // pad
+            "ffffffff" +   // idiag_states
+            // inet_diag_sockid
+            "3039" +         // idiag_sport = 12345
+            "d431" +         // idiag_dport = 54321
+            "01020304000000000000000000000000" + // idiag_src = 1.2.3.4
+            "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4
+            "00000000" +     // idiag_if
+            "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+
+    private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false);
+    private static final int TCP_ALL_STATES = 0xffffffff;
+    @Test
+    public void testInetDiagReqV2TcpInetWithExt() throws Exception {
+        InetSocketAddress local = new InetSocketAddress(
+                InetAddress.getByName("1.2.3.4"), 12345);
+        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
+                54321);
+        byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET,
+                NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES);
+
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg);
+
+        local = new InetSocketAddress(
+                InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
+        remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+                47473);
+        msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
+                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
+    }
+
+    // Hexadecimal representation of InetDiagReqV2 request with no socket specified.
+    private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0100" +         // flags = NLM_F_REQUEST
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "0a" +           // family = AF_INET6
+            "06" +           // protcol = IPPROTO_TCP
+            "00" +           // idiag_ext
+            "00" +           // pad
+            "ffffffff" +     // idiag_states
+            // inet_diag_sockid
+            "0000" +         // idiag_sport
+            "0000" +         // idiag_dport
+            "00000000000000000000000000000000" + // idiag_src
+            "00000000000000000000000000000000" + // idiag_dst
+            "00000000" +     // idiag_if
+            "0000000000000000"; // idiag_cookie
+
+    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false);
+
+    @Test
+    public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception {
+        InetSocketAddress local = new InetSocketAddress(
+                InetAddress.getByName("fe80::fe6a:ed4b"), 12345);
+        InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
+                54321);
+        // Verify no socket specified if either local or remote socket address is null.
+        byte[] msgExt = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
+                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+        byte[] msg;
+        try {
+            msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6,
+                    NLM_F_REQUEST);
+            fail("Both remote and local should be null, expected UnknownHostException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6,
+                    NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+            fail("Both remote and local should be null, expected UnknownHostException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
+                NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msg);
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msgExt);
+    }
+
+    // Hexadecimal representation of InetDiagReqV2 request with v4-mapped v6 address
+    private static final String INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0100" +         // flags = NLM_F_REQUEST
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "0a" +           // family = AF_INET6
+            "06" +           // protcol = IPPROTO_TCP
+            "00" +           // idiag_ext
+            "00" +           // pad
+            "ffffffff" +     // idiag_states
+            // inet_diag_sockid
+            "a817" +     // idiag_sport = 43031
+            "960f" +     // idiag_dport = 38415
+            "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+            "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+            "00000000" +     // idiag_if
+            "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+
+    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX.toCharArray(), false);
+
+    @Test
+    public void testInetDiagReqV2TcpInet6V4Mapped() throws Exception {
+        final Inet6Address srcAddr = Inet6Address.getByAddress(
+                null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+        final Inet6Address dstAddr = Inet6Address.getByAddress(
+                null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+        final byte[] msg = InetDiagMessage.inetDiagReqV2(
+                IPPROTO_TCP,
+                new InetSocketAddress(srcAddr, 43031),
+                new InetSocketAddress(dstAddr, 38415),
+                AF_INET6,
+                NLM_F_REQUEST);
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES, msg);
+    }
+
+    // Hexadecimal representation of InetDiagReqV2 request with SOCK_DESTROY
+    private static final String INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX =
+            // struct nlmsghdr
+            "48000000" +     // length = 72
+            "1500" +         // type = SOCK_DESTROY
+            "0500" +         // flags = NLM_F_REQUEST | NLM_F_ACK
+            "00000000" +     // seqno
+            "00000000" +     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "0a" +           // family = AF_INET6
+            "06" +           // protcol = IPPROTO_TCP
+            "00" +           // idiag_ext
+            "00" +           // pad
+            "ffffffff" +     // idiag_states = TCP_ALL_STATES
+            // inet_diag_sockid
+            "a817" +     // idiag_sport = 43031
+            "960f" +     // idiag_dport = 38415
+            "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1
+            "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
+            "07000000" + // idiag_if = 7
+            "5800000000000000"; // idiag_cookie = 88
+
+    private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES =
+            HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX.toCharArray(), false);
+
+    @Test
+    public void testInetDiagReqV2TcpInet6Destroy() throws Exception {
+        final StructInetDiagSockId sockId = new StructInetDiagSockId(
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415),
+                7  /* ifIndex */,
+                88 /* cookie */);
+        final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, sockId, AF_INET6,
+                SOCK_DESTROY, (short) (NLM_F_REQUEST | NLM_F_ACK), 0 /* pad */, 0 /* idiagExt */,
+                TCP_ALL_STATES);
+
+        assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES, msg);
+    }
+
+    private void assertNlMsgHdr(StructNlMsgHdr hdr, short type, short flags, int seq, int pid) {
+        assertNotNull(hdr);
+        assertEquals(type, hdr.nlmsg_type);
+        assertEquals(flags, hdr.nlmsg_flags);
+        assertEquals(seq, hdr.nlmsg_seq);
+        assertEquals(pid, hdr.nlmsg_pid);
+    }
+
+    private void assertInetDiagSockId(StructInetDiagSockId sockId,
+            InetSocketAddress locSocketAddress, InetSocketAddress remSocketAddress,
+            int ifIndex, long cookie) {
+        assertEquals(locSocketAddress, sockId.locSocketAddress);
+        assertEquals(remSocketAddress, sockId.remSocketAddress);
+        assertEquals(ifIndex, sockId.ifIndex);
+        assertEquals(cookie, sockId.cookie);
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX1 =
+            // struct nlmsghdr
+            "58000000" +     // length = 88
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0200" +         // flags = NLM_F_MULTI
+            "00000000" +     // seqno
+            "f5220000" +     // pid
+            // struct inet_diag_msg
+            "0a" +           // family = AF_INET6
+            "01" +           // idiag_state = 1
+            "02" +           // idiag_timer = 2
+            "ff" +           // idiag_retrans = 255
+                // inet_diag_sockid
+                "a817" +     // idiag_sport = 43031
+                "960f" +     // idiag_dport = 38415
+                "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1
+                "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
+                "07000000" + // idiag_if = 7
+                "5800000000000000" + // idiag_cookie = 88
+            "04000000" +     // idiag_expires = 4
+            "05000000" +     // idiag_rqueue = 5
+            "06000000" +     // idiag_wqueue = 6
+            "a3270000" +     // idiag_uid = 10147
+            "a57e19f0";      // idiag_inode = 4028202661
+
+    private void assertInetDiagMsg1(final NetlinkMessage msg) {
+        assertNotNull(msg);
+
+        assertTrue(msg instanceof InetDiagMessage);
+        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+
+        assertNlMsgHdr(inetDiagMsg.getHeader(),
+                NetlinkConstants.SOCK_DIAG_BY_FAMILY,
+                StructNlMsgHdr.NLM_F_MULTI,
+                0    /* seq */,
+                8949 /* pid */);
+
+        assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+        assertEquals(1, inetDiagMsg.inetDiagMsg.idiag_state);
+        assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_timer);
+        assertEquals(255, inetDiagMsg.inetDiagMsg.idiag_retrans);
+        assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415),
+                7  /* ifIndex */,
+                88 /* cookie */);
+        assertEquals(4, inetDiagMsg.inetDiagMsg.idiag_expires);
+        assertEquals(5, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+        assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+        assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
+        assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode);
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX2 =
+            // struct nlmsghdr
+            "58000000" +     // length = 88
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0200" +         // flags = NLM_F_MULTI
+            "00000000" +     // seqno
+            "f5220000" +     // pid
+            // struct inet_diag_msg
+            "0a" +           // family = AF_INET6
+            "02" +           // idiag_state = 2
+            "10" +           // idiag_timer = 16
+            "20" +           // idiag_retrans = 32
+                // inet_diag_sockid
+                "a845" +     // idiag_sport = 43077
+                "01bb" +     // idiag_dport = 443
+                "20010db8000000000000000000000003" + // idiag_src = 2001:db8::3
+                "20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4
+                "08000000" + // idiag_if = 8
+                "6300000000000000" + // idiag_cookie = 99
+            "30000000" +     // idiag_expires = 48
+            "40000000" +     // idiag_rqueue = 64
+            "50000000" +     // idiag_wqueue = 80
+            "39300000" +     // idiag_uid = 12345
+            "851a0000";      // idiag_inode = 6789
+
+    private void assertInetDiagMsg2(final NetlinkMessage msg) {
+        assertNotNull(msg);
+
+        assertTrue(msg instanceof InetDiagMessage);
+        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+
+        assertNlMsgHdr(inetDiagMsg.getHeader(),
+                NetlinkConstants.SOCK_DIAG_BY_FAMILY,
+                StructNlMsgHdr.NLM_F_MULTI,
+                0    /* seq */,
+                8949 /* pid */);
+
+        assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+        assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_state);
+        assertEquals(16, inetDiagMsg.inetDiagMsg.idiag_timer);
+        assertEquals(32, inetDiagMsg.inetDiagMsg.idiag_retrans);
+        assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::3"), 43077),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::4"), 443),
+                8  /* ifIndex */,
+                99 /* cookie */);
+        assertEquals(48, inetDiagMsg.inetDiagMsg.idiag_expires);
+        assertEquals(64, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+        assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+        assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
+        assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode);
+    }
+
+    private static final byte[] INET_DIAG_MSG_BYTES =
+            HexEncoding.decode(INET_DIAG_MSG_HEX1.toCharArray(), false);
+
+    @Test
+    public void testParseInetDiagResponse() throws Exception {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+    }
+
+
+    private static final byte[] INET_DIAG_MSG_BYTES_MULTIPLE =
+            HexEncoding.decode((INET_DIAG_MSG_HEX1 + INET_DIAG_MSG_HEX2).toCharArray(), false);
+
+    @Test
+    public void testParseInetDiagResponseMultiple() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES_MULTIPLE);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+        assertInetDiagMsg2(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+    }
+
+    private static final String INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX =
+            "a845" +     // idiag_sport = 43077
+            "01bb" +     // idiag_dport = 443
+            "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+            "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+            "08000000" + // idiag_if = 8
+            "6300000000000000"; // idiag_cookie = 99
+
+    private static final byte[] INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES =
+            HexEncoding.decode(INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX.toCharArray(), false);
+
+    @Test
+    public void testParseAndPackInetDiagSockIdV4MappedV6() {
+        final ByteBuffer parseByteBuffer = ByteBuffer.wrap(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES);
+        parseByteBuffer.order(ByteOrder.nativeOrder());
+        final StructInetDiagSockId diagSockId =
+                StructInetDiagSockId.parse(parseByteBuffer, (short) AF_INET6);
+        assertNotNull(diagSockId);
+
+        final ByteBuffer packByteBuffer =
+                ByteBuffer.allocate(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES.length);
+        diagSockId.pack(packByteBuffer);
+
+        // Move position to the head since ByteBuffer#equals compares the values from the current
+        // position.
+        parseByteBuffer.position(0);
+        packByteBuffer.position(0);
+        assertEquals(parseByteBuffer, packByteBuffer);
+    }
+
+    // Hexadecimal representation of InetDiagMessage with v4-mapped v6 address
+    private static final String INET_DIAG_MSG_V4_MAPPED_V6_HEX =
+            // struct nlmsghdr
+            "58000000" +     // length = 88
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0200" +         // flags = NLM_F_MULTI
+            "00000000" +     // seqno
+            "f5220000" +     // pid
+            // struct inet_diag_msg
+            "0a" +           // family = AF_INET6
+            "01" +           // idiag_state = 1
+            "02" +           // idiag_timer = 2
+            "03" +           // idiag_retrans = 3
+                // inet_diag_sockid
+                "a817" +     // idiag_sport = 43031
+                "960f" +     // idiag_dport = 38415
+                "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+                "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+                "07000000" + // idiag_if = 7
+                "5800000000000000" + // idiag_cookie = 88
+            "04000000" +     // idiag_expires = 4
+            "05000000" +     // idiag_rqueue = 5
+            "06000000" +     // idiag_wqueue = 6
+            "a3270000" +     // idiag_uid = 10147
+            "A57E1900";      // idiag_inode = 1670821
+
+    private static final byte[] INET_DIAG_MSG_V4_MAPPED_V6_BYTES =
+            HexEncoding.decode(INET_DIAG_MSG_V4_MAPPED_V6_HEX.toCharArray(), false);
+
+    @Test
+    public void testParseInetDiagResponseV4MappedV6() throws Exception {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_V4_MAPPED_V6_BYTES);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG);
+
+        assertNotNull(msg);
+        assertTrue(msg instanceof InetDiagMessage);
+        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+        final Inet6Address srcAddr = Inet6Address.getByAddress(
+                null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+        final Inet6Address dstAddr = Inet6Address.getByAddress(
+                null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+        assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+                new InetSocketAddress(srcAddr, 43031),
+                new InetSocketAddress(dstAddr, 38415),
+                7  /* ifIndex */,
+                88 /* cookie */);
+    }
+
+    private void doTestIsLoopback(InetAddress srcAddr, InetAddress dstAddr, boolean expected) {
+        final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr());
+        inetDiagMsg.inetDiagMsg.id = new StructInetDiagSockId(
+                new InetSocketAddress(srcAddr, 43031),
+                new InetSocketAddress(dstAddr, 38415)
+        );
+
+        assertEquals(expected, InetDiagMessage.isLoopback(inetDiagMsg));
+    }
+
+    @Test
+    public void testIsLoopback() {
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("127.0.0.1"),
+                InetAddresses.parseNumericAddress("192.0.2.1"),
+                true
+        );
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("192.0.2.1"),
+                InetAddresses.parseNumericAddress("127.7.7.7"),
+                true
+        );
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("::1"),
+                InetAddresses.parseNumericAddress("::1"),
+                true
+        );
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("::1"),
+                InetAddresses.parseNumericAddress("2001:db8::1"),
+                true
+        );
+    }
+
+    @Test
+    public void testIsLoopbackSameSrcDstAddress()  {
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("192.0.2.1"),
+                InetAddresses.parseNumericAddress("192.0.2.1"),
+                true
+        );
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("2001:db8::1"),
+                InetAddresses.parseNumericAddress("2001:db8::1"),
+                true
+        );
+    }
+
+    @Test
+    public void testIsLoopbackNonLoopbackSocket()  {
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("192.0.2.1"),
+                InetAddresses.parseNumericAddress("192.0.2.2"),
+                false
+        );
+        doTestIsLoopback(
+                InetAddresses.parseNumericAddress("2001:db8::1"),
+                InetAddresses.parseNumericAddress("2001:db8::2"),
+                false
+        );
+    }
+
+    @Test
+    public void testIsLoopbackV4MappedV6() throws UnknownHostException {
+        // ::FFFF:127.1.2.3
+        final byte[] addrLoopbackByte = {
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+                (byte) 0x7f, (byte) 0x01, (byte) 0x02, (byte) 0x03,
+        };
+        // ::FFFF:192.0.2.1
+        final byte[] addrNonLoopbackByte1 = {
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+        };
+        // ::FFFF:192.0.2.2
+        final byte[] addrNonLoopbackByte2 = {
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+        };
+
+        final Inet6Address addrLoopback = Inet6Address.getByAddress(null, addrLoopbackByte, -1);
+        final Inet6Address addrNonLoopback1 =
+                Inet6Address.getByAddress(null, addrNonLoopbackByte1, -1);
+        final Inet6Address addrNonLoopback2 =
+                Inet6Address.getByAddress(null, addrNonLoopbackByte2, -1);
+
+        doTestIsLoopback(addrLoopback, addrNonLoopback1, true);
+        doTestIsLoopback(addrNonLoopback1, addrNonLoopback2, false);
+        doTestIsLoopback(addrNonLoopback1, addrNonLoopback1, true);
+    }
+
+    private void doTestContainsUid(final int uid, final Set<Range<Integer>> ranges,
+            final boolean expected) {
+        final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr());
+        inetDiagMsg.inetDiagMsg.idiag_uid = uid;
+        assertEquals(expected, InetDiagMessage.containsUid(inetDiagMsg, ranges));
+    }
+
+    @Test
+    public void testContainsUid() {
+        doTestContainsUid(77 /* uid */,
+                new ArraySet<>(List.of(new Range<>(0, 100))),
+                true /* expected */);
+        doTestContainsUid(77 /* uid */,
+                new ArraySet<>(List.of(new Range<>(77, 77), new Range<>(100, 200))),
+                true /* expected */);
+
+        doTestContainsUid(77 /* uid */,
+                new ArraySet<>(List.of(new Range<>(100, 200))),
+                false /* expected */);
+        doTestContainsUid(77 /* uid */,
+                new ArraySet<>(List.of(new Range<>(0, 76), new Range<>(78, 100))),
+                false /* expected */);
+    }
+
+    private void doTestIsAdbSocket(final int uid, final boolean expected) {
+        final InetDiagMessage inetDiagMsg = new InetDiagMessage(new StructNlMsgHdr());
+        inetDiagMsg.inetDiagMsg.idiag_uid = uid;
+        inetDiagMsg.inetDiagMsg.id = new StructInetDiagSockId(
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 38417),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415)
+        );
+        assertEquals(expected, InetDiagMessage.isAdbSocket(inetDiagMsg));
+    }
+
+    @Test
+    public void testIsAdbSocket() {
+        final int appUid = 10108;
+        doTestIsAdbSocket(SHELL_UID,  true /* expected */);
+        doTestIsAdbSocket(ROOT_UID, false /* expected */);
+        doTestIsAdbSocket(appUid, false /* expected */);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
new file mode 100644
index 0000000..4fc5ec2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NduseroptMessageTest {
+
+    private static final byte ICMP_TYPE_RA = (byte) 134;
+
+    private static final int IFINDEX1 = 15715755;
+    private static final int IFINDEX2 = 1431655765;
+
+    // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding.
+    private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
+
+    // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
+    private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000";
+
+    // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
+    private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000";
+
+    // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064
+    private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006";
+
+    // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8
+    private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008";
+
+    private static final InetAddress SADDR1 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX1);
+    private static final InetAddress SADDR2 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX2);
+
+    private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR;
+    private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR;
+
+    @Test
+    public void testParsing() {
+        NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY));
+        assertMatches(AF_INET6, 0, IFINDEX1, ICMP_TYPE_RA, (byte) 0, SADDR1, msg);
+        assertNull(msg.option);
+
+        msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
+        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
+        assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
+    }
+
+    @Test
+    public void testParseWithinNetlinkMessage() throws Exception {
+        // A NduseroptMessage inside a netlink message. Ensure that it parses the same way both by
+        // parsing the netlink message via NetlinkMessage.parse() and by parsing the option itself
+        // with NduseroptMessage.parse().
+        final String hexBytes =
+                "44000000440000000000000000000000"             // len=68, RTM_NEWNDUSEROPT
+                + "0A0010001E0000008600000000000000"           // IPv6, opt_bytes=16, ifindex=30, RA
+                + "260202580064FF9B0000000000000000"           // pref64, prefix=64:ff9b::/96, 600
+                + "14000100FE800000000000000250B6FFFEB7C499";  // srcaddr=fe80::250:b6ff:feb7:c499
+
+        ByteBuffer buf = toBuffer(hexBytes);
+        assertEquals(68, buf.limit());
+        buf.order(ByteOrder.nativeOrder());
+
+        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
+        assertNotNull(nlMsg);
+        assertTrue(nlMsg instanceof NduseroptMessage);
+
+        NduseroptMessage msg = (NduseroptMessage) nlMsg;
+        InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
+        assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
+        assertPref64Option("64:ff9b::/96", msg.option);
+
+        final String hexBytesWithoutHeader = hexBytes.substring(StructNlMsgHdr.STRUCT_SIZE * 2);
+        ByteBuffer bufWithoutHeader = toBuffer(hexBytesWithoutHeader);
+        assertEquals(52, bufWithoutHeader.limit());
+        msg = parseNduseroptMessage(bufWithoutHeader);
+        assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
+        assertPref64Option("64:ff9b::/96", msg.option);
+    }
+
+    @Test
+    public void testParseRdnssOptionWithinNetlinkMessage() throws Exception {
+        final String hexBytes =
+                "4C000000440000000000000000000000"
+                + "0A0018001E0000008600000000000000"
+                + "1903000000001770FD123456789000000000000000000001"  // RDNSS option
+                + "14000100FE800000000000000250B6FFFEB7C499";
+
+        ByteBuffer buf = toBuffer(hexBytes);
+        assertEquals(76, buf.limit());
+        buf.order(ByteOrder.nativeOrder());
+
+        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
+        assertNotNull(nlMsg);
+        assertTrue(nlMsg instanceof NduseroptMessage);
+
+        NduseroptMessage msg = (NduseroptMessage) nlMsg;
+        InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
+        assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
+        assertRdnssOption(msg.option, 6000 /* lifetime */,
+                (Inet6Address) InetAddresses.parseNumericAddress("fd12:3456:7890::1"));
+    }
+
+    @Test
+    public void testParseTruncatedRdnssOptionWithinNetlinkMessage() throws Exception {
+        final String truncatedHexBytes =
+                "38000000440000000000000000000000"
+                + "0A0018001E0000008600000000000000"
+                + "1903000000001770FD123456789000000000000000000001";  // RDNSS option
+
+        ByteBuffer buf = toBuffer(truncatedHexBytes);
+        buf.order(ByteOrder.nativeOrder());
+        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
+        assertNull(nlMsg);
+    }
+
+    @Test
+    public void testParseUnknownOptionWithinNetlinkMessage() throws Exception {
+        final String hexBytes =
+                "4C000000440000000000000000000000"
+                + "0A0018001E0000008600000000000000"
+                + "310300000000177006676F6F676C652E03636F6D00000000"  // DNSSL option: "google.com"
+                + "14000100FE800000000000000250B6FFFEB7C499";
+
+        ByteBuffer buf = toBuffer(hexBytes);
+        assertEquals(76, buf.limit());
+        buf.order(ByteOrder.nativeOrder());
+
+        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
+        assertNotNull(nlMsg);
+        assertTrue(nlMsg instanceof NduseroptMessage);
+
+        NduseroptMessage msg = (NduseroptMessage) nlMsg;
+        InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
+        assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
+        assertEquals(NdOption.UNKNOWN, msg.option);
+    }
+
+    @Test
+    public void testUnknownOption() {
+        ByteBuffer buf = toBuffer(MSG_PREF64);
+        // Replace the PREF64 option type (38) with an unknown option number.
+        final int optionStart = NduseroptMessage.STRUCT_SIZE;
+        assertEquals(38, buf.get(optionStart));
+        buf.put(optionStart, (byte) 42);
+
+        NduseroptMessage msg = parseNduseroptMessage(buf);
+        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
+        assertEquals(NdOption.UNKNOWN, msg.option);
+
+        buf.flip();
+        assertEquals(42, buf.get(optionStart));
+        buf.put(optionStart, (byte) 38);
+
+        msg = parseNduseroptMessage(buf);
+        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
+        assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
+    }
+
+    @Test
+    public void testZeroLengthOption() {
+        // Make sure an unknown option with a 0-byte length is ignored and parsing continues with
+        // the address, which comes after it.
+        final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR;
+        ByteBuffer buf = toBuffer(hexString);
+        assertEquals(52, buf.limit());
+        NduseroptMessage msg = parseNduseroptMessage(buf);
+        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
+        assertNull(msg.option);
+    }
+
+    @Test
+    public void testTooLongOption() {
+        // Make sure that if an option's length is too long, it's ignored and parsing continues with
+        // the address, which comes after it.
+        final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR;
+        ByteBuffer buf = toBuffer(hexString);
+        assertEquals(52, buf.limit());
+        NduseroptMessage msg = parseNduseroptMessage(buf);
+        assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
+        assertNull(msg.option);
+    }
+
+    @Test
+    public void testOptionsTooLong() {
+        // Header claims 32 bytes of options. Buffer ends before options end.
+        String hexString = HDR_32BYTE + OPT_PREF64;
+        ByteBuffer buf = toBuffer(hexString);
+        assertEquals(32, buf.limit());
+        assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
+
+        // Header claims 32 bytes of options. Buffer ends at end of options with no source address.
+        hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64;
+        buf = toBuffer(hexString);
+        assertEquals(48, buf.limit());
+        assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
+    }
+
+    @Test
+    public void testTruncation() {
+        final int optLen = MSG_PREF64.length() / 2;  // 1 byte = 2 hex chars
+        for (int len = 0; len < optLen; len++) {
+            ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2));
+            NduseroptMessage msg = parseNduseroptMessage(buf);
+            if (len < optLen) {
+                assertNull(msg);
+            } else {
+                assertNotNull(msg);
+                assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
+            }
+        }
+    }
+
+    @Test
+    public void testToString() {
+        NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
+        assertNotNull(msg);
+        assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)",
+                msg.toString());
+    }
+
+    // Convenience method to parse a NduseroptMessage that's not part of a netlink message.
+    private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) {
+        return NduseroptMessage.parse(null, buf);
+    }
+
+    private ByteBuffer toBuffer(String hexString) {
+        return ByteBuffer.wrap(HexEncoding.decode(hexString));
+    }
+
+    private void assertMatches(int family, int optsLen, int ifindex, byte icmpType,
+            byte icmpCode, InetAddress srcaddr, NduseroptMessage msg) {
+        assertNotNull(msg);
+        assertEquals(family, msg.family);
+        assertEquals(ifindex, msg.ifindex);
+        assertEquals(optsLen, msg.opts_len);
+        assertEquals(icmpType, msg.icmp_type);
+        assertEquals(icmpCode, msg.icmp_code);
+        assertEquals(srcaddr, msg.srcaddr);
+    }
+
+    private void assertPref64Option(String prefix, NdOption opt) {
+        assertNotNull(opt);
+        assertTrue(opt instanceof StructNdOptPref64);
+        StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt;
+        assertEquals(new IpPrefix(prefix), pref64Opt.prefix);
+    }
+
+    private void assertRdnssOption(NdOption opt, long lifetime, Inet6Address... servers) {
+        assertNotNull(opt);
+        assertTrue(opt instanceof StructNdOptRdnss);
+        StructNdOptRdnss rdnss = (StructNdOptRdnss) opt;
+        assertEquals(StructNdOptRdnss.TYPE, rdnss.type);
+        assertEquals((byte) (servers.length * 2 + 1), rdnss.header.length);
+        assertEquals(lifetime, rdnss.header.lifetime);
+        assertArrayEquals(servers, rdnss.servers);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
new file mode 100644
index 0000000..143e4d4
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+import static android.system.OsConstants.NETLINK_NETFILTER;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_CTRZERO;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_DYING;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS_CPU;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_UNCONFIRMED;
+import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
+import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK;
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_ERROR;
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_NOOP;
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_OVERRUN;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELADDR;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELLINK;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELROUTE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELRULE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETADDR;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETNEIGH;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETROUTE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETRULE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNDUSEROPT;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWROUTE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWRULE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_SETLINK;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkConstantsTest {
+    private static final short UNKNOWN_FAMILY = 1234;
+
+    private short makeCtType(short msgType) {
+        return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType);
+    }
+
+    @Test
+    public void testStringForNlMsgType() {
+        assertEquals("RTM_NEWLINK", stringForNlMsgType(RTM_NEWLINK, NETLINK_ROUTE));
+        assertEquals("RTM_DELLINK", stringForNlMsgType(RTM_DELLINK, NETLINK_ROUTE));
+        assertEquals("RTM_GETLINK", stringForNlMsgType(RTM_GETLINK, NETLINK_ROUTE));
+        assertEquals("RTM_SETLINK", stringForNlMsgType(RTM_SETLINK, NETLINK_ROUTE));
+        assertEquals("RTM_NEWADDR", stringForNlMsgType(RTM_NEWADDR, NETLINK_ROUTE));
+        assertEquals("RTM_DELADDR", stringForNlMsgType(RTM_DELADDR, NETLINK_ROUTE));
+        assertEquals("RTM_GETADDR", stringForNlMsgType(RTM_GETADDR, NETLINK_ROUTE));
+        assertEquals("RTM_NEWROUTE", stringForNlMsgType(RTM_NEWROUTE, NETLINK_ROUTE));
+        assertEquals("RTM_DELROUTE", stringForNlMsgType(RTM_DELROUTE, NETLINK_ROUTE));
+        assertEquals("RTM_GETROUTE", stringForNlMsgType(RTM_GETROUTE, NETLINK_ROUTE));
+        assertEquals("RTM_NEWNEIGH", stringForNlMsgType(RTM_NEWNEIGH, NETLINK_ROUTE));
+        assertEquals("RTM_DELNEIGH", stringForNlMsgType(RTM_DELNEIGH, NETLINK_ROUTE));
+        assertEquals("RTM_GETNEIGH", stringForNlMsgType(RTM_GETNEIGH, NETLINK_ROUTE));
+        assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE));
+        assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE));
+        assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE));
+        assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE));
+
+        assertEquals("SOCK_DIAG_BY_FAMILY",
+                stringForNlMsgType(SOCK_DIAG_BY_FAMILY, NETLINK_INET_DIAG));
+
+        assertEquals("IPCTNL_MSG_CT_NEW",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_DELETE",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_DELETE), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET_CTRZERO",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_CTRZERO), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET_STATS_CPU",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS_CPU), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET_STATS",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET_DYING",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_DYING), NETLINK_NETFILTER));
+        assertEquals("IPCTNL_MSG_CT_GET_UNCONFIRMED",
+                stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_UNCONFIRMED), NETLINK_NETFILTER));
+    }
+
+    @Test
+    public void testStringForNlMsgType_ControlMessage() {
+        for (int family : new int[]{NETLINK_ROUTE, NETLINK_INET_DIAG, NETLINK_NETFILTER}) {
+            assertEquals("NLMSG_NOOP", stringForNlMsgType(NLMSG_NOOP, family));
+            assertEquals("NLMSG_ERROR", stringForNlMsgType(NLMSG_ERROR, family));
+            assertEquals("NLMSG_DONE", stringForNlMsgType(NLMSG_DONE, family));
+            assertEquals("NLMSG_OVERRUN", stringForNlMsgType(NLMSG_OVERRUN, family));
+        }
+    }
+
+    @Test
+    public void testStringForNlMsgType_UnknownFamily() {
+        assertTrue(stringForNlMsgType(RTM_NEWLINK, UNKNOWN_FAMILY).startsWith("unknown"));
+        assertTrue(stringForNlMsgType(SOCK_DIAG_BY_FAMILY, UNKNOWN_FAMILY).startsWith("unknown"));
+        assertTrue(stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), UNKNOWN_FAMILY)
+                .startsWith("unknown"));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java
new file mode 100644
index 0000000..ab7d9cf
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkErrorMessageTest {
+    private static final String TAG = "NetlinkErrorMessageTest";
+
+    // Hexadecimal representation of packet capture.
+    public static final String NLM_ERROR_OK_HEX =
+            // struct nlmsghdr
+            "24000000" +     // length = 36
+            "0200"     +     // type = 2 (NLMSG_ERROR)
+            "0000"     +     // flags
+            "26350000" +     // seqno
+            "64100000" +     // pid = userspace process
+            // error integer
+            "00000000" +     // "errno" (0 == OK)
+            // struct nlmsghdr
+            "30000000" +     // length (48) of original request
+            "1C00"     +     // type = 28 (RTM_NEWNEIGH)
+            "0501"     +     // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
+            "26350000" +     // seqno
+            "00000000";      // pid = kernel
+    public static final byte[] NLM_ERROR_OK =
+            HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
+
+    @Test
+    public void testParseNlmErrorOk() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof NetlinkErrorMessage);
+        final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg;
+
+        final StructNlMsgHdr hdr = errorMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(36, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(13606, hdr.nlmsg_seq);
+        assertEquals(4196, hdr.nlmsg_pid);
+
+        final StructNlMsgErr err = errorMsg.getNlMsgError();
+        assertNotNull(err);
+        assertEquals(0, err.error);
+        assertNotNull(err.msg);
+        assertEquals(48, err.msg.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type);
+        assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags);
+        assertEquals(13606, err.msg.nlmsg_seq);
+        assertEquals(0, err.msg.nlmsg_pid);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
new file mode 100644
index 0000000..3a72dd1
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2015 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.net.module.util.netlink;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.EACCES;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
+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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import android.content.Context;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.Struct;
+
+import libcore.io.IoUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkUtilsTest {
+    private static final String TAG = "NetlinkUtilsTest";
+    private static final int TEST_SEQNO = 5;
+    private static final int TEST_TIMEOUT_MS = 500;
+
+    @Test
+    public void testGetNeighborsQuery() throws Exception {
+        final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
+        assertNotNull(fd);
+
+        NetlinkUtils.connectSocketToNetlink(fd);
+
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+        assertNotNull(localAddr);
+        assertEquals(0, localAddr.getGroupsMask());
+        assertTrue(0 != localAddr.getPortId());
+
+        final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
+        assertNotNull(req);
+
+        final Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+        final int targetSdk =
+                ctx.getPackageManager()
+                        .getApplicationInfo(ctx.getPackageName(), 0)
+                        .targetSdkVersion;
+
+        // Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages
+        if (SdkLevel.isAtLeastT() && targetSdk > 31) {
+            var ctxt = new String(Files.readAllBytes(Paths.get("/proc/thread-self/attr/current")));
+            assumeFalse("must not be platform app", ctxt.startsWith("u:r:platform_app:s0:"));
+            try {
+                NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS);
+                fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms,"
+                        + " target SDK version: " + targetSdk);
+            } catch (ErrnoException e) {
+                // Expected
+                assertEquals(e.errno, EACCES);
+                return;
+            }
+        }
+
+        // Check that apps targeting lower API levels / running on older platforms succeed
+        assertEquals(req.length,
+                NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS));
+
+        int neighMessageCount = 0;
+        int doneMessageCount = 0;
+
+        while (doneMessageCount == 0) {
+            ByteBuffer response =
+                    NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TEST_TIMEOUT_MS);
+            assertNotNull(response);
+            assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
+            assertEquals(0, response.position());
+            assertEquals(ByteOrder.nativeOrder(), response.order());
+
+            // Verify the messages at least appears minimally reasonable.
+            while (response.remaining() > 0) {
+                final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
+                assertNotNull(msg);
+                final StructNlMsgHdr hdr = msg.getHeader();
+                assertNotNull(hdr);
+
+                if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+                    doneMessageCount++;
+                    continue;
+                }
+
+                assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+                assertTrue(msg instanceof RtNetlinkNeighborMessage);
+                assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
+                assertEquals(TEST_SEQNO, hdr.nlmsg_seq);
+                assertEquals(localAddr.getPortId(), hdr.nlmsg_pid);
+
+                neighMessageCount++;
+            }
+        }
+
+        assertEquals(1, doneMessageCount);
+        // TODO: make sure this test passes sanely in airplane mode.
+        assertTrue(neighMessageCount > 0);
+
+        IoUtils.closeQuietly(fd);
+    }
+
+    @Test
+    public void testBasicWorkingGetAddrQuery() throws Exception {
+        final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
+        assertNotNull(fd);
+
+        NetlinkUtils.connectSocketToNetlink(fd);
+
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+        assertNotNull(localAddr);
+        assertEquals(0, localAddr.getGroupsMask());
+        assertTrue(0 != localAddr.getPortId());
+
+        final int testSeqno = 8;
+        final byte[] req = newGetAddrRequest(testSeqno);
+        assertNotNull(req);
+
+        final long timeout = 500;
+        assertEquals(req.length, NetlinkUtils.sendMessage(fd, req, 0, req.length, timeout));
+
+        int addrMessageCount = 0;
+
+        while (true) {
+            ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout);
+            assertNotNull(response);
+            assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
+            assertEquals(0, response.position());
+            assertEquals(ByteOrder.nativeOrder(), response.order());
+
+            final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
+            assertNotNull(msg);
+            final StructNlMsgHdr nlmsghdr = msg.getHeader();
+            assertNotNull(nlmsghdr);
+
+            if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+                break;
+            }
+
+            assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type);
+            assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
+            assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
+            assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
+            assertTrue(msg instanceof RtNetlinkAddressMessage);
+            addrMessageCount++;
+
+            // From the query response we can see the RTM_NEWADDR messages representing for IPv4
+            // and IPv6 loopback address: 127.0.0.1 and ::1.
+            final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader();
+            final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress();
+            assertTrue(
+                    "Non-IP address family: " + ifaMsg.family,
+                    ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
+            assertTrue(ipAddress.isLoopbackAddress());
+        }
+
+        assertTrue(addrMessageCount > 0);
+
+        IoUtils.closeQuietly(fd);
+    }
+
+    /** A convenience method to create an RTM_GETADDR request message. */
+    private static byte[] newGetAddrRequest(int seqNo) {
+        final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(StructIfaddrMsg.class);
+        final byte[] bytes = new byte[length];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_len = length;
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+        nlmsghdr.nlmsg_seq = seqNo;
+        nlmsghdr.pack(byteBuffer);
+
+        final StructIfaddrMsg addrMsg = new StructIfaddrMsg((byte) AF_UNSPEC /* family */,
+                (short) 0 /* prefixLen */, (short) 0 /* flags */, (short) 0 /* scope */,
+                0 /* index */);
+        addrMsg.pack(byteBuffer);
+
+        return bytes;
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
new file mode 100644
index 0000000..01126d2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static android.system.OsConstants.IFA_F_PERMANENT;
+import static android.system.OsConstants.NETLINK_ROUTE;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkAddressMessageTest {
+    private static final Inet6Address TEST_LINK_LOCAL =
+            (Inet6Address) InetAddresses.parseNumericAddress("FE80::2C41:5CFF:FE09:6665");
+    private static final Inet6Address TEST_GLOBAL_ADDRESS =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:DB8:1::100");
+
+    // An example of the full RTM_NEWADDR message.
+    private static final String RTM_NEWADDR_HEX =
+            "48000000140000000000000000000000"            // struct nlmsghr
+            + "0A4080FD1E000000"                          // struct ifaddrmsg
+            + "14000100FE800000000000002C415CFFFE096665"  // IFA_ADDRESS
+            + "14000600100E0000201C00002A70000045700000"  // IFA_CACHEINFO
+            + "0800080080000000";                         // IFA_FLAGS
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    @Test
+    public void testParseRtmNewAddress() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+
+        final StructNlMsgHdr hdr = addrMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(72, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWADDR, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructIfaddrMsg ifaddrMsgHdr = addrMsg.getIfaddrHeader();
+        assertNotNull(ifaddrMsgHdr);
+        assertEquals((byte) OsConstants.AF_INET6, ifaddrMsgHdr.family);
+        assertEquals(64, ifaddrMsgHdr.prefixLen);
+        assertEquals(0x80, ifaddrMsgHdr.flags);
+        assertEquals(0xFD, ifaddrMsgHdr.scope);
+        assertEquals(30, ifaddrMsgHdr.index);
+
+        assertEquals((Inet6Address) addrMsg.getIpAddress(), TEST_LINK_LOCAL);
+        assertEquals(3600L, addrMsg.getIfacacheInfo().preferred);
+        assertEquals(7200L, addrMsg.getIfacacheInfo().valid);
+        assertEquals(28714, addrMsg.getIfacacheInfo().cstamp);
+        assertEquals(28741, addrMsg.getIfacacheInfo().tstamp);
+        assertEquals(0x80, addrMsg.getFlags());
+    }
+
+    private static final String RTM_NEWADDR_PACK_HEX =
+            "48000000140000000000000000000000"             // struct nlmsghr
+            + "0A4080FD1E000000"                           // struct ifaddrmsg
+            + "14000100FE800000000000002C415CFFFE096665"   // IFA_ADDRESS
+            + "14000600FFFFFFFFFFFFFFFF2A7000002A700000"   // IFA_CACHEINFO
+            + "0800080081000000";                          // IFA_FLAGS(override ifa_flags)
+
+    @Test
+    public void testPackRtmNewAddr() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_PACK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+
+        final ByteBuffer packBuffer = ByteBuffer.allocate(72);
+        packBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        addrMsg.pack(packBuffer);
+        assertEquals(RTM_NEWADDR_PACK_HEX, HexDump.toHexString(packBuffer.array()));
+    }
+
+    private static final String RTM_NEWADDR_TRUNCATED_HEX =
+            "44000000140000000000000000000000"            // struct nlmsghr
+            + "0A4080FD1E000000"                          // struct ifaddrmsg
+            + "10000100FE800000000000002C415CFF"          // IFA_ADDRESS(truncated)
+            + "14000600FFFFFFFFFFFFFFFF2A7000002A700000"  // IFA_CACHEINFO
+            + "0800080080000000";                         // IFA_FLAGS
+
+    @Test
+    public void testTruncatedRtmNewAddr() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_TRUNCATED_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        // Parsing RTM_NEWADDR with truncated IFA_ADDRESS attribute returns null.
+        assertNull(msg);
+    }
+
+    @Test
+    public void testCreateRtmNewAddressMessage() {
+        // Hexadecimal representation of our created packet.
+        final String expectedNewAddressHex =
+                // struct nlmsghdr
+                "48000000" +    // length = 72
+                "1400" +        // type = 20 (RTM_NEWADDR)
+                "0501" +        // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
+                "01000000" +    // seqno = 1
+                "00000000" +    // pid = 0 (send to kernel)
+                // struct IfaddrMsg
+                "0A" +          // family = inet6
+                "40" +          // prefix len = 64
+                "00" +          // flags = 0
+                "FD" +          // scope = RT_SCOPE_LINK
+                "17000000" +    // ifindex = 23
+                // struct nlattr: IFA_ADDRESS
+                "1400" +        // len
+                "0100" +        // type
+                "FE800000000000002C415CFFFE096665" + // IP address = fe80::2C41:5cff:fe09:6665
+                // struct nlattr: IFA_CACHEINFO
+                "1400" +        // len
+                "0600" +        // type
+                "FFFFFFFF" +    // preferred = infinite
+                "FFFFFFFF" +    // valid = infinite
+                "00000000" +    // cstamp
+                "00000000" +    // tstamp
+                // struct nlattr: IFA_FLAGS
+                "0800" +        // len
+                "0800" +        // type
+                "80000000";     // flags = IFA_F_PERMANENT
+        final byte[] expectedNewAddress =
+                HexEncoding.decode(expectedNewAddressHex.toCharArray(), false);
+
+        final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
+                TEST_LINK_LOCAL, (short) 64 /* prefix len */, IFA_F_PERMANENT /* flags */,
+                (byte) RT_SCOPE_LINK /* scope */, 23 /* ifindex */,
+                (long) 0xFFFFFFFF /* preferred */, (long) 0xFFFFFFFF /* valid */);
+        assertArrayEquals(expectedNewAddress, bytes);
+    }
+
+    @Test
+    public void testCreateRtmDelAddressMessage() {
+        // Hexadecimal representation of our created packet.
+        final String expectedDelAddressHex =
+                // struct nlmsghdr
+                "2C000000" + // length = 44
+                "1500" +     // type = 21 (RTM_DELADDR)
+                "0500" +     // flags = NLM_F_ACK | NLM_F_REQUEST
+                "01000000" + // seqno = 1
+                "00000000" + // pid = 0 (send to kernel)
+                // struct IfaddrMsg
+                "0A" +       // family = inet6
+                "40" +       // prefix len = 64
+                "00" +       // flags = 0
+                "00" +       // scope = RT_SCOPE_UNIVERSE
+                "3B000000" + // ifindex = 59
+                // struct nlattr: IFA_ADDRESS
+                "1400" +     // len
+                "0100" +     // type
+                "20010DB8000100000000000000000100"; // IP address = 2001:db8:1::100
+        final byte[] expectedDelAddress =
+                HexEncoding.decode(expectedDelAddressHex.toCharArray(), false);
+
+        final byte[] bytes = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+                TEST_GLOBAL_ADDRESS, (short) 64 /* prefix len */, 59 /* ifindex */);
+        assertArrayEquals(expectedDelAddress, bytes);
+    }
+
+    @Test
+    public void testCreateRtmNewAddressMessage_nullIpAddress() {
+        assertThrows(NullPointerException.class,
+                () -> RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
+                        null /* IP address */, (short) 0 /* prefix len */,
+                        IFA_F_PERMANENT /* flags */, (byte) RT_SCOPE_LINK /* scope */,
+                        23 /* ifindex */, (long) 0xFFFFFFFF /* preferred */,
+                        (long) 0xFFFFFFFF /* valid */));
+    }
+
+    @Test
+    public void testCreateRtmDelAddressMessage_nullIpAddress() {
+        assertThrows(NullPointerException.class,
+                () -> RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+                        null /* IP address */, (short) 0 /* prefix len */, 59 /* ifindex */));
+    }
+
+    @Test
+    public void testCreateRtmNewAddressMessage_u32Flags() {
+        // Hexadecimal representation of our created packet.
+        final String expectedNewAddressHex =
+                // struct nlmsghdr
+                "48000000" +    // length = 72
+                "1400" +        // type = 20 (RTM_NEWADDR)
+                "0501" +        // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
+                "01000000" +    // seqno = 1
+                "00000000" +    // pid = 0 (send to kernel)
+                // struct IfaddrMsg
+                "0A" +          // family = inet6
+                "80" +          // prefix len = 128
+                "00" +          // flags = 0
+                "00" +          // scope = RT_SCOPE_UNIVERSE
+                "17000000" +    // ifindex = 23
+                // struct nlattr: IFA_ADDRESS
+                "1400" +        // len
+                "0100" +        // type
+                "20010DB8000100000000000000000100" + // IP address = 2001:db8:1::100
+                // struct nlattr: IFA_CACHEINFO
+                "1400" +        // len
+                "0600" +        // type
+                "FFFFFFFF" +    // preferred = infinite
+                "FFFFFFFF" +    // valid = infinite
+                "00000000" +    // cstamp
+                "00000000" +    // tstamp
+                // struct nlattr: IFA_FLAGS
+                "0800" +        // len
+                "0800" +        // type
+                "00030000";     // flags = IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE
+        final byte[] expectedNewAddress =
+                HexEncoding.decode(expectedNewAddressHex.toCharArray(), false);
+
+        final byte[] bytes = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
+                TEST_GLOBAL_ADDRESS, (short) 128 /* prefix len */,
+                (int) 0x300 /* flags: IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE */,
+                (byte) RT_SCOPE_UNIVERSE /* scope */, 23 /* ifindex */,
+                (long) 0xFFFFFFFF /* preferred */, (long) 0xFFFFFFFF /* valid */);
+        assertArrayEquals(expectedNewAddress, bytes);
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+        final String expected = "RtNetlinkAddressMessage{ "
+                + "nlmsghdr{"
+                + "StructNlMsgHdr{ nlmsg_len{72}, nlmsg_type{20(RTM_NEWADDR)}, nlmsg_flags{0()}, "
+                + "nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "Ifaddrmsg{"
+                + "family: 10, prefixLen: 64, flags: 128, scope: 253, index: 30}, "
+                + "IP Address{fe80::2c41:5cff:fe09:6665}, "
+                + "IfacacheInfo{"
+                + "preferred: 3600, valid: 7200, cstamp: 28714, tstamp: 28741}, "
+                + "Address Flags{00000080} "
+                + "}";
+        assertEquals(expected, addrMsg.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
new file mode 100644
index 0000000..9db63db
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkLinkMessageTest {
+
+    // An example of the full RTM_NEWLINK message.
+    private static final String RTM_NEWLINK_HEX =
+            "64000000100000000000000000000000"   // struct nlmsghr
+            + "000001001E0000000210000000000000" // struct ifinfo
+            + "0A000300776C616E30000000"         // IFLA_IFNAME(wlan0)
+            + "08000D00B80B0000"                 // IFLA_PROTINFO
+            + "0500100002000000"                 // IFLA_OPERSTATE
+            + "0500110001000000"                 // IFLA_LINKMODE
+            + "08000400DC050000"                 // IFLA_MTU
+            + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
+            + "0A000200FFFFFFFFFFFF0000";        // IFLA_BROADCAST
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    @Test
+    public void testParseRtmNewLink() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+        final StructNlMsgHdr hdr = linkMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(100, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWLINK, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructIfinfoMsg ifinfomsgHdr = linkMsg.getIfinfoHeader();
+        assertNotNull(ifinfomsgHdr);
+        assertEquals((byte) OsConstants.AF_UNSPEC, ifinfomsgHdr.family);
+        assertEquals(OsConstants.ARPHRD_ETHER, ifinfomsgHdr.type);
+        assertEquals(30, ifinfomsgHdr.index);
+        assertEquals(0, ifinfomsgHdr.change);
+
+        assertEquals(ETHER_MTU, linkMsg.getMtu());
+        assertEquals(MacAddress.fromString("92:C3:E3:C9:37:4E"), linkMsg.getHardwareAddress());
+        assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
+    }
+
+    /**
+     * Example:
+     * # adb shell ip tunnel add トン0 mode sit local any remote 8.8.8.8
+     * # adb shell ip link show | grep トン
+     * 33: トン0@NONE: <POINTOPOINT,NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group
+     *     default qlen 1000
+     *
+     * IFLA_IFNAME attribute: \x0c\x00\x03\x00\xe3\x83\x88\xe3\x83\xb3\x30\x00
+     *     length: 0x000c
+     *     type: 0x0003
+     *     value: \xe3\x83\x88\xe3\x83\xb3\x30\x00
+     *            ト (\xe3\x83\x88)
+     *            ン (\xe3\x83\xb3)
+     *            0  (\x30)
+     *            null terminated (\x00)
+     */
+    private static final String RTM_NEWLINK_UTF8_HEX =
+            "34000000100000000000000000000000"   // struct nlmsghr
+            + "000001001E0000000210000000000000" // struct ifinfo
+            + "08000400DC050000"                 // IFLA_MTU
+            + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
+            + "0C000300E38388E383B33000";        // IFLA_IFNAME(トン0)
+
+    @Test
+    public void testParseRtmNewLink_utf8Ifname() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_UTF8_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+        assertTrue(linkMsg.getInterfaceName().equals("トン0"));
+    }
+
+    private static final String RTM_NEWLINK_PACK_HEX =
+            "34000000100000000000000000000000"   // struct nlmsghr
+            + "000001001E0000000210000000000000" // struct ifinfo
+            + "08000400DC050000"                 // IFLA_MTU
+            + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
+            + "0A000300776C616E30000000";        // IFLA_IFNAME(wlan0)
+
+    @Test
+    public void testPackRtmNewLink() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_PACK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+        final ByteBuffer packBuffer = ByteBuffer.allocate(64);
+        packBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        linkMsg.pack(packBuffer);
+        assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBuffer.array()));
+    }
+
+    private static final String RTM_NEWLINK_TRUNCATED_HEX =
+            "54000000100000000000000000000000"   // struct nlmsghr
+            + "000001001E0000000210000000000000" // struct ifinfo
+            + "08000D00B80B0000"                 // IFLA_PROTINFO
+            + "0500100002000000"                 // IFLA_OPERSTATE
+            + "0800010092C3E3C9"                 // IFLA_ADDRESS(truncated)
+            + "0500110001000000"                 // IFLA_LINKMODE
+            + "0A000300776C616E30000000"         // IFLA_IFNAME(wlan0)
+            + "08000400DC050000";                // IFLA_MTU
+
+    @Test
+    public void testTruncatedRtmNewLink() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_TRUNCATED_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+        // Truncated IFLA_ADDRESS attribute doesn't affect parsing other attrs.
+        assertNull(linkMsg.getHardwareAddress());
+        assertEquals(ETHER_MTU, linkMsg.getMtu());
+        assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+        final String expected = "RtNetlinkLinkMessage{ "
+                + "nlmsghdr{"
+                + "StructNlMsgHdr{ nlmsg_len{100}, nlmsg_type{16(RTM_NEWLINK)}, nlmsg_flags{0()}, "
+                + "nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "Ifinfomsg{"
+                + "family: 0, type: 1, index: 30, flags: 4098, change: 0}, "
+                + "Hardware Address{92:c3:e3:c9:37:4e}, " + "MTU{1500}, "
+                + "Ifname{wlan0} "
+                + "}";
+        assertEquals(expected, linkMsg.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java
new file mode 100644
index 0000000..4d8900c
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE;
+import static com.android.testutils.NetlinkTestUtils.makeDelNeighMessage;
+import static com.android.testutils.NetlinkTestUtils.makeNewNeighMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkNeighborMessageTest {
+    private static final String TAG = "RtNetlinkNeighborMessageTest";
+
+    public static final byte[] RTM_DELNEIGH = makeDelNeighMessage(
+            InetAddresses.parseNumericAddress("192.168.159.254"), NUD_STALE);
+
+    public static final byte[] RTM_NEWNEIGH = makeNewNeighMessage(
+            InetAddresses.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), NUD_STALE);
+
+    // An example of the full response from an RTM_GETNEIGH query.
+    private static final String RTM_GETNEIGH_RESPONSE_HEX =
+            // <-- struct nlmsghr             -->|<-- struct ndmsg           -->|<-- struct nlattr: NDA_DST             -->|<-- NDA_LLADDR          -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO                         -->|
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" +
+            "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" +
+            "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000                         0400 0200                   0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000";
+    public static final byte[] RTM_GETNEIGH_RESPONSE =
+            HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @Test
+    public void testParseRtmDelNeigh() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkNeighborMessage);
+        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+        final StructNlMsgHdr hdr = neighMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(76, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
+        assertNotNull(ndmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family);
+        assertEquals(21, ndmsgHdr.ndm_ifindex);
+        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
+        final InetAddress destination = neighMsg.getDestination();
+        assertNotNull(destination);
+        assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
+    }
+
+    @Test
+    public void testParseRtmNewNeigh() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkNeighborMessage);
+        final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+        final StructNlMsgHdr hdr = neighMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(88, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
+        assertNotNull(ndmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family);
+        assertEquals(21, ndmsgHdr.ndm_ifindex);
+        assertEquals(NUD_STALE, ndmsgHdr.ndm_state);
+        final InetAddress destination = neighMsg.getDestination();
+        assertNotNull(destination);
+        assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);
+    }
+
+    @Test
+    public void testParseRtmGetNeighResponse() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+
+        int messageCount = 0;
+        while (byteBuffer.remaining() > 0) {
+            final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+            assertNotNull(msg);
+            assertTrue(msg instanceof RtNetlinkNeighborMessage);
+            final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+            final StructNlMsgHdr hdr = neighMsg.getHeader();
+            assertNotNull(hdr);
+            assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+            assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
+            assertEquals(0, hdr.nlmsg_seq);
+            assertEquals(11070, hdr.nlmsg_pid);
+
+            final int probes = neighMsg.getProbes();
+            assertTrue("Unexpected number of probes. Got " +  probes + ", max=5",
+                    probes < 5);
+            final int ndm_refcnt = neighMsg.getCacheInfo().ndm_refcnt;
+            assertTrue("nda_cacheinfo has unexpectedly high ndm_refcnt: " + ndm_refcnt,
+                    ndm_refcnt < 0x100);
+
+            messageCount++;
+        }
+        // TODO: add more detailed spot checks.
+        assertEquals(14, messageCount);
+    }
+
+    @Test
+    public void testCreateRtmNewNeighMessage() {
+        final int seqNo = 2635;
+        final int ifIndex = 14;
+        final byte[] llAddr =
+                new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 };
+
+        // Hexadecimal representation of our created packet.
+        final String expectedNewNeighHex =
+                // struct nlmsghdr
+                "30000000" +     // length = 48
+                "1c00" +         // type = 28 (RTM_NEWNEIGH)
+                "0501" +         // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
+                "4b0a0000" +     // seqno
+                "00000000" +     // pid (0 == kernel)
+                // struct ndmsg
+                "02" +           // family
+                "00" +           // pad1
+                "0000" +         // pad2
+                "0e000000" +     // interface index (14)
+                "0800" +         // NUD state (0x08 == NUD_DELAY)
+                "00" +           // flags
+                "00" +           // type
+                // struct nlattr: NDA_DST
+                "0800" +         // length = 8
+                "0100" +         // type (1 == NDA_DST, for neighbor messages)
+                "7f000001" +     // IPv4 address (== 127.0.0.1)
+                // struct nlattr: NDA_LLADDR
+                "0a00" +         // length = 10
+                "0200" +         // type (2 == NDA_LLADDR, for neighbor messages)
+                "010203040506" + // MAC Address (== 01:02:03:04:05:06)
+                "0000";          // padding, for 4 byte alignment
+        final byte[] expectedNewNeigh =
+                HexEncoding.decode(expectedNewNeighHex.toCharArray(), false);
+
+        final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage(
+            seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr);
+        if (!Arrays.equals(expectedNewNeigh, bytes)) {
+            assertEquals(expectedNewNeigh.length, bytes.length);
+            for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) {
+                assertEquals(expectedNewNeigh[i], bytes[i]);
+            }
+        }
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java
new file mode 100644
index 0000000..9881653
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkRouteMessageTest {
+    private static final IpPrefix TEST_IPV6_GLOBAL_PREFIX = new IpPrefix("2001:db8:1::/64");
+    private static final Inet6Address TEST_IPV6_LINK_LOCAL_GATEWAY =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::1");
+
+    // An example of the full RTM_NEWROUTE message.
+    private static final String RTM_NEWROUTE_HEX =
+            "88000000180000060000000000000000"            // struct nlmsghr
+            + "0A400000FC02000100000000"                  // struct rtmsg
+            + "08000F00C7060000"                          // RTA_TABLE
+            + "1400010020010DB8000100000000000000000000"  // RTA_DST
+            + "08000400DF020000"                          // RTA_OIF
+            + "0800060000010000"                          // RTA_PRIORITY
+            + "24000C0000000000000000005EEA000000000000"  // RTA_CACHEINFO
+            + "00000000000000000000000000000000"
+            + "14000500FE800000000000000000000000000001"  // RTA_GATEWAY
+            + "0500140000000000";                         // RTA_PREF
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    private void assertRtmRouteMessage(final RtNetlinkRouteMessage routeMsg) {
+        final StructNlMsgHdr hdr = routeMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(136, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWROUTE, hdr.nlmsg_type);
+        assertEquals(0x600, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructRtMsg rtmsg = routeMsg.getRtMsgHeader();
+        assertNotNull(rtmsg);
+        assertEquals((byte) OsConstants.AF_INET6, rtmsg.family);
+        assertEquals(64, rtmsg.dstLen);
+        assertEquals(0, rtmsg.srcLen);
+        assertEquals(0, rtmsg.tos);
+        assertEquals(0xFC, rtmsg.table);
+        assertEquals(NetlinkConstants.RTPROT_KERNEL, rtmsg.protocol);
+        assertEquals(NetlinkConstants.RT_SCOPE_UNIVERSE, rtmsg.scope);
+        assertEquals(NetlinkConstants.RTN_UNICAST, rtmsg.type);
+        assertEquals(0, rtmsg.flags);
+
+        assertEquals(routeMsg.getDestination(), TEST_IPV6_GLOBAL_PREFIX);
+        assertEquals(735, routeMsg.getInterfaceIndex());
+        assertEquals((Inet6Address) routeMsg.getGateway(), TEST_IPV6_LINK_LOCAL_GATEWAY);
+
+        assertNotNull(routeMsg.getRtaCacheInfo());
+    }
+
+    @Test
+    public void testParseRtmRouteMessage() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkRouteMessage);
+        final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+        assertRtmRouteMessage(routeMsg);
+    }
+
+    private static final String RTM_NEWROUTE_PACK_HEX =
+            "4C000000180000060000000000000000"             // struct nlmsghr
+            + "0A400000FC02000100000000"                   // struct rtmsg
+            + "1400010020010DB8000100000000000000000000"   // RTA_DST
+            + "14000500FE800000000000000000000000000001"   // RTA_GATEWAY
+            + "08000400DF020000"                           // RTA_OIF
+            + "24000C0000000000000000005EEA000000000000"   // RTA_CACHEINFO
+            + "00000000000000000000000000000000";
+
+    @Test
+    public void testPackRtmNewRoute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_PACK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkRouteMessage);
+        final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+
+        final ByteBuffer packBuffer = ByteBuffer.allocate(112);
+        packBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        routeMsg.pack(packBuffer);
+        assertEquals(RTM_NEWROUTE_PACK_HEX, HexDump.toHexString(packBuffer.array()));
+    }
+
+    private static final String RTM_NEWROUTE_TRUNCATED_HEX =
+            "48000000180000060000000000000000"             // struct nlmsghr
+            + "0A400000FC02000100000000"                   // struct rtmsg
+            + "1400010020010DB8000100000000000000000000"   // RTA_DST
+            + "10000500FE8000000000000000000000"           // RTA_GATEWAY(truncated)
+            + "08000400DF020000";                          // RTA_OIF
+
+    @Test
+    public void testTruncatedRtmNewRoute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_TRUNCATED_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        // Parsing RTM_NEWROUTE with truncated RTA_GATEWAY attribute returns null.
+        assertNull(msg);
+    }
+
+    private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX =
+            "4C000000180000060000000000000000"             // struct nlmsghr
+            + "0A400000FC02000100000000"                   // struct rtmsg
+            + "1400010020010DB8000100000000000000000000"   // RTA_DST(2001:db8:1::/64)
+            + "1400050000000000000000000000FFFF0A010203"   // RTA_GATEWAY(::ffff:10.1.2.3)
+            + "08000400DF020000";                          // RTA_OIF
+
+    @Test
+    public void testParseRtmRouteMessage_IPv4MappedIPv6Gateway() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 gateway address, which doesn't match
+        // rtm_family after address parsing.
+        assertNull(msg);
+    }
+
+    private static final String RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX =
+            "4C000000180000060000000000000000"             // struct nlmsghr
+            + "0A780000FC02000100000000"                   // struct rtmsg
+            + "1400010000000000000000000000FFFF0A000000"   // RTA_DST(::ffff:10.0.0.0/120)
+            + "14000500FE800000000000000000000000000001"   // RTA_GATEWAY(fe80::1)
+            + "08000400DF020000";                          // RTA_OIF
+
+    @Test
+    public void testParseRtmRouteMessage_IPv4MappedIPv6Destination() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        // Parsing RTM_NEWROUTE with IPv4-mapped IPv6 destination prefix, which doesn't match
+        // rtm_family after address parsing.
+        assertNull(msg);
+    }
+
+    // An example of the full RTM_NEWADDR message.
+    private static final String RTM_NEWADDR_HEX =
+            "48000000140000000000000000000000"            // struct nlmsghr
+            + "0A4080FD1E000000"                          // struct ifaddrmsg
+            + "14000100FE800000000000002C415CFFFE096665"  // IFA_ADDRESS
+            + "14000600100E0000201C00002A70000045700000"  // IFA_CACHEINFO
+            + "0800080080000000";                         // IFA_FLAGS
+
+    @Test
+    public void testParseMultipleRtmMessagesInOneByteBuffer() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX + RTM_NEWADDR_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+
+        // Try to parse the RTM_NEWROUTE message.
+        NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkRouteMessage);
+        final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+        assertRtmRouteMessage(routeMsg);
+
+        // Try to parse the RTM_NEWADDR message.
+        msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkRouteMessage);
+        final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+        final String expected = "RtNetlinkRouteMessage{ "
+                + "nlmsghdr{"
+                + "StructNlMsgHdr{ nlmsg_len{136}, nlmsg_type{24(RTM_NEWROUTE)}, "
+                + "nlmsg_flags{1536(NLM_F_MATCH)}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "Rtmsg{"
+                + "family: 10, dstLen: 64, srcLen: 0, tos: 0, table: 252, protocol: 2, "
+                + "scope: 0, type: 1, flags: 0}, "
+                + "destination{2001:db8:1::}, "
+                + "gateway{fe80::1}, "
+                + "ifindex{735}, "
+                + "rta_cacheinfo{clntref: 0, lastuse: 0, expires: 59998, error: 0, used: 0, "
+                + "id: 0, ts: 0, tsage: 0} "
+                + "}";
+        assertEquals(expected, routeMsg.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java
new file mode 100644
index 0000000..ce190f2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructInetDiagSockIdTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.net.module.util.netlink;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructInetDiagSockIdTest {
+    private static final Inet4Address IPV4_SRC_ADDR =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.0.2.1");
+    private static final Inet4Address IPV4_DST_ADDR =
+            (Inet4Address) InetAddresses.parseNumericAddress("198.51.100.1");
+    private static final Inet6Address IPV6_SRC_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
+    private static final Inet6Address IPV6_DST_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::2");
+    private static final int SRC_PORT = 65297;
+    private static final int DST_PORT = 443;
+    private static final int IF_INDEX = 7;
+    private static final long COOKIE = 561;
+
+    private static final byte[] INET_DIAG_SOCKET_ID_IPV4 =
+            new byte[] {
+                    // src port, dst port
+                    (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb,
+                    // src address
+                    (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // dst address
+                    (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // if index
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // cookie
+                    (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                    (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff
+            };
+
+    private static final byte[] INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE =
+            new byte[] {
+                    // src port, dst port
+                    (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb,
+                    // src address
+                    (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // dst address
+                    (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // if index
+                    (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // cookie
+                    (byte) 0x31, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            };
+
+    private static final byte[] INET_DIAG_SOCKET_ID_IPV6 =
+            new byte[] {
+                    // src port, dst port
+                    (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb,
+                    // src address
+                    (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                    // dst address
+                    (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                    // if index
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // cookie
+                    (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                    (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff
+            };
+
+    private static final byte[] INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE =
+            new byte[] {
+                    // src port, dst port
+                    (byte) 0xff, (byte) 0x11, (byte) 0x01, (byte) 0xbb,
+                    // src address
+                    (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+                    // dst address
+                    (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+                    // if index
+                    (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                    // cookie
+                    (byte) 0x31, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            };
+
+    @Test
+    public void testPackStructInetDiagSockIdWithIpv4() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr);
+        final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE);
+        sockId.pack(buffer);
+        assertArrayEquals(INET_DIAG_SOCKET_ID_IPV4, buffer.array());
+    }
+
+    @Test
+    public void testPackStructInetDiagSockIdWithIpv6() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr);
+        final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE);
+        sockId.pack(buffer);
+        assertArrayEquals(INET_DIAG_SOCKET_ID_IPV6, buffer.array());
+    }
+
+    @Test
+    public void testPackStructInetDiagSockIdWithIpv4IfIndexCookie() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId =
+                new StructInetDiagSockId(srcAddr, dstAddr, IF_INDEX, COOKIE);
+        final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE);
+        sockId.pack(buffer);
+        assertArrayEquals(INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE, buffer.array());
+    }
+
+    @Test
+    public void testPackStructInetDiagSockIdWithIpv6IfIndexCookie() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId =
+                new StructInetDiagSockId(srcAddr, dstAddr, IF_INDEX, COOKIE);
+        final ByteBuffer buffer = ByteBuffer.allocate(StructInetDiagSockId.STRUCT_SIZE);
+        sockId.pack(buffer);
+        assertArrayEquals(INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE, buffer.array());
+    }
+
+    @Test
+    public void testParseStructInetDiagSockIdWithIpv4() {
+        final ByteBuffer buffer = ByteBuffer.wrap(INET_DIAG_SOCKET_ID_IPV4_IF_COOKIE);
+        final StructInetDiagSockId sockId = StructInetDiagSockId.parse(buffer, (byte) AF_INET);
+
+        assertEquals(SRC_PORT, sockId.locSocketAddress.getPort());
+        assertEquals(IPV4_SRC_ADDR, sockId.locSocketAddress.getAddress());
+        assertEquals(DST_PORT, sockId.remSocketAddress.getPort());
+        assertEquals(IPV4_DST_ADDR, sockId.remSocketAddress.getAddress());
+        assertEquals(IF_INDEX, sockId.ifIndex);
+        assertEquals(COOKIE, sockId.cookie);
+    }
+
+    @Test
+    public void testParseStructInetDiagSockIdWithIpv6() {
+        final ByteBuffer buffer = ByteBuffer.wrap(INET_DIAG_SOCKET_ID_IPV6_IF_COOKIE);
+        final StructInetDiagSockId sockId = StructInetDiagSockId.parse(buffer, (byte) AF_INET6);
+
+        assertEquals(SRC_PORT, sockId.locSocketAddress.getPort());
+        assertEquals(IPV6_SRC_ADDR, sockId.locSocketAddress.getAddress());
+        assertEquals(DST_PORT, sockId.remSocketAddress.getPort());
+        assertEquals(IPV6_DST_ADDR, sockId.remSocketAddress.getAddress());
+        assertEquals(IF_INDEX, sockId.ifIndex);
+        assertEquals(COOKIE, sockId.cookie);
+    }
+
+    @Test
+    public void testToStringStructInetDiagSockIdWithIpv4() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV4_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV4_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr);
+        assertEquals("StructInetDiagSockId{ idiag_sport{65297}, idiag_dport{443},"
+                + " idiag_src{192.0.2.1}, idiag_dst{198.51.100.1}, idiag_if{0},"
+                + " idiag_cookie{INET_DIAG_NOCOOKIE}}", sockId.toString());
+    }
+
+    @Test
+    public void testToStringStructInetDiagSockIdWithIpv6() {
+        final InetSocketAddress srcAddr = new InetSocketAddress(IPV6_SRC_ADDR, SRC_PORT);
+        final InetSocketAddress dstAddr = new InetSocketAddress(IPV6_DST_ADDR, DST_PORT);
+        final StructInetDiagSockId sockId = new StructInetDiagSockId(srcAddr, dstAddr);
+        assertEquals("StructInetDiagSockId{ idiag_sport{65297}, idiag_dport{443},"
+                + " idiag_src{2001:db8::1}, idiag_dst{2001:db8::2}, idiag_if{0},"
+                + " idiag_cookie{INET_DIAG_NOCOOKIE}}", sockId.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java
new file mode 100644
index 0000000..beed838
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.StructNdOptPref64.getScaledLifetimePlc;
+import static com.android.net.module.util.netlink.StructNdOptPref64.plcToPrefixLength;
+import static com.android.net.module.util.netlink.StructNdOptPref64.prefixLengthToPlc;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.SuppressLint;
+import android.net.IpPrefix;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNdOptPref64Test {
+
+    private static final String PREFIX1 = "64:ff9b::";
+    private static final String PREFIX2 = "2001:db8:1:2:3:64::";
+
+    private static byte[] prefixBytes(String addrString) throws Exception {
+        InetAddress addr = InetAddress.getByName(addrString);
+        byte[] prefixBytes = new byte[12];
+        System.arraycopy(addr.getAddress(), 0, prefixBytes, 0, 12);
+        return prefixBytes;
+    }
+
+    @SuppressLint("NewApi")
+    private static IpPrefix prefix(String addrString, int prefixLength) throws Exception {
+        return new IpPrefix(InetAddress.getByName(addrString), prefixLength);
+    }
+
+    private void assertPref64OptMatches(int lifetime, IpPrefix prefix, StructNdOptPref64 opt) {
+        assertEquals(StructNdOptPref64.TYPE, opt.type);
+        assertEquals(2, opt.length);
+        assertEquals(lifetime, opt.lifetime);
+        assertEquals(prefix, opt.prefix);
+    }
+
+    private void assertToByteBufferMatches(StructNdOptPref64 opt, String expected) {
+        String actual = HexEncoding.encodeToString(opt.toByteBuffer().array());
+        assertEquals(expected, actual);
+    }
+
+    private ByteBuffer makeNdOptPref64(int lifetime, byte[] prefix, int prefixLengthCode) {
+        if (prefix.length != 12) throw new IllegalArgumentException("Prefix must be 12 bytes");
+
+        ByteBuffer buf = ByteBuffer.allocate(16)
+                .put((byte) StructNdOptPref64.TYPE)
+                .put((byte) StructNdOptPref64.LENGTH)
+                .putShort(getScaledLifetimePlc(lifetime, prefixLengthCode))
+                .put(prefix, 0, 12);
+
+        buf.flip();
+        return buf;
+    }
+
+    @Test
+    public void testParseCannedOption() throws Exception {
+        String hexBytes = "2602"               // type=38, len=2 (16 bytes)
+                + "0088"                       // lifetime=136, PLC=0 (/96)
+                + "20010DB80003000400050006";  // 2001:db8:3:4:5:6/96
+        byte[] rawBytes = HexEncoding.decode(hexBytes);
+        StructNdOptPref64 opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
+        assertPref64OptMatches(136, prefix("2001:DB8:3:4:5:6::", 96), opt);
+        assertToByteBufferMatches(opt, hexBytes);
+
+        hexBytes = "2602"                      // type=38, len=2 (16 bytes)
+                + "2752"                       // lifetime=10064, PLC=2 (/56)
+                + "0064FF9B0000000000000000";  // 64:ff9b::/56
+        rawBytes = HexEncoding.decode(hexBytes);
+        opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
+        assertPref64OptMatches(10064, prefix("64:FF9B::", 56), opt);
+        assertToByteBufferMatches(opt, hexBytes);
+    }
+
+    @Test
+    public void testParsing() throws Exception {
+        // Valid.
+        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 0);
+        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(600, prefix(PREFIX1, 96), opt);
+
+        // Valid, zero lifetime, /64.
+        buf = makeNdOptPref64(0, prefixBytes(PREFIX1), 1);
+        opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(0, prefix(PREFIX1, 64), opt);
+
+        // Valid, low lifetime, /56.
+        buf = makeNdOptPref64(8, prefixBytes(PREFIX2), 2);
+        opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(8, prefix(PREFIX2, 56), opt);
+        assertEquals(new IpPrefix("2001:db8:1::/56"), opt.prefix);  // Prefix is truncated.
+
+        // Valid, maximum lifetime, /32.
+        buf = makeNdOptPref64(65528, prefixBytes(PREFIX2), 5);
+        opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(65528, prefix(PREFIX2, 32), opt);
+        assertEquals(new IpPrefix("2001:db8::/32"), opt.prefix);  // Prefix is truncated.
+
+        // Lifetime not divisible by 8.
+        buf = makeNdOptPref64(300, prefixBytes(PREFIX2), 0);
+        opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(296, prefix(PREFIX2, 96), opt);
+
+        // Invalid prefix length codes.
+        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 6);
+        assertNull(StructNdOptPref64.parse(buf));
+        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 7);
+        assertNull(StructNdOptPref64.parse(buf));
+
+        // Truncated to varying lengths...
+        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 3);
+        final int len = buf.limit();
+        for (int i = 0; i < buf.limit() - 1; i++) {
+            buf.flip();
+            buf.limit(i);
+            assertNull("Option truncated to " + i + " bytes, should have returned null",
+                    StructNdOptPref64.parse(buf));
+        }
+        buf.flip();
+        buf.limit(len);
+        // ... but otherwise OK.
+        opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(600, prefix(PREFIX1, 48), opt);
+    }
+
+    @Test
+    public void testToString() throws Exception {
+        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 4);
+        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
+        assertPref64OptMatches(600, prefix(PREFIX1, 40), opt);
+        assertEquals("NdOptPref64(64:ff9b::/40, 600)", opt.toString());
+    }
+
+    private void assertInvalidPlc(int plc) {
+        assertThrows(IllegalArgumentException.class, () -> plcToPrefixLength(plc));
+    }
+
+    @Test
+    public void testPrefixLengthToPlc() {
+        for (int i = 0; i < 6; i++) {
+            assertEquals(i, prefixLengthToPlc(plcToPrefixLength(i)));
+        }
+        assertInvalidPlc(-1);
+        assertInvalidPlc(6);
+        assertInvalidPlc(7);
+        assertEquals(0, prefixLengthToPlc(96));
+    }
+
+
+    private void assertInvalidParameters(IpPrefix prefix, int lifetime) {
+        assertThrows(IllegalArgumentException.class, () -> new StructNdOptPref64(prefix, lifetime));
+    }
+
+    @Test
+    public void testToByteBuffer() throws Exception {
+        final IpPrefix prefix1 = prefix(PREFIX1, 56);
+        final IpPrefix prefix2 = prefix(PREFIX2, 96);
+
+        StructNdOptPref64 opt = new StructNdOptPref64(prefix1, 600);
+        assertToByteBufferMatches(opt, "2602025A0064FF9B0000000000000000");
+        assertEquals(new IpPrefix("64:ff9b::/56"), opt.prefix);
+        assertEquals(600, opt.lifetime);
+
+        opt = new StructNdOptPref64(prefix2, 65519);
+        assertToByteBufferMatches(opt, "2602FFE820010DB80001000200030064");
+        assertEquals(new IpPrefix("2001:db8:1:2:3:64::/96"), opt.prefix);
+        assertEquals(65512, opt.lifetime);
+
+        assertInvalidParameters(prefix1, 65535);
+        assertInvalidParameters(prefix2, -1);
+        assertInvalidParameters(prefix("1.2.3.4", 32), 600);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java
new file mode 100644
index 0000000..1dcb9b5
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.RdnssOption;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNdOptRdnssTest {
+    private static final String DNS_SERVER1 = "2001:4860:4860::64";
+    private static final String DNS_SERVER2 = "2001:4860:4860::6464";
+
+    private static final Inet6Address[] DNS_SERVER_ADDRESSES = new Inet6Address[] {
+            (Inet6Address) InetAddresses.parseNumericAddress(DNS_SERVER1),
+            (Inet6Address) InetAddresses.parseNumericAddress(DNS_SERVER2),
+    };
+
+    private static final String RDNSS_OPTION_BYTES =
+            "1905"                                 // type=25, len=5 (40 bytes)
+            + "0000"                               // reserved
+            + "00000E10"                           // lifetime=3600
+            + "20014860486000000000000000000064"   // 2001:4860:4860::64
+            + "20014860486000000000000000006464";  // 2001:4860:4860::6464
+
+    private static final String RDNSS_INFINITY_LIFETIME_OPTION_BYTES =
+            "1905"                                 // type=25, len=3 (24 bytes)
+            + "0000"                               // reserved
+            + "FFFFFFFF"                           // lifetime=0xffffffff
+            + "20014860486000000000000000000064"   // 2001:4860:4860::64
+            + "20014860486000000000000000006464";  // 2001:4860:4860::6464
+
+    private void assertRdnssOptMatches(final StructNdOptRdnss opt, int length, long lifetime,
+            final Inet6Address[] servers) {
+        assertEquals(StructNdOptRdnss.TYPE, opt.type);
+        assertEquals(length, opt.length);
+        assertEquals(lifetime, opt.header.lifetime);
+        assertEquals(servers, opt.servers);
+    }
+
+    private ByteBuffer makeRdnssOption(byte type, byte length, long lifetime, String... servers)
+            throws Exception {
+        final ByteBuffer buf = ByteBuffer.allocate(8 + servers.length * 16)
+                .put(type)
+                .put(length)
+                .putShort((short) 0) // Reserved
+                .putInt((int) (lifetime & 0xFFFFFFFFL));
+        for (int i = 0; i < servers.length; i++) {
+            final byte[] rawBytes =
+                    ((Inet6Address) InetAddresses.parseNumericAddress(servers[i])).getAddress();
+            buf.put(rawBytes);
+        }
+        buf.flip();
+        return buf;
+    }
+
+    private void assertToByteBufferMatches(StructNdOptRdnss opt, String expected) {
+        String actual = HexEncoding.encodeToString(opt.toByteBuffer().array());
+        assertEquals(expected, actual);
+    }
+
+    private void doRdnssOptionParsing(final String optionHexString, int length, long lifetime,
+            final Inet6Address[] servers) {
+        final byte[] rawBytes = HexEncoding.decode(optionHexString);
+        final StructNdOptRdnss opt = StructNdOptRdnss.parse(ByteBuffer.wrap(rawBytes));
+        assertRdnssOptMatches(opt, length, lifetime, servers);
+        assertToByteBufferMatches(opt, optionHexString);
+    }
+
+    @Test
+    public void testParsing() throws Exception {
+        doRdnssOptionParsing(RDNSS_OPTION_BYTES, 5 /* length */, 3600 /* lifetime */,
+                DNS_SERVER_ADDRESSES);
+    }
+
+    @Test
+    public void testParsing_infinityLifetime() throws Exception {
+        doRdnssOptionParsing(RDNSS_INFINITY_LIFETIME_OPTION_BYTES, 5 /* length */,
+                0xffffffffL /* lifetime */, DNS_SERVER_ADDRESSES);
+    }
+
+    @Test
+    public void testToByteBuffer() {
+        final StructNdOptRdnss rdnss = new StructNdOptRdnss(DNS_SERVER_ADDRESSES, 3600);
+        assertToByteBufferMatches(rdnss, RDNSS_OPTION_BYTES);
+    }
+
+    @Test
+    public void testToByteBuffer_infinityLifetime() {
+        final StructNdOptRdnss rdnss = new StructNdOptRdnss(DNS_SERVER_ADDRESSES, 0xffffffffL);
+        assertToByteBufferMatches(rdnss, RDNSS_INFINITY_LIFETIME_OPTION_BYTES);
+    }
+
+    @Test
+    public void testParsing_invalidType() throws Exception {
+        final ByteBuffer buf = makeRdnssOption((byte) 38, (byte) 5 /* length */,
+                3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        assertNull(StructNdOptRdnss.parse(buf));
+    }
+
+    @Test
+    public void testParsing_smallOptionLength() throws Exception {
+        final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                (byte) 2 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        assertNull(StructNdOptRdnss.parse(buf));
+    }
+
+    @Test
+    public void testParsing_oddOptionLength() throws Exception {
+        final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                (byte) 6 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        assertNull(StructNdOptRdnss.parse(buf));
+    }
+
+    @Test
+    public void testParsing_truncatedByteBuffer() throws Exception {
+        ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                (byte) 5 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        final int len = buf.limit();
+        for (int i = 0; i < buf.limit() - 1; i++) {
+            buf.flip();
+            buf.limit(i);
+            assertNull("Option truncated to " + i + " bytes, should have returned null",
+                    StructNdOptRdnss.parse(buf));
+        }
+        buf.flip();
+        buf.limit(len);
+
+        final StructNdOptRdnss opt = StructNdOptRdnss.parse(buf);
+        assertRdnssOptMatches(opt, 5 /* length */, 3600 /* lifetime */, DNS_SERVER_ADDRESSES);
+    }
+
+    @Test
+    public void testParsing_invalidByteBufferLength() throws Exception {
+        final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                (byte) 5 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        buf.limit(20); // less than MIN_OPT_LEN * 8
+        assertNull(StructNdOptRdnss.parse(buf));
+    }
+
+    @Test
+    public void testConstructor_nullDnsServerAddressArray() {
+        assertThrows(NullPointerException.class,
+                () -> new StructNdOptRdnss(null /* servers */, 3600 /* lifetime */));
+    }
+
+    @Test
+    public void testConstructor_emptyDnsServerAddressArray() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new StructNdOptRdnss(new Inet6Address[0] /* empty server array */,
+                                           3600 /* lifetime*/));
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer buf = RdnssOption.build(3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);
+        final StructNdOptRdnss opt = StructNdOptRdnss.parse(buf);
+        final String expected = "NdOptRdnss(type: 25, length: 5, reserved: 0, lifetime: 3600,"
+                + "servers:[2001:4860:4860::64,2001:4860:4860::6464])";
+        assertRdnssOptMatches(opt, 5 /* length */, 3600 /* lifetime */, DNS_SERVER_ADDRESSES);
+        assertEquals(expected, opt.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
new file mode 100644
index 0000000..af3fac2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.netlink;
+
+import static com.android.net.module.util.netlink.RtNetlinkAddressMessage.IFA_FLAGS;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNlAttrTest {
+    private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55");
+    private static final String TEST_INTERFACE_NAME = "wlan0";
+    private static final int TEST_ADDR_FLAGS = 0x80;
+
+    @Test
+    public void testGetValueAsMacAddress() {
+        final StructNlAttr attr1 = new StructNlAttr(IFLA_ADDRESS, TEST_MAC_ADDRESS);
+        final MacAddress address1 = attr1.getValueAsMacAddress();
+        assertEquals(address1, TEST_MAC_ADDRESS);
+
+        // Invalid mac address byte array.
+        final byte[] array = new byte[] {
+                (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+                (byte) 0x44, (byte) 0x55, (byte) 0x66,
+        };
+        final StructNlAttr attr2 = new StructNlAttr(IFLA_ADDRESS, array);
+        final MacAddress address2 = attr2.getValueAsMacAddress();
+        assertNull(address2);
+    }
+
+    @Test
+    public void testGetValueAsString() {
+        final StructNlAttr attr1 = new StructNlAttr(IFLA_IFNAME, TEST_INTERFACE_NAME);
+        final String str1 = attr1.getValueAsString();
+        assertEquals(str1, TEST_INTERFACE_NAME);
+
+        final byte[] array = new byte[] {
+                (byte) 0x77, (byte) 0x6c, (byte) 0x61, (byte) 0x6E, (byte) 0x30, (byte) 0x00,
+        };
+        final StructNlAttr attr2 = new StructNlAttr(IFLA_IFNAME, array);
+        final String str2 = attr2.getValueAsString();
+        assertEquals(str2, TEST_INTERFACE_NAME);
+    }
+
+    @Test
+    public void testGetValueAsIntger() {
+        final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
+        final Integer integer1 = attr1.getValueAsInteger();
+        final int int1 = attr1.getValueAsInt(0x08 /* default value */);
+        assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+        assertEquals(int1, TEST_ADDR_FLAGS);
+
+        // Malformed attribute.
+        final byte[] malformed_int = new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x80, };
+        final StructNlAttr attr2 = new StructNlAttr(IFA_FLAGS, malformed_int);
+        final Integer integer2 = attr2.getValueAsInteger();
+        final int int2 = attr2.getValueAsInt(0x08 /* default value */);
+        assertNull(integer2);
+        assertEquals(int2, 0x08 /* default value */);
+
+        // Null attribute value.
+        final byte[] null_int = null;
+        final StructNlAttr attr3 = new StructNlAttr(IFA_FLAGS, null_int);
+        final Integer integer3 = attr3.getValueAsInteger();
+        final int int3 = attr3.getValueAsInt(0x08 /* default value */);
+        assertNull(integer3);
+        assertEquals(int3, 0x08 /* default value */);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java
new file mode 100644
index 0000000..b7f68c6
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.net.module.util.netlink;
+
+import static org.junit.Assert.fail;
+
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNlMsgHdrTest {
+
+    public static final short TEST_NLMSG_LEN = 16;
+    public static final short TEST_NLMSG_FLAGS = StructNlMsgHdr.NLM_F_REQUEST
+            | StructNlMsgHdr.NLM_F_MULTI | StructNlMsgHdr.NLM_F_ACK | StructNlMsgHdr.NLM_F_ECHO;
+    public static final short TEST_NLMSG_SEQ = 1234;
+    public static final short TEST_NLMSG_PID = 5678;
+
+    // Checking the header string nlmsg_{len, ..} of the number can make sure that the checking
+    // number comes from the expected element.
+    // TODO: Verify more flags once StructNlMsgHdr can distinguish the flags which have the same
+    // value. For example, NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200) can't be distinguished.
+    // See StructNlMsgHdrTest#stringForNlMsgFlags.
+    public static final String TEST_NLMSG_LEN_STR = "nlmsg_len{16}";
+    public static final String TEST_NLMSG_FLAGS_STR =
+            "NLM_F_REQUEST|NLM_F_MULTI|NLM_F_ACK|NLM_F_ECHO";
+    public static final String TEST_NLMSG_SEQ_STR = "nlmsg_seq{1234}";
+    public static final String TEST_NLMSG_PID_STR = "nlmsg_pid{5678}";
+
+    private StructNlMsgHdr makeStructNlMsgHdr(short type) {
+        final StructNlMsgHdr struct = new StructNlMsgHdr();
+        struct.nlmsg_len = TEST_NLMSG_LEN;
+        struct.nlmsg_type = type;
+        struct.nlmsg_flags = TEST_NLMSG_FLAGS;
+        struct.nlmsg_seq = TEST_NLMSG_SEQ;
+        struct.nlmsg_pid = TEST_NLMSG_PID;
+        return struct;
+    }
+
+    private static void assertContains(String actualValue, String expectedSubstring) {
+        if (actualValue.contains(expectedSubstring)) return;
+        fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\"");
+    }
+
+    @Test
+    public void testToString() {
+        StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR);
+        String s = struct.toString();
+        assertContains(s, TEST_NLMSG_LEN_STR);
+        assertContains(s, TEST_NLMSG_FLAGS_STR);
+        assertContains(s, TEST_NLMSG_SEQ_STR);
+        assertContains(s, TEST_NLMSG_PID_STR);
+        assertContains(s, "nlmsg_type{20()}");
+
+        struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY);
+        s = struct.toString();
+        assertContains(s, TEST_NLMSG_LEN_STR);
+        assertContains(s, TEST_NLMSG_FLAGS_STR);
+        assertContains(s, TEST_NLMSG_SEQ_STR);
+        assertContains(s, TEST_NLMSG_PID_STR);
+        assertContains(s, "nlmsg_type{20()}");
+    }
+
+    @Test
+    public void testToStringWithNetlinkFamily() {
+        StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR);
+        String s = struct.toString(OsConstants.NETLINK_ROUTE);
+        assertContains(s, TEST_NLMSG_LEN_STR);
+        assertContains(s, TEST_NLMSG_FLAGS_STR);
+        assertContains(s, TEST_NLMSG_SEQ_STR);
+        assertContains(s, TEST_NLMSG_PID_STR);
+        assertContains(s, "nlmsg_type{20(RTM_NEWADDR)}");
+
+        struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY);
+        s = struct.toString(OsConstants.NETLINK_INET_DIAG);
+        assertContains(s, TEST_NLMSG_LEN_STR);
+        assertContains(s, TEST_NLMSG_FLAGS_STR);
+        assertContains(s, TEST_NLMSG_SEQ_STR);
+        assertContains(s, TEST_NLMSG_PID_STR);
+        assertContains(s, "nlmsg_type{20(SOCK_DIAG_BY_FAMILY)}");
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java
new file mode 100644
index 0000000..23e7b15
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.net.module.util.async.CircularByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetPacketHelpersTest {
+    @Test
+    public void decodeNetworkUnsignedInt16() {
+        final byte[] data = new byte[4];
+        data[0] = (byte) 0xFF;
+        data[1] = (byte) 1;
+        data[2] = (byte) 2;
+        data[3] = (byte) 0xFF;
+
+        assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(data, 1));
+
+        CircularByteBuffer buffer = new CircularByteBuffer(100);
+        buffer.writeBytes(data, 0, data.length);
+
+        assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(buffer, 1));
+    }
+
+    @Test
+    public void encodeNetworkUnsignedInt16() {
+        final byte[] data = new byte[4];
+        data[0] = (byte) 0xFF;
+        data[3] = (byte) 0xFF;
+        NetPacketHelpers.encodeNetworkUnsignedInt16(0x0102, data, 1);
+
+        assertEquals((byte) 0xFF, data[0]);
+        assertEquals((byte) 1, data[1]);
+        assertEquals((byte) 2, data[2]);
+        assertEquals((byte) 0xFF, data[3]);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java
new file mode 100644
index 0000000..1fcca70
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.ignoreStubs;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.async.AsyncFile;
+import com.android.net.module.util.async.BufferedFile;
+import com.android.net.module.util.async.EventManager;
+import com.android.net.module.util.async.FileHandle;
+import com.android.net.module.util.async.ReadableByteBuffer;
+import com.android.testutils.async.ReadableDataAnswer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StreamingPacketFileTest {
+    private static final int MAX_PACKET_SIZE = 100;
+
+    @Mock EventManager mockEventManager;
+    @Mock PacketFile.Listener mockFileListener;
+    @Mock AsyncFile mockAsyncFile;
+    @Mock ParcelFileDescriptor mockParcelFileDescriptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager));
+    }
+
+    @Test
+    public void continueReadingAndClose() throws Exception {
+        final int maxBufferedInboundPackets = 3;
+        final int maxBufferedOutboundPackets = 5;
+
+        final StreamingPacketFile file =
+            createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+        final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+        assertEquals(maxBufferedInboundPackets * (MAX_PACKET_SIZE + 2),
+            bufferedFile.getInboundBufferFreeSizeForTest());
+        assertEquals(maxBufferedOutboundPackets * (MAX_PACKET_SIZE + 2),
+            bufferedFile.getOutboundBufferFreeSize());
+        assertEquals(bufferedFile.getOutboundBufferFreeSize() - 2,
+            file.getOutboundFreeSize());
+
+        file.continueReading();
+        verify(mockAsyncFile).enableReadEvents(true);
+
+        file.close();
+        verify(mockAsyncFile).close();
+    }
+
+    @Test
+    public void enqueueOutboundPacket() throws Exception {
+        final int maxBufferedInboundPackets = 10;
+        final int maxBufferedOutboundPackets = 20;
+
+        final StreamingPacketFile file =
+            createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+        final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+        final byte[] packet1 = new byte[11];
+        final byte[] packet2 = new byte[12];
+        packet1[0] = (byte) 1;
+        packet2[0] = (byte) 2;
+
+        assertEquals(0, bufferedFile.getOutboundBufferSize());
+
+        when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0);
+        assertTrue(file.enqueueOutboundPacket(packet1, 0, packet1.length));
+        verify(mockAsyncFile).enableWriteEvents(true);
+
+        assertEquals(packet1.length + 2, bufferedFile.getOutboundBufferSize());
+
+        checkAndResetMocks();
+
+        final int totalLen = packet1.length + packet2.length + 4;
+
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+        when(mockAsyncFile.write(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+        assertTrue(file.enqueueOutboundPacket(packet2, 0, packet2.length));
+
+        assertEquals(0, bufferedFile.getInboundBuffer().size());
+        assertEquals(0, bufferedFile.getOutboundBufferSize());
+
+        assertEquals(0, posCaptor.getValue().intValue());
+        assertEquals(totalLen, lenCaptor.getValue().intValue());
+
+        final byte[] capturedData = arrayCaptor.getValue();
+        assertEquals(packet1.length, NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, 0));
+        assertEquals(packet2.length,
+            NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, packet1.length + 2));
+        assertEquals(packet1[0], capturedData[2]);
+        assertEquals(packet2[0], capturedData[packet1.length + 4]);
+    }
+
+    @Test
+    public void onInboundPacket() throws Exception {
+        final int maxBufferedInboundPackets = 10;
+        final int maxBufferedOutboundPackets = 20;
+
+        final StreamingPacketFile file =
+            createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+        final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+        final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer();
+
+        final int len1 = 11;
+        final int len2 = 12;
+        final byte[] data = new byte[len1 + len2 + 4];
+        NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, 0);
+        NetPacketHelpers.encodeNetworkUnsignedInt16(len2, data, 11 + 2);
+        data[2] = (byte) 1;
+        data[len1 + 4] = (byte) 2;
+
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(0);
+        bufferedFile.onReadReady(mockAsyncFile);
+        verify(mockAsyncFile).enableReadEvents(true);
+        verify(mockFileListener).onInboundBuffered(data.length, data.length);
+        verify(mockFileListener).onInboundPacket(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+        verify(mockEventManager).execute(any());
+
+        byte[] capturedData = arrayCaptor.getValue();
+        assertEquals(2, posCaptor.getValue().intValue());
+        assertEquals(len1, lenCaptor.getValue().intValue());
+        assertEquals((byte) 1, capturedData[2]);
+
+        checkAndResetMocks();
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        file.onBufferedFileInboundData(0);
+        verify(mockFileListener).onInboundPacket(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+        verify(mockEventManager).execute(any());
+
+        capturedData = arrayCaptor.getValue();
+        assertEquals(2, posCaptor.getValue().intValue());
+        assertEquals(len2, lenCaptor.getValue().intValue());
+        assertEquals((byte) 2, capturedData[2]);
+
+        assertEquals(0, bufferedFile.getOutboundBufferSize());
+        assertEquals(0, inboundBuffer.size());
+    }
+
+    @Test
+    public void onReadReady_preambleData() throws Exception {
+        final int maxBufferedInboundPackets = 10;
+        final int maxBufferedOutboundPackets = 20;
+
+        final StreamingPacketFile file =
+            createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+        final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+        final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer();
+
+        final int preambleLen = 23;
+        final int len1 = 11;
+        final byte[] data = new byte[preambleLen + 2 + len1];
+        NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, preambleLen);
+        data[preambleLen + 2] = (byte) 1;
+
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+        when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(5);
+        when(mockFileListener.onPreambleData(
+            any(), eq(0), eq(data.length - 5))).thenReturn(preambleLen - 5);
+        when(mockFileListener.onPreambleData(
+            any(), eq(0), eq(data.length - preambleLen))).thenReturn(0);
+
+        bufferedFile.onReadReady(mockAsyncFile);
+
+        final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+        final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+        final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mockFileListener).onInboundBuffered(data.length, data.length);
+        verify(mockFileListener).onInboundPacket(
+            arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+        verify(mockEventManager).execute(any());
+        verify(mockAsyncFile).enableReadEvents(true);
+
+        final byte[] capturedData = arrayCaptor.getValue();
+        assertEquals(2, posCaptor.getValue().intValue());
+        assertEquals(len1, lenCaptor.getValue().intValue());
+        assertEquals((byte) 1, capturedData[2]);
+
+        assertEquals(0, bufferedFile.getOutboundBufferSize());
+        assertEquals(0, inboundBuffer.size());
+    }
+
+    @Test
+    public void shutdownReading() throws Exception {
+        final int maxBufferedInboundPackets = 10;
+        final int maxBufferedOutboundPackets = 20;
+
+        final StreamingPacketFile file =
+            createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+        final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+        final byte[] data = new byte[100];
+        final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+        when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+        doAnswer(new Answer() {
+            @Override public Object answer(InvocationOnMock invocation) {
+                file.shutdownReading();
+                return Integer.valueOf(-1);
+            }}).when(mockFileListener).onPreambleData(any(), anyInt(), anyInt());
+
+        bufferedFile.onReadReady(mockAsyncFile);
+
+        verify(mockFileListener).onInboundBuffered(data.length, data.length);
+        verify(mockAsyncFile).enableReadEvents(false);
+
+        assertEquals(0, bufferedFile.getInboundBuffer().size());
+    }
+
+    private void checkAndResetMocks() {
+        verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager,
+            mockParcelFileDescriptor));
+        reset(mockFileListener, mockAsyncFile, mockEventManager);
+    }
+
+    private StreamingPacketFile createFile(
+            int maxBufferedInboundPackets, int maxBufferedOutboundPackets) throws Exception {
+        when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile);
+        return new StreamingPacketFile(
+            mockEventManager,
+            FileHandle.fromFileDescriptor(mockParcelFileDescriptor),
+            mockFileListener,
+            MAX_PACKET_SIZE,
+            maxBufferedInboundPackets,
+            maxBufferedOutboundPackets);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
new file mode 100644
index 0000000..e46dd59
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public final class DeviceInfoUtilsTest {
+    /**
+     * Verifies that version string compare logic returns expected result for various cases.
+     * Note that only major and minor number are compared.
+     */
+    @Test
+    public void testMajorMinorVersionCompare() {
+        assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.1", "4.8"));
+        assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("4.9", "4.8.1"));
+        assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5.0", "4.8"));
+        assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5", "4.8"));
+        assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("5", "5.0"));
+        assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5-beta1", "4.8"));
+        assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.0.0", "4.8"));
+        assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8-RC1", "4.8"));
+        assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8", "4.8"));
+        assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("3.10", "4.8.0"));
+        assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("4.7.10.10", "4.8"));
+    }
+
+    @Test
+    public void testGetMajorMinorSubminorVersion() throws Exception {
+        final DeviceInfoUtils.KVersion expected = new DeviceInfoUtils.KVersion(4, 19, 220);
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220"));
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220.50"));
+        assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion(
+                "4.19.220-g500ede0aed22-ab8272303"));
+
+        final DeviceInfoUtils.KVersion expected2 = new DeviceInfoUtils.KVersion(5, 17, 0);
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17"));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17."));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17.beta"));
+        assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion(
+                "5.17-rc6-g52099515ca00-ab8032400"));
+
+        final DeviceInfoUtils.KVersion invalid = new DeviceInfoUtils.KVersion(0, 0, 0);
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion(""));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4."));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4-beta"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("1.x.1"));
+        assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("x.1.1"));
+    }
+
+    @Test
+    public void testVersion() throws Exception {
+        final DeviceInfoUtils.KVersion v1 = new DeviceInfoUtils.KVersion(4, 8, 1);
+        final DeviceInfoUtils.KVersion v2 = new DeviceInfoUtils.KVersion(4, 8, 1);
+        final DeviceInfoUtils.KVersion v3 = new DeviceInfoUtils.KVersion(4, 8, 2);
+        final DeviceInfoUtils.KVersion v4 = new DeviceInfoUtils.KVersion(4, 9, 1);
+        final DeviceInfoUtils.KVersion v5 = new DeviceInfoUtils.KVersion(5, 8, 1);
+
+        assertEquals(v1, v2);
+        assertNotEquals(v1, v3);
+        assertNotEquals(v1, v4);
+        assertNotEquals(v1, v5);
+
+        assertEquals(0, v1.compareTo(v2));
+        assertEquals(-1, v1.compareTo(v3));
+        assertEquals(1, v3.compareTo(v1));
+        assertEquals(-1, v1.compareTo(v4));
+        assertEquals(1, v4.compareTo(v1));
+        assertEquals(-1, v1.compareTo(v5));
+        assertEquals(1, v5.compareTo(v1));
+
+        assertTrue(v2.isInRange(v1, v5));
+        assertTrue(v3.isInRange(v1, v5));
+        assertTrue(v4.isInRange(v1, v5));
+        assertFalse(v5.isInRange(v1, v5));
+        assertFalse(v1.isInRange(v3, v5));
+        assertFalse(v5.isInRange(v2, v4));
+
+        assertTrue(v2.isAtLeast(v1));
+        assertTrue(v3.isAtLeast(v1));
+        assertTrue(v4.isAtLeast(v1));
+        assertTrue(v5.isAtLeast(v1));
+        assertFalse(v1.isAtLeast(v3));
+        assertFalse(v1.isAtLeast(v4));
+        assertFalse(v1.isAtLeast(v5));
+    }
+
+    @Test
+    public void testKernelVersionIsAtLeast() {
+        // Pick a lower kernel version 4.0 which was released at April 2015, the kernel
+        // version running on all test devices nowadays should be higher than it.
+        assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("4.0"));
+
+        // Invalid kernel version.
+        assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("0.0.0"));
+
+        // Pick a higher kernel version which isn't released yet, comparison should return false.
+        // Need to update the target version in the future to make sure the test still passes.
+        assertFalse(DeviceInfoUtils.isKernelVersionAtLeast("20.0.0"));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
new file mode 100644
index 0000000..0f6fa48
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 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.testutils
+
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val ATTEMPTS = 50 // Causes testWaitForIdle to take about 150ms on aosp_crosshatch-eng
+private const val TIMEOUT_MS = 200
+
+@RunWith(JUnit4::class)
+class HandlerUtilsTest {
+    @Test
+    fun testWaitForIdle() {
+        val handlerThread = HandlerThread("testHandler").apply { start() }
+
+        // Tests that waitForIdle can be called many times without ill impact if the service is
+        // already idle.
+        repeat(ATTEMPTS) {
+            handlerThread.waitForIdle(TIMEOUT_MS)
+        }
+
+        // Tests that calling waitForIdle waits for messages to be processed. Use both an
+        // inline runnable that's instantiated at each loop run and a runnable that's instantiated
+        // once for all.
+        val tempRunnable = object : Runnable {
+            // Use StringBuilder preferentially to StringBuffer because StringBuilder is NOT
+            // thread-safe. It's part of the point that both runnables run on the same thread
+            // so if anything is wrong in that space it's better to opportunistically use a class
+            // where things might go wrong, even if there is no guarantee of failure.
+            var memory = StringBuilder()
+            override fun run() {
+                memory.append("b")
+            }
+        }
+        repeat(ATTEMPTS) { i ->
+            handlerThread.threadHandler.post { tempRunnable.memory.append("a"); }
+            handlerThread.threadHandler.post(tempRunnable)
+            handlerThread.waitForIdle(TIMEOUT_MS)
+            assertEquals(tempRunnable.memory.toString(), "ab".repeat(i + 1))
+        }
+    }
+
+    // Statistical test : even if visibleOnHandlerThread doesn't work this is likely to succeed,
+    // but it will be at least flaky.
+    @Test
+    fun testVisibleOnHandlerThread() {
+        val handlerThread = HandlerThread("testHandler").apply { start() }
+        val handler = Handler(handlerThread.looper)
+
+        repeat(ATTEMPTS) { attempt ->
+            var x = -10
+            var y = -11
+            y = visibleOnHandlerThread(handler, ThrowingSupplier<Int> { x = attempt; attempt })
+            assertEquals(attempt, x)
+            assertEquals(attempt, y)
+            handler.post { assertEquals(attempt, x) }
+        }
+
+        assertFailsWith<IllegalArgumentException> {
+            visibleOnHandlerThread(handler) { throw IllegalArgumentException() }
+        }
+
+        // Null values may be returned by the supplier
+        assertNull(visibleOnHandlerThread(handler, ThrowingSupplier<Nothing?> { null }))
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt
new file mode 100644
index 0000000..6f4587b
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/TestDnsServerTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.testutils
+
+import android.net.DnsResolver.CLASS_IN
+import android.net.DnsResolver.TYPE_AAAA
+import android.net.Network
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.DnsPacket
+import com.android.net.module.util.DnsPacket.DnsRecord
+import libcore.net.InetAddressUtils
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import java.net.DatagramPacket
+import java.net.DatagramSocket
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+val TEST_V6_ADDR = InetAddressUtils.parseNumericAddress("2001:db8::3")
+const val TEST_DOMAIN = "hello.example.com"
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TestDnsServerTest {
+    private val network = Mockito.mock(Network::class.java)
+    private val localAddr = InetSocketAddress(InetAddress.getLocalHost(), 0 /* port */)
+    private val testServer: TestDnsServer = TestDnsServer(network, localAddr)
+
+    @After
+    fun tearDown() {
+        if (testServer.isAlive) testServer.stop()
+    }
+
+    @Test
+    fun testStartStop() {
+        repeat(100) {
+            val server = TestDnsServer(network, localAddr)
+            server.start()
+            assertTrue(server.isAlive)
+            server.stop()
+            assertFalse(server.isAlive)
+        }
+
+        // Test illegal start/stop.
+        assertFailsWith<IllegalStateException> { testServer.stop() }
+        testServer.start()
+        assertTrue(testServer.isAlive)
+        assertFailsWith<IllegalStateException> { testServer.start() }
+        testServer.stop()
+        assertFalse(testServer.isAlive)
+        assertFailsWith<IllegalStateException> { testServer.stop() }
+        // TestDnsServer rejects start after stop.
+        assertFailsWith<IllegalStateException> { testServer.start() }
+    }
+
+    @Test
+    fun testHandleDnsQuery() {
+        testServer.setAnswer(TEST_DOMAIN, listOf(TEST_V6_ADDR))
+        testServer.start()
+
+        // Mock query and send it to the test server.
+        val queryHeader = DnsPacket.DnsHeader(0xbeef /* id */,
+                0x0 /* flag */, 1 /* qcount */, 0 /* ancount */)
+        val qlist = listOf(DnsRecord.makeQuestion(TEST_DOMAIN, TYPE_AAAA, CLASS_IN))
+        val queryPacket = TestDnsServer.DnsQueryPacket(queryHeader, qlist, emptyList())
+        val response = resolve(queryPacket, testServer.port)
+
+        // Verify expected answer packet. Set QR bit of flag to 1 for response packet
+        // according to RFC 1035 section 4.1.1.
+        val answerHeader = DnsPacket.DnsHeader(0xbeef,
+            1 shl 15 /* flag */, 1 /* qcount */, 1 /* ancount */)
+        val alist = listOf(DnsRecord.makeAOrAAAARecord(DnsPacket.ANSECTION, TEST_DOMAIN,
+                    CLASS_IN, DEFAULT_TTL_S, TEST_V6_ADDR))
+        val expectedAnswerPacket = TestDnsServer.DnsAnswerPacket(answerHeader, qlist, alist)
+        assertEquals(expectedAnswerPacket, response)
+
+        // Clean up the server in tearDown.
+    }
+
+    private fun resolve(queryDnsPacket: DnsPacket, serverPort: Int): TestDnsServer.DnsAnswerPacket {
+        val bytes = queryDnsPacket.bytes
+        // Create a new client socket, the socket will be bound to a
+        // random port other than the server port.
+        val socket = DatagramSocket(localAddr).also { it.soTimeout = 100 }
+        val queryPacket = DatagramPacket(bytes, bytes.size, localAddr.address, serverPort)
+
+        // Send query and wait for the reply.
+        socket.send(queryPacket)
+        val buffer = ByteArray(MAX_BUF_SIZE)
+        val reply = DatagramPacket(buffer, buffer.size)
+        socket.receive(reply)
+        return TestDnsServer.DnsAnswerPacket(reply.data)
+    }
+
+    // TODO: Add more tests, which includes:
+    //  * Empty question RR packet (or more unexpected states)
+    //  * No answer found (setAnswer empty list at L.78)
+    //  * Test one or multi A record(s)
+    //  * Test multi AAAA records
+    //  * Test CNAME records
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
new file mode 100644
index 0000000..e838bdc
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
@@ -0,0 +1,474 @@
+/*
+ * 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.testutils
+
+import android.annotation.SuppressLint
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+const val SHORT_TIMEOUT_MS = 20L
+const val DEFAULT_LINGER_DELAY_MS = 30000
+const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+const val WIFI = NetworkCapabilities.TRANSPORT_WIFI
+const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR
+const val TEST_INTERFACE_NAME = "testInterfaceName"
+
+@RunWith(JUnit4::class)
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
+class TestableNetworkCallbackTest {
+    private lateinit var mCallback: TestableNetworkCallback
+
+    private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork {
+        override val network: Network = Network(netId)
+    }
+
+    @Before
+    fun setUp() {
+        mCallback = TestableNetworkCallback()
+    }
+
+    @Test
+    fun testLastAvailableNetwork() {
+        // Make sure there is no last available network at first, then the last available network
+        // is returned after onAvailable is called.
+        val net2097 = Network(2097)
+        assertNull(mCallback.lastAvailableNetwork)
+        mCallback.onAvailable(net2097)
+        assertEquals(mCallback.lastAvailableNetwork, net2097)
+
+        // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available
+        // network.
+        mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net2097, LinkProperties())
+        assertEquals(mCallback.lastAvailableNetwork, net2097)
+
+        // Make sure onLost clears the last available network.
+        mCallback.onLost(net2097)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Do the same but with a different network after onLost : make sure the last available
+        // network is the new one, not the original one.
+        val net2098 = Network(2098)
+        mCallback.onAvailable(net2098)
+        mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net2098, LinkProperties())
+        assertEquals(mCallback.lastAvailableNetwork, net2098)
+
+        // Make sure onAvailable changes the last available network even if onLost was not called.
+        val net2099 = Network(2099)
+        mCallback.onAvailable(net2099)
+        assertEquals(mCallback.lastAvailableNetwork, net2099)
+
+        // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily
+        // the last available one. Check that behavior.
+        mCallback.onLost(net2098)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Make sure that losing the really last available one still results in null.
+        mCallback.onLost(net2099)
+        assertNull(mCallback.lastAvailableNetwork)
+
+        // Make sure multiple onAvailable in a row then onLost still results in null.
+        mCallback.onAvailable(net2097)
+        mCallback.onAvailable(net2098)
+        mCallback.onAvailable(net2099)
+        mCallback.onLost(net2097)
+        assertNull(mCallback.lastAvailableNetwork)
+    }
+
+    @Test
+    fun testAssertNoCallback() {
+        mCallback.assertNoCallback(SHORT_TIMEOUT_MS)
+        mCallback.onAvailable(Network(100))
+        assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) }
+        val net = Network(101)
+        mCallback.assertNoCallback { it is Available }
+        mCallback.onAvailable(net)
+        // Expect no blocked status change. Receive other callback does not fail the test.
+        mCallback.assertNoCallback { it is BlockedStatus }
+        mCallback.onBlockedStatusChanged(net, true)
+        assertFails { mCallback.assertNoCallback { it is BlockedStatus } }
+        mCallback.onBlockedStatusChanged(net, false)
+        mCallback.onCapabilitiesChanged(net, NetworkCapabilities())
+        assertFails { mCallback.assertNoCallback { it is CapabilitiesChanged } }
+    }
+
+    @Test
+    fun testCapabilitiesWithAndWithout() {
+        val net = Network(101)
+        val matcher = makeHasNetwork(101)
+        val meteredNc = NetworkCapabilities()
+        val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)
+        // Check that expecting caps (with or without) fails when no callback has been received.
+        assertFails {
+            mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) }
+        }
+        assertFails {
+            mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) }
+        }
+
+        // Add NOT_METERED and check that With succeeds and Without fails.
+        mCallback.onCapabilitiesChanged(net, unmeteredNc)
+        mCallback.expectCaps(matcher) { it.hasCapability(NOT_METERED) }
+        mCallback.onCapabilitiesChanged(net, unmeteredNc)
+        assertFails {
+            mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) }
+        }
+
+        // Don't add NOT_METERED and check that With fails and Without succeeds.
+        mCallback.onCapabilitiesChanged(net, meteredNc)
+        assertFails {
+            mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) }
+        }
+        mCallback.onCapabilitiesChanged(net, meteredNc)
+        mCallback.expectCaps(matcher) { !it.hasCapability(NOT_METERED) }
+    }
+
+    @Test
+    fun testExpectWithPredicate() {
+        val net = Network(193)
+        val netCaps = NetworkCapabilities().addTransportType(CELLULAR)
+        // Check that expecting callbackThat anything fails when no callback has been received.
+        assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onAvailable(net)
+        mCallback.expect<Available> { true }
+        mCallback.onAvailable(net)
+        assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and a negative case
+        mCallback.onBlockedStatusChanged(net, true)
+        mCallback.expect<CallbackEntry> { cb -> cb is BlockedStatus && cb.blocked }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expect<CallbackEntry>(timeoutMs = SHORT_TIMEOUT_MS) { cb ->
+            cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI)
+        } }
+    }
+
+    @Test
+    fun testExpectCaps() {
+        val net = Network(101)
+        val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)
+        // Check that expecting capabilitiesThat anything fails when no callback has been received.
+        assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        mCallback.expectCaps(net) { true }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and a negative case
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        mCallback.expectCaps(net) {
+            it.hasCapability(NOT_METERED) && it.hasTransport(WIFI) && !it.hasTransport(CELLULAR)
+        }
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { it.hasTransport(CELLULAR) } }
+
+        // Try a matching callback on the wrong network
+        mCallback.onCapabilitiesChanged(net, netCaps)
+        assertFails {
+            mCallback.expectCaps(Network(100), SHORT_TIMEOUT_MS) { true }
+        }
+    }
+
+    @Test
+    fun testLinkPropertiesCallbacks() {
+        val net = Network(112)
+        val linkAddress = LinkAddress("fe80::ace:d00d/64")
+        val mtu = 1984
+        val linkProps = LinkProperties().apply {
+            this.mtu = mtu
+            interfaceName = TEST_INTERFACE_NAME
+            addLinkAddress(linkAddress)
+        }
+
+        // Check that expecting linkPropsThat anything fails when no callback has been received.
+        assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { true } }
+
+        // Basic test for true and false
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        mCallback.expect<LinkPropertiesChanged>(net) { true }
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { false } }
+
+        // Try a positive and negative case
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        mCallback.expect<LinkPropertiesChanged>(net) {
+            it.lp.interfaceName == TEST_INTERFACE_NAME &&
+                    it.lp.linkAddresses.contains(linkAddress) &&
+                    it.lp.mtu == mtu
+        }
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) {
+            it.lp.interfaceName != TEST_INTERFACE_NAME
+        } }
+
+        // Try a matching callback on the wrong network
+        mCallback.onLinkPropertiesChanged(net, linkProps)
+        assertFails { mCallback.expect<LinkPropertiesChanged>(Network(114), SHORT_TIMEOUT_MS) {
+            it.lp.interfaceName == TEST_INTERFACE_NAME
+        } }
+    }
+
+    @Test
+    fun testExpect() {
+        val net = Network(103)
+        // Test expectCallback fails when nothing was sent.
+        assertFails { mCallback.expect<BlockedStatus>(net, SHORT_TIMEOUT_MS) }
+
+        // Test onAvailable is seen and can be expected
+        mCallback.onAvailable(net)
+        mCallback.expect<Available>(net, SHORT_TIMEOUT_MS)
+
+        // Test onAvailable won't return calls with a different network
+        mCallback.onAvailable(Network(106))
+        assertFails { mCallback.expect<Available>(net, SHORT_TIMEOUT_MS) }
+
+        // Test onAvailable won't return calls with a different callback
+        mCallback.onAvailable(net)
+        assertFails { mCallback.expect<BlockedStatus>(net, SHORT_TIMEOUT_MS) }
+    }
+
+    @Test
+    fun testAllExpectOverloads() {
+        // This test should never run, it only checks that all overloads exist and build
+        assumeTrue(false)
+        val hn = object : TestableNetworkCallback.HasNetwork { override val network = ANY_NETWORK }
+
+        // Method with all arguments (version that takes a Network)
+        mCallback.expect(AVAILABLE, ANY_NETWORK, 10, "error") { true }
+
+        // Java overloads omitting one argument. One line for omitting each argument, in positional
+        // order. Versions that take a Network.
+        mCallback.expect(AVAILABLE, 10, "error") { true }
+        mCallback.expect(AVAILABLE, ANY_NETWORK, "error") { true }
+        mCallback.expect(AVAILABLE, ANY_NETWORK, 10) { true }
+        mCallback.expect(AVAILABLE, ANY_NETWORK, 10, "error")
+
+        // Java overloads for omitting two arguments. One line for omitting each pair of arguments.
+        // Versions that take a Network.
+        mCallback.expect(AVAILABLE, "error") { true }
+        mCallback.expect(AVAILABLE, 10) { true }
+        mCallback.expect(AVAILABLE, 10, "error")
+        mCallback.expect(AVAILABLE, ANY_NETWORK) { true }
+        mCallback.expect(AVAILABLE, ANY_NETWORK, "error")
+        mCallback.expect(AVAILABLE, ANY_NETWORK, 10)
+
+        // Java overloads for omitting three arguments. One line for each remaining argument.
+        // Versions that take a Network.
+        mCallback.expect(AVAILABLE) { true }
+        mCallback.expect(AVAILABLE, "error")
+        mCallback.expect(AVAILABLE, 10)
+        mCallback.expect(AVAILABLE, ANY_NETWORK)
+
+        // Java overload for omitting all four arguments.
+        mCallback.expect(AVAILABLE)
+
+        // Same orders as above, but versions that take a HasNetwork. Except overloads that
+        // were already tested because they omitted the Network argument
+        mCallback.expect(AVAILABLE, hn, 10, "error") { true }
+        mCallback.expect(AVAILABLE, hn, "error") { true }
+        mCallback.expect(AVAILABLE, hn, 10) { true }
+        mCallback.expect(AVAILABLE, hn, 10, "error")
+
+        mCallback.expect(AVAILABLE, hn) { true }
+        mCallback.expect(AVAILABLE, hn, "error")
+        mCallback.expect(AVAILABLE, hn, 10)
+
+        mCallback.expect(AVAILABLE, hn)
+
+        // Same as above but for reified versions.
+        mCallback.expect<Available>(ANY_NETWORK, 10, "error") { true }
+        mCallback.expect<Available>(timeoutMs = 10, errorMsg = "error") { true }
+        mCallback.expect<Available>(network = ANY_NETWORK, errorMsg = "error") { true }
+        mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10) { true }
+        mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10, errorMsg = "error")
+
+        mCallback.expect<Available>(errorMsg = "error") { true }
+        mCallback.expect<Available>(timeoutMs = 10) { true }
+        mCallback.expect<Available>(timeoutMs = 10, errorMsg = "error")
+        mCallback.expect<Available>(network = ANY_NETWORK) { true }
+        mCallback.expect<Available>(network = ANY_NETWORK, errorMsg = "error")
+        mCallback.expect<Available>(network = ANY_NETWORK, timeoutMs = 10)
+
+        mCallback.expect<Available> { true }
+        mCallback.expect<Available>(errorMsg = "error")
+        mCallback.expect<Available>(timeoutMs = 10)
+        mCallback.expect<Available>(network = ANY_NETWORK)
+        mCallback.expect<Available>()
+
+        mCallback.expect<Available>(hn, 10, "error") { true }
+        mCallback.expect<Available>(network = hn, errorMsg = "error") { true }
+        mCallback.expect<Available>(network = hn, timeoutMs = 10) { true }
+        mCallback.expect<Available>(network = hn, timeoutMs = 10, errorMsg = "error")
+
+        mCallback.expect<Available>(network = hn) { true }
+        mCallback.expect<Available>(network = hn, errorMsg = "error")
+        mCallback.expect<Available>(network = hn, timeoutMs = 10)
+
+        mCallback.expect<Available>(network = hn)
+    }
+
+    @Test
+    fun testExpectClass() {
+        val net = Network(1)
+        mCallback.onAvailable(net)
+        assertFails { mCallback.expect(LOST, net) }
+    }
+
+    @Test
+    fun testPoll() {
+        assertNull(mCallback.poll(SHORT_TIMEOUT_MS))
+        TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+                threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+            sleep; onAvailable(133)    | poll(2) = Available(133) time 1..4
+                                       | poll(1) = null
+            onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3
+            onBlockedStatus(199)       | poll(1) = BlockedStatus(199) time 0..3
+        """)
+    }
+
+    @Test
+    fun testEventuallyExpect() {
+        // TODO: Current test does not verify the inline one. Also verify the behavior after
+        // aligning two eventuallyExpect()
+        val net1 = Network(100)
+        val net2 = Network(101)
+        mCallback.onAvailable(net1)
+        mCallback.onCapabilitiesChanged(net1, NetworkCapabilities())
+        mCallback.onLinkPropertiesChanged(net1, LinkProperties())
+        mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) {
+            net1.equals(it.network)
+        }
+        // No further new callback. Expect no callback.
+        assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED, SHORT_TIMEOUT_MS) }
+
+        // Verify no predicate set.
+        mCallback.onAvailable(net2)
+        mCallback.onLinkPropertiesChanged(net2, LinkProperties())
+        mCallback.onBlockedStatusChanged(net1, false)
+        mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) }
+        // Verify no callback received if the callback does not happen.
+        assertFails { mCallback.eventuallyExpect(LOSING, SHORT_TIMEOUT_MS) }
+    }
+
+    @Test
+    fun testEventuallyExpectOnMultiThreads() {
+        TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+                threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+                onAvailable(100)                   | eventually(CapabilitiesChanged(100), 1) fails
+                sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 3)
+                onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails
+                onSuspended(100) ; sleep ; onLost(100)  | eventually(Lost(100), 3)
+        """)
+    }
+}
+
+private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable)
+
+val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|")
+private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> {
+    return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
+}
+
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
+private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
+    // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for
+    // all callback types. This is implemented above by enumerating the subclasses of
+    // CallbackEntry and reading their simpleName.
+    Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t ->
+        val record = i.interpret(t.strArg(1), cb)
+        assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record))
+        // Strictly speaking testing for is CallbackEntry is useless as it's been tested above
+        // but the compiler can't figure things out from the isInstance call. It does understand
+        // from the assertTrue(is CallbackEntry) that this is true, which allows to access
+        // the 'network' member below.
+        assertTrue(record is CallbackEntry)
+        assertEquals(record.network.netId, t.intArg(3))
+    },
+    // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for
+    // all callback types. NetworkCapabilities and LinkProperties just get an empty object
+    // as their argument. Losing gets the default linger timer. Blocked gets false.
+    Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t ->
+        val net = Network(t.intArg(2))
+        when (t.strArg(1)) {
+            "Available" -> cb.onAvailable(net)
+            // PreCheck not used in tests. Add it here if it becomes useful.
+            "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities())
+            "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties())
+            "Suspended" -> cb.onNetworkSuspended(net)
+            "Resumed" -> cb.onNetworkResumed(net)
+            "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS)
+            "Lost" -> cb.onLost(net)
+            "Unavailable" -> cb.onUnavailable()
+            "BlockedStatus" -> cb.onBlockedStatusChanged(net, false)
+            else -> fail("Unknown callback type")
+        }
+    },
+    Regex("""poll\((\d+)\)""") to { i, cb, t -> cb.poll(t.timeArg(1)) },
+    // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects
+    // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and
+    // likewise for all callback types.
+    Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t ->
+        val net = Network(t.intArg(2))
+        val timeout = t.timeArg(3)
+        when (t.strArg(1)) {
+            "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network }
+            "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network }
+            "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network }
+            "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network }
+            "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network }
+            "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network }
+            "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network }
+            "CapabilitiesChanged" ->
+                cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network }
+            "LinkPropertiesChanged" ->
+                cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network }
+            else -> fail("Unknown callback type")
+        }
+    }
+)
diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java
new file mode 100644
index 0000000..4570d0a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTestJava.java
@@ -0,0 +1,76 @@
+/*
+ * 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.testutils;
+
+import static com.android.testutils.RecorderCallback.CallbackEntry.AVAILABLE;
+import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Test;
+
+public class TestableNetworkCallbackTestJava {
+    @Test
+    void testAllExpectOverloads() {
+        // This test should never run, it only checks that all overloads exist and build
+        assumeTrue(false);
+        final TestableNetworkCallback callback = new TestableNetworkCallback();
+        TestableNetworkCallback.HasNetwork hn = TestableNetworkCallbackKt::anyNetwork;
+
+        // Method with all arguments (version that takes a Network)
+        callback.expect(AVAILABLE, anyNetwork(), 10, "error", cb -> true);
+
+        // Overloads omitting one argument. One line for omitting each argument, in positional
+        // order. Versions that take a Network.
+        callback.expect(AVAILABLE, 10, "error", cb -> true);
+        callback.expect(AVAILABLE, anyNetwork(), "error", cb -> true);
+        callback.expect(AVAILABLE, anyNetwork(), 10, cb -> true);
+        callback.expect(AVAILABLE, anyNetwork(), 10, "error");
+
+        // Overloads for omitting two arguments. One line for omitting each pair of arguments.
+        // Versions that take a Network.
+        callback.expect(AVAILABLE, "error", cb -> true);
+        callback.expect(AVAILABLE, 10, cb -> true);
+        callback.expect(AVAILABLE, 10, "error");
+        callback.expect(AVAILABLE, anyNetwork(), cb -> true);
+        callback.expect(AVAILABLE, anyNetwork(), "error");
+        callback.expect(AVAILABLE, anyNetwork(), 10);
+
+        // Overloads for omitting three arguments. One line for each remaining argument.
+        // Versions that take a Network.
+        callback.expect(AVAILABLE, cb -> true);
+        callback.expect(AVAILABLE, "error");
+        callback.expect(AVAILABLE, 10);
+        callback.expect(AVAILABLE, anyNetwork());
+
+        // Java overload for omitting all four arguments.
+        callback.expect(AVAILABLE);
+
+        // Same orders as above, but versions that take a HasNetwork. Except overloads that
+        // were already tested because they omitted the Network argument
+        callback.expect(AVAILABLE, hn, 10, "error", cb -> true);
+        callback.expect(AVAILABLE, hn, "error", cb -> true);
+        callback.expect(AVAILABLE, hn, 10, cb -> true);
+        callback.expect(AVAILABLE, hn, 10, "error");
+
+        callback.expect(AVAILABLE, hn, cb -> true);
+        callback.expect(AVAILABLE, hn, "error");
+        callback.expect(AVAILABLE, hn, 10);
+
+        callback.expect(AVAILABLE, hn);
+    }
+}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
new file mode 100644
index 0000000..5fe7ac3
--- /dev/null
+++ b/staticlibs/testutils/Android.bp
@@ -0,0 +1,88 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "net-tests-utils",
+    srcs: [
+        "devicetests/**/*.java",
+        "devicetests/**/*.kt",
+    ],
+    defaults: [
+        "framework-connectivity-test-defaults",
+        "lib_mockito_extended"
+    ],
+    libs: [
+        "androidx.annotation_annotation",
+        "net-utils-device-common-bpf",  // TestBpfMap extends IBpfMap.
+    ],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "kotlin-reflect",
+        "libnanohttpd",
+        "net-tests-utils-host-device-common",
+        "net-utils-device-common",
+        "net-utils-device-common-async",
+        "net-utils-device-common-netlink",
+        "net-utils-device-common-wear",
+        "modules-utils-build_system",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    // Consider using net-tests-utils instead if writing device code.
+    // That library has a lot more useful tools into it for users that
+    // work on Android and includes this lib.
+    name: "net-tests-utils-host-device-common",
+    srcs: [
+        "hostdevice/**/*.java",
+        "hostdevice/**/*.kt",
+    ],
+    host_supported: true,
+    visibility: [
+        "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
+        "//packages/modules/Connectivity/staticlibs/client-libs/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests/cts/hostside",
+    ],
+    // There are downstream branches using an old version of Kotlin
+    // that used to reserve the right to make breaking changes to the
+    // Result type and disallowed returning an instance of it.
+    // Later versions allowed this and there was never a change,
+    // so no matter the version returning Result is always fine,
+    // but on sc-mainline-prod the compiler rejects it without
+    // the following flag.
+    kotlincflags: ["-Xallow-result-return-type"],
+    libs: [
+        "jsr305",
+    ],
+    static_libs: [
+        "kotlin-test"
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_test_host {
+    name: "net-tests-utils-host-common",
+    srcs: [
+        "host/**/*.java",
+        "host/**/*.kt",
+    ],
+    libs: ["tradefed"],
+    test_suites: ["ats", "device-tests", "general-tests", "cts", "mts-networking"],
+    data: [":ConnectivityTestPreparer"],
+}
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
new file mode 100644
index 0000000..f7118cf
--- /dev/null
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "ConnectivityTestPreparer",
+    srcs: ["src/**/*.kt"],
+    sdk_version: "system_current",
+    // Allow running the test on any device with SDK Q+, even when built from a branch that uses
+    // an unstable SDK, by targeting a stable SDK regardless of the build SDK.
+    min_sdk_version: "29",
+    target_sdk_version: "30",
+    static_libs: [
+        "androidx.test.rules",
+        "modules-utils-build_system",
+        "net-tests-utils",
+    ],
+    host_required: ["net-tests-utils-host-common"],
+    lint: { strict_updatability_linting: true },
+}
diff --git a/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml
new file mode 100644
index 0000000..015b41f
--- /dev/null
+++ b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.testutils.connectivitypreparer">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <!-- For wifi scans -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.testutils.connectivitypreparer"
+                     android:label="Connectivity test target preparer" />
+</manifest>
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
new file mode 100644
index 0000000..d75d9ca
--- /dev/null
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.testutils.connectivitypreparer
+
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.ConnectUtil
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ConnectivityCheckTest {
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+    private val pm by lazy { context.packageManager }
+    private val connectUtil by lazy { ConnectUtil(context) }
+
+    @Test
+    fun testCheckConnectivity() {
+        checkWifiSetup()
+        checkTelephonySetup()
+    }
+
+    private fun checkWifiSetup() {
+        if (!pm.hasSystemFeature(FEATURE_WIFI)) return
+        connectUtil.ensureWifiValidated()
+    }
+
+    private fun checkTelephonySetup() {
+        if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
+        val tm = context.getSystemService(TelephonyManager::class.java)
+                ?: fail("Could not get telephony service")
+
+        val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
+                "testing, you can use atest X -- " +
+                "--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
+                ":ignore-connectivity-check:true"
+        // Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
+        if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
+            fail("The device has no SIM card inserted. $commonError")
+        } else if (tm.simState != TelephonyManager.SIM_STATE_READY) {
+            fail("The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " +
+                    commonError)
+        }
+        assertTrue(tm.isDataConnectivityPossible,
+            "The device is not setup with a SIM card that supports data connectivity. " +
+                    commonError)
+        connectUtil.ensureCellularValidated()
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt
new file mode 100644
index 0000000..cf0490c
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.net.MacAddress
+import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import java.net.Inet4Address
+import java.net.InetAddress
+import java.nio.ByteBuffer
+
+private const val ARP_SENDER_MAC_OFFSET = ETHER_HEADER_LEN + 8
+private const val ARP_TARGET_IPADDR_OFFSET = ETHER_HEADER_LEN + 24
+
+private val TYPE_ARP = byteArrayOf(0x08, 0x06)
+// Arp reply header for IPv4 over ethernet
+private val ARP_REPLY_IPV4 = byteArrayOf(0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02)
+
+/**
+ * A class that can be used to reply to ARP packets on a [TapPacketReader].
+ */
+class ArpResponder(
+    reader: TapPacketReader,
+    table: Map<Inet4Address, MacAddress>,
+    name: String = ArpResponder::class.java.simpleName
+) : PacketResponder(reader, ArpRequestFilter(), name) {
+    // Copy the map if not already immutable (toMap) to make sure it is not modified
+    private val table = table.toMap()
+
+    override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) {
+        val targetIp = InetAddress.getByAddress(
+                packet.copyFromIndexWithLength(ARP_TARGET_IPADDR_OFFSET, 4))
+                as Inet4Address
+
+        val macAddr = table[targetIp]?.toByteArray() ?: return
+        val senderMac = packet.copyFromIndexWithLength(ARP_SENDER_MAC_OFFSET, 6)
+        reader.sendResponse(ByteBuffer.wrap(
+                // Ethernet header
+                senderMac + macAddr + TYPE_ARP +
+                        // ARP message
+                        ARP_REPLY_IPV4 +
+                        macAddr /* sender MAC */ +
+                        targetIp.address /* sender IP addr */ +
+                        macAddr /* target mac */ +
+                        targetIp.address /* target IP addr */
+        ))
+    }
+}
+
+private fun ByteArray.copyFromIndexWithLength(start: Int, len: Int) =
+        copyOfRange(start, start + len)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt
new file mode 100644
index 0000000..82f1d9b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+import android.net.NetworkSpecifier
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
+
+/**
+ * Test utility to create [NetworkSpecifier]s on different SDK versions.
+ */
+object CompatUtil {
+    @JvmStatic
+    fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier {
+        // Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead
+        if (!isAtLeastS()) {
+            return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+        }
+        // TestNetworkSpecifier is not part of the SDK in some branches using this utility
+        // TODO: replace with a direct call to the constructor
+        return makeNetworkSpecifierInternal("android.net.TestNetworkSpecifier", ifName)
+    }
+
+    @JvmStatic
+    fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier {
+        // Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead
+        if (!isAtLeastS()) {
+            return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+        }
+        // EthernetNetworkSpecifier is not part of the SDK in some branches using this utility
+        // TODO: replace with a direct call to the constructor
+        return makeNetworkSpecifierInternal("android.net.EthernetNetworkSpecifier", ifName)
+    }
+
+    private fun makeNetworkSpecifierInternal(clazz: String, specifier: String): NetworkSpecifier {
+        // StringNetworkSpecifier was removed after R (and was hidden API before that)
+        return Class.forName(clazz)
+                .getConstructor(String::class.java).newInstance(specifier) as NetworkSpecifier
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
new file mode 100644
index 0000000..9e72f4b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
@@ -0,0 +1,240 @@
+package com.android.testutils
+
+import android.os.SystemClock
+import java.util.concurrent.CyclicBarrier
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+// The table contains pairs associating a regexp with the code to run. The statement is matched
+// against each matcher in sequence and when a match is found the associated code is run, passing
+// it the TrackRecord under test and the result of the regexp match.
+typealias InterpretMatcher<T> = Pair<Regex, (ConcurrentInterpreter<T>, T, MatchResult) -> Any?>
+
+// The default unit of time for interpreted tests
+const val INTERPRET_TIME_UNIT = 60L // ms
+
+/**
+ * A small interpreter for testing parallel code.
+ *
+ * The interpreter will read a list of lines consisting of "|"-separated statements, e.g. :
+ *   sleep 2 ; unblock thread2 | wait thread2 time 2..5
+ *   sendMessage "x"           | obtainMessage = "x" time 0..1
+ *
+ * Each column runs in a different concurrent thread and all threads wait for each other in
+ * between lines. Each statement is split on ";" then matched with regular expressions in the
+ * instructionTable constant, which contains the code associated with each statement. The
+ * interpreter supports an object being passed to the interpretTestSpec() method to be passed
+ * in each lambda (think about the object under test), and an optional transform function to be
+ * executed on the object at the start of every thread.
+ *
+ * The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default
+ * value but can be passed to the constructor. Whitespace is ignored.
+ *
+ * The interpretation table has to be passed as an argument. It's a table associating a regexp
+ * with the code that should execute, as a function taking three arguments : the interpreter,
+ * the regexp match, and the object. See the individual tests for the DSL of that test.
+ * Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable
+ * constant below for an example of how to write an interpreting table.
+ * Some expressions already exist by default and can be used by all interpreters. Refer to
+ * getDefaultInstructions() below for a list and documentation.
+ */
+open class ConcurrentInterpreter<T>(localInterpretTable: List<InterpretMatcher<T>>) {
+    private val interpretTable: List<InterpretMatcher<T>> =
+            localInterpretTable + getDefaultInstructions()
+    // The last time the thread became blocked, with base System.currentTimeMillis(). This should
+    // be set immediately before any time the thread gets blocked.
+    internal val lastBlockedTime = ThreadLocal<Long>()
+
+    // Split the line into multiple statements separated by ";" and execute them. Return whatever
+    // the last statement returned.
+    fun interpretMultiple(instr: String, r: T): Any? {
+        return instr.split(";").map { interpret(it.trim(), r) }.last()
+    }
+
+    // Match the statement to a regex and interpret it.
+    fun interpret(instr: String, r: T): Any? {
+        val (matcher, code) =
+                interpretTable.find { instr matches it.first } ?: throw SyntaxException(instr)
+        val match = matcher.matchEntire(instr) ?: throw SyntaxException(instr)
+        return code(this, r, match)
+    }
+
+    /**
+     * Spins as many threads as needed by the test spec and interpret each program concurrently.
+     *
+     * All threads wait on a CyclicBarrier after each line.
+     * |lineShift| says how many lines after the call the spec starts. This is used for error
+     * reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
+     * than the line at which the expression starts.
+     *
+     * This method is mostly meant for implementations that extend the ConcurrentInterpreter
+     * class to add their own directives and instructions. These may need to operate on some
+     * data, which can be passed in |initial|. For example, an interpreter specialized in callbacks
+     * may want to pass the callback there. In some cases, it's necessary that each thread
+     * performs a transformation *after* it starts on that value before starting ; in this case,
+     * the transformation can be passed to |threadTransform|. The default is to return |initial| as
+     * is. Look at some existing child classes of this interpreter for some examples of how this
+     * can be used.
+     *
+     * @param spec The test spec, as a string of lines separated by pipes.
+     * @param initial An initial value passed to all threads.
+     * @param lineShift How many lines after the call the spec starts, for error reporting.
+     * @param threadTransform an optional transformation that each thread will apply to |initial|
+     */
+    fun interpretTestSpec(
+        spec: String,
+        initial: T,
+        lineShift: Int = 0,
+        threadTransform: (T) -> T = { it }
+    ) {
+        // For nice stack traces
+        val callSite = getCallingMethod()
+        val lines = spec.trim().trim('\n').split("\n").map { it.split("|") }
+        // |lines| contains arrays of strings that make up the statements of a thread : in other
+        // words, it's an array that contains a list of statements for each column in the spec.
+        // E.g. if the string is """
+        //   a | b | c
+        //   d | e | f
+        // """, then lines is [ [ "a", "b", "c" ], [ "d", "e", "f" ] ].
+        val threadCount = lines[0].size
+        assertTrue(lines.all { it.size == threadCount })
+        val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } }
+        // |threadInstructions| is a list where each element is the list of instructions for the
+        // thread at the index. In other words, it's just |lines| transposed. In the example
+        // above, it would be [ [ "a", "d" ], [ "b", "e" ], [ "c", "f" ] ]
+        // mapIndexed below will pass in |instructions| the list of instructions for this thread.
+        val barrier = CyclicBarrier(threadCount)
+        var crash: InterpretException? = null
+        threadInstructions.mapIndexed { threadIndex, instructions ->
+            Thread {
+                val threadLocal = threadTransform(initial)
+                lastBlockedTime.set(System.currentTimeMillis())
+                barrier.await()
+                var lineNum = 0
+                instructions.forEach {
+                    if (null != crash) return@Thread
+                    lineNum += 1
+                    try {
+                        interpretMultiple(it, threadLocal)
+                    } catch (e: Throwable) {
+                        // If fail() or some exception was called, the thread will come here ; if
+                        // the exception isn't caught the process will crash, which is not nice for
+                        // testing. Instead, catch the exception, cancel other threads, and report
+                        // nicely. Catch throwable because fail() is AssertionError, which inherits
+                        // from Error.
+                        crash = InterpretException(threadIndex, it,
+                                callSite.lineNumber + lineNum + lineShift,
+                                callSite.className, callSite.methodName, callSite.fileName, e)
+                    }
+                    lastBlockedTime.set(System.currentTimeMillis())
+                    barrier.await()
+                }
+            }.also { it.start() }
+        }.forEach { it.join() }
+        // If the test failed, crash with line number
+        crash?.let { throw it }
+    }
+
+    // Helper to get the stack trace for a calling method
+    private fun getCallingStackTrace(): Array<StackTraceElement> {
+        try {
+            throw RuntimeException()
+        } catch (e: RuntimeException) {
+            return e.stackTrace
+        }
+    }
+
+    // Find the calling method. This is the first method in the stack trace that is annotated
+    // with @Test.
+    fun getCallingMethod(): StackTraceElement {
+        val stackTrace = getCallingStackTrace()
+        return stackTrace.find { element ->
+            val clazz = Class.forName(element.className)
+            // Because the stack trace doesn't list the formal arguments, find all methods with
+            // this name and return this name if any of them is annotated with @Test.
+            clazz.declaredMethods
+                    .filter { method -> method.name == element.methodName }
+                    .any { method -> method.getAnnotation(org.junit.Test::class.java) != null }
+        } ?: stackTrace[3]
+        // If no method is annotated return the 4th one, because that's what it usually is :
+        // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec
+    }
+}
+
+/**
+ * Default instructions available to all interpreters.
+ * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
+ * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
+ *   string "null" or an int. Returns Unit.
+ * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
+ *   y time units.
+ * EXPR // any text : comments are ignored.
+ * EXPR fails : checks that EXPR throws some exception.
+ */
+private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>(
+    // Interpret an empty line as doing nothing.
+    Regex("") to { _, _, _ -> null },
+    // Ignore comments.
+    Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) },
+    // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y
+    Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r ->
+        val lateStart = System.currentTimeMillis()
+        i.interpret(r.strArg(1), t)
+        val end = System.currentTimeMillis()
+        // There is uncertainty in measuring time.
+        // It takes some (small) time for the thread to even measure the time at which it
+        // starts interpreting the instruction. It is therefore possible that thread A sleeps for
+        // n milliseconds, and B expects to have waited for at least n milliseconds, but because
+        // B started measuring after 1ms or so, B thinks it didn't wait long enough.
+        // To avoid this, when the `time` instruction tests the instruction took at least X and
+        // at most Y, it tests X against a time measured since *before* the thread blocked but
+        // Y against a time measured as late as possible. This ensures that the timer is
+        // sufficiently lenient in both directions that there are no flaky measures.
+        val minTime = end - lateStart
+        val maxTime = end - i.lastBlockedTime.get()!!
+
+        assertTrue(maxTime >= r.timeArg(2),
+                "Should have taken at least ${r.timeArg(2)} but took less than $maxTime")
+        assertTrue(minTime <= r.timeArg(3),
+                "Should have taken at most ${r.timeArg(3)} but took more than $minTime")
+    },
+    // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported
+    Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r ->
+        i.interpret(r.strArg(1), t).also {
+            if ("null" == r.strArg(2)) assertNull(it) else assertEquals(r.intArg(2), it)
+        }
+    },
+    // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units.
+    Regex("""sleep(\((\d+)\))?""") to { i, t, r ->
+        SystemClock.sleep(if (r.strArg(2).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(2))
+    },
+    Regex("""(.*)\s*fails""") to { i, t, r ->
+        assertFails { i.interpret(r.strArg(1), t) }
+    }
+)
+
+class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause)
+class InterpretException(
+    threadIndex: Int,
+    instr: String,
+    lineNum: Int,
+    className: String,
+    methodName: String,
+    fileName: String,
+    cause: Throwable
+) : RuntimeException("Failure: $instr", cause) {
+    init {
+        stackTrace = arrayOf(StackTraceElement(
+                className,
+                "$methodName:thread$threadIndex",
+                fileName,
+                lineNum)) + super.getStackTrace()
+    }
+}
+
+// Some small helpers to avoid to say the large ".groupValues[index].trim()" every time
+fun MatchResult.strArg(index: Int) = this.groupValues[index].trim()
+fun MatchResult.intArg(index: Int) = strArg(index).toInt()
+fun MatchResult.timeArg(index: Int) = INTERPRET_TIME_UNIT * intArg(index)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
new file mode 100644
index 0000000..b1d64f8
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+import android.Manifest.permission
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.wifi.ScanResult
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiManager
+import android.os.ParcelFileDescriptor
+import android.os.SystemClock
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val MAX_WIFI_CONNECT_RETRIES = 10
+private const val WIFI_CONNECT_INTERVAL_MS = 500L
+private const val WIFI_CONNECT_TIMEOUT_MS = 30_000L
+
+// Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi,
+// the error code constants are not (b/204277752)
+private const val WIFI_ERROR_IN_PROGRESS = 1
+private const val WIFI_ERROR_BUSY = 2
+
+class ConnectUtil(private val context: Context) {
+    private val TAG = ConnectUtil::class.java.simpleName
+
+    private val cm = context.getSystemService(ConnectivityManager::class.java)
+            ?: fail("Could not find ConnectivityManager")
+    private val wifiManager = context.getSystemService(WifiManager::class.java)
+            ?: fail("Could not find WifiManager")
+
+    fun ensureWifiConnected(): Network = ensureWifiConnected(requireValidated = false)
+    fun ensureWifiValidated(): Network = ensureWifiConnected(requireValidated = true)
+
+    fun ensureCellularValidated(): Network {
+        val cb = TestableNetworkCallback()
+        cm.requestNetwork(
+            NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET).build(), cb)
+        return tryTest {
+            val errorMsg = "The device does not have mobile data available. Check that it is " +
+                    "setup with a SIM card that has a working data plan, that the APN " +
+                    "configuration is valid, and that the device can access the internet through " +
+                    "mobile data."
+            cb.eventuallyExpect<CapabilitiesChanged>(errorMsg) {
+                it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+            }.network
+        } cleanup {
+            cm.unregisterNetworkCallback(cb)
+        }
+    }
+
+    private fun ensureWifiConnected(requireValidated: Boolean): Network {
+        val callback = TestableNetworkCallback(timeoutMs = WIFI_CONNECT_TIMEOUT_MS)
+        cm.registerNetworkCallback(NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .build(), callback)
+
+        return tryTest {
+            val connInfo = wifiManager.connectionInfo
+            Log.d(TAG, "connInfo=" + connInfo)
+            if (connInfo == null || connInfo.networkId == -1) {
+                clearWifiBlocklist()
+                val pfd = getInstrumentation().uiAutomation.executeShellCommand("svc wifi enable")
+                // Read the output stream to ensure the command has completed
+                ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.readBytes() }
+                val config = getOrCreateWifiConfiguration()
+                connectToWifiConfig(config)
+            }
+            val errorMsg = if (requireValidated) {
+                "The wifi access point did not have access to the internet after " +
+                        "$WIFI_CONNECT_TIMEOUT_MS ms. Check that it has a working connection."
+            } else {
+                "Could not connect to a wifi access point within $WIFI_CONNECT_TIMEOUT_MS ms. " +
+                        "Check that the test device has a wifi network configured, and that the " +
+                        "test access point is functioning properly."
+            }
+            val cb = callback.eventuallyExpect<CapabilitiesChanged>(errorMsg) {
+                (!requireValidated || it.caps.hasCapability(NET_CAPABILITY_VALIDATED))
+            }
+            cb.network
+        } cleanup {
+            cm.unregisterNetworkCallback(callback)
+        }
+    }
+
+    private fun connectToWifiConfig(config: WifiConfiguration) {
+        repeat(MAX_WIFI_CONNECT_RETRIES) {
+            val error = runAsShell(permission.NETWORK_SETTINGS) {
+                val listener = ConnectWifiListener()
+                wifiManager.connect(config, listener)
+                listener.connectFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+            } ?: return // Connect succeeded
+
+            // Only retry for IN_PROGRESS and BUSY
+            if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) {
+                fail("Failed to connect to " + config.SSID + ": " + error)
+            }
+            Log.w(TAG, "connect failed with $error; waiting before retry")
+            SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS)
+        }
+        fail("Failed to connect to ${config.SSID} after $MAX_WIFI_CONNECT_RETRIES retries")
+    }
+
+    private class ConnectWifiListener : WifiManager.ActionListener {
+        /**
+         * Future completed when the connect process ends. Provides the error code or null if none.
+         */
+        val connectFuture = CompletableFuture<Int?>()
+        override fun onSuccess() {
+            connectFuture.complete(null)
+        }
+
+        override fun onFailure(reason: Int) {
+            connectFuture.complete(reason)
+        }
+    }
+
+    private fun getOrCreateWifiConfiguration(): WifiConfiguration {
+        val configs = runAsShell(permission.NETWORK_SETTINGS) {
+            wifiManager.getConfiguredNetworks()
+        }
+        // If no network is configured, add a config for virtual access points if applicable
+        if (configs.size == 0) {
+            val scanResults = getWifiScanResults()
+            val virtualConfig = maybeConfigureVirtualNetwork(scanResults)
+            assertNotNull(virtualConfig, "The device has no configured wifi network")
+            return virtualConfig
+        }
+        // No need to add a configuration: there is already one.
+        if (configs.size > 1) {
+            // For convenience in case of local testing on devices with multiple saved configs,
+            // prefer the first configuration that is in range.
+            // In actual tests, there should only be one configuration, and it should be usable as
+            // assumed by WifiManagerTest.testConnect.
+            Log.w(TAG, "Multiple wifi configurations found: " +
+                    configs.joinToString(", ") { it.SSID })
+            val scanResultsList = getWifiScanResults()
+            Log.i(TAG, "Scan results: " + scanResultsList.joinToString(", ") {
+                "${it.SSID} (${it.level})"
+            })
+
+            val scanResults = scanResultsList.map { "\"${it.SSID}\"" }.toSet()
+            return configs.firstOrNull { scanResults.contains(it.SSID) } ?: configs[0]
+        }
+        return configs[0]
+    }
+
+    private fun getWifiScanResults(): List<ScanResult> {
+        val scanResultsFuture = CompletableFuture<List<ScanResult>>()
+        runAsShell(permission.NETWORK_SETTINGS) {
+            val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+                override fun onReceive(context: Context, intent: Intent) {
+                    scanResultsFuture.complete(wifiManager.scanResults)
+                }
+            }
+            context.registerReceiver(receiver,
+                    IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
+            wifiManager.startScan()
+        }
+        return try {
+            scanResultsFuture.get(WIFI_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+        } catch (e: Exception) {
+            throw AssertionError("Wifi scan results not received within timeout", e)
+        }
+    }
+
+    /**
+     * If a virtual wifi network is detected, add a configuration for that network.
+     * TODO(b/158150376): have the test infrastructure add virtual wifi networks when appropriate.
+     */
+    private fun maybeConfigureVirtualNetwork(scanResults: List<ScanResult>): WifiConfiguration? {
+        // Virtual wifi networks used on the emulator and cloud testing infrastructure
+        val virtualSsids = listOf("VirtWifi", "AndroidWifi")
+        Log.d(TAG, "Wifi scan results: $scanResults")
+        val virtualScanResult = scanResults.firstOrNull { virtualSsids.contains(it.SSID) }
+                ?: return null
+
+        // Only add the virtual configuration if the virtual AP is detected in scans
+        val virtualConfig = WifiConfiguration()
+        // ASCII SSIDs need to be surrounded by double quotes
+        virtualConfig.SSID = "\"${virtualScanResult.SSID}\""
+        virtualConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
+        runAsShell(permission.NETWORK_SETTINGS) {
+            val networkId = wifiManager.addNetwork(virtualConfig)
+            assertTrue(networkId >= 0)
+            assertTrue(wifiManager.enableNetwork(networkId, false /* attemptConnect */))
+        }
+        return virtualConfig
+    }
+
+    /**
+     * Re-enable wifi networks that were blocked, typically because no internet connection was
+     * detected the last time they were connected. This is necessary to make sure wifi can reconnect
+     * to them.
+     */
+    private fun clearWifiBlocklist() {
+        runAsShell(permission.NETWORK_SETTINGS, permission.ACCESS_WIFI_STATE) {
+            for (cfg in wifiManager.configuredNetworks) {
+                assertTrue(wifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */))
+            }
+        }
+    }
+}
+
+private inline fun <reified T : CallbackEntry> TestableNetworkCallback.eventuallyExpect(
+    errorMsg: String,
+    crossinline predicate: (T) -> Boolean = { true }
+): T = history.poll(defaultTimeoutMs, mark) { it is T && predicate(it) }.also {
+    assertNotNull(it, errorMsg)
+} as T
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
new file mode 100644
index 0000000..936b568
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+@file:JvmName("ContextUtils")
+
+package com.android.testutils
+
+import android.content.Context
+import android.os.UserHandle
+import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.util.function.BiConsumer
+
+// Helper function so that Java doesn't have to pass a method that returns Unit
+fun mockContextAsUser(context: Context, functor: BiConsumer<Context, UserHandle>? = null) =
+    mockContextAsUser(context) { c, h -> functor?.accept(c, h) }
+
+/**
+ * Return a context with assigned user and delegate to original context.
+ *
+ * @param context the mock context to set up createContextAsUser on. After this function
+ *                is called, client code can call createContextAsUser and expect a context that
+ *                will return the correct user and userId.
+ *
+ * @param functor additional code to run on the created context-as-user instances, for example to
+ *                set up further mocks on these contexts.
+ */
+fun mockContextAsUser(context: Context, functor: ((Context, UserHandle) -> Unit)? = null) {
+    doAnswer { invocation ->
+        val asUserContext = mock(Context::class.java, delegatesTo<Context>(context))
+        val user = invocation.arguments[0] as UserHandle
+        val userId = user.identifier
+        doReturn(user).`when`(asUserContext).user
+        doReturn(userId).`when`(asUserContext).userId
+        functor?.let { it(asUserContext, user) }
+        asUserContext
+    }.`when`(context).createContextAsUser(any(UserHandle::class.java), anyInt() /* flags */)
+}
+
+/**
+ * Helper function to mock the desired system service.
+ *
+ * @param context the mock context to set up the getSystemService and getSystemServiceName.
+ * @param clazz the system service class that intents to mock.
+ * @param service the system service name that intents to mock.
+ */
+fun <T> mockService(context: Context, clazz: Class<T>, name: String, service: T) {
+    doReturn(service).`when`(context).getSystemService(name)
+    doReturn(name).`when`(context).getSystemServiceName(clazz)
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
new file mode 100644
index 0000000..35f22b9
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import com.android.modules.utils.build.UnboundedSdkLevel
+import java.util.regex.Pattern
+import org.junit.Assume.assumeTrue
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
+const val SC_V2 = Build.VERSION_CODES.S_V2
+
+private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
+private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
+
+private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
+    return (minExclusive == null || !isAtMost(minExclusive)) &&
+            (maxInclusive == null || isAtMost(maxInclusive))
+}
+
+private fun isAtMost(sdkVersionOrCodename: String): Boolean {
+    // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it
+    // is intended for mainline modules that are now R+.
+    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+        // Assume that any codename passed as argument from current code is a more recent build than
+        // Q: this util did not exist before Q, and codenames are only used before the corresponding
+        // build is finalized. This util could list 28 older codenames to check against (as per
+        // ro.build.version.known_codenames in more recent builds), but this does not seem valuable.
+        val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true
+        return Build.VERSION.SDK_INT <= intVersion
+    }
+    return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename)
+}
+
+/**
+ * Returns true if the development SDK version of the device is in the provided annotation range.
+ *
+ * If the device is not using a release SDK, the development SDK differs from
+ * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel].
+ */
+fun isDevSdkInRange(
+    ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?,
+    ignoreAfter: DevSdkIgnoreRule.IgnoreAfter?
+): Boolean {
+    val minExclusive =
+            if (ignoreUpTo?.value == 0) ignoreUpTo.codename
+            else ignoreUpTo?.value?.toString()
+    val maxInclusive =
+            if (ignoreAfter?.value == 0) ignoreAfter.codename
+            else ignoreAfter?.value?.toString()
+    return isDevSdkInRange(minExclusive, maxInclusive)
+}
+
+private fun getMaxTargetSdk(description: Description): Int? {
+    return description.annotations.firstNotNullOfOrNull {
+        MAX_TARGET_SDK_ANNOTATION_RE.matcher(it.annotationClass.simpleName).let { m ->
+            if (m.find()) m.group(1).toIntOrNull() else null
+        }
+    }
+}
+
+/**
+ * A test rule to ignore tests based on the development SDK level.
+ *
+ * If the device is not using a release SDK, the development SDK is considered to be higher than
+ * [Build.VERSION.SDK_INT].
+ *
+ * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or
+ *                        SDK level.
+ * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or
+ *                         SDK level.
+ */
+class DevSdkIgnoreRule @JvmOverloads constructor(
+    private val ignoreClassUpTo: String? = null,
+    private val ignoreClassAfter: String? = null
+) : TestRule {
+    /**
+     * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
+     * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
+     */
+    @JvmOverloads
+    constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this(
+            ignoreClassUpTo?.toString(), ignoreClassAfter?.toString())
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return IgnoreBySdkStatement(base, description)
+    }
+
+    /**
+     * Ignore the test for any development SDK that is strictly after [value].
+     *
+     * If the device is not using a release SDK, the development SDK is considered to be higher
+     * than [Build.VERSION.SDK_INT].
+     */
+    annotation class IgnoreAfter(val value: Int = 0, val codename: String = "")
+
+    /**
+     * Ignore the test for any development SDK that lower than or equal to [value].
+     *
+     * If the device is not using a release SDK, the development SDK is considered to be higher
+     * than [Build.VERSION.SDK_INT].
+     */
+    annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "")
+
+    private inner class IgnoreBySdkStatement(
+        private val base: Statement,
+        private val description: Description
+    ) : Statement() {
+        override fun evaluate() {
+            val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java)
+            val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java)
+
+            val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " +
+                    "with SDK ${Build.VERSION.SDK_INT}"
+            assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
+            assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter))
+
+            val maxTargetSdk = getMaxTargetSdk(description)
+            if (maxTargetSdk != null) {
+                assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk",
+                        targetSdk <= maxTargetSdk)
+            }
+            base.evaluate()
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
new file mode 100644
index 0000000..2e73666
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import org.junit.runner.Description
+import org.junit.runner.Runner
+import org.junit.runner.manipulation.Filter
+import org.junit.runner.manipulation.Filterable
+import org.junit.runner.manipulation.NoTestsRemainException
+import org.junit.runner.manipulation.Sortable
+import org.junit.runner.manipulation.Sorter
+import org.junit.runner.notification.RunNotifier
+import kotlin.jvm.Throws
+
+/**
+ * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule].
+ *
+ * Generally [DevSdkIgnoreRule] should be used for that purpose (using rules is preferable over
+ * replacing the test runner), however JUnit runners inspect all methods in the test class before
+ * processing test rules. This may cause issues if the test methods are referencing classes that do
+ * not exist on the SDK of the device the test is run on.
+ *
+ * This runner inspects [IgnoreAfter] and [IgnoreUpTo] annotations on the test class, and will skip
+ * the whole class if they do not match the development SDK as defined in [DevSdkIgnoreRule].
+ * Otherwise, it will delegate to [AndroidJUnit4] to run the test as usual.
+ *
+ * Example usage:
+ *
+ *     @RunWith(DevSdkIgnoreRunner::class)
+ *     @IgnoreUpTo(Build.VERSION_CODES.Q)
+ *     class MyTestClass { ... }
+ */
+class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner(), Filterable, Sortable {
+    private val baseRunner = klass.let {
+        val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
+        val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
+
+        if (isDevSdkInRange(ignoreUpTo, ignoreAfter)) AndroidJUnit4(klass) else null
+    }
+
+    override fun run(notifier: RunNotifier) {
+        if (baseRunner != null) {
+            baseRunner.run(notifier)
+            return
+        }
+
+        // Report a single, skipped placeholder test for this class, as the class is expected to
+        // report results when run. In practice runners that apply the Filterable implementation
+        // would see a NoTestsRemainException and not call the run method.
+        notifier.fireTestIgnored(
+                Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
+    }
+
+    override fun getDescription(): Description {
+        return baseRunner?.description ?: Description.createSuiteDescription(klass)
+    }
+
+    /**
+     * Get the test count before applying the [Filterable] implementation.
+     */
+    override fun testCount(): Int {
+        // When ignoring the tests, a skipped placeholder test is reported, so test count is 1.
+        return baseRunner?.testCount() ?: 1
+    }
+
+    @Throws(NoTestsRemainException::class)
+    override fun filter(filter: Filter?) {
+        baseRunner?.filter(filter) ?: throw NoTestsRemainException()
+    }
+
+    override fun sort(sorter: Sorter?) {
+        baseRunner?.sort(sorter)
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt
new file mode 100644
index 0000000..3d98cc3
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.testutils
+
+import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.modules.utils.build.SdkLevel
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+
+private val TAG = DeviceConfigRule::class.simpleName
+
+private const val TIMEOUT_MS = 20_000L
+
+/**
+ * A [TestRule] that helps set [DeviceConfig] for tests and clean up the test configuration
+ * automatically on teardown.
+ *
+ * The rule can also optionally retry tests when they fail following an external change of
+ * DeviceConfig before S; this typically happens because device config flags are synced while the
+ * test is running, and DisableConfigSyncTargetPreparer is only usable starting from S.
+ *
+ * @param retryCountBeforeSIfConfigChanged if > 0, when the test fails before S, check if
+ *        the configs that were set through this rule were changed, and retry the test
+ *        up to the specified number of times if yes.
+ */
+class DeviceConfigRule @JvmOverloads constructor(
+    val retryCountBeforeSIfConfigChanged: Int = 0
+) : TestRule {
+    // Maps (namespace, key) -> value
+    private val originalConfig = mutableMapOf<Pair<String, String>, String?>()
+    private val usedConfig = mutableMapOf<Pair<String, String>, String?>()
+
+    /**
+     * Actions to be run after cleanup of the config, for the current test only.
+     */
+    private val currentTestCleanupActions = mutableListOf<ThrowingRunnable>()
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return TestValidationUrlStatement(base, description)
+    }
+
+    private inner class TestValidationUrlStatement(
+        private val base: Statement,
+        private val description: Description
+    ) : Statement() {
+        override fun evaluate() {
+            var retryCount = if (SdkLevel.isAtLeastS()) 1 else retryCountBeforeSIfConfigChanged + 1
+            while (retryCount > 0) {
+                retryCount--
+                tryTest {
+                    base.evaluate()
+                    // Can't use break/return out of a loop here because this is a tryTest lambda,
+                    // so set retryCount to exit instead
+                    retryCount = 0
+                }.catch<Throwable> { e -> // junit AssertionFailedError does not extend Exception
+                    if (retryCount == 0) throw e
+                    usedConfig.forEach { (key, value) ->
+                        val currentValue = runAsShell(READ_DEVICE_CONFIG) {
+                            DeviceConfig.getProperty(key.first, key.second)
+                        }
+                        if (currentValue != value) {
+                            Log.w(TAG, "Test failed with unexpected device config change, retrying")
+                            return@catch
+                        }
+                    }
+                    throw e
+                } cleanupStep {
+                    runAsShell(WRITE_DEVICE_CONFIG) {
+                        originalConfig.forEach { (key, value) ->
+                            DeviceConfig.setProperty(
+                                    key.first, key.second, value, false /* makeDefault */)
+                        }
+                    }
+                } cleanupStep {
+                    originalConfig.clear()
+                    usedConfig.clear()
+                } cleanup {
+                    // Fold all cleanup actions into cleanup steps of an empty tryTest, so they are
+                    // all run even if exceptions are thrown, and exceptions are reported properly.
+                    currentTestCleanupActions.fold(tryTest { }) {
+                        tryBlock, action -> tryBlock.cleanupStep { action.run() }
+                    }.cleanup {
+                        currentTestCleanupActions.clear()
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Set a configuration key/value. After the test case ends, it will be restored to the value it
+     * had when this method was first called.
+     */
+    fun setConfig(namespace: String, key: String, value: String?): String? {
+        Log.i(TAG, "Setting config \"$key\" to \"$value\"")
+        val readWritePermissions = arrayOf(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+
+        val keyPair = Pair(namespace, key)
+        val existingValue = runAsShell(*readWritePermissions) {
+            DeviceConfig.getProperty(namespace, key)
+        }
+        if (!originalConfig.containsKey(keyPair)) {
+            originalConfig[keyPair] = existingValue
+        }
+        usedConfig[keyPair] = value
+        if (existingValue == value) {
+            // Already the correct value. There may be a race if a change is already in flight,
+            // but if multiple threads update the config there is no way to fix that anyway.
+            Log.i(TAG, "\"$key\" already had value \"$value\"")
+            return value
+        }
+
+        val future = CompletableFuture<String>()
+        val listener = DeviceConfig.OnPropertiesChangedListener {
+            // The listener receives updates for any change to any key, so don't react to
+            // changes that do not affect the relevant key
+            if (!it.keyset.contains(key)) return@OnPropertiesChangedListener
+            // "null" means absent in DeviceConfig : there is no such thing as a present but
+            // null value, so the following works even if |value| is null.
+            if (it.getString(key, null) == value) {
+                future.complete(value)
+            }
+        }
+
+        return tryTest {
+            runAsShell(*readWritePermissions) {
+                DeviceConfig.addOnPropertiesChangedListener(
+                        DeviceConfig.NAMESPACE_CONNECTIVITY,
+                        inlineExecutor,
+                        listener)
+                DeviceConfig.setProperty(
+                        DeviceConfig.NAMESPACE_CONNECTIVITY,
+                        key,
+                        value,
+                        false /* makeDefault */)
+                // Don't drop the permission until the config is applied, just in case
+                future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+            }.also {
+                Log.i(TAG, "Config \"$key\" successfully set to \"$value\"")
+            }
+        } cleanup {
+            DeviceConfig.removeOnPropertiesChangedListener(listener)
+        }
+    }
+
+    private val inlineExecutor get() = Executor { r -> r.run() }
+
+    /**
+     * Add an action to be run after config cleanup when the current test case ends.
+     */
+    fun runAfterNextCleanup(action: ThrowingRunnable) {
+        currentTestCleanupActions.add(action)
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
new file mode 100644
index 0000000..ce55fdc
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
@@ -0,0 +1,176 @@
+/*
+ * 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.testutils;
+
+import android.os.VintfRuntimeInfo;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for device information.
+ */
+public class DeviceInfoUtils {
+    /**
+     * Class for a three-part kernel version number.
+     */
+    public static class KVersion {
+        public final int major;
+        public final int minor;
+        public final int sub;
+
+        public KVersion(int major, int minor, int sub) {
+            this.major = major;
+            this.minor = minor;
+            this.sub = sub;
+        }
+
+        /**
+         * Compares with other version numerically.
+         *
+         * @param  other the other version to compare
+         * @return the value 0 if this == other;
+         *         a value less than 0 if this < other and
+         *         a value greater than 0 if this > other.
+         */
+        public int compareTo(final KVersion other) {
+            int res = Integer.compare(this.major, other.major);
+            if (res == 0) {
+                res = Integer.compare(this.minor, other.minor);
+            }
+            if (res == 0) {
+                res = Integer.compare(this.sub, other.sub);
+            }
+            return res;
+        }
+
+        /**
+         * At least satisfied with the given version.
+         *
+         * @param  from the start version to compare
+         * @return return true if this version is at least satisfied with the given version.
+         *         otherwise, return false.
+         */
+        public boolean isAtLeast(final KVersion from) {
+            return compareTo(from) >= 0;
+        }
+
+        /**
+         * Falls within the given range [from, to).
+         *
+         * @param  from the start version to compare
+         * @param  to   the end version to compare
+         * @return return true if this version falls within the given range.
+         *         otherwise, return false.
+         */
+        public boolean isInRange(final KVersion from, final KVersion to) {
+            return isAtLeast(from) && !isAtLeast(to);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof KVersion)) return false;
+            KVersion that = (KVersion) o;
+            return this.major == that.major
+                    && this.minor == that.minor
+                    && this.sub == that.sub;
+        }
+    };
+
+    /**
+     * Get a two-part kernel version number (major and minor) from a given string.
+     *
+     * TODO: use class KVersion.
+     */
+    private static Pair<Integer, Integer> getMajorMinorVersion(String version) {
+        // Only gets major and minor number of the version string.
+        final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
+        final Matcher m = versionPattern.matcher(version);
+        if (m.matches()) {
+            final int major = Integer.parseInt(m.group(1));
+            final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
+            return new Pair<>(major, minor);
+        } else {
+            return new Pair<>(0, 0);
+        }
+    }
+
+    /**
+     * Compares two version strings numerically. Compare only major and minor number of the
+     * version string. The version comparison uses #Integer.compare. Possible version
+     * 5, 5.10, 5-beta1, 4.8-RC1, 4.7.10.10 and so on.
+     *
+     * @param  s1 the first version string to compare
+     * @param  s2 the second version string to compare
+     * @return the value 0 if s1 == s2;
+     *         a value less than 0 if s1 < s2 and
+     *         a value greater than 0 if s1 > s2.
+     *
+     * TODO: use class KVersion.
+     */
+    public static int compareMajorMinorVersion(final String s1, final String s2) {
+        final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1);
+        final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2);
+
+        if (Objects.equals(v1.first, v2.first)) {
+            return Integer.compare(v1.second, v2.second);
+        } else {
+            return Integer.compare(v1.first, v2.first);
+        }
+    }
+
+    /**
+     * Get a three-part kernel version number (major, minor and subminor) from a given string.
+     * Any version string must at least have major and minor number. If the subminor number can't
+     * be parsed from string. Assign zero as subminor number. Invalid version is treated as
+     * version 0.0.0.
+     */
+    public static KVersion getMajorMinorSubminorVersion(final String version) {
+        // The kernel version is a three-part version number (major, minor and subminor). Get
+        // the three-part version numbers and discard the remaining stuff if any.
+        // For example:
+        //   4.19.220-g500ede0aed22-ab8272303 --> 4.19.220
+        //   5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0
+        final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*");
+        final Matcher m = versionPattern.matcher(version);
+        if (m.matches()) {
+            final int major = Integer.parseInt(m.group(1));
+            final int minor = Integer.parseInt(m.group(2));
+            final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4));
+            return new KVersion(major, minor, sub);
+        } else {
+            return new KVersion(0, 0, 0);
+        }
+    }
+
+    /**
+     * Check if the current kernel version is at least satisfied with the given version.
+     *
+     * @param  version the start version to compare
+     * @return return true if the current version is at least satisfied with the given version.
+     *         otherwise, return false.
+     */
+    public static boolean isKernelVersionAtLeast(final String version) {
+        final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+        final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+        final KVersion from = DeviceInfoUtils.getMajorMinorSubminorVersion(version);
+        return current.isAtLeast(from);
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt
new file mode 100644
index 0000000..6a804bf
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DnsAnswerProvider.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.testutils
+
+import android.net.DnsResolver.CLASS_IN
+import com.android.net.module.util.DnsPacket
+import com.android.net.module.util.DnsPacket.ANSECTION
+import java.net.InetAddress
+import java.util.concurrent.ConcurrentHashMap
+
+const val DEFAULT_TTL_S = 5L
+
+/**
+ * Helper class to store the mapping of DNS queries.
+ *
+ * DnsAnswerProvider is built atop a ConcurrentHashMap and as such it provides the same
+ * guarantees as ConcurrentHashMap between writing and reading elements. Specifically :
+ * - Setting an answer happens-before reading the same answer.
+ * - Callers can read and write concurrently from DnsAnswerProvider and expect no
+ *   ConcurrentModificationException.
+ * Freshness of the answers depends on ordering of the threads ; if callers need a
+ * freshness guarantee, they need to provide the happens-before relationship from a
+ * write that they want to observe to the read that they need to be observed.
+ */
+class DnsAnswerProvider {
+    private val mDnsKeyToRecords = ConcurrentHashMap<String, List<DnsPacket.DnsRecord>>()
+
+    /**
+     * Get answer for the specified hostname.
+     *
+     * @param query the target hostname.
+     * @param type type of record, could be A or AAAA.
+     *
+     * @return list of [DnsPacket.DnsRecord] associated to the query. Empty if no record matches.
+     */
+    fun getAnswer(query: String, type: Int) = mDnsKeyToRecords[query]
+            .orEmpty().filter { it.nsType == type }
+
+    /** Set answer for the specified {@code query}.
+     *
+     * @param query the target hostname
+     * @param addresses [List<InetAddress>] which could be used to generate multiple A or AAAA
+     *                  RRs with the corresponding addresses.
+     */
+    fun setAnswer(query: String, hosts: List<InetAddress>) = mDnsKeyToRecords.put(query, hosts.map {
+            DnsPacket.DnsRecord.makeAOrAAAARecord(ANSECTION, query, CLASS_IN, DEFAULT_TTL_S, it)
+        })
+
+    fun clearAnswer(query: String) = mDnsKeyToRecords.remove(query)
+    fun clearAll() = mDnsKeyToRecords.clear()
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java
new file mode 100644
index 0000000..d103748
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DumpTestUtils.java
@@ -0,0 +1,128 @@
+/*
+ * 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.testutils;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Utilities for testing output of service dumps.
+ */
+public class DumpTestUtils {
+
+    private static String dumpService(String serviceName, boolean adoptPermission, String... args)
+            throws RemoteException, InterruptedException, ErrnoException {
+        final IBinder ib = ServiceManager.getService(serviceName);
+        FileDescriptor[] pipe = Os.pipe();
+
+        // Start a thread to read the dump output, or dump might block if it fills the pipe.
+        final CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<String> output = new AtomicReference<>();
+        // Used to send exceptions back to the main thread to ensure that the test fails cleanly.
+        AtomicReference<Exception> exception = new AtomicReference<>();
+        new Thread(() -> {
+            try {
+                output.set(Streams.readFully(
+                        new InputStreamReader(new FileInputStream(pipe[0]),
+                                StandardCharsets.UTF_8)));
+                latch.countDown();
+            } catch (Exception e) {
+                exception.set(e);
+                latch.countDown();
+            }
+        }).start();
+
+        final int timeoutMs = 5_000;
+        final String what = "service '" + serviceName + "' with args: " + Arrays.toString(args);
+        try {
+            if (adoptPermission) {
+                runAsShell(android.Manifest.permission.DUMP, () -> ib.dump(pipe[1], args));
+            } else {
+                ib.dump(pipe[1], args);
+            }
+            IoUtils.closeQuietly(pipe[1]);
+            assertTrue("Dump of " + what + " timed out after " + timeoutMs + "ms",
+                    latch.await(timeoutMs, TimeUnit.MILLISECONDS));
+        } finally {
+            // Closing the fds will terminate the thread if it's blocked on read.
+            IoUtils.closeQuietly(pipe[0]);
+            if (pipe[1].valid()) IoUtils.closeQuietly(pipe[1]);
+        }
+        if (exception.get() != null) {
+            fail("Exception dumping " + what + ": " + exception.get());
+        }
+        return output.get();
+    }
+
+    /**
+     * Dumps the specified service and returns a string. Sends a dump IPC to the given service
+     * with the specified args and a pipe, then reads from the pipe in a separate thread.
+     * The current process must already have the DUMP permission.
+     *
+     * @param serviceName the service to dump.
+     * @param args the arguments to pass to the dump function.
+     * @return The dump text.
+     * @throws RemoteException dumping the service failed.
+     * @throws InterruptedException the dump timed out.
+     * @throws ErrnoException opening or closing the pipe for the dump failed.
+     */
+    public static String dumpService(String serviceName, String... args)
+            throws RemoteException, InterruptedException, ErrnoException {
+        return dumpService(serviceName, false, args);
+    }
+
+    /**
+     * Dumps the specified service and returns a string. Sends a dump IPC to the given service
+     * with the specified args and a pipe, then reads from the pipe in a separate thread.
+     * Adopts the {@code DUMP} permission via {@code adoptShellPermissionIdentity} and then releases
+     * it. This method should not be used if the caller already has the shell permission identity.
+     * TODO: when Q and R are no longer supported, use
+     * {@link android.app.UiAutomation#getAdoptedShellPermissions} to automatically acquire the
+     * shell permission if the caller does not already have it.
+     *
+     * @param serviceName the service to dump.
+     * @param args the arguments to pass to the dump function.
+     * @return The dump text.
+     * @throws RemoteException dumping the service failed.
+     * @throws InterruptedException the dump timed out.
+     * @throws ErrnoException opening or closing the pipe for the dump failed.
+     */
+    public static String dumpServiceWithShellPermission(String serviceName, String... args)
+            throws RemoteException, InterruptedException, ErrnoException {
+        return dumpService(serviceName, true, args);
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt
new file mode 100644
index 0000000..1f82a35
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.testutils
+
+import android.net.DnsResolver
+import android.net.InetAddresses
+import android.os.Looper
+import android.os.Handler
+import com.android.internal.annotations.GuardedBy
+import java.net.InetAddress
+import java.util.concurrent.Executor
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+
+const val TYPE_UNSPECIFIED = -1
+// TODO: Integrate with NetworkMonitorTest.
+class FakeDns(val mockResolver: DnsResolver) {
+    class DnsEntry(val hostname: String, val type: Int, val addresses: List<InetAddress>) {
+        fun match(host: String, type: Int) = hostname.equals(host) && type == type
+    }
+
+    @GuardedBy("answers")
+    val answers = ArrayList<DnsEntry>()
+
+    fun getAnswer(hostname: String, type: Int): DnsEntry? = synchronized(answers) {
+        return answers.firstOrNull { it.match(hostname, type) }
+    }
+
+    fun setAnswer(hostname: String, answer: Array<String>, type: Int) = synchronized(answers) {
+        val ans = DnsEntry(hostname, type, generateAnswer(answer))
+        // Replace or remove the existing one.
+        when (val index = answers.indexOfFirst { it.match(hostname, type) }) {
+            -1 -> answers.add(ans)
+            else -> answers[index] = ans
+        }
+    }
+
+    private fun generateAnswer(answer: Array<String>) =
+            answer.filterNotNull().map { InetAddresses.parseNumericAddress(it) }
+
+    fun startMocking() {
+        // Mock DnsResolver.query() w/o type
+        doAnswer {
+            mockAnswer(it, 1, -1, 3, 5)
+        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* flags */,
+                any() /* executor */, any() /* cancellationSignal */, any() /*callback*/)
+        // Mock DnsResolver.query() w/ type
+        doAnswer {
+            mockAnswer(it, 1, 2, 4, 6)
+        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* nsType */,
+                anyInt() /* flags */, any() /* executor */, any() /* cancellationSignal */,
+        any() /*callback*/)
+    }
+
+    private fun mockAnswer(
+        it: InvocationOnMock,
+        posHos: Int,
+        posType: Int,
+        posExecutor: Int,
+        posCallback: Int
+    ) {
+        val hostname = it.arguments[posHos] as String
+        val executor = it.arguments[posExecutor] as Executor
+        val callback = it.arguments[posCallback] as DnsResolver.Callback<List<InetAddress>>
+        var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED
+        val answer = getAnswer(hostname, type)
+
+        if (answer != null && !answer.addresses.isNullOrEmpty()) {
+            Handler(Looper.getMainLooper()).post({ executor.execute({
+                    callback.onAnswer(answer.addresses, 0); }) })
+        }
+    }
+
+    /** Clears all entries. */
+    fun clearAll() = synchronized(answers) {
+        answers.clear()
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
new file mode 100644
index 0000000..f00ca11
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+@file:JvmName("HandlerUtils")
+
+package com.android.testutils
+
+import android.os.ConditionVariable
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.Log
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
+import java.lang.Exception
+import java.util.concurrent.Executor
+import kotlin.test.fail
+
+private const val TAG = "HandlerUtils"
+
+/**
+ * Block until the specified Handler or HandlerThread becomes idle, or until timeoutMs has passed.
+ */
+fun HandlerThread.waitForIdle(timeoutMs: Int) = threadHandler.waitForIdle(timeoutMs.toLong())
+fun HandlerThread.waitForIdle(timeoutMs: Long) = threadHandler.waitForIdle(timeoutMs)
+fun Handler.waitForIdle(timeoutMs: Int) = waitForIdle(timeoutMs.toLong())
+fun Handler.waitForIdle(timeoutMs: Long) {
+    val cv = ConditionVariable(false)
+    post(cv::open)
+    if (!cv.block(timeoutMs)) {
+        fail("Handler did not become idle after ${timeoutMs}ms")
+    }
+}
+
+/**
+ * Block until the given Serial Executor becomes idle, or until timeoutMs has passed.
+ */
+fun waitForIdleSerialExecutor(executor: Executor, timeoutMs: Long) {
+    val cv = ConditionVariable()
+    executor.execute(cv::open)
+    if (!cv.block(timeoutMs)) {
+        fail("Executor did not become idle after ${timeoutMs}ms")
+    }
+}
+
+/**
+ * Executes a block of code that returns a value, making its side effects visible on the caller and
+ * the handler thread.
+ *
+ * After this function returns, the side effects of the passed block of code are guaranteed to be
+ * observed both on the thread running the handler and on the thread running this method.
+ * To achieve this, this method runs the passed block on the handler and blocks this thread
+ * until it's executed, so keep in mind this method will block, (including, if the handler isn't
+ * running, blocking forever).
+ */
+fun <T> visibleOnHandlerThread(handler: Handler, supplier: ThrowingSupplier<T>): T {
+    val cv = ConditionVariable()
+    var rv: Result<T> = Result.failure(RuntimeException("Not run"))
+    handler.post {
+        try {
+            rv = Result.success(supplier.get())
+        } catch (exception: Exception) {
+            Log.e(TAG, "visibleOnHandlerThread caught exception", exception)
+            rv = Result.failure(exception)
+        }
+        cv.open()
+    }
+    // After block() returns, the handler thread has seen the change (since it ran it)
+    // and this thread also has seen the change (since cv.open() happens-before cv.block()
+    // returns).
+    cv.block()
+    return rv.getOrThrow()
+}
+
+/** Overload of visibleOnHandlerThread but executes a block of code that does not return a value. */
+inline fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable){
+    visibleOnHandlerThread(handler, ThrowingSupplier<Unit> { r.run() })
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
new file mode 100644
index 0000000..d7961a0
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+import java.net.InetAddress
+
+/**
+ * A class that forwards packets from the external {@link TestNetworkInterface} to the internal
+ * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
+ */
+class NatExternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    extAddr: InetAddress,
+    natMap: PacketBridge.NatMap
+) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
+
+    /**
+     * Rewrite addresses, ports and fix up checksums for packets received on the external
+     * interface.
+     *
+     * Incoming response from external interface which is being forwarded to the internal
+     * interface with translated address, e.g. 1.2.3.4:80 -> 8.8.8.8:1234
+     * will be translated into 8.8.8.8:80 -> 192.168.1.1:5678.
+     *
+     * For packets that are not an incoming response, do not forward them to the
+     * internal interface.
+     */
+    override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
+        val (addrPos, addrLen) = getAddressPositionAndLength(version)
+
+        // TODO: support one external address per ip version.
+        val extAddrBuf = mExtAddr.address
+        if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
+
+        // Get internal address by port.
+        val transportOffset =
+            if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
+            else PacketReflector.IPV6_HEADER_LENGTH
+        val dstPort = getPortAt(buf, transportOffset + DESTINATION_PORT_OFFSET)
+        val intAddrInfo = synchronized(mNatMap) { mNatMap.fromExternalPort(dstPort) }
+        // No mapping, skip. This usually happens if the connection is initiated directly on
+        // the external interface, e.g. DNS64 resolution, network validation, etc.
+        if (intAddrInfo == null) return
+
+        val intAddrBuf = intAddrInfo.address.address
+        val intPort = intAddrInfo.port
+
+        // Copy the original destination to into the source address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + i] = buf[addrPos + addrLen + i]
+        }
+
+        // Copy the internal address into the destination address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + addrLen + i] = intAddrBuf[i]
+        }
+
+        // Copy the internal port into the destination port.
+        setPortAt(intPort, buf, transportOffset + DESTINATION_PORT_OFFSET)
+
+        // Fix IP and Transport layer checksum.
+        fixPacketChecksum(buf, len, version, proto.toByte())
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
new file mode 100644
index 0000000..fa39d19
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+import java.net.InetAddress
+
+/**
+ * A class that forwards packets from the internal {@link TestNetworkInterface} to the external
+ * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
+ */
+class NatInternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    extAddr: InetAddress,
+    natMap: PacketBridge.NatMap
+) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
+
+    /**
+     * Rewrite addresses, ports and fix up checksums for packets received on the internal
+     * interface.
+     *
+     * Outgoing packet from the internal interface which is being forwarded to the
+     * external interface with translated address, e.g. 192.168.1.1:5678 -> 8.8.8.8:80
+     * will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
+     *
+     * The external port, e.g. 1234 in the above example, is the port number assigned by
+     * the forwarder when creating the mapping to identify the source address and port when
+     * the response is coming from the external interface. See {@link PacketBridge.NatMap}
+     * for detail.
+     */
+    override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
+        val (addrPos, addrLen) = getAddressPositionAndLength(version)
+
+        // TODO: support one external address per ip version.
+        val extAddrBuf = mExtAddr.address
+        if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
+
+        val srcAddr = getInetAddressAt(buf, addrPos, addrLen)
+
+        // Copy the original destination to into the source address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + i] = buf[addrPos + addrLen + i]
+        }
+
+        // Copy the external address into the destination address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + addrLen + i] = extAddrBuf[i]
+        }
+
+        // Add an entry to NAT mapping table.
+        val transportOffset =
+            if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
+            else PacketReflector.IPV6_HEADER_LENGTH
+        val srcPort = getPortAt(buf, transportOffset)
+        val extPort = synchronized(mNatMap) { mNatMap.toExternalPort(srcAddr, srcPort, proto) }
+        // Copy the external port to into the source port.
+        setPortAt(extPort, buf, transportOffset)
+
+        // Fix IP and Transport layer checksum.
+        fixPacketChecksum(buf, len, version, proto.toByte())
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
new file mode 100644
index 0000000..0a2b5d4
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils;
+
+import static com.android.testutils.PacketReflector.IPPROTO_TCP;
+import static com.android.testutils.PacketReflector.IPPROTO_UDP;
+import static com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.IPV6_PROTO_OFFSET;
+import static com.android.testutils.PacketReflector.TCP_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.UDP_HEADER_LENGTH;
+
+import android.annotation.NonNull;
+import android.net.TestNetworkInterface;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Objects;
+
+/**
+ * A class that forwards packets from a {@link TestNetworkInterface} to another
+ * {@link TestNetworkInterface} with NAT.
+ *
+ * For testing purposes, a {@link TestNetworkInterface} provides a {@link FileDescriptor}
+ * which allows content injection on the test network. However, this could be hard to use
+ * because the callers need to compose IP packets in order to inject content to the
+ * test network.
+ *
+ * In order to remove the need of composing the IP packets, this class forwards IP packets to
+ * the {@link FileDescriptor} of another {@link TestNetworkInterface} instance. Thus,
+ * the TCP/IP headers could be parsed/composed automatically by the protocol stack of this
+ * additional {@link TestNetworkInterface}, while the payload is supplied by the
+ * servers run on the interface.
+ *
+ * To make it work, an internal interface and an external interface are defined, where
+ * the client might send packets from the internal interface which are originated from
+ * multiple addresses to a server that listens on the external address.
+ *
+ * When forwarding the outgoing packet on the internal interface, a simple NAT mechanism
+ * is implemented during forwarding, which will swap the source and destination,
+ * but replacing the source address with the external address,
+ * e.g. 192.168.1.1:1234 -> 8.8.8.8:80 will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
+ *
+ * For the above example, a client who sends http request will have a hallucination that
+ * it is talking to a remote server at 8.8.8.8. Also, the server listens on 1.2.3.4 will
+ * have a different hallucination that the request is sent from a remote client at 8.8.8.8,
+ * to a local address 1.2.3.4.
+ *
+ * And a NAT mapping is created at the time when the outgoing packet is forwarded.
+ * With a different internal source port, the instance learned that when a response with the
+ * destination port 1234, it should forward the packet to the internal address 192.168.1.1.
+ *
+ * For the incoming packet received from external interface, for example a http response sent
+ * from the http server, the same mechanism is applied but in a different direction,
+ * where the source and destination will be swapped, and the source address will be replaced
+ * with the internal address, which is obtained from the NAT mapping described above.
+ */
+public abstract class NatPacketForwarderBase extends Thread {
+    private static final String TAG = "NatPacketForwarder";
+    static final int DESTINATION_PORT_OFFSET = 2;
+
+    // The source fd to read packets from.
+    @NonNull
+    final FileDescriptor mSrcFd;
+    // The buffer to temporarily hold the entire packet after receiving.
+    @NonNull
+    final byte[] mBuf;
+    // The destination fd to write packets to.
+    @NonNull
+    final FileDescriptor mDstFd;
+    // The NAT mapping table shared between two NatPacketForwarder instances to map from
+    // the source port to the associated internal address. The map can be read/write from two
+    // different threads on any given time whenever receiving packets on the
+    // {@link TestNetworkInterface}. Thus, synchronize on the object when reading/writing is needed.
+    @GuardedBy("mNatMap")
+    @NonNull
+    final PacketBridge.NatMap mNatMap;
+    // The address of the external interface. See {@link NatPacketForwarder}.
+    @NonNull
+    final InetAddress mExtAddr;
+
+    /**
+     * Construct a {@link NatPacketForwarderBase}.
+     *
+     * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and
+     * forwards them to the {@code dstFd} of another {@link TestNetworkInterface} with
+     * NAT applied. See {@link NatPacketForwarderBase}.
+     *
+     * To apply NAT, the address of the external interface needs to be supplied through
+     * {@code extAddr} to identify the external interface. And a shared NAT mapping table,
+     * {@code natMap} is needed to be shared between these two instances.
+     *
+     * Note that this class is not useful if the instance is not managed by a
+     * {@link PacketBridge} to set up a two-way communication.
+     *
+     * @param srcFd   {@link FileDescriptor} to read packets from.
+     * @param mtu     MTU of the test network.
+     * @param dstFd   {@link FileDescriptor} to write packets to.
+     * @param extAddr the external address, which is the address of the external interface.
+     *                See {@link NatPacketForwarderBase}.
+     * @param natMap  the NAT mapping table shared between two {@link NatPacketForwarderBase}
+     *                instance.
+     */
+    public NatPacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu,
+            @NonNull FileDescriptor dstFd, @NonNull InetAddress extAddr,
+            @NonNull PacketBridge.NatMap natMap) {
+        super(TAG);
+        mSrcFd = Objects.requireNonNull(srcFd);
+        mBuf = new byte[mtu];
+        mDstFd = Objects.requireNonNull(dstFd);
+        mExtAddr = Objects.requireNonNull(extAddr);
+        mNatMap = Objects.requireNonNull(natMap);
+    }
+
+    /**
+     * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface},
+     * which includes re-write addresses, ports and fix up checksums.
+     * Subclasses should override this method to implement a simple NAT.
+     */
+    abstract void preparePacketForForwarding(@NonNull byte[] buf, int len, int version, int proto);
+
+    private void forwardPacket(@NonNull byte[] buf, int len) {
+        try {
+            Os.write(mDstFd, buf, 0, len);
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Error writing packet: " + e.getMessage());
+        }
+    }
+
+    // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols.
+    private void processPacket() {
+        final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf);
+        if (len < 1) {
+            // Usually happens when socket read is being interrupted, e.g. stopping PacketForwarder.
+            return;
+        }
+
+        final int version = mBuf[0] >>> 4;
+        final int protoPos, ipHdrLen;
+        switch (version) {
+            case 4:
+                ipHdrLen = IPV4_HEADER_LENGTH;
+                protoPos = PacketReflector.IPV4_PROTO_OFFSET;
+                break;
+            case 6:
+                ipHdrLen = IPV6_HEADER_LENGTH;
+                protoPos = IPV6_PROTO_OFFSET;
+                break;
+            default:
+                throw new IllegalStateException("Unexpected version: " + version);
+        }
+        if (len < ipHdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+
+        final byte proto = mBuf[protoPos];
+        final int transportHdrLen;
+        switch (proto) {
+            case IPPROTO_TCP:
+                transportHdrLen = TCP_HEADER_LENGTH;
+                break;
+            case IPPROTO_UDP:
+                transportHdrLen = UDP_HEADER_LENGTH;
+                break;
+            // TODO: Support ICMP.
+            default:
+                return; // Unknown protocol, ignored.
+        }
+
+        if (len < ipHdrLen + transportHdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+        // Re-write addresses, ports and fix up checksums.
+        preparePacketForForwarding(mBuf, len, version, proto);
+        // Send the packet to the destination fd.
+        forwardPacket(mBuf, len);
+    }
+
+    @Override
+    public void run() {
+        Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
+        while (!interrupted() && mSrcFd.valid()) {
+            processPacket();
+        }
+        Log.i(TAG, "exiting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt
new file mode 100644
index 0000000..3f5460b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+@file:JvmName("NetlinkTestUtils")
+
+package com.android.testutils
+
+import com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH
+import com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH
+import libcore.util.HexEncoding
+import libcore.util.HexEncoding.encodeToString
+import java.net.Inet6Address
+import java.net.InetAddress
+
+private const val VRRP_MAC_ADDR = "00005e000164"
+
+/**
+ * Make a RTM_NEWNEIGH netlink message.
+ */
+@JvmOverloads
+fun makeNewNeighMessage(
+    neighAddr: InetAddress,
+    nudState: Short,
+    linkLayerAddr: String = VRRP_MAC_ADDR
+) = makeNeighborMessage(
+        neighAddr = neighAddr,
+        type = RTM_NEWNEIGH,
+        nudState = nudState,
+        linkLayerAddr = linkLayerAddr
+)
+
+/**
+ * Make a RTM_DELNEIGH netlink message.
+ */
+fun makeDelNeighMessage(
+    neighAddr: InetAddress,
+    nudState: Short
+) = makeNeighborMessage(
+        neighAddr = neighAddr,
+        type = RTM_DELNEIGH,
+        nudState = nudState
+)
+
+private fun makeNeighborMessage(
+    neighAddr: InetAddress,
+    type: Short,
+    nudState: Short,
+    linkLayerAddr: String = VRRP_MAC_ADDR
+) = HexEncoding.decode(
+    /* ktlint-disable indent */
+    // -- struct nlmsghdr --
+                         // length = 88 or 76:
+    (if (neighAddr is Inet6Address) "58000000" else "4c000000") +
+    type.toLEHex() +     // type
+    "0000" +             // flags
+    "00000000" +         // seqno
+    "00000000" +         // pid (0 == kernel)
+    // struct ndmsg
+                         // family (AF_INET6 or AF_INET)
+    (if (neighAddr is Inet6Address) "0a" else "02") +
+    "00" +               // pad1
+    "0000" +             // pad2
+    "15000000" +         // interface index (21 == wlan0, on test device)
+    nudState.toLEHex() + // NUD state
+    "00" +               // flags
+    "01" +               // type
+    // -- struct nlattr: NDA_DST --
+                         // length = 20 or 8:
+    (if (neighAddr is Inet6Address) "1400" else "0800") +
+    "0100" +             // type (1 == NDA_DST, for neighbor messages)
+                         // IP address:
+    encodeToString(neighAddr.address) +
+    // -- struct nlattr: NDA_LLADDR --
+    "0a00" +             // length = 10
+    "0200" +             // type (2 == NDA_LLADDR, for neighbor messages)
+    linkLayerAddr +      // MAC Address(default == 00:00:5e:00:01:64)
+    "0000" +             // padding, for 4 byte alignment
+    // -- struct nlattr: NDA_PROBES --
+    "0800" +             // length = 8
+    "0400" +             // type (4 == NDA_PROBES, for neighbor messages)
+    "01000000" +         // number of probes
+    // -- struct nlattr: NDA_CACHEINFO --
+    "1400" +             // length = 20
+    "0300" +             // type (3 == NDA_CACHEINFO, for neighbor messages)
+    "05190000" +         // ndm_used, as "clock ticks ago"
+    "05190000" +         // ndm_confirmed, as "clock ticks ago"
+    "190d0000" +         // ndm_updated, as "clock ticks ago"
+    "00000000",          // ndm_refcnt
+    false /* allowSingleChar */)
+    /* ktlint-enable indent */
+
+/**
+ * Convert a [Short] to a little-endian hex string.
+ */
+private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this))
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java
new file mode 100644
index 0000000..642da7a
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.testutils;
+
+import android.net.NetworkStats;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.os.RemoteException;
+
+/**
+ * A shim class that allows {@link TestableNetworkStatsProviderCbBinder} to be built against
+ * different SDK versions.
+ */
+public class NetworkStatsProviderCbStubCompat extends INetworkStatsProviderCallback.Stub {
+    @Override
+    public void notifyStatsUpdated(int token, NetworkStats ifaceStats, NetworkStats uidStats)
+            throws RemoteException {}
+
+    @Override
+    public void notifyAlertReached() throws RemoteException {}
+
+    /** Added in T. */
+    public void notifyLimitReached() throws RemoteException {}
+
+    /** Added in T. */
+    public void notifyWarningReached() throws RemoteException {}
+
+    /** Added in S, removed in T. */
+    public void notifyWarningOrLimitReached() throws RemoteException {}
+
+    @Override
+    public void unregister() throws RemoteException {}
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java
new file mode 100644
index 0000000..a77aa02
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.testutils;
+
+import android.net.netstats.provider.INetworkStatsProvider;
+
+/**
+ * A shim class that allows {@link TestableNetworkStatsProviderBinder} to be built against
+ * different SDK versions.
+ */
+public class NetworkStatsProviderStubCompat extends INetworkStatsProvider.Stub {
+    @Override
+    public void onRequestStatsUpdate(int token) {}
+
+    // Removed and won't be called in S+.
+    public void onSetLimit(String iface, long quotaBytes) {}
+
+    @Override
+    public void onSetAlert(long bytes) {}
+
+    // Added in S.
+    public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {}
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
new file mode 100644
index 0000000..8324b25
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.net.NetworkStats
+import kotlin.test.assertTrue
+
+@JvmOverloads
+fun orderInsensitiveEquals(
+    leftStats: NetworkStats,
+    rightStats: NetworkStats,
+    compareTime: Boolean = false
+): Boolean {
+    if (leftStats == rightStats) return true
+    if (compareTime && leftStats.getElapsedRealtime() != rightStats.getElapsedRealtime()) {
+        return false
+    }
+
+    // While operations such as add/subtract will preserve empty entries. This will make
+    // the result be hard to verify during test. Remove them before comparing since they
+    // are not really affect correctness.
+    // TODO (b/152827872): Remove empty entries after addition/subtraction.
+    val leftTrimmedEmpty = leftStats.removeEmptyEntries()
+    val rightTrimmedEmpty = rightStats.removeEmptyEntries()
+
+    if (leftTrimmedEmpty.size() != rightTrimmedEmpty.size()) return false
+    val left = NetworkStats.Entry()
+    val right = NetworkStats.Entry()
+    // Order insensitive compare.
+    for (i in 0 until leftTrimmedEmpty.size()) {
+        leftTrimmedEmpty.getValues(i, left)
+        val j: Int = rightTrimmedEmpty.findIndexHinted(left.iface, left.uid, left.set, left.tag,
+                left.metered, left.roaming, left.defaultNetwork, i)
+        if (j == -1) return false
+        rightTrimmedEmpty.getValues(j, right)
+        if (left != right) return false
+    }
+    return true
+}
+
+/**
+ * Assert that two {@link NetworkStats} are equals, assuming the order of the records are not
+ * necessarily the same.
+ *
+ * @note {@code elapsedRealtime} is not compared by default, given that in test cases that is not
+ *       usually used.
+ */
+@JvmOverloads
+fun assertNetworkStatsEquals(
+    expected: NetworkStats,
+    actual: NetworkStats,
+    compareTime: Boolean = false
+) {
+    assertTrue(orderInsensitiveEquals(expected, actual, compareTime),
+            "expected: " + expected + " but was: " + actual)
+}
+
+/**
+ * Assert that after being parceled then unparceled, {@link NetworkStats} is equal to the original
+ * object.
+ */
+fun assertParcelingIsLossless(stats: NetworkStats) {
+    assertParcelingIsLossless(stats, { a, b -> orderInsensitiveEquals(a, b) })
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
new file mode 100644
index 0000000..463c470
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils;
+
+import android.annotation.NonNull;
+
+/**
+ * Utilities to help Kotlin test to verify java @NonNull code
+ */
+public class NonNullTestUtils {
+    /**
+     * This method allows Kotlin to pass nullable to @NonNull java code for testing.
+     * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by
+     * Foo(NonNullTestUtils.nullUnsafe(nullableVar)).
+     */
+    @NonNull public static <T> T nullUnsafe(T v) {
+        return v;
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
new file mode 100644
index 0000000..d50f78a
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.TestNetworkSpecifier
+import android.os.Binder
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import java.net.InetAddress
+import libcore.io.IoUtils
+
+private const val MIN_PORT_NUMBER = 1025
+private const val MAX_PORT_NUMBER = 65535
+
+/**
+ * A class that set up two {@link TestNetworkInterface} with NAT, and forward packets between them.
+ *
+ * See {@link NatPacketForwarder} for more detailed information.
+ */
+class PacketBridge(
+    context: Context,
+    internalAddr: LinkAddress,
+    externalAddr: LinkAddress,
+    dnsAddr: InetAddress
+) {
+    private val natMap = NatMap()
+    private val binder = Binder()
+
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+    private val tnm = context.getSystemService(TestNetworkManager::class.java)!!
+
+    // Create test networks.
+    private val internalIface = tnm.createTunInterface(listOf(internalAddr))
+    private val externalIface = tnm.createTunInterface(listOf(externalAddr))
+
+    // Register test networks to ConnectivityService.
+    private val internalNetworkCallback: TestableNetworkCallback
+    private val externalNetworkCallback: TestableNetworkCallback
+    val internalNetwork: Network
+    val externalNetwork: Network
+    init {
+        val (inCb, inNet) = createTestNetwork(internalIface, internalAddr, dnsAddr)
+        val (exCb, exNet) = createTestNetwork(externalIface, externalAddr, dnsAddr)
+        internalNetworkCallback = inCb
+        externalNetworkCallback = exCb
+        internalNetwork = inNet
+        externalNetwork = exNet
+    }
+
+    // Setup the packet bridge.
+    private val internalFd = internalIface.fileDescriptor.fileDescriptor
+    private val externalFd = externalIface.fileDescriptor.fileDescriptor
+
+    private val pr1 = NatInternalPacketForwarder(
+        internalFd,
+        1500,
+        externalFd,
+        externalAddr.address,
+        natMap
+    )
+    private val pr2 = NatExternalPacketForwarder(
+        externalFd,
+        1500,
+        internalFd,
+        externalAddr.address,
+        natMap
+    )
+
+    fun start() {
+        IoUtils.setBlocking(internalFd, true /* blocking */)
+        IoUtils.setBlocking(externalFd, true /* blocking */)
+        pr1.start()
+        pr2.start()
+    }
+
+    fun stop() {
+        pr1.interrupt()
+        pr2.interrupt()
+        cm.unregisterNetworkCallback(internalNetworkCallback)
+        cm.unregisterNetworkCallback(externalNetworkCallback)
+    }
+
+    /**
+     * Creates a test network with given test TUN interface and addresses.
+     */
+    private fun createTestNetwork(
+        testIface: TestNetworkInterface,
+        addr: LinkAddress,
+        dnsAddr: InetAddress
+    ): Pair<TestableNetworkCallback, Network> {
+        // Make a network request to hold the test network
+        val nr = NetworkRequest.Builder()
+            .clearCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+            .setNetworkSpecifier(TestNetworkSpecifier(testIface.interfaceName))
+            .build()
+        val testCb = TestableNetworkCallback()
+        cm.requestNetwork(nr, testCb)
+
+        val lp = LinkProperties().apply {
+            addLinkAddress(addr)
+            interfaceName = testIface.interfaceName
+            addDnsServer(dnsAddr)
+        }
+        tnm.setupTestNetwork(lp, true /* isMetered */, binder)
+
+        // Wait for available before return.
+        val network = testCb.expect<Available>().network
+        return testCb to network
+    }
+
+    /**
+     * A helper class to maintain the mappings between internal addresses/ports and external
+     * ports.
+     *
+     * This class assigns an unused external port number if the mapping between
+     * srcaddress:srcport:protocol and the external port does not exist yet.
+     *
+     * Note that this class is not thread-safe. The instance of the class needs to be
+     * synchronized in the callers when being used in multiple threads.
+     */
+    class NatMap {
+        data class AddressInfo(val address: InetAddress, val port: Int, val protocol: Int)
+
+        private val mToExternalPort = HashMap<AddressInfo, Int>()
+        private val mFromExternalPort = HashMap<Int, AddressInfo>()
+
+        // Skip well-known port 0~1024.
+        private var nextExternalPort = MIN_PORT_NUMBER
+
+        fun toExternalPort(addr: InetAddress, port: Int, protocol: Int): Int {
+            val info = AddressInfo(addr, port, protocol)
+            val extPort: Int
+            if (!mToExternalPort.containsKey(info)) {
+                extPort = nextExternalPort++
+                if (nextExternalPort > MAX_PORT_NUMBER) {
+                    throw IllegalStateException("Available ports are exhausted")
+                }
+                mToExternalPort[info] = extPort
+                mFromExternalPort[extPort] = info
+            } else {
+                extPort = mToExternalPort[info]!!
+            }
+            return extPort
+        }
+
+        fun fromExternalPort(port: Int): AddressInfo? {
+            return mFromExternalPort[port]
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
new file mode 100644
index 0000000..69392d4
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2014 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.testutils;
+
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
+
+import android.annotation.NonNull;
+import android.net.TestNetworkInterface;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * A class that echoes packets received on a {@link TestNetworkInterface} back to itself.
+ *
+ * For testing purposes, sometimes a mocked environment to simulate a simple echo from the
+ * server side is needed. This is particularly useful if the test, e.g. VpnTest, is
+ * heavily relying on the outside world.
+ *
+ * This class reads packets from the {@link FileDescriptor} of a {@link TestNetworkInterface}, and:
+ *   1. For TCP and UDP packets, simply swaps the source address and the destination
+ *      address, then send it back to the {@link FileDescriptor}.
+ *   2. For ICMP ping packets, composes a ping reply and sends it back to the sender.
+ *   3. Ignore all other packets.
+ */
+public class PacketReflector extends Thread {
+
+    static final int IPV4_HEADER_LENGTH = 20;
+    static final int IPV6_HEADER_LENGTH = 40;
+
+    static final int IPV4_ADDR_OFFSET = 12;
+    static final int IPV6_ADDR_OFFSET = 8;
+    static final int IPV4_ADDR_LENGTH = 4;
+    static final int IPV6_ADDR_LENGTH = 16;
+
+    static final int IPV4_PROTO_OFFSET = 9;
+    static final int IPV6_PROTO_OFFSET = 6;
+
+    static final byte IPPROTO_ICMP = 1;
+    static final byte IPPROTO_TCP = 6;
+    static final byte IPPROTO_UDP = 17;
+    private static final byte IPPROTO_ICMPV6 = 58;
+
+    private static final int ICMP_HEADER_LENGTH = 8;
+    static final int TCP_HEADER_LENGTH = 20;
+    static final int UDP_HEADER_LENGTH = 8;
+
+    private static final byte ICMP_ECHO = 8;
+    private static final byte ICMP_ECHOREPLY = 0;
+
+    private static String TAG = "PacketReflector";
+
+    @NonNull
+    private final FileDescriptor mFd;
+    @NonNull
+    private final byte[] mBuf;
+
+    /**
+     * Construct a {@link PacketReflector} from the given {@code fd} of
+     * a {@link TestNetworkInterface}.
+     *
+     * @param fd {@link FileDescriptor} to read/write packets.
+     * @param mtu MTU of the test network.
+     */
+    public PacketReflector(@NonNull FileDescriptor fd, int mtu) {
+        super("PacketReflector");
+        mFd = Objects.requireNonNull(fd);
+        mBuf = new byte[mtu];
+    }
+
+    private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) {
+        for (int i = 0; i < len; i++) {
+            byte b = buf[pos1 + i];
+            buf[pos1 + i] = buf[pos2 + i];
+            buf[pos2 + i] = b;
+        }
+    }
+
+    private static void swapAddresses(@NonNull byte[] buf, int version) {
+        int addrPos, addrLen;
+        switch (version) {
+            case 4:
+                addrPos = IPV4_ADDR_OFFSET;
+                addrLen = IPV4_ADDR_LENGTH;
+                break;
+            case 6:
+                addrPos = IPV6_ADDR_OFFSET;
+                addrLen = IPV6_ADDR_LENGTH;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
+    }
+
+    // Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
+    // This is used by the test to "connect to itself" through the VPN.
+    private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + TCP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    // Echo UDP packets: swap source and destination addresses, and source and destination ports.
+    // This is used by the test to check that the bytes it sends are echoed back.
+    private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + UDP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Swap dst and src ports.
+        int portOffset = hdrLen;
+        swapBytes(buf, portOffset, portOffset + 2, 2);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + ICMP_HEADER_LENGTH) {
+            return;
+        }
+
+        byte type = buf[hdrLen];
+        if (!(version == 4 && type == ICMP_ECHO) &&
+                !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
+            return;
+        }
+
+        // Save the ping packet we received.
+        byte[] request = buf.clone();
+
+        // Swap src and dst IP addresses, and send the packet back.
+        // This effectively pings the device to see if it replies.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+
+        // The device should have replied, and buf should now contain a ping response.
+        int received = PacketReflectorUtil.readPacket(mFd, buf);
+        if (received != len) {
+            Log.i(TAG, "Reflecting ping did not result in ping response: " +
+                    "read=" + received + " expected=" + len);
+            return;
+        }
+
+        byte replyType = buf[hdrLen];
+        if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
+                || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
+            Log.i(TAG, "Received unexpected ICMP reply: original " + type
+                    + ", reply " + replyType);
+            return;
+        }
+
+        // Compare the response we got with the original packet.
+        // The only thing that should have changed are addresses, type and checksum.
+        // Overwrite them with the received bytes and see if the packet is otherwise identical.
+        request[hdrLen] = buf[hdrLen];          // Type
+        request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
+        request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
+
+        // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
+        // the request and reply may have different IPv6 flow label: ignore that as well.
+        if (version == 6) {
+            request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f);
+            request[2] = buf[2];
+            request[3] = buf[3];
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (buf[i] != request[i]) {
+                Log.i(TAG, "Received non-matching packet when expecting ping response.");
+                return;
+            }
+        }
+
+        // Now swap the addresses again and reflect the packet. This sends a ping reply.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+    }
+
+    private void writePacket(@NonNull byte[] buf, int len) {
+        try {
+            Os.write(mFd, buf, 0, len);
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Error writing packet: " + e.getMessage());
+        }
+    }
+
+    // Reads one packet from our mFd, and possibly writes the packet back.
+    private void processPacket() {
+        int len = PacketReflectorUtil.readPacket(mFd, mBuf);
+        if (len < 1) {
+            // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector.
+            return;
+        }
+
+        int version = mBuf[0] >> 4;
+        int protoPos, hdrLen;
+        if (version == 4) {
+            hdrLen = IPV4_HEADER_LENGTH;
+            protoPos = IPV4_PROTO_OFFSET;
+        } else if (version == 6) {
+            hdrLen = IPV6_HEADER_LENGTH;
+            protoPos = IPV6_PROTO_OFFSET;
+        } else {
+            throw new IllegalStateException("Unexpected version: " + version);
+        }
+
+        if (len < hdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+
+        byte proto = mBuf[protoPos];
+        switch (proto) {
+            case IPPROTO_ICMP:
+                // fall through
+            case IPPROTO_ICMPV6:
+                processIcmpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_TCP:
+                processTcpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_UDP:
+                processUdpPacket(mBuf, version, len, hdrLen);
+                break;
+        }
+    }
+
+    public void run() {
+        Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid());
+        while (!interrupted() && mFd.valid()) {
+            processPacket();
+        }
+        Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid());
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
new file mode 100644
index 0000000..498b1a3
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("PacketReflectorUtil")
+
+package com.android.testutils
+
+import android.system.ErrnoException
+import android.system.Os
+import android.system.OsConstants
+import com.android.net.module.util.IpUtils
+import com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH
+import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH
+import java.io.FileDescriptor
+import java.io.InterruptedIOException
+import java.net.InetAddress
+import java.nio.ByteBuffer
+
+fun readPacket(fd: FileDescriptor, buf: ByteArray): Int {
+    return try {
+        Os.read(fd, buf, 0, buf.size)
+    } catch (e: ErrnoException) {
+        // Ignore normal use cases such as the EAGAIN error indicates that the read operation
+        // cannot be completed immediately, or the EINTR error indicates that the read
+        // operation was interrupted by a signal.
+        if (e.errno == OsConstants.EAGAIN || e.errno == OsConstants.EINTR) {
+            -1
+        } else {
+            throw e
+        }
+    } catch (e: InterruptedIOException) {
+        -1
+    }
+}
+
+fun getInetAddressAt(buf: ByteArray, pos: Int, len: Int): InetAddress =
+    InetAddress.getByAddress(buf.copyOfRange(pos, pos + len))
+
+/**
+ * Reads a 16-bit unsigned int at pos in big endian, with no alignment requirements.
+ */
+fun getPortAt(buf: ByteArray, pos: Int): Int {
+    return (buf[pos].toInt() and 0xff shl 8) + (buf[pos + 1].toInt() and 0xff)
+}
+
+fun setPortAt(port: Int, buf: ByteArray, pos: Int) {
+    buf[pos] = (port ushr 8).toByte()
+    buf[pos + 1] = (port and 0xff).toByte()
+}
+
+fun getAddressPositionAndLength(version: Int) = when (version) {
+    4 -> PacketReflector.IPV4_ADDR_OFFSET to PacketReflector.IPV4_ADDR_LENGTH
+    6 -> PacketReflector.IPV6_ADDR_OFFSET to PacketReflector.IPV6_ADDR_LENGTH
+    else -> throw IllegalArgumentException("Unknown IP version $version")
+}
+
+private const val IPV4_CHKSUM_OFFSET = 10
+private const val UDP_CHECKSUM_OFFSET = 6
+private const val TCP_CHECKSUM_OFFSET = 16
+
+fun fixPacketChecksum(buf: ByteArray, len: Int, version: Int, protocol: Byte) {
+    // Fill Ip checksum for IPv4. IPv6 header doesn't have a checksum field.
+    if (version == 4) {
+        val checksum = IpUtils.ipChecksum(ByteBuffer.wrap(buf), 0)
+        // Place checksum in Big-endian order.
+        buf[IPV4_CHKSUM_OFFSET] = (checksum.toInt() ushr 8).toByte()
+        buf[IPV4_CHKSUM_OFFSET + 1] = (checksum.toInt() and 0xff).toByte()
+    }
+
+    // Fill transport layer checksum.
+    val transportOffset = if (version == 4) IPV4_HEADER_LENGTH else IPV6_HEADER_LENGTH
+    when (protocol) {
+        PacketReflector.IPPROTO_UDP -> {
+            val checksumPos = transportOffset + UDP_CHECKSUM_OFFSET
+            // Clear before calculate.
+            buf[checksumPos + 1] = 0x00
+            buf[checksumPos] = buf[checksumPos + 1]
+            val checksum = IpUtils.udpChecksum(
+                ByteBuffer.wrap(buf), 0,
+                transportOffset
+            )
+            buf[checksumPos] = (checksum.toInt() ushr 8).toByte()
+            buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte()
+        }
+        PacketReflector.IPPROTO_TCP -> {
+            val checksumPos = transportOffset + TCP_CHECKSUM_OFFSET
+            // Clear before calculate.
+            buf[checksumPos + 1] = 0x00
+            buf[checksumPos] = buf[checksumPos + 1]
+            val transportLen: Int = len - transportOffset
+            val checksum = IpUtils.tcpChecksum(
+                ByteBuffer.wrap(buf), 0, transportOffset,
+                transportLen
+            )
+            buf[checksumPos] = (checksum.toInt() ushr 8).toByte()
+            buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte()
+        }
+        // TODO: Support ICMP.
+        else -> throw IllegalArgumentException("Unsupported protocol: $protocol")
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
new file mode 100644
index 0000000..964c6c6
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import java.util.function.Predicate
+
+private const val POLL_FREQUENCY_MS = 1000L
+
+/**
+ * A class that can be used to reply to packets from a [TapPacketReader].
+ *
+ * A reply thread will be created to reply to incoming packets asynchronously.
+ * The receiver creates a new read head on the [TapPacketReader], to read packets, so it does not
+ * affect packets obtained through [TapPacketReader.popPacket].
+ *
+ * @param reader a [TapPacketReader] to obtain incoming packets and reply to them.
+ * @param packetFilter A filter to apply to incoming packets.
+ * @param name Name to use for the internal responder thread.
+ */
+abstract class PacketResponder(
+    private val reader: TapPacketReader,
+    private val packetFilter: Predicate<ByteArray>,
+    name: String
+) {
+    private val replyThread = ReplyThread(name)
+
+    protected abstract fun replyToPacket(packet: ByteArray, reader: TapPacketReader)
+
+    /**
+     * Start the [PacketResponder].
+     */
+    fun start() {
+        replyThread.start()
+    }
+
+    /**
+     * Stop the [PacketResponder].
+     *
+     * The responder cannot be used anymore after being stopped.
+     */
+    fun stop() {
+        replyThread.interrupt()
+        replyThread.join()
+    }
+
+    private inner class ReplyThread(name: String) : Thread(name) {
+        override fun run() {
+            try {
+                // Create a new ReadHead so other packets polled on the reader are not affected
+                val recvPackets = reader.receivedPackets.newReadHead()
+                while (!isInterrupted) {
+                    recvPackets.poll(POLL_FREQUENCY_MS, packetFilter::test)?.let {
+                        replyToPacket(it, reader)
+                    }
+                }
+            } catch (e: InterruptedException) {
+                // Exit gracefully
+            }
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt
new file mode 100644
index 0000000..14ed8e9
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ParcelUtils.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+@file:JvmName("ParcelUtils")
+
+package com.android.testutils
+
+import android.os.Parcel
+import android.os.Parcelable
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+/**
+ * Return a new instance of `T` after being parceled then unparceled.
+ */
+fun <T : Parcelable> parcelingRoundTrip(source: T): T {
+    val creator: Parcelable.Creator<T>
+    try {
+        creator = source.javaClass.getField("CREATOR").get(null) as Parcelable.Creator<T>
+    } catch (e: IllegalAccessException) {
+        fail("Missing CREATOR field: " + e.message)
+    } catch (e: NoSuchFieldException) {
+        fail("Missing CREATOR field: " + e.message)
+    }
+
+    var p = Parcel.obtain()
+    source.writeToParcel(p, /* flags */ 0)
+    p.setDataPosition(0)
+    val marshalled = p.marshall()
+    p = Parcel.obtain()
+    p.unmarshall(marshalled, 0, marshalled.size)
+    p.setDataPosition(0)
+    return creator.createFromParcel(p)
+}
+
+/**
+ * Assert that after being parceled then unparceled, `source` is equal to the original
+ * object. If a customized equals function is provided, uses the provided one.
+ */
+@JvmOverloads
+fun <T : Parcelable> assertParcelingIsLossless(
+    source: T,
+    equals: (T, T) -> Boolean = { a, b -> a == b }
+) {
+    val actual = parcelingRoundTrip(source)
+    assertTrue(equals(source, actual), "Expected $source, but was $actual")
+}
+
+@JvmOverloads
+fun <T : Parcelable> assertParcelSane(
+    obj: T,
+    fieldCount: Int,
+    equals: (T, T) -> Boolean = { a, b -> a == b }
+) {
+    assertFieldCountEquals(fieldCount, obj::class.java)
+    assertParcelingIsLossless(obj, equals)
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
new file mode 100644
index 0000000..51d57bc
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
@@ -0,0 +1,208 @@
+/*
+ * 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.testutils;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.MacAddress;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RdnssOption;
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+
+/**
+ * ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface.
+ * TODO: rename to NdResponder
+ */
+public class RouterAdvertisementResponder extends PacketResponder {
+    private static final String TAG = "RouterAdvertisementResponder";
+    private static final Inet6Address DNS_SERVER =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64");
+    private final TapPacketReader mPacketReader;
+    // Maps IPv6 address to MacAddress and isRouter boolean.
+    private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>();
+    private final IpPrefix mPrefix;
+
+    public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) {
+        super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG);
+        mPacketReader = packetReader;
+        mPrefix = Objects.requireNonNull(prefix);
+    }
+
+    public RouterAdvertisementResponder(TapPacketReader packetReader) {
+        this(packetReader, makeRandomPrefix());
+    }
+
+    private static IpPrefix makeRandomPrefix() {
+        final byte[] prefixBytes = new IpPrefix("2001:db8::/64").getAddress().getAddress();
+        final Random r = new Random();
+        for (int i = 4; i < 8; i++) {
+            prefixBytes[i] = (byte) r.nextInt();
+        }
+        return new IpPrefix(prefixBytes, 64);
+    }
+
+    /** Returns true if the packet is a router solicitation or neighbor solicitation message. */
+    private static boolean isRsOrNs(byte[] packet) {
+        final ByteBuffer buffer = ByteBuffer.wrap(packet);
+        final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer);
+        if (ethHeader.etherType != ETHER_TYPE_IPV6) {
+            return false;
+        }
+        final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer);
+        if (ipv6Header.nextHeader != IPPROTO_ICMPV6) {
+            return false;
+        }
+        final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer);
+        return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION
+            || icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION;
+    }
+
+    /**
+     * Adds a new router to be advertised.
+     * @param mac the mac address of the router.
+     * @param ip the link-local address of the router.
+     */
+    public void addRouterEntry(MacAddress mac, Inet6Address ip) {
+        mNeighborMap.put(ip, new Pair<>(mac, true));
+    }
+
+    /**
+     * Adds a new neighbor to be advertised.
+     * @param mac the mac address of the neighbor.
+     * @param ip the link-local address of the neighbor.
+     */
+    public void addNeighborEntry(MacAddress mac, Inet6Address ip) {
+        mNeighborMap.put(ip, new Pair<>(mac, false));
+    }
+
+    /**
+     * @return the prefix that is announced in the Router Advertisements sent by this object.
+     */
+    public IpPrefix getPrefix() {
+        return mPrefix;
+    }
+
+    private ByteBuffer buildPrefixOption() {
+        return PrefixInformationOption.build(
+                mPrefix, (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS),
+                3600 /* valid lifetime */, 3600 /* preferred lifetime */);
+    }
+
+    private ByteBuffer buildRdnssOption() {
+        return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER);
+    }
+
+    private ByteBuffer buildSllaOption(MacAddress srcMac) {
+        return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+    }
+
+    private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) {
+        return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST,
+                (byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/,
+                0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption(),
+                buildSllaOption(srcMac));
+    }
+
+    private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) {
+        try {
+            reader.sendResponse(buffer);
+        } catch (IOException e) {
+            // Throwing an exception here will crash the test process. Let's stick to logging, as
+            // the test will fail either way.
+            Log.e(TAG, "Failed to send buffer", e);
+        }
+    }
+
+    private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) {
+        for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) {
+            final boolean isRouter = it.getValue().second;
+            if (!isRouter) {
+                continue;
+            }
+            final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey());
+            sendResponse(reader, raResponse);
+        }
+    }
+
+    private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac,
+            Inet6Address dstIp, Inet6Address targetIp) {
+        final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp);
+        if (neighbor == null) {
+            return;
+        }
+
+        final MacAddress srcMac = neighbor.first;
+        final boolean isRouter = neighbor.second;
+        int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+        if (isRouter) {
+            flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
+        }
+
+        final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
+        final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp,
+                flags, targetIp, tlla);
+        sendResponse(reader, naResponse);
+    }
+
+    @Override
+    protected void replyToPacket(byte[] packet, TapPacketReader reader) {
+        final ByteBuffer buf = ByteBuffer.wrap(packet);
+        // Messages are filtered by parent class, so it is safe to assume that packet is either an
+        // RS or NS.
+        final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+        final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+        final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
+
+        if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) {
+            replyToRouterSolicitation(reader, ethHdr.srcMac);
+        } else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) {
+            final NsHeader nsHeader = Struct.parse(NsHeader.class, buf);
+            replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target);
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java
new file mode 100644
index 0000000..b25b9f2
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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.testutils;
+
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.PacketReader;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import kotlin.Lazy;
+import kotlin.LazyKt;
+
+/**
+ * A packet reader that runs on a TAP interface.
+ *
+ * It also implements facilities to reply to received packets.
+ */
+public class TapPacketReader extends PacketReader {
+    private final FileDescriptor mTapFd;
+    private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>();
+    private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead =
+            LazyKt.lazy(mReceivedPackets::newReadHead);
+
+    public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
+        super(h, maxPacketSize);
+        mTapFd = tapFd;
+    }
+
+
+    /**
+     * Attempt to start the FdEventsReader on its handler thread.
+     *
+     * As opposed to {@link android.net.util.FdEventsReader#start()}, this method will not report
+     * failure to start, so it is only appropriate in tests that will fail later if that happens.
+     */
+    public void startAsyncForTest() {
+        getHandler().post(this::start);
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        return mTapFd;
+    }
+
+    @Override
+    protected void handlePacket(byte[] recvbuf, int length) {
+        final byte[] newPacket = Arrays.copyOf(recvbuf, length);
+        if (!mReceivedPackets.add(newPacket)) {
+            throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!");
+        }
+    }
+
+    /**
+     * @deprecated This method does not actually "pop" (which generally means the last packet).
+     * Use {@link #poll(long)}, which has the same behavior, instead.
+     */
+    @Nullable
+    @Deprecated
+    public byte[] popPacket(long timeoutMs) {
+        return poll(timeoutMs);
+    }
+
+    /**
+     * @deprecated This method does not actually "pop" (which generally means the last packet).
+     * Use {@link #poll(long, Predicate)}, which has the same behavior, instead.
+     */
+    @Nullable
+    @Deprecated
+    public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+        return poll(timeoutMs, filter);
+    }
+
+    /**
+     * Get the next packet that was received on the interface.
+     */
+    @Nullable
+    public byte[] poll(long timeoutMs) {
+        return mReadHead.getValue().poll(timeoutMs, packet -> true);
+    }
+
+    /**
+     * Get the next packet that was received on the interface and matches the specified filter.
+     */
+    @Nullable
+    public byte[] poll(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+        return mReadHead.getValue().poll(timeoutMs, filter::test);
+    }
+
+    /**
+     * Get the {@link ArrayTrackRecord} that records all packets received by the reader since its
+     * creation.
+     */
+    public ArrayTrackRecord<byte[]> getReceivedPackets() {
+        return mReceivedPackets;
+    }
+
+    /*
+     * Send a response on the TAP interface.
+     *
+     * The passed ByteBuffer is flipped after use.
+     *
+     * @param packet The packet to send.
+     * @throws IOException if the interface can't be written to.
+     */
+    public void sendResponse(final ByteBuffer packet) throws IOException {
+        try (FileOutputStream out = new FileOutputStream(mTapFd)) {
+            byte[] packetBytes = new byte[packet.limit()];
+            packet.get(packetBytes);
+            packet.flip();  // So we can reuse it in the future.
+            out.write(packetBytes);
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
new file mode 100644
index 0000000..701666c
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import kotlin.test.assertFalse
+import kotlin.test.fail
+
+private const val HANDLER_TIMEOUT_MS = 10_000L
+
+/**
+ * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test.
+ *
+ * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer.
+ * @param autoStart Whether to initialize the interface and start the reader automatically for every
+ *                  test. If false, each test must either call start() and stop(), or be annotated
+ *                  with TapPacketReaderTest before using the reader or interface.
+ */
+class TapPacketReaderRule @JvmOverloads constructor(
+    private val maxPacketSize: Int = 1500,
+    private val autoStart: Boolean = true
+) : TestRule {
+    // Use lateinit as the below members can't be initialized in the rule constructor (the
+    // InstrumentationRegistry may not be ready), but from the point of view of test cases using
+    // this rule with autoStart = true, the members are always initialized (in setup/test/teardown):
+    // tests cases should be able use them directly.
+    // lateinit also allows getting good exceptions detailing what went wrong if the members are
+    // referenced before they could be initialized (typically if autoStart is false and the test
+    // does not call start or use @TapPacketReaderTest).
+    lateinit var iface: TestNetworkInterface
+    lateinit var reader: TapPacketReader
+
+    @Volatile
+    private var readerRunning = false
+
+    /**
+     * Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and
+     * start the [TapPacketReader] before the test, and tear them down afterwards.
+     *
+     * For use when [TapPacketReaderRule] is created with autoStart = false.
+     */
+    annotation class TapPacketReaderTest
+
+    /**
+     * Initialize the tap interface and start the [TapPacketReader].
+     *
+     * Tests using this method must also call [stop] before exiting.
+     * @param handler Handler to run the reader on. Callers are responsible for safely terminating
+     *                the handler when the test ends. If null, a handler thread managed by the
+     *                rule will be used.
+     */
+    @JvmOverloads
+    fun start(handler: Handler? = null) {
+        if (this::iface.isInitialized) {
+            fail("${TapPacketReaderRule::class.java.simpleName} was already started")
+        }
+
+        val ctx = InstrumentationRegistry.getInstrumentation().context
+        iface = runAsShell(MANAGE_TEST_NETWORKS) {
+            val tnm = ctx.getSystemService(TestNetworkManager::class.java)
+                    ?: fail("Could not obtain the TestNetworkManager")
+            tnm.createTapInterface()
+        }
+        val usedHandler = handler ?: HandlerThread(
+                TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler
+        reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
+        reader.startAsyncForTest()
+        readerRunning = true
+    }
+
+    /**
+     * Stop the [TapPacketReader].
+     *
+     * Tests calling [start] must call this method before exiting. If a handler was specified in
+     * [start], all messages on that handler must also be processed after calling this method and
+     * before exiting.
+     *
+     * If [start] was not called, calling this method is a no-op.
+     */
+    fun stop() {
+        // The reader may not be initialized if the test case did not use the rule, even though
+        // other test cases in the same class may be using it (so test classes may call stop in
+        // tearDown even if start is not called for all test cases).
+        if (!this::reader.isInitialized) return
+        reader.handler.post {
+            reader.stop()
+            readerRunning = false
+        }
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return TapReaderStatement(base, description)
+    }
+
+    private inner class TapReaderStatement(
+        private val base: Statement,
+        private val description: Description
+    ) : Statement() {
+        override fun evaluate() {
+            val shouldStart = autoStart ||
+                    description.getAnnotation(TapPacketReaderTest::class.java) != null
+            if (shouldStart) {
+                start()
+            }
+
+            try {
+                base.evaluate()
+            } finally {
+                if (shouldStart) {
+                    stop()
+                    reader.handler.looper.apply {
+                        quitSafely()
+                        thread.join(HANDLER_TIMEOUT_MS)
+                        assertFalse(thread.isAlive,
+                                "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
+                    }
+                }
+
+                if (this@TapPacketReaderRule::iface.isInitialized) {
+                    iface.fileDescriptor.close()
+                }
+            }
+
+            assertFalse(readerRunning,
+                    "stop() was not called, or the provided handler did not process the stop " +
+                    "message before the test ended. If not using autostart, make sure to call " +
+                    "stop() after the test. If a handler is specified in start(), make sure all " +
+                    "messages are processed after calling stop(), before quitting (for example " +
+                    "by using HandlerThread#quitSafely and HandlerThread#join).")
+        }
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
new file mode 100644
index 0000000..733bd98
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestBpfMap.java
@@ -0,0 +1,139 @@
+/*
+ * 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.testutils;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * Fake BPF map class for tests that have no privilege to access real BPF maps. TestBpfMap does not
+ * load JNI and all member functions do not access real BPF maps.
+ *
+ * Implements IBpfMap so that any class using IBpfMap can use this class in its tests.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class TestBpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
+    private final ConcurrentHashMap<K, V> mMap = new ConcurrentHashMap<>();
+
+    // TODO: Remove this constructor
+    public TestBpfMap(final Class<K> key, final Class<V> value) {
+    }
+
+    @Override
+    public void forEach(ThrowingBiConsumer<K, V> action) throws ErrnoException {
+        // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to
+        // implement the entry deletion in the iteration if required.
+        for (Map.Entry<K, V> entry : mMap.entrySet()) {
+            action.accept(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public void updateEntry(K key, V value) throws ErrnoException {
+        mMap.put(key, value);
+    }
+
+    @Override
+    public void insertEntry(K key, V value) throws ErrnoException,
+            IllegalArgumentException {
+        // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry.
+        if (mMap.get(key) != null) {
+            throw new IllegalArgumentException(key + " already exist");
+        }
+        mMap.put(key, value);
+    }
+
+    @Override
+    public void replaceEntry(K key, V value) throws ErrnoException, NoSuchElementException {
+        if (!mMap.containsKey(key)) throw new NoSuchElementException();
+        mMap.put(key, value);
+    }
+
+    @Override
+    public boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+        // Returns true if inserted, false if replaced.
+        boolean ret = !mMap.containsKey(key);
+        mMap.put(key, value);
+        return ret;
+    }
+
+    @Override
+    public boolean deleteEntry(Struct key) throws ErrnoException {
+        return mMap.remove(key) != null;
+    }
+
+    @Override
+    public boolean isEmpty() throws ErrnoException {
+        return mMap.isEmpty();
+    }
+
+    @Override
+    public K getNextKey(@NonNull K key) {
+        // Expensive, but since this is only for tests...
+        Iterator<K> it = mMap.keySet().iterator();
+        while (it.hasNext()) {
+            if (Objects.equals(it.next(), key)) {
+                return it.hasNext() ? it.next() : null;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public K getFirstKey() {
+        for (K key : mMap.keySet()) {
+            return key;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean containsKey(@NonNull K key) throws ErrnoException {
+        return mMap.containsKey(key);
+    }
+
+    @Override
+    public V getValue(@NonNull K key) throws ErrnoException {
+        // Return value for a given key. Otherwise, return null without an error ENOENT.
+        // BpfMap#getValue treats that the entry is not found as no error.
+        return mMap.get(key);
+    }
+
+    @Override
+    public void clear() throws ErrnoException {
+        // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
+        mMap.clear();
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt
new file mode 100644
index 0000000..e1b771b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestDnsServer.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.testutils
+
+import android.net.Network
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE
+import com.android.net.module.util.DnsPacket
+import java.net.DatagramPacket
+import java.net.DatagramSocket
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.SocketAddress
+import java.net.SocketException
+import java.util.ArrayList
+
+private const val TAG = "TestDnsServer"
+private const val VDBG = true
+@VisibleForTesting(visibility = PRIVATE)
+const val MAX_BUF_SIZE = 8192
+
+/**
+ * A simple implementation of Dns Server that can be bound on specific address and Network.
+ *
+ * The caller should use start() to make the server start a new thread to receive DNS queries
+ * on the bound address, [isAlive] to check status, and stop() for stopping.
+ * The server allows user to manipulate the records to be answered through
+ * [setAnswer] at runtime.
+ *
+ * This server runs on its own thread. Please make sure writing the query to the socket
+ * happens-after using [setAnswer] to guarantee the correct answer is returned. If possible,
+ * use [setAnswer] before calling [start] for simplicity.
+ */
+class TestDnsServer(network: Network, addr: InetSocketAddress) {
+    enum class Status {
+        NOT_STARTED, STARTED, STOPPED
+    }
+    @GuardedBy("thread")
+    private var status: Status = Status.NOT_STARTED
+    private val thread = ReceivingThread()
+    private val socket = DatagramSocket(addr).also { network.bindSocket(it) }
+    private val ansProvider = DnsAnswerProvider()
+
+    // The buffer to store the received packet. They are being reused for
+    // efficiency and it's fine because they are only ever accessed
+    // on the server thread in a sequential manner.
+    private val buffer = ByteArray(MAX_BUF_SIZE)
+    private val packet = DatagramPacket(buffer, buffer.size)
+
+    fun setAnswer(hostname: String, answer: List<InetAddress>) =
+        ansProvider.setAnswer(hostname, answer)
+
+    private fun processPacket() {
+        // Blocking read and try construct a DnsQueryPacket object.
+        socket.receive(packet)
+        val q = DnsQueryPacket(packet.data)
+        handleDnsQuery(q, packet.socketAddress)
+    }
+
+    // TODO: Add support to reply some error with a DNS reply packet with failure RCODE.
+    private fun handleDnsQuery(q: DnsQueryPacket, src: SocketAddress) {
+        val queryRecords = q.queryRecords
+        if (queryRecords.size != 1) {
+            throw IllegalArgumentException(
+                "Expected one dns query record but got ${queryRecords.size}"
+            )
+        }
+        val answerRecords = queryRecords[0].let { ansProvider.getAnswer(it.dName, it.nsType) }
+
+        if (VDBG) {
+            Log.v(TAG, "handleDnsPacket: " +
+                        queryRecords.map { "${it.dName},${it.nsType}" }.joinToString() +
+                        " ansCount=${answerRecords.size} socketAddress=$src")
+        }
+
+        val bytes = q.getAnswerPacket(answerRecords).bytes
+        val reply = DatagramPacket(bytes, bytes.size, src)
+        socket.send(reply)
+    }
+
+    fun start() {
+        synchronized(thread) {
+            if (status != Status.NOT_STARTED) {
+                throw IllegalStateException("unexpected status: $status")
+            }
+            thread.start()
+            status = Status.STARTED
+        }
+    }
+    fun stop() {
+        synchronized(thread) {
+            if (status != Status.STARTED) {
+                throw IllegalStateException("unexpected status: $status")
+            }
+            // The thread needs to be interrupted before closing the socket to prevent a data
+            // race where the thread tries to read from the socket while it's being closed.
+            // DatagramSocket is not thread-safe and running both concurrently can end up in
+            // getPort() returning -1 after it's been checked not to, resulting in a crash by
+            // IllegalArgumentException inside the DatagramSocket implementation.
+            thread.interrupt()
+            socket.close()
+            thread.join()
+            status = Status.STOPPED
+        }
+    }
+    val isAlive get() = thread.isAlive
+    val port get() = socket.localPort
+
+    inner class ReceivingThread : Thread() {
+        override fun run() {
+            while (!interrupted() && !socket.isClosed) {
+                try {
+                    processPacket()
+                } catch (e: InterruptedException) {
+                    // The caller terminated the server, exit.
+                    break
+                } catch (e: SocketException) {
+                    // The caller terminated the server, exit.
+                    break
+                }
+            }
+            Log.i(TAG, "exiting socket={$socket}")
+        }
+    }
+
+    @VisibleForTesting(visibility = PRIVATE)
+    class DnsQueryPacket : DnsPacket {
+        constructor(data: ByteArray) : super(data)
+        constructor(header: DnsHeader, qd: List<DnsRecord>, an: List<DnsRecord>) :
+                super(header, qd, an)
+
+        init {
+            if (mHeader.isResponse) {
+                throw ParseException("Not a query packet")
+            }
+        }
+
+        val queryRecords: List<DnsRecord>
+            get() = mRecords[QDSECTION]
+
+        fun getAnswerPacket(ar: List<DnsRecord>): DnsAnswerPacket {
+            // Set QR bit of flag to 1 for response packet according to RFC 1035 section 4.1.1.
+            val flags = 1 shl 15
+            val qr = ArrayList(mRecords[QDSECTION])
+            // Copy the query packet header id to the answer packet as RFC 1035 section 4.1.1.
+            val header = DnsHeader(mHeader.id, flags, qr.size, ar.size)
+            return DnsAnswerPacket(header, qr, ar)
+        }
+    }
+
+    class DnsAnswerPacket : DnsPacket {
+        constructor(header: DnsHeader, qr: List<DnsRecord>, ar: List<DnsRecord>) :
+                super(header, qr, ar)
+        @VisibleForTesting(visibility = PRIVATE)
+        constructor(bytes: ByteArray) : super(bytes)
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
new file mode 100644
index 0000000..740bf63
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.net.Uri
+import com.android.net.module.util.ArrayTrackRecord
+import fi.iki.elonen.NanoHTTPD
+import java.io.IOException
+
+/**
+ * A minimal HTTP server running on a random available port.
+ *
+ * @param host The host to listen to, or null to listen on all hosts
+ */
+class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) {
+    // Map of URL path -> HTTP response code
+    private val responses = HashMap<Request, Response>()
+
+    /**
+     * A record of all requests received by the server since it was started.
+     */
+    val requestsRecord = ArrayTrackRecord<Request>()
+
+    /**
+     * A request received by the test server.
+     */
+    data class Request(
+        val path: String,
+        val method: Method = Method.GET,
+        val queryParameters: String = ""
+    ) {
+        /**
+         * Returns whether the specified [Uri] matches parameters of this request.
+         */
+        fun matches(uri: Uri) = (uri.path ?: "") == path && (uri.query ?: "") == queryParameters
+    }
+
+    /**
+     * Add a response for GET requests with the path and query parameters of the specified [Uri].
+     */
+    fun addResponse(
+        uri: Uri,
+        statusCode: Response.IStatus,
+        headers: Map<String, String>? = null,
+        content: String = ""
+    ) {
+        addResponse(Request(uri.path
+                ?: "", Method.GET, uri.query ?: ""),
+                statusCode, headers, content)
+    }
+
+    /**
+     * Add a response for the given request.
+     */
+    fun addResponse(
+        request: Request,
+        statusCode: Response.IStatus,
+        headers: Map<String, String>? = null,
+        content: String = ""
+    ) {
+        val response = newFixedLengthResponse(statusCode, "text/plain", content)
+        headers?.forEach {
+            (key, value) -> response.addHeader(key, value)
+        }
+        responses[request] = response
+    }
+
+    override fun serve(session: IHTTPSession): Response {
+        val request = Request(session.uri
+                ?: "", session.method, session.queryParameterString ?: "")
+        requestsRecord.add(request)
+
+        // For PUT and POST, call parseBody to read InputStream before responding.
+        if (Method.PUT == session.method || Method.POST == session.method) {
+            try {
+                session.parseBody(HashMap())
+            } catch (e: Exception) {
+                when (e) {
+                    is IOException, is ResponseException -> e.toResponse()
+                    else -> throw e
+                }
+            }
+        }
+
+        // Default response is a 404
+        return responses[request] ?: super.serve(session)
+    }
+
+    fun Exception.toResponse() =
+        newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", this.toString())
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
new file mode 100644
index 0000000..84fb47b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.LinkAddress
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.LinkProperties
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.os.Binder
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.android.modules.utils.build.SdkLevel.isAtLeastR
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertTrue
+
+/**
+ * Create a test network based on a TUN interface with a LinkAddress.
+ *
+ * TODO: remove this function after fixing all the callers to use a list of LinkAddresses.
+ * This method will block until the test network is available. Requires
+ * [android.Manifest.permission.CHANGE_NETWORK_STATE] and
+ * [android.Manifest.permission.MANAGE_TEST_NETWORKS].
+ */
+fun initTestNetwork(
+    context: Context,
+    interfaceAddr: LinkAddress,
+    setupTimeoutMs: Long = 10_000L
+): TestNetworkTracker {
+    return initTestNetwork(context, listOf(interfaceAddr), setupTimeoutMs)
+}
+
+/**
+ * Create a test network based on a TUN interface with a LinkAddress list.
+ *
+ * This method will block until the test network is available. Requires
+ * [android.Manifest.permission.CHANGE_NETWORK_STATE] and
+ * [android.Manifest.permission.MANAGE_TEST_NETWORKS].
+ */
+fun initTestNetwork(
+    context: Context,
+    linkAddrs: List<LinkAddress>,
+    setupTimeoutMs: Long = 10_000L
+): TestNetworkTracker {
+    return initTestNetwork(context, linkAddrs, lp = null, setupTimeoutMs = setupTimeoutMs)
+}
+
+/**
+ * Create a test network based on a TUN interface
+ *
+ * This method will block until the test network is available. Requires
+ * [android.Manifest.permission.CHANGE_NETWORK_STATE] and
+ * [android.Manifest.permission.MANAGE_TEST_NETWORKS].
+ *
+ * This is only usable starting from R as [TestNetworkManager] has no support for specifying
+ * LinkProperties on Q.
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+fun initTestNetwork(
+    context: Context,
+    lp: LinkProperties,
+    setupTimeoutMs: Long = 10_000L
+): TestNetworkTracker {
+    return initTestNetwork(context, lp.linkAddresses, lp, setupTimeoutMs)
+}
+
+private fun initTestNetwork(
+    context: Context,
+    linkAddrs: List<LinkAddress>,
+    lp: LinkProperties?,
+    setupTimeoutMs: Long = 10_000L
+): TestNetworkTracker {
+    val tnm = context.getSystemService(TestNetworkManager::class.java)!!
+    val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs)
+    else tnm.createTunInterface(linkAddrs.toTypedArray())
+    val lpWithIface = if (lp == null) null else LinkProperties(lp).apply {
+        interfaceName = iface.interfaceName
+    }
+    return TestNetworkTracker(context, iface, tnm, lpWithIface, setupTimeoutMs)
+}
+
+/**
+ * Utility class to create and track test networks.
+ *
+ * This class is not thread-safe.
+ */
+class TestNetworkTracker internal constructor(
+    val context: Context,
+    val iface: TestNetworkInterface,
+    val tnm: TestNetworkManager,
+    val lp: LinkProperties?,
+    setupTimeoutMs: Long
+) : TestableNetworkCallback.HasNetwork {
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+    private val binder = Binder()
+
+    private val networkCallback: NetworkCallback
+    override val network: Network
+    val testIface: TestNetworkInterface
+
+    init {
+        val networkFuture = CompletableFuture<Network>()
+        val networkRequest = NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                // Test networks do not have NOT_VPN or TRUSTED capabilities by default
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(iface.interfaceName))
+                .build()
+        networkCallback = object : NetworkCallback() {
+            override fun onAvailable(network: Network) {
+                networkFuture.complete(network)
+            }
+        }
+        cm.requestNetwork(networkRequest, networkCallback)
+
+        network = try {
+            if (lp != null) {
+                assertTrue(isAtLeastR(), "Cannot specify TestNetwork LinkProperties before R")
+                tnm.setupTestNetwork(lp, true /* isMetered */, binder)
+            } else {
+                tnm.setupTestNetwork(iface.interfaceName, binder)
+            }
+            networkFuture.get(setupTimeoutMs, TimeUnit.MILLISECONDS)
+        } catch (e: Throwable) {
+            cm.unregisterNetworkCallback(networkCallback)
+            throw e
+        }
+
+        testIface = iface
+    }
+
+    fun teardown() {
+        cm.unregisterNetworkCallback(networkCallback)
+        tnm.teardownTestNetwork(network)
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
new file mode 100644
index 0000000..f571f64
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+@file:JvmName("TestPermissionUtil")
+
+package com.android.testutils
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
+
+/**
+ * Run the specified [task] with the specified [permissions] obtained through shell
+ * permission identity.
+ *
+ * Passing in an empty list of permissions can grant all shell permissions, but this is
+ * discouraged as it also causes the process to temporarily lose non-shell permissions.
+ */
+fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
+    val autom = InstrumentationRegistry.getInstrumentation().uiAutomation
+
+    // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all
+    // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can
+    // detect this, refuse to do it.
+    //
+    // TODO: when R is deprecated, we could try to make this work instead.
+    // - Get the list of previously-adopted permissions.
+    // - Adopt the union of the previously-adopted and newly-requested permissions.
+    // - Run the task.
+    // - Adopt the previously-adopted permissions, dropping the ones just adopted.
+    //
+    // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted
+    // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions.
+    if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) {
+        throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested")
+    }
+
+    autom.adoptShellPermissionIdentity(*permissions)
+    try {
+        return task()
+    } finally {
+        autom.dropShellPermissionIdentity()
+    }
+}
+
+/**
+ * Convenience overload of [runAsShell] that uses a [ThrowingSupplier] for Java callers, when
+ * only one/two/three permissions are needed.
+ */
+@JvmOverloads
+fun <T> runAsShell(
+    perm1: String,
+    perm2: String = "",
+    perm3: String = "",
+    supplier: ThrowingSupplier<T>
+): T = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { supplier.get() }
+
+/**
+ * Convenience overload of [runAsShell] that uses a [ThrowingRunnable] for Java callers, when
+ * only one/two/three permissions are needed.
+ */
+@JvmOverloads
+fun runAsShell(
+    perm1: String,
+    perm2: String = "",
+    perm3: String = "",
+    runnable: ThrowingRunnable
+): Unit = runAsShell(*getNonEmptyVarargs(perm1, perm2, perm3)) { runnable.run() }
+
+/**
+ * Get an array containing the first consecutive non-empty arguments out of three arguments.
+ *
+ * The first argument is assumed to be non-empty.
+ */
+private fun getNonEmptyVarargs(arg1: String, arg2: String, arg3: String): Array<String> {
+    return when {
+        arg2 == "" -> arrayOf(arg1)
+        arg3 == "" -> arrayOf(arg1, arg2)
+        else -> arrayOf(arg1, arg2, arg3)
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
new file mode 100644
index 0000000..8dc1bc4
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2021 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.testutils;
+
+import android.content.Context
+import android.net.KeepalivePacketData
+import android.net.LinkProperties
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.QosFilter
+import android.net.Uri
+import android.os.Looper
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Assert.assertArrayEquals
+
+// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
+// requests filed by the test and should never match normal internet requests. 70 is the default
+// score of Ethernet networks, it's as good a value as any other.
+private const val TEST_NETWORK_SCORE = 70
+
+private class Provider(context: Context, looper: Looper) :
+            NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+
+public open class TestableNetworkAgent(
+    context: Context,
+    looper: Looper,
+    val nc: NetworkCapabilities,
+    val lp: LinkProperties,
+    conf: NetworkAgentConfig
+) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
+        nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+
+    val DEFAULT_TIMEOUT_MS = 5000L
+
+    val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+    sealed class CallbackEntry {
+        object OnBandwidthUpdateRequested : CallbackEntry()
+        object OnNetworkUnwanted : CallbackEntry()
+        data class OnAddKeepalivePacketFilter(
+            val slot: Int,
+            val packet: KeepalivePacketData
+        ) : CallbackEntry()
+        data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
+        data class OnStartSocketKeepalive(
+            val slot: Int,
+            val interval: Int,
+            val packet: KeepalivePacketData
+        ) : CallbackEntry()
+        data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
+        data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
+        object OnAutomaticReconnectDisabled : CallbackEntry()
+        data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
+        data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
+        object OnNetworkCreated : CallbackEntry()
+        object OnNetworkDestroyed : CallbackEntry()
+        data class OnDscpPolicyStatusUpdated(val policyId: Int, val status: Int) : CallbackEntry()
+        data class OnRegisterQosCallback(
+            val callbackId: Int,
+            val filter: QosFilter
+        ) : CallbackEntry()
+        data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
+    }
+
+    override fun onBandwidthUpdateRequested() {
+        history.add(OnBandwidthUpdateRequested)
+    }
+
+    override fun onNetworkUnwanted() {
+        history.add(OnNetworkUnwanted)
+    }
+
+    override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
+        history.add(OnAddKeepalivePacketFilter(slot, packet))
+    }
+
+    override fun onRemoveKeepalivePacketFilter(slot: Int) {
+        history.add(OnRemoveKeepalivePacketFilter(slot))
+    }
+
+    override fun onStartSocketKeepalive(
+        slot: Int,
+        interval: Duration,
+        packet: KeepalivePacketData
+    ) {
+        history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
+    }
+
+    override fun onStopSocketKeepalive(slot: Int) {
+        history.add(OnStopSocketKeepalive(slot))
+    }
+
+    override fun onSaveAcceptUnvalidated(accept: Boolean) {
+        history.add(OnSaveAcceptUnvalidated(accept))
+    }
+
+    override fun onAutomaticReconnectDisabled() {
+        history.add(OnAutomaticReconnectDisabled)
+    }
+
+    override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
+        history.add(OnSignalStrengthThresholdsUpdated(thresholds))
+    }
+
+    fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) {
+        expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+            assertArrayEquals(thresholds, it.thresholds)
+        }
+    }
+
+    override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
+        history.add(OnRegisterQosCallback(qosCallbackId, filter))
+    }
+
+    override fun onQosCallbackUnregistered(qosCallbackId: Int) {
+        history.add(OnUnregisterQosCallback(qosCallbackId))
+    }
+
+    override fun onValidationStatus(status: Int, uri: Uri?) {
+        history.add(OnValidationStatus(status, uri))
+    }
+
+    override fun onNetworkCreated() {
+        history.add(OnNetworkCreated)
+    }
+
+    override fun onNetworkDestroyed() {
+        history.add(OnNetworkDestroyed)
+    }
+
+    override fun onDscpPolicyStatusUpdated(policyId: Int, status: Int) {
+        history.add(OnDscpPolicyStatusUpdated(policyId, status))
+    }
+
+    // Expects the initial validation event that always occurs immediately after registering
+    // a NetworkAgent whose network does not require validation (which test networks do
+    // not, since they lack the INTERNET capability). It always contains the default argument
+    // for the URI.
+    fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let {
+        assertEquals(it.status, VALID_NETWORK)
+        // The returned Uri is parsed from the empty string, which means it's an
+        // instance of the (private) Uri.StringUri. There are no real good ways
+        // to check this, the least bad is to just convert it to a string and
+        // make sure it's empty.
+        assertEquals("", it.uri.toString())
+    }
+
+    inline fun <reified T : CallbackEntry> expectCallback(): T {
+        val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+        assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+        return foundCallback
+    }
+
+    inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+        val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+        assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+        assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+    }
+
+    inline fun <reified T : CallbackEntry> eventuallyExpect() =
+            history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
+                assertNotNull(it, "Callback ${T::class} not received")
+    } as T
+
+    fun assertNoCallback() {
+        assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
+                "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
+        assertNull(history.peek())
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
new file mode 100644
index 0000000..df9c61a
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2019 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.testutils
+
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.util.Log
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Losing
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.RecorderCallback.CallbackEntry.Resumed
+import com.android.testutils.RecorderCallback.CallbackEntry.Suspended
+import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+
+object NULL_NETWORK : Network(-1)
+object ANY_NETWORK : Network(-2)
+fun anyNetwork() = ANY_NETWORK
+
+open class RecorderCallback private constructor(
+    private val backingRecord: ArrayTrackRecord<CallbackEntry>
+) : NetworkCallback() {
+    public constructor() : this(ArrayTrackRecord())
+    protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord())
+
+    private val TAG = this::class.simpleName
+
+    sealed class CallbackEntry {
+        // To get equals(), hashcode(), componentN() etc for free, the child classes of
+        // this class are data classes. But while data classes can inherit from other classes,
+        // they may only have visible members in the constructors, so they couldn't declare
+        // a constructor with a non-val arg to pass to CallbackEntry. Instead, force all
+        // subclasses to implement a `network' property, which can be done in a data class
+        // constructor by specifying override.
+        abstract val network: Network
+
+        data class Available(override val network: Network) : CallbackEntry()
+        data class CapabilitiesChanged(
+            override val network: Network,
+            val caps: NetworkCapabilities
+        ) : CallbackEntry()
+        data class LinkPropertiesChanged(
+            override val network: Network,
+            val lp: LinkProperties
+        ) : CallbackEntry()
+        data class Suspended(override val network: Network) : CallbackEntry()
+        data class Resumed(override val network: Network) : CallbackEntry()
+        data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry()
+        data class Lost(override val network: Network) : CallbackEntry()
+        data class Unavailable private constructor(
+            override val network: Network
+        ) : CallbackEntry() {
+            constructor() : this(NULL_NETWORK)
+        }
+        data class BlockedStatus(
+            override val network: Network,
+            val blocked: Boolean
+        ) : CallbackEntry()
+        data class BlockedStatusInt(
+            override val network: Network,
+            val reason: Int
+        ) : CallbackEntry()
+        // Convenience constants for expecting a type
+        companion object {
+            @JvmField
+            val AVAILABLE = Available::class
+            @JvmField
+            val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class
+            @JvmField
+            val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class
+            @JvmField
+            val SUSPENDED = Suspended::class
+            @JvmField
+            val RESUMED = Resumed::class
+            @JvmField
+            val LOSING = Losing::class
+            @JvmField
+            val LOST = Lost::class
+            @JvmField
+            val UNAVAILABLE = Unavailable::class
+            @JvmField
+            val BLOCKED_STATUS = BlockedStatus::class
+            @JvmField
+            val BLOCKED_STATUS_INT = BlockedStatusInt::class
+        }
+    }
+
+    val history = backingRecord.newReadHead()
+    val mark get() = history.mark
+
+    override fun onAvailable(network: Network) {
+        Log.d(TAG, "onAvailable $network")
+        history.add(Available(network))
+    }
+
+    // PreCheck is not used in the tests today. For backward compatibility with existing tests that
+    // expect the callbacks not to record this, do not listen to PreCheck here.
+
+    override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
+        Log.d(TAG, "onCapabilitiesChanged $network $caps")
+        history.add(CapabilitiesChanged(network, caps))
+    }
+
+    override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
+        Log.d(TAG, "onLinkPropertiesChanged $network $lp")
+        history.add(LinkPropertiesChanged(network, lp))
+    }
+
+    override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
+        Log.d(TAG, "onBlockedStatusChanged $network $blocked")
+        history.add(BlockedStatus(network, blocked))
+    }
+
+    // Cannot do:
+    // fun onBlockedStatusChanged(network: Network, blocked: Int) {
+    // because on S, that needs to be "override fun", and on R, that cannot be "override fun".
+    override fun onNetworkSuspended(network: Network) {
+        Log.d(TAG, "onNetworkSuspended $network $network")
+        history.add(Suspended(network))
+    }
+
+    override fun onNetworkResumed(network: Network) {
+        Log.d(TAG, "$network onNetworkResumed $network")
+        history.add(Resumed(network))
+    }
+
+    override fun onLosing(network: Network, maxMsToLive: Int) {
+        Log.d(TAG, "onLosing $network $maxMsToLive")
+        history.add(Losing(network, maxMsToLive))
+    }
+
+    override fun onLost(network: Network) {
+        Log.d(TAG, "onLost $network")
+        history.add(Lost(network))
+    }
+
+    override fun onUnavailable() {
+        Log.d(TAG, "onUnavailable")
+        history.add(Unavailable())
+    }
+}
+
+private const val DEFAULT_TIMEOUT = 30_000L // ms
+private const val DEFAULT_NO_CALLBACK_TIMEOUT = 200L // ms
+private val NOOP = Runnable {}
+
+/**
+ * See comments on the public constructor below for a description of the arguments.
+ */
+open class TestableNetworkCallback private constructor(
+    src: TestableNetworkCallback?,
+    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT,
+    val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
+    val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java
+) : RecorderCallback(src) {
+    /**
+     * Construct a testable network callback.
+     * @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This
+     *                  should be long in most cases, because the success case doesn't incur
+     *                  the wait.
+     * @param noCallbackTimeoutMs the timeout for expecting that no callback is received. Default
+     *                            200ms. Because the success case does incur the timeout, this
+     *                            should be short in most cases, but not so short as to frequently
+     *                            time out before an incorrect callback is received.
+     * @param waiterFunc a function to use before asserting no callback. For some specific tests,
+     *                   it is useful to run test-specific code before asserting no callback to
+     *                   increase the likelihood that a spurious callback is correctly detected.
+     *                   As an example, a unit test using mock loopers may want to use this to
+     *                   make sure the loopers are drained before asserting no callback, since
+     *                   one of them may cause a callback to be called. @see ConnectivityServiceTest
+     *                   for such an example.
+     */
+    @JvmOverloads
+    constructor(
+        timeoutMs: Long = DEFAULT_TIMEOUT,
+        noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
+        waiterFunc: Runnable = NOOP
+    ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
+
+    fun createLinkedCopy() = TestableNetworkCallback(
+            this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc)
+
+    // The last available network, or null if any network was lost since the last call to
+    // onAvailable. TODO : fix this by fixing the tests that rely on this behavior
+    val lastAvailableNetwork: Network?
+        get() = when (val it = history.lastOrNull { it is Available || it is Lost }) {
+            is Available -> it.network
+            else -> null
+        }
+
+    /**
+     * Get the next callback or null if timeout.
+     *
+     * With no argument, this method waits out the default timeout. To wait forever, pass
+     * Long.MAX_VALUE.
+     */
+    @JvmOverloads
+    fun poll(timeoutMs: Long = defaultTimeoutMs, predicate: (CallbackEntry) -> Boolean = { true }) =
+            history.poll(timeoutMs, predicate)
+
+    /*****
+     * expect family of methods.
+     * These methods fetch the next callback and assert it matches the conditions : type,
+     * passed predicate. If no callback is received within the timeout, these methods fail.
+     */
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: Network = ANY_NETWORK,
+        timeoutMs: Long = defaultTimeoutMs,
+        errorMsg: String? = null,
+        test: (T) -> Boolean = { true }
+    ) = expect<CallbackEntry>(network, timeoutMs, errorMsg) {
+        if (type.isInstance(it)) {
+            test(it as T) // Cast can't fail since type.isInstance(it) and type: KClass<T>
+        } else {
+            fail("Expected callback ${type.simpleName}, got $it")
+        }
+    } as T
+
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: HasNetwork,
+        timeoutMs: Long = defaultTimeoutMs,
+        errorMsg: String? = null,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, network.network, timeoutMs, errorMsg, test)
+
+    // Java needs an explicit overload to let it omit arguments in the middle, so define these
+    // here. Note that @JvmOverloads give us the versions without the last arguments too, so
+    // there is no need to explicitly define versions without the test predicate.
+    // Without |network|
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        timeoutMs: Long,
+        errorMsg: String?,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, ANY_NETWORK, timeoutMs, errorMsg, test)
+
+    // Without |timeout|, in Network and HasNetwork versions
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: Network,
+        errorMsg: String?,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, network, defaultTimeoutMs, errorMsg, test)
+
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: HasNetwork,
+        errorMsg: String?,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, network.network, defaultTimeoutMs, errorMsg, test)
+
+    // Without |errorMsg|, in Network and HasNetwork versions
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: Network,
+        timeoutMs: Long,
+        test: (T) -> Boolean
+    ) = expect(type, network, timeoutMs, null, test)
+
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: HasNetwork,
+        timeoutMs: Long,
+        test: (T) -> Boolean
+    ) = expect(type, network.network, timeoutMs, null, test)
+
+    // Without |network| or |timeout|
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        errorMsg: String?,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, ANY_NETWORK, defaultTimeoutMs, errorMsg, test)
+
+    // Without |network| or |errorMsg|
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        timeoutMs: Long,
+        test: (T) -> Boolean = { true }
+    ) = expect(type, ANY_NETWORK, timeoutMs, null, test)
+
+    // Without |timeout| or |errorMsg|, in Network and HasNetwork versions
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: Network,
+        test: (T) -> Boolean
+    ) = expect(type, network, defaultTimeoutMs, null, test)
+
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        network: HasNetwork,
+        test: (T) -> Boolean
+    ) = expect(type, network.network, defaultTimeoutMs, null, test)
+
+    // Without |network| or |timeout| or |errorMsg|
+    @JvmOverloads
+    fun <T : CallbackEntry> expect(
+        type: KClass<T>,
+        test: (T) -> Boolean
+    ) = expect(type, ANY_NETWORK, defaultTimeoutMs, null, test)
+
+    // Kotlin reified versions. Don't call methods above, or the predicate would need to be noinline
+    inline fun <reified T : CallbackEntry> expect(
+        network: Network = ANY_NETWORK,
+        timeoutMs: Long = defaultTimeoutMs,
+        errorMsg: String? = null,
+        test: (T) -> Boolean = { true }
+    ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+            .also {
+                if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+                if (ANY_NETWORK !== network && it.network != network) {
+                    fail("Expected network $network for callback : $it")
+                }
+                if (!test(it)) {
+                    fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+                }
+            } as T
+
+    inline fun <reified T : CallbackEntry> expect(
+        network: HasNetwork,
+        timeoutMs: Long = defaultTimeoutMs,
+        errorMsg: String? = null,
+        test: (T) -> Boolean = { true }
+    ) = expect(network.network, timeoutMs, errorMsg, test)
+
+    /*****
+     * assertNoCallback family of methods.
+     * These methods make sure that no callback that matches the predicate was received.
+     * If no predicate is given, they make sure that no callback at all was received.
+     * These methods run the waiter func given in the constructor if any.
+     */
+    @JvmOverloads
+    fun assertNoCallback(
+        timeoutMs: Long = defaultNoCallbackTimeoutMs,
+        valid: (CallbackEntry) -> Boolean = { true }
+    ) {
+        waiterFunc.run()
+        history.poll(timeoutMs) { valid(it) }?.let { fail("Expected no callback but got $it") }
+    }
+
+    fun assertNoCallback(valid: (CallbackEntry) -> Boolean) =
+            assertNoCallback(defaultNoCallbackTimeoutMs, valid)
+
+    /*****
+     * eventuallyExpect family of methods.
+     * These methods make sure a callback that matches the type/predicate is received eventually.
+     * Any callback of the wrong type, or doesn't match the optional predicate, is ignored.
+     * They fail if no callback matching the predicate is received within the timeout.
+     */
+    inline fun <reified T : CallbackEntry> eventuallyExpect(
+        timeoutMs: Long = defaultTimeoutMs,
+        from: Int = mark,
+        crossinline predicate: (T) -> Boolean = { true }
+    ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
+        assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
+    } as T
+
+    @JvmOverloads
+    fun <T : CallbackEntry> eventuallyExpect(
+        type: KClass<T>,
+        timeoutMs: Long = defaultTimeoutMs,
+        predicate: (cb: T) -> Boolean = { true }
+    ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also {
+        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
+    } as T
+
+    fun <T : CallbackEntry> eventuallyExpect(
+        type: KClass<T>,
+        timeoutMs: Long = defaultTimeoutMs,
+        from: Int = mark,
+        predicate: (cb: T) -> Boolean = { true }
+    ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also {
+        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
+    } as T
+
+    // Expects onAvailable and the callbacks that follow it. These are:
+    // - onSuspended, iff the network was suspended when the callbacks fire.
+    // - onCapabilitiesChanged.
+    // - onLinkPropertiesChanged.
+    // - onBlockedStatusChanged.
+    //
+    // @param network the network to expect the callbacks on.
+    // @param suspended whether to expect a SUSPENDED callback.
+    // @param validated the expected value of the VALIDATED capability in the
+    //        onCapabilitiesChanged callback.
+    // @param tmt how long to wait for the callbacks.
+    @JvmOverloads
+    fun expectAvailableCallbacks(
+        net: Network,
+        suspended: Boolean = false,
+        validated: Boolean? = true,
+        blocked: Boolean = false,
+        tmt: Long = defaultTimeoutMs
+    ) {
+        expectAvailableCallbacksCommon(net, suspended, validated, tmt)
+        expect<BlockedStatus>(net, tmt) { it.blocked == blocked }
+    }
+
+    fun expectAvailableCallbacks(
+        net: Network,
+        suspended: Boolean,
+        validated: Boolean,
+        blockedReason: Int,
+        tmt: Long
+    ) {
+        expectAvailableCallbacksCommon(net, suspended, validated, tmt)
+        expect<BlockedStatusInt>(net) { it.reason == blockedReason }
+    }
+
+    private fun expectAvailableCallbacksCommon(
+        net: Network,
+        suspended: Boolean,
+        validated: Boolean?,
+        tmt: Long
+    ) {
+        expect<Available>(net, tmt)
+        if (suspended) {
+            expect<Suspended>(net, tmt)
+        }
+        expect<CapabilitiesChanged>(net, tmt) {
+            validated == null || validated == it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        expect<LinkPropertiesChanged>(net, tmt)
+    }
+
+    // Backward compatibility for existing Java code. Use named arguments instead and remove all
+    // these when there is no user left.
+    fun expectAvailableAndSuspendedCallbacks(
+        net: Network,
+        validated: Boolean,
+        tmt: Long = defaultTimeoutMs
+    ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt)
+
+    // Expects the available callbacks (where the onCapabilitiesChanged must contain the
+    // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
+    // one we just sent.
+    // TODO: this is likely a bug. Fix it and remove this method.
+    fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) {
+        val mark = history.mark
+        expectAvailableCallbacks(net, tmt = tmt)
+        val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged }
+        assertEquals(firstCaps, expect<CapabilitiesChanged>(net, tmt))
+    }
+
+    // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
+    // then expects another onCapabilitiesChanged that has the validated bit set. This is used
+    // when a network connects and satisfies a callback, and then immediately validates.
+    fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) {
+        expectAvailableCallbacks(net, validated = false, tmt = tmt)
+        expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+    }
+
+    fun expectAvailableThenValidatedCallbacks(
+        net: Network,
+        blockedReason: Int,
+        tmt: Long = defaultTimeoutMs
+    ) {
+        expectAvailableCallbacks(net, validated = false, suspended = false,
+                blockedReason = blockedReason, tmt = tmt)
+        expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+    }
+
+    // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing
+    // calls with networkAgent can be routed through here without moving MockNetworkAgent.
+    // TODO: clean this up, remove this method.
+    interface HasNetwork {
+        val network: Network
+    }
+
+    fun expectAvailableCallbacks(
+        n: HasNetwork,
+        suspended: Boolean,
+        validated: Boolean,
+        blocked: Boolean,
+        timeoutMs: Long
+    ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs)
+
+    fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) {
+        expectAvailableAndSuspendedCallbacks(n.network, expectValidated)
+    }
+
+    fun expectAvailableCallbacksValidated(n: HasNetwork) {
+        expectAvailableCallbacks(n.network)
+    }
+
+    fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) {
+        expectAvailableCallbacks(n.network, blocked = true)
+    }
+
+    fun expectAvailableCallbacksUnvalidated(n: HasNetwork) {
+        expectAvailableCallbacks(n.network, validated = false)
+    }
+
+    fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) {
+        expectAvailableCallbacks(n.network, validated = false, blocked = true)
+    }
+
+    fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) {
+        expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs)
+    }
+
+    fun expectAvailableThenValidatedCallbacks(n: HasNetwork) {
+        expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs)
+    }
+
+    @JvmOverloads
+    fun expectCaps(
+        n: HasNetwork,
+        tmt: Long = defaultTimeoutMs,
+        valid: (NetworkCapabilities) -> Boolean = { true }
+    ) = expect<CapabilitiesChanged>(n.network, tmt) { valid(it.caps) }.caps
+
+    @JvmOverloads
+    fun expectCaps(
+        n: Network,
+        tmt: Long = defaultTimeoutMs,
+        valid: (NetworkCapabilities) -> Boolean
+    ) = expect<CapabilitiesChanged>(n, tmt) { valid(it.caps) }.caps
+
+    fun expectCaps(
+        n: HasNetwork,
+        valid: (NetworkCapabilities) -> Boolean
+    ) = expect<CapabilitiesChanged>(n.network) { valid(it.caps) }.caps
+
+    fun expectCaps(
+        tmt: Long,
+        valid: (NetworkCapabilities) -> Boolean
+    ) = expect<CapabilitiesChanged>(ANY_NETWORK, tmt) { valid(it.caps) }.caps
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt
new file mode 100644
index 0000000..21bd60c
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkOfferCallback.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkRequest
+import android.util.Log
+import com.android.net.module.util.ArrayTrackRecord
+import kotlin.test.fail
+
+class TestableNetworkOfferCallback(val timeoutMs: Long, private val noCallbackTimeoutMs: Long)
+            : NetworkProvider.NetworkOfferCallback {
+    private val TAG = this::class.simpleName
+    val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+    sealed class CallbackEntry {
+        data class OnNetworkNeeded(val request: NetworkRequest) : CallbackEntry()
+        data class OnNetworkUnneeded(val request: NetworkRequest) : CallbackEntry()
+    }
+
+    /**
+     * Called by the system when a network for this offer is needed to satisfy some
+     * networking request.
+     */
+    override fun onNetworkNeeded(request: NetworkRequest) {
+        Log.d(TAG, "onNetworkNeeded $request")
+        history.add(CallbackEntry.OnNetworkNeeded(request))
+    }
+
+    /**
+     * Called by the system when this offer is no longer valuable for this request.
+     */
+    override fun onNetworkUnneeded(request: NetworkRequest) {
+        Log.d(TAG, "onNetworkUnneeded $request")
+        history.add(CallbackEntry.OnNetworkUnneeded(request))
+    }
+
+    inline fun <reified T : CallbackEntry> expectCallbackThat(
+        crossinline predicate: (T) -> Boolean
+    ) {
+        val event = history.poll(timeoutMs)
+                ?: fail("Did not receive callback after ${timeoutMs}ms")
+        if (event !is T || !predicate(event)) fail("Received unexpected callback $event")
+    }
+
+    fun expectOnNetworkNeeded(capabilities: NetworkCapabilities) =
+            expectCallbackThat<CallbackEntry.OnNetworkNeeded> {
+                it.request.canBeSatisfiedBy(capabilities)
+            }
+
+    fun expectOnNetworkUnneeded(capabilities: NetworkCapabilities) =
+            expectCallbackThat<CallbackEntry.OnNetworkUnneeded> {
+                it.request.canBeSatisfiedBy(capabilities)
+            }
+
+    fun assertNoCallback() {
+        val cb = history.poll(noCallbackTimeoutMs)
+        if (null != cb) fail("Expected no callback but got $cb")
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
new file mode 100644
index 0000000..4a7b351
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.net.netstats.provider.NetworkStatsProvider
+import android.util.Log
+import com.android.net.module.util.ArrayTrackRecord
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 200L
+const val TOKEN_ANY = -1
+
+open class TestableNetworkStatsProvider(
+    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT_MS
+) : NetworkStatsProvider() {
+    sealed class CallbackType {
+        data class OnRequestStatsUpdate(val token: Int) : CallbackType()
+        data class OnSetWarningAndLimit(
+            val iface: String,
+            val warningBytes: Long,
+            val limitBytes: Long
+        ) : CallbackType()
+        data class OnSetLimit(val iface: String, val limitBytes: Long) : CallbackType() {
+            // Add getter for backward compatibility since old tests do not recognize limitBytes.
+            val quotaBytes: Long
+                get() = limitBytes
+        }
+        data class OnSetAlert(val quotaBytes: Long) : CallbackType()
+    }
+
+    private val TAG = this::class.simpleName
+    val history = ArrayTrackRecord<CallbackType>().newReadHead()
+    // See ReadHead#mark
+    val mark get() = history.mark
+
+    override fun onRequestStatsUpdate(token: Int) {
+        Log.d(TAG, "onRequestStatsUpdate $token")
+        history.add(CallbackType.OnRequestStatsUpdate(token))
+    }
+
+    override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+        Log.d(TAG, "onSetWarningAndLimit $iface $warningBytes $limitBytes")
+        history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes))
+    }
+
+    override fun onSetLimit(iface: String, quotaBytes: Long) {
+        Log.d(TAG, "onSetLimit $iface $quotaBytes")
+        history.add(CallbackType.OnSetLimit(iface, quotaBytes))
+    }
+
+    override fun onSetAlert(quotaBytes: Long) {
+        Log.d(TAG, "onSetAlert $quotaBytes")
+        history.add(CallbackType.OnSetAlert(quotaBytes))
+    }
+
+    fun expectOnRequestStatsUpdate(token: Int, timeout: Long = defaultTimeoutMs): Int {
+        val event = history.poll(timeout)
+        assertTrue(event is CallbackType.OnRequestStatsUpdate)
+        if (token != TOKEN_ANY) {
+            assertEquals(token, event.token)
+        }
+        return event.token
+    }
+
+    fun expectOnSetLimit(iface: String, quotaBytes: Long, timeout: Long = defaultTimeoutMs) {
+        assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(timeout))
+    }
+
+    fun expectOnSetAlert(quotaBytes: Long, timeout: Long = defaultTimeoutMs) {
+        assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(timeout))
+    }
+
+    fun pollForNextCallback(timeout: Long = defaultTimeoutMs) =
+        history.poll(timeout) ?: fail("Did not receive callback after ${timeout}ms")
+
+    inline fun <reified T : CallbackType> expectCallback(
+        timeout: Long = defaultTimeoutMs,
+        predicate: (T) -> Boolean = { true }
+    ): T {
+        return pollForNextCallback(timeout).also { assertTrue(it is T && predicate(it)) } as T
+    }
+
+    // Expects a callback of the specified type matching the predicate within the timeout.
+    // Any callback that doesn't match the predicate will be skipped. Fails only if
+    // no matching callback is received within the timeout.
+    // TODO : factorize the code for this with the identical call in TestableNetworkCallback.
+    // There should be a common superclass doing this generically.
+    // TODO : have a better error message to have this fail. Right now the failure when no
+    // matching callback arrives comes from the casting to a non-nullable T.
+    // TODO : in fact, completely removing this method and have clients use
+    // history.poll(timeout, index, predicate) directly might be simpler.
+    inline fun <reified T : CallbackType> eventuallyExpect(
+        timeoutMs: Long = defaultTimeoutMs,
+        from: Int = mark,
+        crossinline predicate: (T) -> Boolean = { true }
+    ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T
+
+    fun drainCallbacks() {
+        history.mark = history.size
+    }
+
+    @JvmOverloads
+    fun assertNoCallback(timeout: Long = defaultTimeoutMs) {
+        val cb = history.poll(timeout)
+        cb?.let { fail("Expected no callback but got $cb") }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
new file mode 100644
index 0000000..643346b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import com.android.net.module.util.ArrayTrackRecord
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 200L
+
+open class TestableNetworkStatsProviderBinder : NetworkStatsProviderStubCompat() {
+    sealed class CallbackType {
+        data class OnRequestStatsUpdate(val token: Int) : CallbackType()
+        data class OnSetAlert(val quotaBytes: Long) : CallbackType()
+        data class OnSetWarningAndLimit(
+            val iface: String,
+            val warningBytes: Long,
+            val limitBytes: Long
+        ) : CallbackType()
+    }
+
+    private val history = ArrayTrackRecord<CallbackType>().ReadHead()
+
+    override fun onRequestStatsUpdate(token: Int) {
+        history.add(CallbackType.OnRequestStatsUpdate(token))
+    }
+
+    override fun onSetAlert(quotaBytes: Long) {
+        history.add(CallbackType.OnSetAlert(quotaBytes))
+    }
+
+    override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+        history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes))
+    }
+
+    fun expectOnRequestStatsUpdate(token: Int) {
+        assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(DEFAULT_TIMEOUT_MS))
+    }
+
+    fun expectOnSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+        assertEquals(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes),
+                history.poll(DEFAULT_TIMEOUT_MS))
+    }
+
+    fun expectOnSetAlert(quotaBytes: Long) {
+        assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(DEFAULT_TIMEOUT_MS))
+    }
+
+    @JvmOverloads
+    fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) {
+        val cb = history.poll(timeout)
+        cb?.let { fail("Expected no callback but got $cb") }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
new file mode 100644
index 0000000..5547c90
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import android.net.NetworkStats
+import com.android.net.module.util.ArrayTrackRecord
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 3000L
+
+open class TestableNetworkStatsProviderCbBinder : NetworkStatsProviderCbStubCompat() {
+    sealed class CallbackType {
+        data class NotifyStatsUpdated(
+            val token: Int,
+            val ifaceStats: NetworkStats,
+            val uidStats: NetworkStats
+        ) : CallbackType()
+        object NotifyWarningReached : CallbackType()
+        object NotifyLimitReached : CallbackType()
+        object NotifyWarningOrLimitReached : CallbackType()
+        object NotifyAlertReached : CallbackType()
+        object Unregister : CallbackType()
+    }
+
+    private val history = ArrayTrackRecord<CallbackType>().ReadHead()
+
+    override fun notifyStatsUpdated(token: Int, ifaceStats: NetworkStats, uidStats: NetworkStats) {
+        history.add(CallbackType.NotifyStatsUpdated(token, ifaceStats, uidStats))
+    }
+
+    override fun notifyWarningReached() {
+        history.add(CallbackType.NotifyWarningReached)
+    }
+
+    override fun notifyLimitReached() {
+        history.add(CallbackType.NotifyLimitReached)
+    }
+
+    override fun notifyWarningOrLimitReached() {
+        // Older callback is split into notifyLimitReached and notifyWarningReached in T.
+        history.add(CallbackType.NotifyWarningOrLimitReached)
+    }
+
+    override fun notifyAlertReached() {
+        history.add(CallbackType.NotifyAlertReached)
+    }
+
+    override fun unregister() {
+        history.add(CallbackType.Unregister)
+    }
+
+    fun expectNotifyStatsUpdated() {
+        val event = history.poll(DEFAULT_TIMEOUT_MS)
+        assertTrue(event is CallbackType.NotifyStatsUpdated)
+    }
+
+    fun expectNotifyStatsUpdated(ifaceStats: NetworkStats, uidStats: NetworkStats) {
+        val event = history.poll(DEFAULT_TIMEOUT_MS)!!
+        if (event !is CallbackType.NotifyStatsUpdated) {
+            throw Exception("Expected NotifyStatsUpdated callback, but got ${event::class}")
+        }
+        // TODO: verify token.
+        assertNetworkStatsEquals(ifaceStats, event.ifaceStats)
+        assertNetworkStatsEquals(uidStats, event.uidStats)
+    }
+
+    fun expectNotifyWarningReached() =
+            assertEquals(CallbackType.NotifyWarningReached, history.poll(DEFAULT_TIMEOUT_MS))
+
+    fun expectNotifyLimitReached() =
+            assertEquals(CallbackType.NotifyLimitReached, history.poll(DEFAULT_TIMEOUT_MS))
+
+    fun expectNotifyWarningOrLimitReached() =
+            assertEquals(CallbackType.NotifyWarningOrLimitReached, history.poll(DEFAULT_TIMEOUT_MS))
+
+    fun expectNotifyAlertReached() =
+            assertEquals(CallbackType.NotifyAlertReached, history.poll(DEFAULT_TIMEOUT_MS))
+
+    // Assert there is no callback in current queue.
+    fun assertNoCallback() {
+        val cb = history.poll(0)
+        cb?.let { fail("Expected no callback but got $cb") }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
new file mode 100644
index 0000000..48b57d7
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils.async;
+
+import android.os.ParcelFileDescriptor;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import com.android.net.module.util.async.CircularByteBuffer;
+import com.android.net.module.util.async.OsAccess;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+public class FakeOsAccess extends OsAccess {
+    public static final boolean ENABLE_FINE_DEBUG = true;
+
+    public static final int DEFAULT_FILE_DATA_QUEUE_SIZE = 8 * 1024;
+
+    private enum FileType { PAIR, PIPE }
+
+    // Common poll() constants:
+    private static final short POLLIN  = 0x0001;
+    private static final short POLLOUT = 0x0004;
+    private static final short POLLERR = 0x0008;
+    private static final short POLLHUP = 0x0010;
+
+    private static final Constructor<FileDescriptor> FD_CONSTRUCTOR;
+    private static final Field FD_FIELD_DESCRIPTOR;
+    private static final Field PFD_FIELD_DESCRIPTOR;
+    private static final Field PFD_FIELD_GUARD;
+    private static final Method CLOSE_GUARD_METHOD_CLOSE;
+
+    private final int mReadQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE;
+    private final int mWriteQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE;
+    private final HashMap<Integer, File> mFiles = new HashMap<>();
+    private final byte[] mTmpBuffer = new byte[1024];
+    private final long mStartTime;
+    private final String mLogTag;
+    private int mFileNumberGen = 3;
+    private boolean mHasRateLimitedData;
+
+    public FakeOsAccess(String logTag) {
+        mLogTag = logTag;
+        mStartTime = monotonicTimeMillis();
+    }
+
+    @Override
+    public long monotonicTimeMillis() {
+        return System.nanoTime() / 1000000;
+    }
+
+    @Override
+    public FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd) {
+        try {
+            return (FileDescriptor) PFD_FIELD_DESCRIPTOR.get(fd);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void close(ParcelFileDescriptor fd) {
+        if (fd != null) {
+            close(getInnerFileDescriptor(fd));
+
+            try {
+                // Reduce CloseGuard warnings.
+                Object guard = PFD_FIELD_GUARD.get(fd);
+                CLOSE_GUARD_METHOD_CLOSE.invoke(guard);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public synchronized void close(FileDescriptor fd) {
+        if (fd != null) {
+            File file = getFileOrNull(fd);
+            if (file != null) {
+                file.decreaseRefCount();
+                mFiles.remove(getFileDescriptorNumber(fd));
+                setFileDescriptorNumber(fd, -1);
+                notifyAll();
+            }
+        }
+    }
+
+    private File getFile(String func, FileDescriptor fd) throws IOException {
+        File file = getFileOrNull(fd);
+        if (file == null) {
+            throw newIOException(func, "Unknown file descriptor: " + getFileDebugName(fd));
+        }
+        return file;
+    }
+
+    private File getFileOrNull(FileDescriptor fd) {
+        return mFiles.get(getFileDescriptorNumber(fd));
+    }
+
+    @Override
+    public String getFileDebugName(ParcelFileDescriptor fd) {
+        return (fd != null ? getFileDebugName(getInnerFileDescriptor(fd)) : "null");
+    }
+
+    public String getFileDebugName(FileDescriptor fd) {
+        if (fd == null) {
+            return "null";
+        }
+
+        final int fdNumber = getFileDescriptorNumber(fd);
+        File file = mFiles.get(fdNumber);
+
+        StringBuilder sb = new StringBuilder();
+        if (file != null) {
+            if (file.name != null) {
+                sb.append(file.name);
+                sb.append("/");
+            }
+            sb.append(file.type);
+            sb.append("/");
+        } else {
+            sb.append("BADFD/");
+        }
+        sb.append(fdNumber);
+        return sb.toString();
+    }
+
+    public synchronized void setFileName(FileDescriptor fd, String name) {
+        File file = getFileOrNull(fd);
+        if (file != null) {
+            file.name = name;
+        }
+    }
+
+    @Override
+    public synchronized void setNonBlocking(FileDescriptor fd) throws IOException {
+        File file = getFile("fcntl", fd);
+        file.isBlocking = false;
+    }
+
+    @Override
+    public synchronized int read(FileDescriptor fd, byte[] buffer, int pos, int len)
+            throws IOException {
+        checkBoundaries("read", buffer, pos, len);
+
+        File file = getFile("read", fd);
+        if (file.readQueue == null) {
+            throw newIOException("read", "File not readable");
+        }
+        file.checkNonBlocking("read");
+
+        if (len == 0) {
+            return 0;
+        }
+
+        final int availSize = file.readQueue.size();
+        if (availSize == 0) {
+            if (file.isEndOfStream) {
+                // Java convention uses -1 to indicate end of stream.
+                return -1;
+            }
+            return 0;  // EAGAIN
+        }
+
+        final int readCount = Math.min(len, availSize);
+        file.readQueue.readBytes(buffer, pos, readCount);
+        maybeTransferData(file);
+        return readCount;
+    }
+
+    @Override
+    public synchronized int write(FileDescriptor fd, byte[] buffer, int pos, int len)
+            throws IOException {
+        checkBoundaries("write", buffer, pos, len);
+
+        File file = getFile("write", fd);
+        if (file.writeQueue == null) {
+            throw newIOException("read", "File not writable");
+        }
+        if (file.type == FileType.PIPE && file.sink.openCount == 0) {
+            throw newIOException("write", "The other end of pipe is closed");
+        }
+        file.checkNonBlocking("write");
+
+        if (len == 0) {
+            return 0;
+        }
+
+        final int originalFreeSize = file.writeQueue.freeSize();
+        if (originalFreeSize == 0) {
+            return 0;  // EAGAIN
+        }
+
+        final int writeCount = Math.min(len, originalFreeSize);
+        file.writeQueue.writeBytes(buffer, pos, writeCount);
+        maybeTransferData(file);
+
+        if (file.writeQueue.freeSize() < originalFreeSize) {
+            final int additionalQueuedCount = originalFreeSize - file.writeQueue.freeSize();
+            Log.i(mLogTag, logStr("Delaying transfer of " + additionalQueuedCount
+                + " bytes, queued=" + file.writeQueue.size() + ", type=" + file.type
+                + ", src_red=" + file.outboundLimiter + ", dst_red=" + file.sink.inboundLimiter));
+        }
+
+        return writeCount;
+    }
+
+    private void maybeTransferData(File file) {
+        boolean hasChanges = copyFileBuffers(file, file.sink);
+        hasChanges = copyFileBuffers(file.source, file) || hasChanges;
+
+        if (hasChanges) {
+            // TODO(b/245971639): Avoid notifying if no-one is polling.
+            notifyAll();
+        }
+    }
+
+    private boolean copyFileBuffers(File src, File dst) {
+        if (src.writeQueue == null || dst.readQueue == null) {
+            return false;
+        }
+
+        final int originalCopyCount = Math.min(mTmpBuffer.length,
+            Math.min(src.writeQueue.size(), dst.readQueue.freeSize()));
+
+        final int allowedCopyCount = RateLimiter.limit(
+            src.outboundLimiter, dst.inboundLimiter, originalCopyCount);
+
+        if (allowedCopyCount < originalCopyCount) {
+            if (ENABLE_FINE_DEBUG) {
+                Log.i(mLogTag, logStr("Delaying transfer of "
+                    + (originalCopyCount - allowedCopyCount) + " bytes, original="
+                    + originalCopyCount + ", allowed=" + allowedCopyCount
+                    + ", type=" + src.type));
+            }
+            if (originalCopyCount > 0) {
+                mHasRateLimitedData = true;
+            }
+            if (allowedCopyCount == 0) {
+                return false;
+            }
+        }
+
+        boolean hasChanges = false;
+        if (allowedCopyCount > 0) {
+            if (dst.readQueue.size() == 0 || src.writeQueue.freeSize() == 0) {
+                hasChanges = true;  // Read queue had no data, or write queue was full.
+            }
+            src.writeQueue.readBytes(mTmpBuffer, 0, allowedCopyCount);
+            dst.readQueue.writeBytes(mTmpBuffer, 0, allowedCopyCount);
+        }
+
+        if (!dst.isEndOfStream && src.openCount == 0
+                && src.writeQueue.size() == 0 && dst.readQueue.size() == 0) {
+            dst.isEndOfStream = true;
+            hasChanges = true;
+        }
+
+        return hasChanges;
+    }
+
+    public void clearInboundRateLimit(FileDescriptor fd) {
+        setInboundRateLimit(fd, Integer.MAX_VALUE);
+    }
+
+    public void clearOutboundRateLimit(FileDescriptor fd) {
+        setOutboundRateLimit(fd, Integer.MAX_VALUE);
+    }
+
+    public synchronized void setInboundRateLimit(FileDescriptor fd, int bytesPerSecond) {
+        File file = getFileOrNull(fd);
+        if (file != null) {
+            file.inboundLimiter.setBytesPerSecond(bytesPerSecond);
+            maybeTransferData(file);
+        }
+    }
+
+    public synchronized void setOutboundRateLimit(FileDescriptor fd, int bytesPerSecond) {
+        File file = getFileOrNull(fd);
+        if (file != null) {
+            file.outboundLimiter.setBytesPerSecond(bytesPerSecond);
+            maybeTransferData(file);
+        }
+    }
+
+    public synchronized ParcelFileDescriptor[] socketpair() throws IOException {
+        int fdNumber1 = getNextFd("socketpair");
+        int fdNumber2 = getNextFd("socketpair");
+
+        File file1 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize);
+        File file2 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize);
+
+        return registerFilePair(fdNumber1, file1, fdNumber2, file2);
+    }
+
+    @Override
+    public synchronized ParcelFileDescriptor[] pipe() throws IOException {
+        int fdNumber1 = getNextFd("pipe");
+        int fdNumber2 = getNextFd("pipe");
+
+        File file1 = new File(FileType.PIPE, mReadQueueSize, 0);
+        File file2 = new File(FileType.PIPE, 0, mWriteQueueSize);
+
+        return registerFilePair(fdNumber1, file1, fdNumber2, file2);
+    }
+
+    private ParcelFileDescriptor[] registerFilePair(
+            int fdNumber1, File file1, int fdNumber2, File file2) {
+        file1.sink = file2;
+        file1.source = file2;
+        file2.sink = file1;
+        file2.source = file1;
+
+        mFiles.put(fdNumber1, file1);
+        mFiles.put(fdNumber2, file2);
+        return new ParcelFileDescriptor[] {
+            newParcelFileDescriptor(fdNumber1), newParcelFileDescriptor(fdNumber2)};
+    }
+
+    @Override
+    public short getPollInMask() {
+        return POLLIN;
+    }
+
+    @Override
+    public short getPollOutMask() {
+        return POLLOUT;
+    }
+
+    @Override
+    public synchronized int poll(StructPollfd[] fds, int timeoutMs) throws IOException {
+        if (timeoutMs < 0) {
+            timeoutMs = (int) TimeUnit.HOURS.toMillis(1);  // Make "infinite" equal to 1 hour.
+        }
+
+        if (fds == null || fds.length > 1000) {
+            throw newIOException("poll", "Invalid fds param");
+        }
+        for (StructPollfd pollFd : fds) {
+            getFile("poll", pollFd.fd);
+        }
+
+        int waitCallCount = 0;
+        final long deadline = monotonicTimeMillis() + timeoutMs;
+        while (true) {
+            if (mHasRateLimitedData) {
+                mHasRateLimitedData = false;
+                for (File file : mFiles.values()) {
+                    if (file.inboundLimiter.getLastRequestReduction() != 0) {
+                        copyFileBuffers(file.source, file);
+                    }
+                    if (file.outboundLimiter.getLastRequestReduction() != 0) {
+                        copyFileBuffers(file, file.sink);
+                    }
+                }
+            }
+
+            final int readyCount = calculateReadyCount(fds);
+            if (readyCount > 0) {
+                if (ENABLE_FINE_DEBUG) {
+                    Log.v(mLogTag, logStr("Poll returns " + readyCount
+                            + " after " + waitCallCount + " wait calls"));
+                }
+                return readyCount;
+            }
+
+            long remainingTimeoutMs = deadline - monotonicTimeMillis();
+            if (remainingTimeoutMs <= 0) {
+                if (ENABLE_FINE_DEBUG) {
+                    Log.v(mLogTag, logStr("Poll timeout " + timeoutMs
+                            + "ms after " + waitCallCount + " wait calls"));
+                }
+                return 0;
+            }
+
+            if (mHasRateLimitedData) {
+                remainingTimeoutMs = Math.min(RateLimiter.BUCKET_DURATION_MS, remainingTimeoutMs);
+            }
+
+            try {
+                wait(remainingTimeoutMs);
+            } catch (InterruptedException e) {
+                // Ignore and retry
+            }
+            waitCallCount++;
+        }
+    }
+
+    private int calculateReadyCount(StructPollfd[] fds) {
+        int fdCount = 0;
+        for (StructPollfd pollFd : fds) {
+            pollFd.revents = 0;
+
+            File file = getFileOrNull(pollFd.fd);
+            if (file == null) {
+                Log.w(mLogTag, logStr("Ignoring FD concurrently closed by a buggy app: "
+                        + getFileDebugName(pollFd.fd)));
+                continue;
+            }
+
+            if (ENABLE_FINE_DEBUG) {
+                Log.v(mLogTag, logStr("calculateReadyCount fd=" + getFileDebugName(pollFd.fd)
+                        + ", events=" + pollFd.events + ", eof=" + file.isEndOfStream
+                        + ", r=" + (file.readQueue != null ? file.readQueue.size() : -1)
+                        + ", w=" + (file.writeQueue != null ? file.writeQueue.freeSize() : -1)));
+            }
+
+            if ((pollFd.events & POLLIN) != 0) {
+                if (file.readQueue != null && file.readQueue.size() != 0) {
+                    pollFd.revents |= POLLIN;
+                }
+                if (file.isEndOfStream) {
+                    pollFd.revents |= POLLHUP;
+                }
+            }
+
+            if ((pollFd.events & POLLOUT) != 0) {
+                if (file.type == FileType.PIPE && file.sink.openCount == 0) {
+                    pollFd.revents |= POLLERR;
+                }
+                if (file.writeQueue != null && file.writeQueue.freeSize() != 0) {
+                    pollFd.revents |= POLLOUT;
+                }
+            }
+
+            if (pollFd.revents != 0) {
+                fdCount++;
+            }
+        }
+        return fdCount;
+    }
+
+    private int getNextFd(String func) throws IOException {
+        if (mFileNumberGen > 100000) {
+            throw newIOException(func, "Too many files open");
+        }
+
+        return mFileNumberGen++;
+    }
+
+    private static IOException newIOException(String func, String message) {
+        return new IOException(message + ", func=" + func);
+    }
+
+    public static void checkBoundaries(String func, byte[] buffer, int pos, int len)
+            throws IOException {
+        if (((buffer.length | pos | len) < 0 || pos > buffer.length - len)) {
+            throw newIOException(func, "Invalid array bounds");
+        }
+    }
+
+    private ParcelFileDescriptor newParcelFileDescriptor(int fdNumber) {
+        try {
+            return new ParcelFileDescriptor(newFileDescriptor(fdNumber));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private FileDescriptor newFileDescriptor(int fdNumber) {
+        try {
+            return FD_CONSTRUCTOR.newInstance(Integer.valueOf(fdNumber));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public int getFileDescriptorNumber(FileDescriptor fd) {
+        try {
+            return (Integer) FD_FIELD_DESCRIPTOR.get(fd);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void setFileDescriptorNumber(FileDescriptor fd, int fdNumber) {
+        try {
+            FD_FIELD_DESCRIPTOR.set(fd, Integer.valueOf(fdNumber));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String logStr(String message) {
+        return "[FakeOs " + (monotonicTimeMillis() - mStartTime) + "] " + message;
+    }
+
+    private class File {
+        final FileType type;
+        final CircularByteBuffer readQueue;
+        final CircularByteBuffer writeQueue;
+        final RateLimiter inboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE);
+        final RateLimiter outboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE);
+        String name;
+        int openCount = 1;
+        boolean isBlocking = true;
+        File sink;
+        File source;
+        boolean isEndOfStream;
+
+        File(FileType type, int readQueueSize, int writeQueueSize) {
+            this.type = type;
+            readQueue = (readQueueSize > 0 ? new CircularByteBuffer(readQueueSize) : null);
+            writeQueue = (writeQueueSize > 0 ? new CircularByteBuffer(writeQueueSize) : null);
+        }
+
+        void decreaseRefCount() {
+            if (openCount <= 0) {
+                throw new IllegalStateException();
+            }
+            openCount--;
+        }
+
+        void checkNonBlocking(String func) throws IOException {
+            if (isBlocking) {
+                throw newIOException(func, "File in blocking mode");
+            }
+        }
+    }
+
+    static {
+        try {
+            FD_CONSTRUCTOR = FileDescriptor.class.getDeclaredConstructor(int.class);
+            FD_CONSTRUCTOR.setAccessible(true);
+
+            Field descriptorIntField;
+            try {
+                descriptorIntField = FileDescriptor.class.getDeclaredField("descriptor");
+            } catch (NoSuchFieldException e) {
+                descriptorIntField = FileDescriptor.class.getDeclaredField("fd");
+            }
+            FD_FIELD_DESCRIPTOR = descriptorIntField;
+            FD_FIELD_DESCRIPTOR.setAccessible(true);
+
+            PFD_FIELD_DESCRIPTOR = ParcelFileDescriptor.class.getDeclaredField("mFd");
+            PFD_FIELD_DESCRIPTOR.setAccessible(true);
+
+            PFD_FIELD_GUARD = ParcelFileDescriptor.class.getDeclaredField("mGuard");
+            PFD_FIELD_GUARD.setAccessible(true);
+
+            CLOSE_GUARD_METHOD_CLOSE = Class.forName("dalvik.system.CloseGuard")
+                .getDeclaredMethod("close");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java
new file mode 100644
index 0000000..d5cca0a
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils.async;
+
+import com.android.net.module.util.async.OsAccess;
+
+import java.util.Arrays;
+
+/**
+ * Limits the number of bytes processed to the given maximum of bytes per second.
+ *
+ * The limiter tracks the total for the past second, along with sums for each 10ms
+ * in the past second, allowing the total to be adjusted as the time passes.
+ */
+public final class RateLimiter {
+    private static final int PERIOD_DURATION_MS = 1000;
+    private static final int BUCKET_COUNT = 100;
+
+    public static final int BUCKET_DURATION_MS = PERIOD_DURATION_MS / BUCKET_COUNT;
+
+    private final OsAccess mOsAccess;
+    private final int[] mStatBuckets = new int[BUCKET_COUNT];
+    private int mMaxPerPeriodBytes;
+    private int mMaxPerBucketBytes;
+    private int mRecordedPeriodBytes;
+    private long mLastLimitTimestamp;
+    private int mLastRequestReduction;
+
+    public RateLimiter(OsAccess osAccess, int bytesPerSecond) {
+        mOsAccess = osAccess;
+        setBytesPerSecond(bytesPerSecond);
+        clear();
+    }
+
+    public int getBytesPerSecond() {
+        return mMaxPerPeriodBytes;
+    }
+
+    public void setBytesPerSecond(int bytesPerSecond) {
+        mMaxPerPeriodBytes = bytesPerSecond;
+        mMaxPerBucketBytes = Math.max(1, (mMaxPerPeriodBytes / BUCKET_COUNT) * 2);
+    }
+
+    public void clear() {
+        mLastLimitTimestamp = mOsAccess.monotonicTimeMillis();
+        mRecordedPeriodBytes = 0;
+        Arrays.fill(mStatBuckets, 0);
+    }
+
+    public static int limit(RateLimiter limiter1, RateLimiter limiter2, int requestedBytes) {
+        final long now = limiter1.mOsAccess.monotonicTimeMillis();
+        final int allowedCount = Math.min(limiter1.calculateLimit(now, requestedBytes),
+            limiter2.calculateLimit(now, requestedBytes));
+        limiter1.recordBytes(now, requestedBytes, allowedCount);
+        limiter2.recordBytes(now, requestedBytes, allowedCount);
+        return allowedCount;
+    }
+
+    public int limit(int requestedBytes) {
+        final long now = mOsAccess.monotonicTimeMillis();
+        final int allowedCount = calculateLimit(now, requestedBytes);
+        recordBytes(now, requestedBytes, allowedCount);
+        return allowedCount;
+    }
+
+    public int getLastRequestReduction() {
+        return mLastRequestReduction;
+    }
+
+    public boolean acceptAllOrNone(int requestedBytes) {
+        final long now = mOsAccess.monotonicTimeMillis();
+        final int allowedCount = calculateLimit(now, requestedBytes);
+        if (allowedCount < requestedBytes) {
+            return false;
+        }
+        recordBytes(now, requestedBytes, allowedCount);
+        return true;
+    }
+
+    private int calculateLimit(long now, int requestedBytes) {
+        // First remove all stale bucket data and adjust the total.
+        final long currentBucketAbsIdx = now / BUCKET_DURATION_MS;
+        final long staleCutoffIdx = currentBucketAbsIdx - BUCKET_COUNT;
+        for (long i = mLastLimitTimestamp / BUCKET_DURATION_MS; i < staleCutoffIdx; i++) {
+            final int idx = (int) (i % BUCKET_COUNT);
+            mRecordedPeriodBytes -= mStatBuckets[idx];
+            mStatBuckets[idx] = 0;
+        }
+
+        final int bucketIdx = (int) (currentBucketAbsIdx % BUCKET_COUNT);
+        final int maxAllowed = Math.min(mMaxPerPeriodBytes - mRecordedPeriodBytes,
+            Math.min(mMaxPerBucketBytes - mStatBuckets[bucketIdx], requestedBytes));
+        return Math.max(0, maxAllowed);
+    }
+
+    private void recordBytes(long now, int requestedBytes, int actualBytes) {
+        mStatBuckets[(int) ((now / BUCKET_DURATION_MS) % BUCKET_COUNT)] += actualBytes;
+        mRecordedPeriodBytes += actualBytes;
+        mLastRequestReduction = requestedBytes - actualBytes;
+        mLastLimitTimestamp = now;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{max=");
+        sb.append(mMaxPerPeriodBytes);
+        sb.append(",max_bucket=");
+        sb.append(mMaxPerBucketBytes);
+        sb.append(",total=");
+        sb.append(mRecordedPeriodBytes);
+        sb.append(",last_red=");
+        sb.append(mLastRequestReduction);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java b/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java
new file mode 100644
index 0000000..4bf5527
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils.async;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+
+public class ReadableDataAnswer implements Answer {
+    private final ArrayList<byte[]> mBuffers = new ArrayList<>();
+    private int mBufferPos;
+
+    public ReadableDataAnswer(byte[] ... buffers) {
+        for (byte[] buffer : buffers) {
+            addBuffer(buffer);
+        }
+    }
+
+    public void addBuffer(byte[] buffer) {
+        if (buffer.length != 0) {
+            mBuffers.add(buffer);
+        }
+    }
+
+    public int getRemainingSize() {
+        int totalSize = 0;
+        for (byte[] buffer : mBuffers) {
+            totalSize += buffer.length;
+        }
+        return totalSize - mBufferPos;
+    }
+
+    private void cleanupBuffers() {
+        if (!mBuffers.isEmpty() && mBufferPos == mBuffers.get(0).length) {
+            mBuffers.remove(0);
+            mBufferPos = 0;
+        }
+    }
+
+    @Override
+    public Object answer(InvocationOnMock invocation) throws Throwable {
+        cleanupBuffers();
+
+        if (mBuffers.isEmpty()) {
+            return Integer.valueOf(0);
+        }
+
+        byte[] src = mBuffers.get(0);
+
+        byte[] dst = invocation.<byte[]>getArgument(0);
+        int dstPos = invocation.<Integer>getArgument(1);
+        int dstLen = invocation.<Integer>getArgument(2);
+
+        int copyLen = Math.min(dstLen, src.length - mBufferPos);
+        System.arraycopy(src, mBufferPos, dst, dstPos, copyLen);
+        mBufferPos += copyLen;
+
+        cleanupBuffers();
+        return Integer.valueOf(copyLen);
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt
new file mode 100644
index 0000000..843c41e
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk30.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.testutils.filters
+
+/**
+ * Only run this test in the CtsNetTestCasesMaxTargetSdk30 suite.
+ */
+annotation class CtsNetTestCasesMaxTargetSdk30(val reason: String)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt
new file mode 100644
index 0000000..be0103d
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.testutils.filters
+
+/**
+ * Only run this test in the CtsNetTestCasesMaxTargetSdk31 suite.
+ */
+annotation class CtsNetTestCasesMaxTargetSdk31(val reason: String)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt
new file mode 100644
index 0000000..5af890f
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils.filters
+
+/**
+ * Only run this test in the CtsNetTestCasesMaxTargetSdk33 suite.
+ */
+annotation class CtsNetTestCasesMaxTargetSdk33(val reason: String)
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
new file mode 100644
index 0000000..3fc74aa
--- /dev/null
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+import com.android.ddmlib.testrunner.TestResult
+import com.android.tradefed.config.Option
+import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.result.CollectingTestListener
+import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
+import com.android.tradefed.targetprep.BaseTargetPreparer
+import com.android.tradefed.targetprep.TargetSetupError
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller
+
+private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
+private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
+private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+// As per the <instrumentation> defined in the checker manifest
+private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
+private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+
+/**
+ * A target preparer that sets up and verifies a device for connectivity tests.
+ *
+ * For quick and dirty local testing, the connectivity check can be disabled by running tests with
+ * "atest -- \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ */
+open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
+    private val installer = SuiteApkInstaller()
+
+    @Option(name = IGNORE_CONN_CHECK_OPTION,
+            description = "Disables the check for mobile data and wifi")
+    private var ignoreConnectivityCheck = false
+
+    override fun setUp(testInformation: TestInformation) {
+        if (isDisabled) return
+        disableGmsUpdate(testInformation)
+        runPreparerApk(testInformation)
+    }
+
+    private fun runPreparerApk(testInformation: TestInformation) {
+        installer.setCleanApk(true)
+        installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
+        installer.setShouldGrantPermission(true)
+        installer.setUp(testInformation)
+
+        val runner = DefaultRemoteAndroidTestRunner(
+                CONNECTIVITY_PKG_NAME,
+                CONNECTIVITY_CHECK_RUNNER_NAME,
+                testInformation.device.iDevice)
+        runner.runOptions = "--no-hidden-api-checks"
+
+        val receiver = CollectingTestListener()
+        if (!testInformation.device.runInstrumentationTests(runner, receiver)) {
+            throw TargetSetupError("Device state check failed to complete",
+                    testInformation.device.deviceDescriptor)
+        }
+
+        val runResult = receiver.currentRunResults
+        if (runResult.isRunFailure) {
+            throw TargetSetupError("Failed to check device state before the test: " +
+                    runResult.runFailureMessage, testInformation.device.deviceDescriptor)
+        }
+
+        val ignoredTestClasses = mutableSetOf<String>()
+        if (ignoreConnectivityCheck) {
+            ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+        }
+
+        val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
+            if (TestResult.TestStatus.FAILURE != testResult.status ||
+                    ignoredTestClasses.contains(testDescription.className)) {
+                null
+            } else {
+                "$testDescription: ${testResult.stackTrace}"
+            }
+        }.joinToString("\n")
+        if (errorMsg.isBlank()) return
+
+        throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
+                testInformation.device.deviceDescriptor)
+    }
+
+    private fun disableGmsUpdate(testInformation: TestInformation) {
+        // This will be a no-op on devices without root (su) or not using gservices, but that's OK.
+        testInformation.device.executeShellCommand("su 0 am broadcast " +
+                "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
+                "-e finsky.play_services_auto_update_enabled false")
+    }
+
+    private fun clearGmsUpdateOverride(testInformation: TestInformation) {
+        testInformation.device.executeShellCommand("su 0 am broadcast " +
+                "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
+                "--esn finsky.play_services_auto_update_enabled")
+    }
+
+    override fun tearDown(testInformation: TestInformation, e: Throwable?) {
+        if (isTearDownDisabled) return
+        installer.tearDown(testInformation, e)
+        clearGmsUpdateOverride(testInformation)
+    }
+}
diff --git a/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt
new file mode 100644
index 0000000..63f05a6
--- /dev/null
+++ b/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.targetprep.BaseTargetPreparer
+
+/**
+ * A target preparer that disables DeviceConfig sync while running a test.
+ *
+ * Without this preparer, tests that rely on stable values of DeviceConfig flags, for example to
+ * test behavior when setting the flag and resetting it afterwards, may flake as the flags may
+ * be synced with remote servers during the test.
+ */
+class DisableConfigSyncTargetPreparer : BaseTargetPreparer() {
+    private var syncDisabledOriginalValue = "none"
+
+    override fun setUp(testInfo: TestInformation) {
+        if (isDisabled) return
+        syncDisabledOriginalValue = readSyncDisabledOriginalValue(testInfo)
+
+        // The setter is the same in current and legacy S versions
+        testInfo.exec("cmd device_config set_sync_disabled_for_tests until_reboot")
+    }
+
+    override fun tearDown(testInfo: TestInformation, e: Throwable?) {
+        if (isTearDownDisabled) return
+        // May fail harmlessly if called before S
+        testInfo.exec("cmd device_config set_sync_disabled_for_tests $syncDisabledOriginalValue")
+    }
+
+    private fun readSyncDisabledOriginalValue(testInfo: TestInformation): String {
+        return when (val reply = testInfo.exec("cmd device_config get_sync_disabled_for_tests")) {
+            "until_reboot", "persistent", "none" -> reply
+            // Reply does not match known modes, try legacy commands used on S and some T builds
+            else -> when (testInfo.exec("cmd device_config is_sync_disabled_for_tests")) {
+                // The legacy command just said "true" for "until_reboot" or "persistent". There is
+                // no way to know which one was used, so just reset to "until_reboot" to be
+                // conservative.
+                "true" -> "until_reboot"
+                else -> "none"
+            }
+        }
+    }
+}
+
+private fun TestInformation.exec(cmd: String) = this.device.executeShellCommand(cmd)
\ No newline at end of file
diff --git a/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
new file mode 100644
index 0000000..f24e4f1
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2019 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.net.module.util
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import java.util.concurrent.locks.StampedLock
+import kotlin.concurrent.withLock
+
+/**
+ * A List that additionally offers the ability to append via the add() method, and to retrieve
+ * an element by its index optionally waiting for it to become available.
+ */
+interface TrackRecord<E> : List<E> {
+    /**
+     * Adds an element to this queue, waking up threads waiting for one. Returns true, as
+     * per the contract for List.
+     */
+    fun add(e: E): Boolean
+
+    /**
+     * Returns the first element after {@param pos}, possibly blocking until one is available, or
+     * null if no such element can be found within the timeout.
+     * If a predicate is given, only elements matching the predicate are returned.
+     *
+     * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
+     * @param pos the position at which to start polling.
+     * @param predicate an optional predicate to filter elements to be returned.
+     * @return an element matching the predicate, or null if timeout.
+     */
+    fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean = { true }): E?
+}
+
+/**
+ * A thread-safe implementation of TrackRecord that is backed by an ArrayList.
+ *
+ * This class also supports the creation of a read-head for easier single-thread access.
+ * Refer to the documentation of {@link ArrayTrackRecord.ReadHead}.
+ */
+class ArrayTrackRecord<E> : TrackRecord<E> {
+    private val lock = ReentrantLock()
+    private val condition = lock.newCondition()
+    // Backing store. This stores the elements in this ArrayTrackRecord.
+    private val elements = ArrayList<E>()
+
+    // The list iterator for RecordingQueue iterates over a snapshot of the collection at the
+    // time the operator is created. Because TrackRecord is only ever mutated by appending,
+    // that makes this iterator thread-safe as it sees an effectively immutable List.
+    class ArrayTrackRecordIterator<E>(
+        private val list: ArrayList<E>,
+        start: Int,
+        private val end: Int
+    ) : ListIterator<E> {
+        var index = start
+        override fun hasNext() = index < end
+        override fun next() = list[index++]
+        override fun hasPrevious() = index > 0
+        override fun nextIndex() = index + 1
+        override fun previous() = list[--index]
+        override fun previousIndex() = index - 1
+    }
+
+    // List<E> implementation
+    override val size get() = lock.withLock { elements.size }
+    override fun contains(element: E) = lock.withLock { elements.contains(element) }
+    override fun containsAll(elements: Collection<E>) = lock.withLock {
+        this.elements.containsAll(elements)
+    }
+    override operator fun get(index: Int) = lock.withLock { elements[index] }
+    override fun indexOf(element: E): Int = lock.withLock { elements.indexOf(element) }
+    override fun lastIndexOf(element: E): Int = lock.withLock { elements.lastIndexOf(element) }
+    override fun isEmpty() = lock.withLock { elements.isEmpty() }
+    override fun listIterator(index: Int) = ArrayTrackRecordIterator(elements, index, size)
+    override fun listIterator() = listIterator(0)
+    override fun iterator() = listIterator()
+    override fun subList(fromIndex: Int, toIndex: Int): List<E> = lock.withLock {
+        elements.subList(fromIndex, toIndex)
+    }
+
+    // TrackRecord<E> implementation
+    override fun add(e: E): Boolean {
+        lock.withLock {
+            elements.add(e)
+            condition.signalAll()
+        }
+        return true
+    }
+    override fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean) = lock.withLock {
+        elements.getOrNull(pollForIndexReadLocked(timeoutMs, pos, predicate))
+    }
+
+    // For convenience
+    fun getOrNull(pos: Int, predicate: (E) -> Boolean) = lock.withLock {
+        if (pos < 0 || pos > size) null else elements.subList(pos, size).find(predicate)
+    }
+
+    // Returns the index of the next element whose position is >= pos matching the predicate, if
+    // necessary waiting until such a time that such an element is available, with a timeout.
+    // If no such element is found within the timeout -1 is returned.
+    private fun pollForIndexReadLocked(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean): Int {
+        val deadline = System.currentTimeMillis() + timeoutMs
+        var index = pos
+        do {
+            while (index < elements.size) {
+                if (predicate(elements[index])) return index
+                ++index
+            }
+        } while (condition.await(deadline - System.currentTimeMillis()))
+        return -1
+    }
+
+    /**
+     * Returns a ReadHead over this ArrayTrackRecord. The returned ReadHead is tied to the
+     * current thread.
+     */
+    fun newReadHead() = ReadHead()
+
+    /**
+     * ReadHead is an object that helps users of ArrayTrackRecord keep track of how far
+     * it has read this far in the ArrayTrackRecord. A ReadHead is always associated with
+     * a single instance of ArrayTrackRecord. Multiple ReadHeads can be created and used
+     * on the same instance of ArrayTrackRecord concurrently, and the ArrayTrackRecord
+     * instance can also be used concurrently. ReadHead maintains the current index that is
+     * the next to be read, and calls this the "mark".
+     *
+     * In a ReadHead, {@link poll(Long, (E) -> Boolean)} works similarly to a LinkedBlockingQueue.
+     * It can be called repeatedly and will return the elements as they arrive.
+     *
+     * Intended usage looks something like this :
+     * val TrackRecord<MyObject> record = ArrayTrackRecord().newReadHead()
+     * Thread().start {
+     *   // do stuff
+     *   record.add(something)
+     *   // do stuff
+     * }
+     *
+     * val obj1 = record.poll(timeout)
+     * // do something with obj1
+     * val obj2 = record.poll(timeout)
+     * // do something with obj2
+     *
+     * The point is that the caller does not have to track the mark like it would have to if
+     * it was using ArrayTrackRecord directly.
+     *
+     * Thread safety :
+     * A ReadHead delegates all TrackRecord methods to its associated ArrayTrackRecord, and
+     * inherits its thread-safe properties for all the TrackRecord methods.
+     *
+     * Poll() operates under its own set of rules that only allow execution on multiple threads
+     * within constrained boundaries, and never concurrently or pseudo-concurrently. This is
+     * because concurrent calls to poll() fundamentally do not make sense. poll() will move
+     * the mark according to what events remained to be read by this read head, and therefore
+     * if multiple threads were calling poll() concurrently on the same ReadHead, what
+     * happens to the mark and the return values could not be useful because there is no way to
+     * provide either a guarantee not to skip objects nor a guarantee about the mark position at
+     * the exit of poll(). This is even more true in the presence of a predicate to filter
+     * returned elements, because one thread might be filtering out the events the other is
+     * interested in. For this reason, this class will fail-fast if any concurrent access is
+     * detected with ConcurrentAccessException.
+     * It is possible to use poll() on different threads as long as the following can be
+     * guaranteed : one thread must call poll() for the last time, then execute a write barrier,
+     * then the other thread must execute a read barrier before calling poll() for the first time.
+     * This allows in particular to call poll in @Before and @After methods in JUnit unit tests,
+     * because JUnit will enforce those barriers by creating the testing thread after executing
+     * @Before and joining the thread after executing @After.
+     *
+     * peek() can be used by multiple threads concurrently, but only if no thread is calling
+     * poll() outside of the boundaries above. For simplicity, it can be considered that peek()
+     * is safe to call only when poll() is safe to call.
+     *
+     * Polling concurrently from the same ArrayTrackRecord is supported by creating multiple
+     * ReadHeads on the same instance of ArrayTrackRecord (or of course by using ArrayTrackRecord
+     * directly). Each ReadHead is then guaranteed to see all events always and
+     * guarantees are made on the value of the mark upon return. {@see poll(Long, (E) -> Boolean)}
+     * for details. Be careful to create each ReadHead on the thread it is meant to be used on, or
+     * to have a clear synchronization point between creation and use.
+     *
+     * Users of a ReadHead can ask for the current position of the mark at any time, on a thread
+     * where it's safe to call peek(). This mark can be used later to replay the history of events
+     * either on this ReadHead, on the associated ArrayTrackRecord or on another ReadHead
+     * associated with the same ArrayTrackRecord. It might look like this in the reader thread :
+     *
+     * val markAtStart = record.mark
+     * // Start processing interesting events
+     * while (val element = record.poll(timeout) { it.isInteresting() }) {
+     *   // Do something with element
+     * }
+     * // Look for stuff that happened while searching for interesting events
+     * val firstElementReceived = record.getOrNull(markAtStart)
+     * val firstSpecialElement = record.getOrNull(markAtStart) { it.isSpecial() }
+     * // Get the first special element since markAtStart, possibly blocking until one is available
+     * val specialElement = record.poll(timeout, markAtStart) { it.isSpecial() }
+     */
+    inner class ReadHead : TrackRecord<E> by this@ArrayTrackRecord {
+        // This lock only controls access to the readHead member below. The ArrayTrackRecord
+        // object has its own synchronization following different (and more usual) semantics.
+        // See the comment on the ReadHead class for details.
+        private val slock = StampedLock()
+        private var readHead = 0
+
+        // A special mark used to track the start of the last poll() operation.
+        private var pollMark = 0
+
+        /**
+         * @return the current value of the mark.
+         */
+        var mark
+            get() = checkThread { readHead }
+            set(v: Int) = rewind(v)
+        fun rewind(v: Int) {
+            val stamp = slock.tryWriteLock()
+            if (0L == stamp) concurrentAccessDetected()
+            readHead = v
+            pollMark = v
+            slock.unlockWrite(stamp)
+        }
+
+        private fun <T> checkThread(r: (Long) -> T): T {
+            // tryOptimisticRead is a read barrier, guarantees writes from other threads are visible
+            // after it
+            val stamp = slock.tryOptimisticRead()
+            val result = r(stamp)
+            // validate also performs a read barrier, guaranteeing that if validate returns true,
+            // then any change either happens-before tryOptimisticRead, or happens-after validate.
+            if (!slock.validate(stamp)) concurrentAccessDetected()
+            return result
+        }
+
+        private fun concurrentAccessDetected(): Nothing {
+            throw ConcurrentModificationException(
+                    "ReadHeads can't be used concurrently. Check your threading model.")
+        }
+
+        /**
+         * Returns the first element after the mark, optionally blocking until one is available, or
+         * null if no such element can be found within the timeout.
+         * If a predicate is given, only elements matching the predicate are returned.
+         *
+         * Upon return the mark will be set to immediately after the returned element, or after
+         * the last element in the queue if null is returned. This means this method will always
+         * skip elements that do not match the predicate, even if it returns null.
+         *
+         * This method can only be used by the thread that created this ManagedRecordingQueue.
+         * If used on another thread, this throws IllegalStateException.
+         *
+         * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
+         * @param predicate an optional predicate to filter elements to be returned.
+         * @return an element matching the predicate, or null if timeout.
+         */
+        fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? {
+            val stamp = slock.tryWriteLock()
+            if (0L == stamp) concurrentAccessDetected()
+            pollMark = readHead
+            try {
+                lock.withLock {
+                    val index = pollForIndexReadLocked(timeoutMs, readHead, predicate)
+                    readHead = if (index < 0) size else index + 1
+                    return getOrNull(index)
+                }
+            } finally {
+                slock.unlockWrite(stamp)
+            }
+        }
+
+        /**
+         * Returns a list of events that were observed since the last time poll() was called on this
+         * ReadHead.
+         *
+         * @return list of events since poll() was called.
+         */
+        fun backtrace(): List<E> {
+            val stamp = slock.tryReadLock()
+            if (0L == stamp) concurrentAccessDetected()
+
+            try {
+                lock.withLock {
+                    return ArrayList(subList(pollMark, mark))
+                }
+            } finally {
+                slock.unlockRead(stamp)
+            }
+        }
+
+        /**
+         * Returns the first element after the mark or null. This never blocks.
+         *
+         * This method is subject to threading restrictions. It can be used concurrently on
+         * multiple threads but not if any other thread might be executing poll() at the same
+         * time. See the class comment for details.
+         */
+        fun peek(): E? = checkThread { getOrNull(readHead) }
+    }
+}
+
+// Private helper
+private fun Condition.await(timeoutMs: Long) = this.await(timeoutMs, TimeUnit.MILLISECONDS)
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
new file mode 100644
index 0000000..9f28234
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+@file:JvmName("Cleanup")
+
+package com.android.testutils
+
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
+import javax.annotation.CheckReturnValue
+
+/**
+ * Utility to do cleanup in tests without replacing exceptions with those from a finally block.
+ *
+ * This utility is meant for tests that want to do cleanup after they execute their test
+ * logic, whether the test fails (and throws) or not.
+ *
+ * The usual way of doing this is to have a try{}finally{} block and put cleanup in finally{}.
+ * However, if any code in finally{} throws, the exception thrown in finally{} is thrown before
+ * any thrown in try{} ; that means errors reported from tests are from finally{} even if they
+ * have been caused by errors in try{}. This is unhelpful in tests, because it results in a
+ * stacktrace for a symptom rather than a stacktrace for a cause.
+ *
+ * To alleviate this, tests are encouraged to make sure the code in finally{} can't throw, or
+ * that the code in try{} can't cause it to fail. This is not always realistic ; not only does
+ * it require the developer thinks about complex interactions of code, test code often relies
+ * on bricks provided by other teams, not controlled by the team writing the test, which may
+ * start throwing with an update (see b/198998862 for an example).
+ *
+ * This utility allows a different approach : it offers a new construct, tryTest{}cleanup{} similar
+ * to try{}finally{}, but that will always throw the first exception that happens. In other words,
+ * if only tryTest{} throws or only cleanup{} throws, that exception will be thrown, but contrary
+ * to the standard try{}finally{}, if both throws, the construct throws the exception that happened
+ * in tryTest{} rather than the one that happened in cleanup{}.
+ *
+ * Kotlin usage is as try{}finally{}, but with multiple finally{} blocks :
+ * tryTest {
+ *   testing code
+ * } cleanupStep {
+ *   cleanup code 1
+ * } cleanupStep {
+ *   cleanup code 2
+ * } cleanup {
+ *   cleanup code 3
+ * }
+ * Catch blocks can be added with the following syntax :
+ * tryTest {
+ *   testing code
+ * }.catch<ExceptionType> { it ->
+ *   do something to it
+ * }
+ *
+ * Java doesn't allow this kind of syntax, so instead a function taking lambdas is provided.
+ * testAndCleanup(() -> {
+ *   testing code
+ * }, () -> {
+ *   cleanup code 1
+ * }, () -> {
+ *   cleanup code 2
+ * });
+ */
+
+@CheckReturnValue
+fun <T> tryTest(block: () -> T) = TryExpr(
+        try {
+            Result.success(block())
+        } catch (e: Throwable) {
+            Result.failure(e)
+        })
+
+// Some downstream branches have an older kotlin that doesn't know about value classes.
+// TODO : Change this to "value class" when aosp no longer merges into such branches.
+@Suppress("INLINE_CLASS_DEPRECATED")
+inline class TryExpr<T>(val result: Result<T>) {
+    inline infix fun <reified E : Throwable> catch(block: (E) -> T): TryExpr<T> {
+        val originalException = result.exceptionOrNull()
+        if (originalException !is E) return this
+        return TryExpr(try {
+            Result.success(block(originalException))
+        } catch (e: Throwable) {
+            Result.failure(e)
+        })
+    }
+
+    @CheckReturnValue
+    inline infix fun cleanupStep(block: () -> Unit): TryExpr<T> {
+        try {
+            block()
+        } catch (e: Throwable) {
+            val originalException = result.exceptionOrNull()
+            return TryExpr(if (null == originalException) {
+                Result.failure(e)
+            } else {
+                originalException.addSuppressed(e)
+                Result.failure(originalException)
+            })
+        }
+        return this
+    }
+
+    inline infix fun cleanup(block: () -> Unit): T = cleanupStep(block).result.getOrThrow()
+}
+
+// Java support
+fun <T> testAndCleanup(tryBlock: ThrowingSupplier<T>, vararg cleanupBlock: ThrowingRunnable): T {
+    return cleanupBlock.fold(tryTest { tryBlock.get() }) { previousExpr, nextCleanup ->
+        previousExpr.cleanupStep { nextCleanup.run() }
+    }.cleanup {}
+}
+fun testAndCleanup(tryBlock: ThrowingRunnable, vararg cleanupBlock: ThrowingRunnable) {
+    return testAndCleanup(ThrowingSupplier { tryBlock.run() }, *cleanupBlock)
+}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
new file mode 100644
index 0000000..af4f96d
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+@file:JvmName("ConcurrentUtils")
+
+package com.android.testutils
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.system.measureTimeMillis
+
+// For Java usage
+fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() }
+
+fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS)
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt
new file mode 100644
index 0000000..ec485fe
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConnectivityModuleTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.testutils
+
+/**
+ * Indicates that the test covers functionality that was rolled out in a connectivity module update.
+ *
+ * Annotated MTS tests will typically only be run in Connectivity/Tethering module MTS, and not when
+ * only other modules (such as NetworkStack) have been updated.
+ * Annotated CTS tests will always be run, as the Connectivity module should be at least newer than
+ * the CTS suite.
+ */
+annotation class ConnectivityModuleTest
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt
new file mode 100644
index 0000000..9e97d51
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+/**
+ * Indicates that the test covers functionality that was rolled out in a resolv module update.
+ */
+annotation class DnsResolverModuleTest
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt
new file mode 100644
index 0000000..678f977
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/FileUtils.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.testutils
+
+// This function is private because the 2 is hardcoded here, and is not correct if not called
+// directly from __LINE__ or __FILE__.
+private fun callerStackTrace(): StackTraceElement = try {
+    throw RuntimeException()
+} catch (e: RuntimeException) {
+    e.stackTrace[2] // 0 is here, 1 is get() in __FILE__ or __LINE__
+}
+val __FILE__: String get() = callerStackTrace().fileName
+val __LINE__: Int get() = callerStackTrace().lineNumber
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java b/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java
new file mode 100644
index 0000000..da36e4d
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/FunctionalUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.testutils;
+
+import java.util.function.Supplier;
+
+/**
+ * A class grouping some utilities to deal with exceptions.
+ */
+public class FunctionalUtils {
+    /**
+     * Like a Consumer, but declared to throw an exception.
+     * @param <T>
+     */
+    @FunctionalInterface
+    public interface ThrowingConsumer<T> {
+        /** @see java.util.function.Consumer */
+        void accept(T t) throws Exception;
+    }
+
+    /**
+     * Like a Supplier, but declared to throw an exception.
+     * @param <T>
+     */
+    @FunctionalInterface
+    public interface ThrowingSupplier<T> {
+        /** @see java.util.function.Supplier */
+        T get() throws Exception;
+    }
+
+    /**
+     * Like a Runnable, but declared to throw an exception.
+     */
+    @FunctionalInterface
+    public interface ThrowingRunnable {
+        /** @see java.lang.Runnable */
+        void run() throws Exception;
+    }
+
+    /**
+     * Convert a supplier that throws into one that doesn't.
+     *
+     * The returned supplier returns null in cases where the source throws.
+     */
+    public static <T> Supplier<T> ignoreExceptions(ThrowingSupplier<T> func) {
+        return () -> {
+            try {
+                return func.get();
+            } catch (Exception e) {
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Convert a runnable that throws into one that doesn't.
+     *
+     * All exceptions are ignored by the returned Runnable.
+     */
+    public static Runnable ignoreExceptions(ThrowingRunnable r) {
+        return () -> {
+            try {
+                r.run();
+            } catch (Exception e) {
+            }
+        };
+    }
+
+    // Java has Function<T, R> and BiFunction<T, U, V> but nothing for higher-arity functions.
+    // Function3 is what Kotlin and Scala use (they also have higher-arity variants, with
+    // FunctionN taking N arguments, as the JVM does not have variadic formal parameters)
+    /**
+     * A function with three arguments.
+     * @param <TArg1> Type of the first argument
+     * @param <TArg2> Type of the second argument
+     * @param <TArg3> Type of the third argument
+     * @param <TResult> Type of the return value
+     */
+    public interface Function3<TArg1, TArg2, TArg3, TResult> {
+        /**
+         * Apply the function to the arguments
+         */
+        TResult apply(TArg1 a1, TArg2 a2, TArg3 a3);
+    }
+}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
new file mode 100644
index 0000000..1883387
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+@file:JvmName("MiscAsserts")
+
+package com.android.testutils
+
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import java.lang.reflect.Modifier
+import kotlin.system.measureTimeMillis
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+private const val TAG = "Connectivity unit test"
+
+fun <T> assertEmpty(ts: Array<T>) = ts.size.let { len ->
+    assertEquals(0, len, "Expected empty array, but length was $len")
+}
+
+fun <T> assertEmpty(ts: Collection<T>) = ts.size.let { len ->
+    assertEquals(0, len, "Expected empty collection, but length was $len")
+}
+
+fun <T> assertLength(expected: Int, got: Array<T>) = got.size.let { len ->
+    assertEquals(expected, len, "Expected array of length $expected, but was $len for $got")
+}
+
+fun <T> assertLength(expected: Int, got: List<T>) = got.size.let { len ->
+    assertEquals(expected, len, "Expected list of length $expected, but was $len for $got")
+}
+
+// Bridge method to help write this in Java. If you're writing Kotlin, consider using
+// kotlin.test.assertFailsWith instead, as that method is reified and inlined.
+fun <T : Exception> assertThrows(expected: Class<T>, block: ThrowingRunnable): T {
+    return assertFailsWith(expected.kotlin) { block.run() }
+}
+
+fun <T : Exception> assertThrows(msg: String, expected: Class<T>, block: ThrowingRunnable): T {
+    return assertFailsWith(expected.kotlin, msg) { block.run() }
+}
+
+fun <T> assertEqualBothWays(o1: T, o2: T) {
+    assertTrue(o1 == o2)
+    assertTrue(o2 == o1)
+}
+
+fun <T> assertNotEqualEitherWay(o1: T, o2: T) {
+    assertFalse(o1 == o2)
+    assertFalse(o2 == o1)
+}
+
+fun assertStringContains(got: String, want: String) {
+    assertTrue(got.contains(want), "$got did not contain \"${want}\"")
+}
+
+fun assertContainsExactly(actual: IntArray, vararg expected: Int) {
+    // IntArray#sorted() returns a list, so it's fine to test with equals()
+    assertEquals(actual.sorted(), expected.sorted(),
+            "$actual does not contain exactly $expected")
+}
+
+fun assertContainsStringsExactly(actual: Array<String>, vararg expected: String) {
+    assertEquals(actual.sorted(), expected.sorted(),
+            "$actual does not contain exactly $expected")
+}
+
+fun <T> assertContainsAll(list: Collection<T>, vararg elems: T) {
+    assertContainsAll(list, elems.asList())
+}
+
+fun <T> assertContainsAll(list: Collection<T>, elems: Collection<T>) {
+    elems.forEach { assertTrue(list.contains(it), "$it not in list") }
+}
+
+fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: Runnable) {
+    assertRunsInAtMost(descr, timeLimit) { fn.run() }
+}
+
+fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: () -> Unit) {
+    val timeTaken = measureTimeMillis(fn)
+    val msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit)
+    assertTrue(timeTaken <= timeLimit, msg)
+}
+
+/**
+ * Verifies that the number of nonstatic fields in a java class equals a given count.
+ * Note: this is essentially not useful for Kotlin code where fields are not really a thing.
+ *
+ * This assertion serves as a reminder to update test code around it if fields are added
+ * after the test is written.
+ * @param count Expected number of nonstatic fields in the class.
+ * @param clazz Class to test.
+ */
+fun <T> assertFieldCountEquals(count: Int, clazz: Class<T>) {
+    assertEquals(count, clazz.declaredFields.filter {
+        !Modifier.isStatic(it.modifiers) && !Modifier.isTransient(it.modifiers)
+    }.size)
+}
+
+fun <T> assertSameElements(expected: List<T>, actual: List<T>) {
+    val expectedSet: HashSet<T> = HashSet(expected)
+    assertEquals(expectedSet.size, expected.size, "expected list contains duplicates")
+    val actualSet: HashSet<T> = HashSet(actual)
+    assertEquals(actualSet.size, actual.size, "actual list contains duplicates")
+    assertEquals(expectedSet, actualSet)
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt
new file mode 100644
index 0000000..fe312a0
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+/**
+ * Indicates that the test covers functionality that was rolled out in a NetworkStack module update.
+ */
+annotation class NetworkStackModuleTest
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt b/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt
new file mode 100644
index 0000000..1bb6d68
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/PacketFilter.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+import java.net.Inet4Address
+import java.util.function.Predicate
+
+// Some of the below constants are duplicated with NetworkStackConstants, but this is a hostdevice
+// library usable for host-side tests, so device-side utils are not usable, and there is no
+// host-side non-test library to host common constants.
+private const val ETHER_TYPE_OFFSET = 12
+private const val ETHER_HEADER_LENGTH = 14
+private const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
+private const val IPV6_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 6
+private const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
+private const val IPV4_DST_OFFSET = ETHER_HEADER_LENGTH + 16
+private const val IPV4_HEADER_LENGTH = 20
+private const val IPV6_HEADER_LENGTH = 40
+private const val IPV4_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
+private const val IPV6_PAYLOAD_OFFSET = ETHER_HEADER_LENGTH + IPV6_HEADER_LENGTH
+private const val UDP_HEADER_LENGTH = 8
+private const val BOOTP_OFFSET = IPV4_PAYLOAD_OFFSET + UDP_HEADER_LENGTH
+private const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
+private const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
+private const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
+private const val ARP_OPCODE_OFFSET = ETHER_HEADER_LENGTH + 6
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
+ * [offset].
+ */
+class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
+    override fun test(packet: ByteArray) =
+            bytes.withIndex().all { it.value == packet[offset + it.index] }
+}
+
+private class UdpPortFilter(
+    private val udpOffset: Int,
+    private val src: Short?,
+    private val dst: Short?
+) : Predicate<ByteArray> {
+    override fun test(t: ByteArray): Boolean {
+        if (src != null && !OffsetFilter(udpOffset,
+                        src.toInt().ushr(8).toByte(), src.toByte()).test(t)) {
+            return false
+        }
+
+        if (dst != null && !OffsetFilter(udpOffset + 2,
+                        dst.toInt().ushr(8).toByte(), dst.toByte()).test(t)) {
+            return false
+        }
+        return true
+    }
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
+ */
+class IPv4UdpFilter @JvmOverloads constructor(
+    srcPort: Short? = null,
+    dstPort: Short? = null
+) : Predicate<ByteArray> {
+    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
+            OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */)).and(
+            UdpPortFilter(IPV4_PAYLOAD_OFFSET, srcPort, dstPort))
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv6 datagram.
+ */
+class IPv6UdpFilter @JvmOverloads constructor(
+    srcPort: Short? = null,
+    dstPort: Short? = null
+) : Predicate<ByteArray> {
+    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x86.toByte(), 0xdd.toByte() /* IPv6 */).and(
+            OffsetFilter(IPV6_PROTOCOL_OFFSET, 17 /* UDP */)).and(
+            UdpPortFilter(IPV6_PAYLOAD_OFFSET, srcPort, dstPort))
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets sent to the specified IPv4 destination.
+ */
+class IPv4DstFilter(dst: Inet4Address) : Predicate<ByteArray> {
+    private val impl = OffsetFilter(IPV4_DST_OFFSET, *dst.address)
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped ARP requests.
+ */
+class ArpRequestFilter : Predicate<ByteArray> {
+    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x06 /* ARP */)
+            .and(OffsetFilter(ARP_OPCODE_OFFSET, 0x00, 0x01 /* request */))
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
+ */
+class DhcpClientPacketFilter : Predicate<ByteArray> {
+    private val impl = IPv4UdpFilter(srcPort = 68, dstPort = 67)
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
+ * contains the specified option with the specified [bytes] as value.
+ */
+class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
+    override fun test(packet: ByteArray): Boolean {
+        val option = findDhcpOption(packet, option) ?: return false
+        return option.contentEquals(bytes)
+    }
+}
+
+/**
+ * Find a DHCP option in a packet and return its value, if found.
+ */
+fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
+        findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
+            val optionLen = packet[it + 1]
+            return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
+        }
+
+private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
+    if (packet.size <= searchOffset + 2 /* type, length bytes */) return null
+
+    return if (packet[searchOffset] == option) searchOffset else {
+        val optionLen = packet[searchOffset + 1]
+        findOptionOffset(packet, option, searchOffset + 2 + optionLen)
+    }
+}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
new file mode 100644
index 0000000..5952365
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document limitations that prevent a test from being
+ * executed in presubmit on older builds.
+ */
+annotation class SkipMainlinePresubmit(val reason: String)
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt b/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt
new file mode 100644
index 0000000..69ed048
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.testutils
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document hardware or test bench limitations.
+ */
+annotation class SkipPresubmit(val reason: String)
\ No newline at end of file
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 1c99722..89a55a7 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -136,7 +136,7 @@
     private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
 
-    private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 6_000;
+    private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 10_000;
     private static final int JOB_NETWORK_STATE_TIMEOUT_MS = 10_000;
     private static final int LAUNCH_ACTIVITY_TIMEOUT_MS = 10_000;
 
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
index ff06a90..aa90f5f 100644
--- a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -16,24 +16,33 @@
 
 package android.security.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import java.lang.Integer;
-import java.lang.String;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.ArrayList;
 
 /**
  * Host-side tests for values in /proc/net.
  *
  * These tests analyze /proc/net to verify that certain networking properties are correct.
  */
-public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ProcNetTest extends BaseHostJUnit4Test implements IBuildReceiver, IDeviceTest {
     private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
     private static final int MIN_ACQ_EXPIRES = 3600;
     // Global sysctls. Must be present and set to 1.
@@ -72,13 +81,12 @@
      */
     @Override
     public void setDevice(ITestDevice device) {
-        super.setDevice(device);
         mDevice = device;
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    /** Run before each test case. */
+    @Before
+    public void setUp() throws Exception {
         mSysctlDirs = getSysctlDirs();
     }
 
@@ -113,6 +121,7 @@
     /**
      * Checks that SPI default timeouts are overridden, and set to a reasonable length of time
      */
+    @Test
     public void testMinAcqExpires() throws Exception {
         int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
         assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
@@ -122,6 +131,7 @@
      * Checks that the sysctls for multinetwork kernel features are present and
      * enabled.
      */
+    @Test
     public void testProcSysctls() throws Exception {
         for (String sysctl : GLOBAL_SYSCTLS) {
             int value = readIntFromPath(sysctl);
@@ -138,6 +148,7 @@
     /**
      * Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
      */
+    @Test
     public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
         for (String interfaceDir : mSysctlDirs) {
             String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
@@ -153,6 +164,7 @@
      * Verify that router_solicitations exists and is set to the expected value
      * and verify that router_solicitation_max_interval exists and is in an acceptable interval.
      */
+    @Test
     public void testRouterSolicitations() throws Exception {
         for (String interfaceDir : mSysctlDirs) {
             String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
@@ -172,7 +184,11 @@
      * (This repeats the VTS test, and is here for good performance of the internet as a whole.)
      * TODO: revisit this once a better CC algorithm like BBR2 is available.
      */
+    @Test
     public void testCongestionControl() throws Exception {
+        final DeviceSdkLevel dsl = new DeviceSdkLevel(mDevice);
+        assumeTrue(dsl.isDeviceAtLeastV());
+
         String path = "/proc/sys/net/ipv4/tcp_congestion_control";
         String value = mDevice.executeAdbCommand("shell", "cat", path).trim();
         assertEquals(value, "cubic");
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 1276d59..6de663a 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -56,6 +56,7 @@
         "modules-utils-build",
         "net-utils-framework-common",
         "truth-prebuilt",
+        "TetheringIntegrationTestsBaseLib",
     ],
 
     // uncomment when b/13249961 is fixed
diff --git a/tests/cts/net/native/dns/AndroidTest.xml b/tests/cts/net/native/dns/AndroidTest.xml
index d49696b..5d68ac7 100644
--- a/tests/cts/net/native/dns/AndroidTest.xml
+++ b/tests/cts/net/native/dns/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 60befca..e0fe929 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -38,9 +38,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 
-import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.testutils.Cleanup.testAndCleanup;
 
 import static org.junit.Assert.assertEquals;
@@ -82,6 +80,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.ThrowingRunnable;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.build.SdkLevel;
@@ -99,6 +99,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -143,7 +144,7 @@
     // runWithShellPermissionIdentity, and callWithShellPermissionIdentity ensures Shell Permission
     // is not interrupted by another operation (which would drop all previously adopted
     // permissions).
-    private Object mShellPermissionsIdentityLock = new Object();
+    private final Object mShellPermissionsIdentityLock = new Object();
 
     private Context mContext;
     private ConnectivityManager mConnectivityManager;
@@ -177,6 +178,20 @@
         Log.i(TAG, "Waited for broadcast idle for " + (SystemClock.elapsedRealtime() - st) + "ms");
     }
 
+    private void runWithShellPermissionIdentity(ThrowingRunnable runnable,
+            String... permissions) {
+        synchronized (mShellPermissionsIdentityLock) {
+            SystemUtil.runWithShellPermissionIdentity(runnable, permissions);
+        }
+    }
+
+    private <T> T callWithShellPermissionIdentity(Callable<T> callable, String... permissions)
+            throws Exception {
+        synchronized (mShellPermissionsIdentityLock) {
+            return SystemUtil.callWithShellPermissionIdentity(callable, permissions);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
@@ -199,7 +214,7 @@
             runWithShellPermissionIdentity(() -> {
                 final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
                 tnm.teardownTestNetwork(mTestNetwork);
-            });
+            }, android.Manifest.permission.MANAGE_TEST_NETWORKS);
             mTestNetwork = null;
         }
 
@@ -249,7 +264,7 @@
             doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
                     subId, carrierConfigReceiver, testNetworkCallback);
         }, () -> {
-            runWithShellPermissionIdentity(
+                runWithShellPermissionIdentity(
                     () -> mCarrierConfigManager.overrideConfig(subId, null),
                     android.Manifest.permission.MODIFY_PHONE_STATE);
             mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
@@ -276,24 +291,20 @@
                 CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
                 new String[] {getCertHashForThisPackage()});
 
-        synchronized (mShellPermissionsIdentityLock) {
-            runWithShellPermissionIdentity(
-                    () -> {
-                        mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
-                        mCarrierConfigManager.notifyConfigChangedForSubId(subId);
-                    },
-                    android.Manifest.permission.MODIFY_PHONE_STATE);
-        }
+        runWithShellPermissionIdentity(
+                () -> {
+                    mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
+                    mCarrierConfigManager.notifyConfigChangedForSubId(subId);
+                },
+                android.Manifest.permission.MODIFY_PHONE_STATE);
 
         // TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
         // shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
         // permissions are updated.
-        synchronized (mShellPermissionsIdentityLock) {
-            runWithShellPermissionIdentity(
-                    () -> mConnectivityManager.requestNetwork(
-                            CELLULAR_NETWORK_REQUEST, testNetworkCallback),
-                    android.Manifest.permission.CONNECTIVITY_INTERNAL);
-        }
+        runWithShellPermissionIdentity(
+                () -> mConnectivityManager.requestNetwork(
+                        CELLULAR_NETWORK_REQUEST, testNetworkCallback),
+                android.Manifest.permission.CONNECTIVITY_INTERNAL);
 
         final Network network = testNetworkCallback.waitForAvailable();
         assertNotNull(network);
@@ -494,7 +505,7 @@
                     final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]);
                     tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER);
                     return tni;
-                });
+                }, android.Manifest.permission.MANAGE_TEST_NETWORKS);
     }
 
     private static class TestConnectivityDiagnosticsCallback
@@ -648,11 +659,9 @@
 
             final PersistableBundle carrierConfigs;
             try {
-                synchronized (mShellPermissionsIdentityLock) {
-                    carrierConfigs = callWithShellPermissionIdentity(
-                            () -> mCarrierConfigManager.getConfigForSubId(subId),
-                            android.Manifest.permission.READ_PHONE_STATE);
-                }
+                carrierConfigs = callWithShellPermissionIdentity(
+                        () -> mCarrierConfigManager.getConfigForSubId(subId),
+                        android.Manifest.permission.READ_PHONE_STATE);
             } catch (Exception exception) {
                 // callWithShellPermissionIdentity() threw an Exception - cache it and allow
                 // waitForCarrierConfigChanged() to throw it
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 3a76cc2..59aefa5 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2908,7 +2908,6 @@
 
     @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
     @Test
-    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
         assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
diff --git a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt b/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
new file mode 100644
index 0000000..bc13442
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts
+
+import android.net.DnsResolver
+import android.net.Network
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import android.os.Process
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.DnsPacket
+import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN
+import com.android.net.module.util.TrackRecord
+import com.android.testutils.IPv6UdpFilter
+import com.android.testutils.TapPacketReader
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val MDNS_REGISTRATION_TIMEOUT_MS = 10_000L
+private const val MDNS_PORT = 5353.toShort()
+const val MDNS_CALLBACK_TIMEOUT = 2000L
+const val MDNS_NO_CALLBACK_TIMEOUT_MS = 200L
+
+interface NsdEvent
+open class NsdRecord<T : NsdEvent> private constructor(
+    private val history: ArrayTrackRecord<T>,
+    private val expectedThreadId: Int? = null
+) : TrackRecord<T> by history {
+    constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
+
+    val nextEvents = history.newReadHead()
+
+    override fun add(e: T): Boolean {
+        if (expectedThreadId != null) {
+            assertEquals(
+                expectedThreadId, Process.myTid(),
+                "Callback is running on the wrong thread"
+            )
+        }
+        return history.add(e)
+    }
+
+    inline fun <reified V : NsdEvent> expectCallbackEventually(
+        timeoutMs: Long = MDNS_CALLBACK_TIMEOUT,
+        crossinline predicate: (V) -> Boolean = { true }
+    ): V = nextEvents.poll(timeoutMs) { e -> e is V && predicate(e) } as V?
+        ?: fail("Callback for ${V::class.java.simpleName} not seen after $timeoutMs ms")
+
+    inline fun <reified V : NsdEvent> expectCallback(timeoutMs: Long = MDNS_CALLBACK_TIMEOUT): V {
+        val nextEvent = nextEvents.poll(timeoutMs)
+        assertNotNull(
+            nextEvent, "No callback received after $timeoutMs ms, expected " +
+                    "${V::class.java.simpleName}"
+        )
+        assertTrue(
+            nextEvent is V, "Expected ${V::class.java.simpleName} but got " +
+                    nextEvent.javaClass.simpleName
+        )
+        return nextEvent
+    }
+
+    inline fun assertNoCallback(timeoutMs: Long = MDNS_NO_CALLBACK_TIMEOUT_MS) {
+        val cb = nextEvents.poll(timeoutMs)
+        assertNull(cb, "Expected no callback but got $cb")
+    }
+}
+
+class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
+    NsdManager.DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
+    sealed class DiscoveryEvent : NsdEvent {
+        data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+            DiscoveryEvent()
+
+        data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+            DiscoveryEvent()
+
+        data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
+        data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
+        data class ServiceFound(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
+        data class ServiceLost(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
+    }
+
+    override fun onStartDiscoveryFailed(serviceType: String, err: Int) {
+        add(DiscoveryEvent.StartDiscoveryFailed(serviceType, err))
+    }
+
+    override fun onStopDiscoveryFailed(serviceType: String, err: Int) {
+        add(DiscoveryEvent.StopDiscoveryFailed(serviceType, err))
+    }
+
+    override fun onDiscoveryStarted(serviceType: String) {
+        add(DiscoveryEvent.DiscoveryStarted(serviceType))
+    }
+
+    override fun onDiscoveryStopped(serviceType: String) {
+        add(DiscoveryEvent.DiscoveryStopped(serviceType))
+    }
+
+    override fun onServiceFound(si: NsdServiceInfo) {
+        add(DiscoveryEvent.ServiceFound(si))
+    }
+
+    override fun onServiceLost(si: NsdServiceInfo) {
+        add(DiscoveryEvent.ServiceLost(si))
+    }
+
+    fun waitForServiceDiscovered(
+        serviceName: String,
+        serviceType: String,
+        expectedNetwork: Network? = null
+    ): NsdServiceInfo {
+        val serviceFound = expectCallbackEventually<DiscoveryEvent.ServiceFound> {
+            it.serviceInfo.serviceName == serviceName &&
+                    (expectedNetwork == null ||
+                            expectedNetwork == it.serviceInfo.network)
+        }.serviceInfo
+        // Discovered service types have a dot at the end
+        assertEquals("$serviceType.", serviceFound.serviceType)
+        return serviceFound
+    }
+}
+
+class NsdRegistrationRecord(expectedThreadId: Int? = null) : NsdManager.RegistrationListener,
+    NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
+    sealed class RegistrationEvent : NsdEvent {
+        abstract val serviceInfo: NsdServiceInfo
+
+        data class RegistrationFailed(
+            override val serviceInfo: NsdServiceInfo,
+            val errorCode: Int
+        ) : RegistrationEvent()
+
+        data class UnregistrationFailed(
+            override val serviceInfo: NsdServiceInfo,
+            val errorCode: Int
+        ) : RegistrationEvent()
+
+        data class ServiceRegistered(override val serviceInfo: NsdServiceInfo) :
+            RegistrationEvent()
+
+        data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo) :
+            RegistrationEvent()
+    }
+
+    override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
+        add(RegistrationEvent.RegistrationFailed(si, err))
+    }
+
+    override fun onUnregistrationFailed(si: NsdServiceInfo, err: Int) {
+        add(RegistrationEvent.UnregistrationFailed(si, err))
+    }
+
+    override fun onServiceRegistered(si: NsdServiceInfo) {
+        add(RegistrationEvent.ServiceRegistered(si))
+    }
+
+    override fun onServiceUnregistered(si: NsdServiceInfo) {
+        add(RegistrationEvent.ServiceUnregistered(si))
+    }
+}
+
+class NsdResolveRecord : NsdManager.ResolveListener,
+    NsdRecord<NsdResolveRecord.ResolveEvent>() {
+    sealed class ResolveEvent : NsdEvent {
+        data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+            ResolveEvent()
+
+        data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+        data class ResolutionStopped(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+        data class StopResolutionFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+            ResolveEvent()
+    }
+
+    override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
+        add(ResolveEvent.ResolveFailed(si, err))
+    }
+
+    override fun onServiceResolved(si: NsdServiceInfo) {
+        add(ResolveEvent.ServiceResolved(si))
+    }
+
+    override fun onResolutionStopped(si: NsdServiceInfo) {
+        add(ResolveEvent.ResolutionStopped(si))
+    }
+
+    override fun onStopResolutionFailed(si: NsdServiceInfo, err: Int) {
+        super.onStopResolutionFailed(si, err)
+        add(ResolveEvent.StopResolutionFailed(si, err))
+    }
+}
+
+class NsdServiceInfoCallbackRecord : NsdManager.ServiceInfoCallback,
+    NsdRecord<NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent>() {
+    sealed class ServiceInfoCallbackEvent : NsdEvent {
+        data class RegisterCallbackFailed(val errorCode: Int) : ServiceInfoCallbackEvent()
+        data class ServiceUpdated(val serviceInfo: NsdServiceInfo) : ServiceInfoCallbackEvent()
+        object ServiceUpdatedLost : ServiceInfoCallbackEvent()
+        object UnregisterCallbackSucceeded : ServiceInfoCallbackEvent()
+    }
+
+    override fun onServiceInfoCallbackRegistrationFailed(err: Int) {
+        add(ServiceInfoCallbackEvent.RegisterCallbackFailed(err))
+    }
+
+    override fun onServiceUpdated(si: NsdServiceInfo) {
+        add(ServiceInfoCallbackEvent.ServiceUpdated(si))
+    }
+
+    override fun onServiceLost() {
+        add(ServiceInfoCallbackEvent.ServiceUpdatedLost)
+    }
+
+    override fun onServiceInfoCallbackUnregistered() {
+        add(ServiceInfoCallbackEvent.UnregisterCallbackSucceeded)
+    }
+}
+
+fun TapPacketReader.pollForMdnsPacket(
+    timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS,
+    predicate: (TestDnsPacket) -> Boolean
+): ByteArray? {
+    val mdnsProbeFilter = IPv6UdpFilter(srcPort = MDNS_PORT, dstPort = MDNS_PORT).and {
+        val mdnsPayload = it.copyOfRange(
+            ETHER_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN, it.size
+        )
+        try {
+            predicate(TestDnsPacket(mdnsPayload))
+        } catch (e: DnsPacket.ParseException) {
+            false
+        }
+    }
+    return poll(timeoutMs, mdnsProbeFilter)
+}
+
+fun TapPacketReader.pollForProbe(
+    serviceName: String,
+    serviceType: String,
+    timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
+): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isProbeFor("$serviceName.$serviceType.local") }
+
+fun TapPacketReader.pollForAdvertisement(
+    serviceName: String,
+    serviceType: String,
+    timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
+): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isReplyFor("$serviceName.$serviceType.local") }
+
+fun TapPacketReader.pollForQuery(
+    recordName: String,
+    recordType: Int,
+    timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
+): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isQueryFor(recordName, recordType) }
+
+fun TapPacketReader.pollForReply(
+    serviceName: String,
+    serviceType: String,
+    timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
+): ByteArray? = pollForMdnsPacket(timeoutMs) {
+    it.isReplyFor("$serviceName.$serviceType.local")
+}
+
+class TestDnsPacket(data: ByteArray) : DnsPacket(data) {
+    val header: DnsHeader
+        get() = mHeader
+    val records: Array<List<DnsRecord>>
+        get() = mRecords
+    fun isProbeFor(name: String): Boolean = mRecords[QDSECTION].any {
+        it.dName == name && it.nsType == DnsResolver.TYPE_ANY
+    }
+
+    fun isReplyFor(name: String): Boolean = mRecords[ANSECTION].any {
+        it.dName == name && it.nsType == DnsResolver.TYPE_SRV
+    }
+
+    fun isQueryFor(name: String, type: Int): Boolean = mRecords[QDSECTION].any {
+        it.dName == name && it.nsType == type
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
new file mode 100644
index 0000000..c2bb7cd
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts
+
+import android.net.EthernetTetheringTestBase
+import android.net.LinkAddress
+import android.net.TestNetworkInterface
+import android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL
+import android.net.TetheringManager.TETHERING_ETHERNET
+import android.net.TetheringManager.TetheringRequest
+import android.net.nsd.NsdManager
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TapPacketReader
+import com.android.testutils.tryTest
+import java.util.Random
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class NsdManagerDownstreamTetheringTest : EthernetTetheringTestBase() {
+    private val nsdManager by lazy { context.getSystemService(NsdManager::class.java)!! }
+    private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        setIncludeTestInterfaces(true)
+    }
+
+    @After
+    override fun tearDown() {
+        super.tearDown()
+        setIncludeTestInterfaces(false)
+    }
+
+    @Test
+    fun testMdnsDiscoveryCanSendPacketOnLocalOnlyDownstreamTetheringInterface() {
+        assumeFalse(isInterfaceForTetheringAvailable)
+
+        var downstreamIface: TestNetworkInterface? = null
+        var tetheringEventCallback: MyTetheringEventCallback? = null
+        var downstreamReader: TapPacketReader? = null
+
+        val discoveryRecord = NsdDiscoveryRecord()
+
+        tryTest {
+            downstreamIface = createTestInterface()
+            val iface = tetheredInterface
+            assertEquals(iface, downstreamIface?.interfaceName)
+            val request = TetheringRequest.Builder(TETHERING_ETHERNET)
+                .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
+            tetheringEventCallback = enableEthernetTethering(
+                iface, request,
+                null /* any upstream */
+            ).apply {
+                awaitInterfaceLocalOnly()
+            }
+            // This shouldn't be flaky because the TAP interface will buffer all packets even
+            // before the reader is started.
+            downstreamReader = makePacketReader(downstreamIface)
+            waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS)
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+            discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
+            assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
+        } cleanupStep {
+            maybeStopTapPacketReader(downstreamReader)
+        } cleanupStep {
+            maybeCloseTestInterface(downstreamIface)
+        } cleanup {
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback)
+        }
+    }
+
+    @Test
+    fun testMdnsDiscoveryWorkOnTetheringInterface() {
+        assumeFalse(isInterfaceForTetheringAvailable)
+        setIncludeTestInterfaces(true)
+
+        var downstreamIface: TestNetworkInterface? = null
+        var tetheringEventCallback: MyTetheringEventCallback? = null
+        var downstreamReader: TapPacketReader? = null
+
+        val discoveryRecord = NsdDiscoveryRecord()
+
+        tryTest {
+            downstreamIface = createTestInterface()
+            val iface = tetheredInterface
+            assertEquals(iface, downstreamIface?.interfaceName)
+
+            val localAddr = LinkAddress("192.0.2.3/28")
+            val clientAddr = LinkAddress("192.0.2.2/28")
+            val request = TetheringRequest.Builder(TETHERING_ETHERNET)
+                .setStaticIpv4Addresses(localAddr, clientAddr)
+                .setShouldShowEntitlementUi(false).build()
+            tetheringEventCallback = enableEthernetTethering(
+                iface, request,
+                null /* any upstream */
+            ).apply {
+                awaitInterfaceTethered()
+            }
+
+            val fd = downstreamIface?.fileDescriptor?.fileDescriptor
+            assertNotNull(fd)
+            downstreamReader = makePacketReader(fd, getMTU(downstreamIface))
+
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+            discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
+            assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+            // TODO: Add another test to check packet reply can trigger serviceFound.
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
+        } cleanupStep {
+            maybeStopTapPacketReader(downstreamReader)
+        } cleanupStep {
+            maybeCloseTestInterface(downstreamIface)
+        } cleanup {
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback)
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 17a135a..27bd5d3 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -38,36 +38,26 @@
 import android.net.TestNetworkManager
 import android.net.TestNetworkSpecifier
 import android.net.connectivity.ConnectivityCompatChanges
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StartDiscoveryFailed
-import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StopDiscoveryFailed
-import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
-import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
-import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
-import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed
-import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolutionStopped
-import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed
-import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved
-import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.StopResolutionFailed
-import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.RegisterCallbackFailed
-import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
-import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
-import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
+import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
+import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
+import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
+import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import android.net.cts.NsdResolveRecord.ResolveEvent.ResolutionStopped
+import android.net.cts.NsdResolveRecord.ResolveEvent.ServiceResolved
+import android.net.cts.NsdResolveRecord.ResolveEvent.StopResolutionFailed
+import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
+import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
+import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
 import android.net.cts.util.CtsNetUtils
 import android.net.nsd.NsdManager
-import android.net.nsd.NsdManager.DiscoveryListener
-import android.net.nsd.NsdManager.RegistrationListener
-import android.net.nsd.NsdManager.ResolveListener
 import android.net.nsd.NsdServiceInfo
 import android.net.nsd.OffloadEngine
 import android.net.nsd.OffloadServiceInfo
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
-import android.os.Process.myTid
 import android.platform.test.annotations.AppModeFull
 import android.system.ErrnoException
 import android.system.Os
@@ -84,19 +74,13 @@
 import com.android.compatibility.common.util.PollingCheck
 import com.android.compatibility.common.util.PropertyUtil
 import com.android.modules.utils.build.SdkLevel.isAtLeastU
-import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.DnsPacket
 import com.android.net.module.util.HexDump
-import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
-import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN
-import com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN
 import com.android.net.module.util.PacketBuilder
-import com.android.net.module.util.TrackRecord
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.IPv6UdpFilter
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TapPacketReader
@@ -123,7 +107,6 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
-import kotlin.test.assertTrue
 import kotlin.test.fail
 import org.junit.After
 import org.junit.Assert.assertArrayEquals
@@ -137,7 +120,6 @@
 
 private const val TAG = "NsdManagerTest"
 private const val TIMEOUT_MS = 2000L
-private const val NO_CALLBACK_TIMEOUT_MS = 200L
 // Registration may take a long time if there are devices with the same hostname on the network,
 // as the device needs to try another name and probe again. This is especially true since when using
 // mdnsresponder the usual hostname is "Android", and on conflict "Android-2", "Android-3", ... are
@@ -159,7 +141,9 @@
     val ignoreRule = DevSdkIgnoreRule()
 
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
-    private val nsdManager by lazy { context.getSystemService(NsdManager::class.java)!! }
+    private val nsdManager by lazy {
+        context.getSystemService(NsdManager::class.java) ?: fail("Could not get NsdManager service")
+    }
 
     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
@@ -185,192 +169,6 @@
         }
     }
 
-    private interface NsdEvent
-    private open class NsdRecord<T : NsdEvent> private constructor(
-        private val history: ArrayTrackRecord<T>,
-        private val expectedThreadId: Int? = null
-    ) : TrackRecord<T> by history {
-        constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
-
-        val nextEvents = history.newReadHead()
-
-        override fun add(e: T): Boolean {
-            if (expectedThreadId != null) {
-                assertEquals(expectedThreadId, myTid(), "Callback is running on the wrong thread")
-            }
-            return history.add(e)
-        }
-
-        inline fun <reified V : NsdEvent> expectCallbackEventually(
-            timeoutMs: Long = TIMEOUT_MS,
-            crossinline predicate: (V) -> Boolean = { true }
-        ): V = nextEvents.poll(timeoutMs) { e -> e is V && predicate(e) } as V?
-                ?: fail("Callback for ${V::class.java.simpleName} not seen after $timeoutMs ms")
-
-        inline fun <reified V : NsdEvent> expectCallback(timeoutMs: Long = TIMEOUT_MS): V {
-            val nextEvent = nextEvents.poll(timeoutMs)
-            assertNotNull(nextEvent, "No callback received after $timeoutMs ms, " +
-                    "expected ${V::class.java.simpleName}")
-            assertTrue(nextEvent is V, "Expected ${V::class.java.simpleName} but got " +
-                    nextEvent.javaClass.simpleName)
-            return nextEvent
-        }
-
-        inline fun assertNoCallback(timeoutMs: Long = NO_CALLBACK_TIMEOUT_MS) {
-            val cb = nextEvents.poll(timeoutMs)
-            assertNull(cb, "Expected no callback but got $cb")
-        }
-    }
-
-    private class NsdRegistrationRecord(expectedThreadId: Int? = null) : RegistrationListener,
-            NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
-        sealed class RegistrationEvent : NsdEvent {
-            abstract val serviceInfo: NsdServiceInfo
-
-            data class RegistrationFailed(
-                override val serviceInfo: NsdServiceInfo,
-                val errorCode: Int
-            ) : RegistrationEvent()
-
-            data class UnregistrationFailed(
-                override val serviceInfo: NsdServiceInfo,
-                val errorCode: Int
-            ) : RegistrationEvent()
-
-            data class ServiceRegistered(override val serviceInfo: NsdServiceInfo) :
-                    RegistrationEvent()
-            data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo) :
-                    RegistrationEvent()
-        }
-
-        override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
-            add(RegistrationFailed(si, err))
-        }
-
-        override fun onUnregistrationFailed(si: NsdServiceInfo, err: Int) {
-            add(UnregistrationFailed(si, err))
-        }
-
-        override fun onServiceRegistered(si: NsdServiceInfo) {
-            add(ServiceRegistered(si))
-        }
-
-        override fun onServiceUnregistered(si: NsdServiceInfo) {
-            add(ServiceUnregistered(si))
-        }
-    }
-
-    private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
-            DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
-        sealed class DiscoveryEvent : NsdEvent {
-            data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int) :
-                    DiscoveryEvent()
-
-            data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int) :
-                    DiscoveryEvent()
-
-            data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
-            data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
-            data class ServiceFound(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
-            data class ServiceLost(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
-        }
-
-        override fun onStartDiscoveryFailed(serviceType: String, err: Int) {
-            add(StartDiscoveryFailed(serviceType, err))
-        }
-
-        override fun onStopDiscoveryFailed(serviceType: String, err: Int) {
-            add(StopDiscoveryFailed(serviceType, err))
-        }
-
-        override fun onDiscoveryStarted(serviceType: String) {
-            add(DiscoveryStarted(serviceType))
-        }
-
-        override fun onDiscoveryStopped(serviceType: String) {
-            add(DiscoveryStopped(serviceType))
-        }
-
-        override fun onServiceFound(si: NsdServiceInfo) {
-            add(ServiceFound(si))
-        }
-
-        override fun onServiceLost(si: NsdServiceInfo) {
-            add(ServiceLost(si))
-        }
-
-        fun waitForServiceDiscovered(
-            serviceName: String,
-            serviceType: String,
-            expectedNetwork: Network? = null
-        ): NsdServiceInfo {
-            val serviceFound = expectCallbackEventually<ServiceFound> {
-                it.serviceInfo.serviceName == serviceName &&
-                        (expectedNetwork == null ||
-                                expectedNetwork == it.serviceInfo.network)
-            }.serviceInfo
-            // Discovered service types have a dot at the end
-            assertEquals("$serviceType.", serviceFound.serviceType)
-            return serviceFound
-        }
-    }
-
-    private class NsdResolveRecord : ResolveListener,
-            NsdRecord<NsdResolveRecord.ResolveEvent>() {
-        sealed class ResolveEvent : NsdEvent {
-            data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
-                    ResolveEvent()
-
-            data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
-            data class ResolutionStopped(val serviceInfo: NsdServiceInfo) : ResolveEvent()
-            data class StopResolutionFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
-                    ResolveEvent()
-        }
-
-        override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
-            add(ResolveFailed(si, err))
-        }
-
-        override fun onServiceResolved(si: NsdServiceInfo) {
-            add(ServiceResolved(si))
-        }
-
-        override fun onResolutionStopped(si: NsdServiceInfo) {
-            add(ResolutionStopped(si))
-        }
-
-        override fun onStopResolutionFailed(si: NsdServiceInfo, err: Int) {
-            super.onStopResolutionFailed(si, err)
-            add(StopResolutionFailed(si, err))
-        }
-    }
-
-    private class NsdServiceInfoCallbackRecord : NsdManager.ServiceInfoCallback,
-            NsdRecord<NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent>() {
-        sealed class ServiceInfoCallbackEvent : NsdEvent {
-            data class RegisterCallbackFailed(val errorCode: Int) : ServiceInfoCallbackEvent()
-            data class ServiceUpdated(val serviceInfo: NsdServiceInfo) : ServiceInfoCallbackEvent()
-            object ServiceUpdatedLost : ServiceInfoCallbackEvent()
-            object UnregisterCallbackSucceeded : ServiceInfoCallbackEvent()
-        }
-
-        override fun onServiceInfoCallbackRegistrationFailed(err: Int) {
-            add(RegisterCallbackFailed(err))
-        }
-
-        override fun onServiceUpdated(si: NsdServiceInfo) {
-            add(ServiceUpdated(si))
-        }
-
-        override fun onServiceLost() {
-            add(ServiceUpdatedLost)
-        }
-
-        override fun onServiceInfoCallbackUnregistered() {
-            add(UnregisterCallbackSucceeded)
-        }
-    }
-
     private class TestNsdOffloadEngine : OffloadEngine,
         NsdRecord<TestNsdOffloadEngine.OffloadEvent>() {
         sealed class OffloadEvent : NsdEvent {
@@ -1414,54 +1212,6 @@
     }
 }
 
-private fun TapPacketReader.pollForMdnsPacket(
-    timeoutMs: Long = REGISTRATION_TIMEOUT_MS,
-    predicate: (TestDnsPacket) -> Boolean
-): ByteArray? {
-    val mdnsProbeFilter = IPv6UdpFilter(srcPort = MDNS_PORT, dstPort = MDNS_PORT).and {
-        val mdnsPayload = it.copyOfRange(
-                ETHER_HEADER_LEN + IPV6_HEADER_LEN + UDP_HEADER_LEN, it.size)
-        try {
-            predicate(TestDnsPacket(mdnsPayload))
-        } catch (e: DnsPacket.ParseException) {
-            false
-        }
-    }
-    return poll(timeoutMs, mdnsProbeFilter)
-}
-
-private fun TapPacketReader.pollForProbe(
-    serviceName: String,
-    serviceType: String,
-    timeoutMs: Long = REGISTRATION_TIMEOUT_MS
-): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isProbeFor("$serviceName.$serviceType.local") }
-
-private fun TapPacketReader.pollForAdvertisement(
-    serviceName: String,
-    serviceType: String,
-    timeoutMs: Long = REGISTRATION_TIMEOUT_MS
-): ByteArray? = pollForMdnsPacket(timeoutMs) { it.isReplyFor("$serviceName.$serviceType.local") }
-
-private class TestDnsPacket(data: ByteArray) : DnsPacket(data) {
-    val header: DnsHeader
-        get() = mHeader
-    val records: Array<List<DnsRecord>>
-        get() = mRecords
-
-    fun isProbeFor(name: String): Boolean = mRecords[QDSECTION].any {
-        it.dName == name && it.nsType == 0xff /* ANY */
-    }
-
-    fun isReplyFor(name: String): Boolean = mRecords[ANSECTION].any {
-        it.dName == name && it.nsType == 0x21 /* SRV */
-    }
-}
-
-private fun ByteArray?.utf8ToString(): String {
-    if (this == null) return ""
-    return String(this, StandardCharsets.UTF_8)
-}
-
 private fun ByteArray.indexOf(sub: ByteArray): Int {
     var subIndex = 0
     forEachIndexed { i, b ->
@@ -1481,3 +1231,8 @@
     }
     return -1
 }
+
+private fun ByteArray?.utf8ToString(): String {
+    if (this == null) return ""
+    return String(this, StandardCharsets.UTF_8)
+}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index aa09b84..96330e2 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -65,6 +65,7 @@
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.ConnectivitySettingsUtils;
 import com.android.testutils.ConnectUtil;
 
@@ -590,8 +591,12 @@
                         callback.waitForAvailable());
             }
 
-            runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabledForReason(
-                    TelephonyManager.DATA_ENABLED_REASON_USER, enabled));
+            if (SdkLevel.isAtLeastS()) {
+                runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabledForReason(
+                        TelephonyManager.DATA_ENABLED_REASON_USER, enabled));
+            } else {
+                runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabled(enabled));
+            }
             if (enabled) {
                 assertNotNull("Enabling mobile data did not connect mobile data",
                         callback.waitForAvailable());
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 28edcb2..edd201d 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -64,6 +64,9 @@
 import java.util.function.Consumer;
 
 public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
+    // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+    // please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
+    // tools in ConnectivityServiceTest.
     private final NetworkCapabilities mNetworkCapabilities;
     private final HandlerThread mHandlerThread;
     private final Context mContext;
@@ -468,4 +471,8 @@
     public boolean isBypassableVpn() {
         return mNetworkAgentConfig.isBypassableVpn();
     }
+
+    // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+    // please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
+    // tools in ConnectivityServiceTest.
 }
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index 4ff131b..70ddc17 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -51,6 +51,7 @@
 
 import com.google.android.collect.Sets;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -63,7 +64,8 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class NetworkStatsTest {
-
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test2";
     private static final int TEST_UID = 1001;
@@ -339,6 +341,7 @@
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
 
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void testGroupedByIfaceEmpty() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3);
@@ -348,6 +351,7 @@
         assertEquals(0, grouped.size());
     }
 
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
@@ -366,6 +370,7 @@
                 DEFAULT_NETWORK_ALL, 384L, 24L, 0L, 6L, 0L);
     }
 
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
@@ -1068,7 +1073,7 @@
     }
 
     @Test
-    public void testClearInterfaces() {
+    public void testWithoutInterfaces() {
         final NetworkStats stats = new NetworkStats(TEST_START, 1);
         final NetworkStats.Entry entry1 = new NetworkStats.Entry(
                 "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
@@ -1093,10 +1098,10 @@
         assertValues(stats, 2, "test3", 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
                 ROAMING_NO, DEFAULT_NETWORK_NO, 1, 2L, 3L, 4L, 5L);
 
-        // Clear interfaces.
-        final NetworkStats ifaceClearedStats = stats.clearInterfaces();
+        // Get stats without interfaces.
+        final NetworkStats ifaceClearedStats = stats.withoutInterfaces();
 
-        // Verify that the interfaces are cleared, and key-duplicated items are merged.
+        // Verify that the interfaces do not exist, and key-duplicated items are merged.
         assertEquals(2, ifaceClearedStats.size());
         assertValues(ifaceClearedStats, 0, null /* iface */, 10100, SET_DEFAULT, TAG_NONE,
                 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
diff --git a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
new file mode 100644
index 0000000..3043d50
--- /dev/null
+++ b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt
@@ -0,0 +1,173 @@
+package com.android.metrics
+
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.CONNECTIVITY_MANAGED_CAPABILITIES
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE
+import android.net.NetworkCapabilities.NET_CAPABILITY_IMS
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1
+import android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkScore
+import android.net.NetworkScore.POLICY_EXITING
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
+import android.os.Build
+import android.os.Handler
+import android.stats.connectivity.MeteredState
+import android.stats.connectivity.ValidatedState
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.BitUtils
+import com.android.server.CSTest
+import com.android.server.FromS
+import com.android.server.connectivity.FullScore
+import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+private fun <T> Handler.onHandler(f: () -> T): T {
+    val future = CompletableFuture<T>()
+    post { future.complete(f()) }
+    return future.get()
+}
+
+private fun flags(vararg flags: Int) = flags.fold(0L) { acc, it -> acc or (1L shl it) }
+
+private fun Number.toTransportsString() = StringBuilder().also { sb ->
+    BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
+            { NetworkCapabilities.transportNameOf(it) }, "|") }.toString()
+
+private fun Number.toCapsString() = StringBuilder().also { sb ->
+    BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
+            { NetworkCapabilities.capabilityNameOf(it) }, "&") }.toString()
+
+private fun Number.toPolicyString() = StringBuilder().also {sb ->
+    BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
+            { FullScore.policyNameOf(it) }, "|") }.toString()
+
+private fun Number.exceptCSManaged() = this.toLong() and CONNECTIVITY_MANAGED_CAPABILITIES.inv()
+
+private val NetworkCapabilities.meteredState get() = when {
+    hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) ->
+        MeteredState.METERED_TEMPORARILY_UNMETERED
+    hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ->
+        MeteredState.METERED_NO
+    else ->
+        MeteredState.METERED_YES
+}
+
+private val NetworkCapabilities.validatedState get() = when {
+    hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) -> ValidatedState.VS_PORTAL
+    hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY) -> ValidatedState.VS_PARTIAL
+    hasCapability(NET_CAPABILITY_VALIDATED) -> ValidatedState.VS_VALID
+    else -> ValidatedState.VS_INVALID
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class ConnectivitySampleMetricsTest : CSTest() {
+    @Test
+    fun testSampleConnectivityState() {
+        val wifi1Caps = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_NOT_METERED)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NET_CAPABILITY_NOT_ROAMING)
+                .build()
+        val wifi1Score = NetworkScore.Builder().setExiting(true).build()
+        val agentWifi1 = Agent(nc = wifi1Caps, score = FromS(wifi1Score)).also { it.connect() }
+
+        val wifi2Caps = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_ENTERPRISE)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NET_CAPABILITY_NOT_ROAMING)
+                .addEnterpriseId(NET_ENTERPRISE_ID_3)
+                .build()
+        val wifi2Score = NetworkScore.Builder().setTransportPrimary(true).build()
+        val agentWifi2 = Agent(nc = wifi2Caps, score = FromS(wifi2Score)).also { it.connect() }
+
+        val cellCaps = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_IMS)
+                .addCapability(NET_CAPABILITY_ENTERPRISE)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NET_CAPABILITY_NOT_ROAMING)
+                .addEnterpriseId(NET_ENTERPRISE_ID_1)
+                .build()
+        val cellScore = NetworkScore.Builder().build()
+        val agentCell = Agent(nc = cellCaps, score = FromS(cellScore)).also { it.connect() }
+
+        val stats = csHandler.onHandler { service.sampleConnectivityState() }
+        assertEquals(3, stats.networks.networkDescriptionList.size)
+        val foundCell = stats.networks.networkDescriptionList.find {
+            it.transportTypes == (1 shl TRANSPORT_CELLULAR)
+        } ?: fail("Can't find cell network (searching by transport)")
+        val foundWifi1 = stats.networks.networkDescriptionList.find {
+            it.transportTypes == (1 shl TRANSPORT_WIFI) &&
+                    0L != (it.capabilities and (1L shl NET_CAPABILITY_NOT_METERED))
+        } ?: fail("Can't find wifi1 (searching by WIFI transport and the NOT_METERED capability)")
+        val foundWifi2 = stats.networks.networkDescriptionList.find {
+            it.transportTypes == (1 shl TRANSPORT_WIFI) &&
+                    0L != (it.capabilities and (1L shl NET_CAPABILITY_ENTERPRISE))
+        } ?: fail("Can't find wifi2 (searching by WIFI transport and the ENTERPRISE capability)")
+
+        fun checkNetworkDescription(
+                network: String,
+                found: NetworkDescription,
+                expected: NetworkCapabilities
+        ) {
+            assertEquals(expected.transportTypesInternal, found.transportTypes.toLong(),
+                    "Transports differ for network $network, " +
+                            "expected ${expected.transportTypesInternal.toTransportsString()}, " +
+                            "found ${found.transportTypes.toTransportsString()}")
+            val expectedCaps = expected.capabilitiesInternal.exceptCSManaged()
+            val foundCaps = found.capabilities.exceptCSManaged()
+            assertEquals(expectedCaps, foundCaps,
+                    "Capabilities differ for network $network, " +
+                            "expected ${expectedCaps.toCapsString()}, " +
+                            "found ${foundCaps.toCapsString()}")
+            assertEquals(expected.enterpriseIdsInternal, found.enterpriseId,
+                    "Enterprise IDs differ for network $network, " +
+                            "expected ${expected.enterpriseIdsInternal}," +
+                            " found ${found.enterpriseId}")
+            assertEquals(expected.meteredState, found.meteredState,
+                    "Metered states differ for network $network, " +
+                            "expected ${expected.meteredState}, " +
+                            "found ${found.meteredState}")
+            assertEquals(expected.validatedState, found.validatedState,
+                    "Validated states differ for network $network, " +
+                            "expected ${expected.validatedState}, " +
+                            "found ${found.validatedState}")
+        }
+
+        checkNetworkDescription("Cell network", foundCell, cellCaps)
+        checkNetworkDescription("Wifi1", foundWifi1, wifi1Caps)
+        checkNetworkDescription("Wifi2", foundWifi2, wifi2Caps)
+
+        assertEquals(0, foundCell.scorePolicies, "Cell score policies incorrect, expected 0, " +
+                        "found ${foundCell.scorePolicies.toPolicyString()}")
+        val expectedWifi1Policies = flags(POLICY_EXITING, POLICY_IS_UNMETERED)
+        assertEquals(expectedWifi1Policies, foundWifi1.scorePolicies,
+                "Wifi1 score policies incorrect, " +
+                        "expected ${expectedWifi1Policies.toPolicyString()}, " +
+                        "found ${foundWifi1.scorePolicies.toPolicyString()}")
+        val expectedWifi2Policies = flags(POLICY_TRANSPORT_PRIMARY)
+        assertEquals(expectedWifi2Policies, foundWifi2.scorePolicies,
+                "Wifi2 score policies incorrect, " +
+                        "expected ${expectedWifi2Policies.toPolicyString()}, " +
+                        "found ${foundWifi2.scorePolicies.toPolicyString()}")
+    }
+}
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index 7f893df..3f6e88d 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -21,12 +21,15 @@
 import android.stats.connectivity.NsdEventType
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
+import java.util.Random
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 
@@ -34,14 +37,20 @@
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
 class NetworkNsdReportedMetricsTest {
     private val deps = mock(NetworkNsdReportedMetrics.Dependencies::class.java)
+    private val random = mock(Random::class.java)
+
+    @Before
+    fun setUp() {
+        doReturn(random).`when`(deps).makeRandomGenerator()
+    }
 
     @Test
     fun testReportServiceRegistrationSucceeded() {
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceRegistrationSucceeded(transactionId, durationMs)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceRegistrationSucceeded(true /* isLegacy */, transactionId, durationMs)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -60,8 +69,8 @@
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
-        metrics.reportServiceRegistrationFailed(transactionId, durationMs)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceRegistrationFailed(false /* isLegacy */, transactionId, durationMs)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -80,8 +89,14 @@
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceUnregistration(transactionId, durationMs)
+        val repliedRequestsCount = 25
+        val sentPacketCount = 50
+        val conflictDuringProbingCount = 2
+        val conflictAfterProbingCount = 1
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceUnregistration(true /* isLegacy */, transactionId, durationMs,
+                repliedRequestsCount, sentPacketCount, conflictDuringProbingCount,
+                conflictAfterProbingCount)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -92,6 +107,10 @@
             assertEquals(NsdEventType.NET_REGISTER, it.type)
             assertEquals(MdnsQueryResult.MQR_SERVICE_UNREGISTERED, it.queryResult)
             assertEquals(durationMs, it.eventDurationMillisec)
+            assertEquals(repliedRequestsCount, it.repliedRequestsCount)
+            assertEquals(sentPacketCount, it.sentPacketCount)
+            assertEquals(conflictDuringProbingCount, it.conflictDuringProbingCount)
+            assertEquals(conflictAfterProbingCount, it.conflictAfterProbingCount)
         }
     }
 
@@ -99,8 +118,8 @@
     fun testReportServiceDiscoveryStarted() {
         val clientId = 99
         val transactionId = 100
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceDiscoveryStarted(transactionId)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceDiscoveryStarted(true /* isLegacy */, transactionId)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -118,8 +137,8 @@
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
-        metrics.reportServiceDiscoveryFailed(transactionId, durationMs)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceDiscoveryFailed(false /* isLegacy */, transactionId, durationMs)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -142,9 +161,9 @@
         val lostCallbackCount = 49
         val servicesCount = 75
         val sentQueryCount = 150
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceDiscoveryStop(transactionId, durationMs, foundCallbackCount,
-                lostCallbackCount, servicesCount, sentQueryCount)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId, durationMs,
+                foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -169,9 +188,9 @@
         val transactionId = 100
         val durationMs = 10L
         val sentQueryCount = 0
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceResolved(transactionId, durationMs, true /* isServiceFromCache */,
-                sentQueryCount)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceResolved(true /* isLegacy */, transactionId, durationMs,
+                true /* isServiceFromCache */, sentQueryCount)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -192,8 +211,8 @@
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
-        metrics.reportServiceResolutionFailed(transactionId, durationMs)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceResolutionFailed(false /* isLegacy */, transactionId, durationMs)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -212,8 +231,8 @@
         val clientId = 99
         val transactionId = 100
         val durationMs = 10L
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
-        metrics.reportServiceResolutionStop(transactionId, durationMs)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
+        metrics.reportServiceResolutionStop(true /* isLegacy */, transactionId, durationMs)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
@@ -231,7 +250,7 @@
     fun testReportServiceInfoCallbackRegistered() {
         val clientId = 99
         val transactionId = 100
-        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
         metrics.reportServiceInfoCallbackRegistered(transactionId)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
@@ -249,13 +268,13 @@
     fun testReportServiceInfoCallbackRegistrationFailed() {
         val clientId = 99
         val transactionId = 100
-        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
         metrics.reportServiceInfoCallbackRegistrationFailed(transactionId)
 
         val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
         verify(deps).statsWrite(eventCaptor.capture())
         eventCaptor.value.let {
-            assertTrue(it.isLegacy)
+            assertFalse(it.isLegacy)
             assertEquals(clientId, it.clientId)
             assertEquals(transactionId, it.transactionId)
             assertEquals(NsdEventType.NET_SERVICE_INFO_CALLBACK, it.type)
@@ -272,7 +291,7 @@
         val updateCallbackCount = 100
         val lostCallbackCount = 10
         val sentQueryCount = 150
-        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+        val metrics = NetworkNsdReportedMetrics(clientId, deps)
         metrics.reportServiceInfoCallbackUnregistered(transactionId, durationMs,
                 updateCallbackCount, lostCallbackCount, false /* isServiceFromCache */,
                 sentQueryCount)
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index e5dec56..2fccdcb 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -75,10 +75,7 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
-import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
-import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
@@ -492,6 +489,8 @@
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.ConnectivityServiceTest
  */
+// TODO : move methods from this test to smaller tests in the 'connectivityservice' directory
+// to enable faster testing of smaller groups of functionality.
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
@@ -756,6 +755,9 @@
             if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
             if (Context.ACTIVITY_SERVICE.equals(name)) return mActivityManager;
             if (Context.TELEPHONY_SUBSCRIPTION_SERVICE.equals(name)) return mSubscriptionManager;
+            // StatsManager is final and can't be mocked, and uses static methods for mostly
+            // everything. The simplest fix is to return null and not have metrics in tests.
+            if (Context.STATS_MANAGER.equals(name)) return null;
             return super.getSystemService(name);
         }
 
@@ -1016,6 +1018,9 @@
     }
 
     private class TestNetworkAgentWrapper extends NetworkAgentWrapper {
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
         private static final int VALIDATION_RESULT_INVALID = 0;
 
         private static final long DATA_STALL_TIMESTAMP = 10L;
@@ -1340,6 +1345,9 @@
      * operations have been processed and test for them.
      */
     private static class MockNetworkFactory extends NetworkFactory {
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSTest and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
         private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
 
         static class RequestEntry {
@@ -1476,6 +1484,10 @@
     }
 
     private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSTest and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
+
         // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
         // not inherit from NetworkAgent.
         private TestNetworkAgentWrapper mMockNetworkAgent;
@@ -1852,6 +1864,9 @@
 
         MockitoAnnotations.initMocks(this);
 
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSTest and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
         doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
         doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
         doReturn(PRIMARY_USER_INFO).when(mUserManager).getUserInfo(PRIMARY_USER);
@@ -1938,6 +1953,9 @@
         setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
         setAlwaysOnNetworks(false);
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSTest and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
     }
 
     private void initMockedResources() {
@@ -1974,6 +1992,9 @@
         final ConnectivityResources mConnRes;
         final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>();
 
+        // Note : Please do not add any new instrumentation here. If you need new instrumentation,
+        // please add it in CSTest and use subclasses of CSTest instead of adding more
+        // tools in ConnectivityServiceTest.
         ConnectivityServiceDependencies(final Context mockResContext) {
             mConnRes = new ConnectivityResources(mockResContext);
         }
@@ -2583,23 +2604,6 @@
     }
 
     @Test
-    public void testNetworkTypes() {
-        // Ensure that our mocks for the networkAttributes config variable work as expected. If they
-        // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
-        // will fail. Failing here is much easier to debug.
-        assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
-        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
-        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
-        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
-        assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
-
-        // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
-        // mocks, this assert exercises the ConnectivityService code path that ensures that
-        // TYPE_ETHERNET is supported if the ethernet service is running.
-        assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
-    }
-
-    @Test
     public void testNetworkFeature() throws Exception {
         // Connect the cell agent and wait for the connected broadcast.
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -18801,4 +18805,7 @@
 
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
     }
+
+    // Note : adding tests is ConnectivityServiceTest is deprecated, as it is too big for
+    // maintenance. Please consider adding new tests in subclasses of CSTest instead.
 }
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 695cfe8..71bd330 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -35,24 +35,20 @@
 import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
 import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
-
 import static com.android.networkstack.apishim.api33.ConstantsShim.REGISTER_NSD_OFFLOAD_ENGINE;
 import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
 import static com.android.server.NsdService.MdnsListener;
 import static com.android.server.NsdService.NO_TRANSACTION;
 import static com.android.server.NsdService.parseTypeAndSubtype;
 import static com.android.testutils.ContextUtils.mockService;
-
 import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -229,8 +225,8 @@
         doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
         doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
                 eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
-        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
-        doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyBoolean(), anyInt());
+        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any(), any());
+        doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyInt());
         doReturn(mClock).when(mDeps).makeClock();
         doReturn(TEST_TIME_MS).when(mClock).elapsedRealtime();
         mService = makeService();
@@ -415,7 +411,7 @@
         // this needs to use a timeout
         verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
         final int discId = discIdCaptor.getValue();
-        verify(mMetrics).reportServiceDiscoveryStarted(discId);
+        verify(mMetrics).reportServiceDiscoveryStarted(true /* isLegacy */, discId);
 
         final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
                 discId,
@@ -484,7 +480,7 @@
         final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
-        verify(mMetrics).reportServiceResolved(getAddrId, 10L /* durationMs */,
+        verify(mMetrics).reportServiceResolved(true /* isLegacy */, getAddrId, 10L /* durationMs */,
                 false /* isServiceFromCache */, 0 /* sentQueryCount */);
 
         final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
@@ -512,7 +508,7 @@
         // this needs to use a timeout
         verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
         final int discId = discIdCaptor.getValue();
-        verify(mMetrics).reportServiceDiscoveryStarted(discId);
+        verify(mMetrics).reportServiceDiscoveryStarted(true /* isLegacy */, discId);
 
         final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
                 discId,
@@ -562,7 +558,8 @@
                 .onServiceRegistered(registeredInfoCaptor.capture());
         final NsdServiceInfo registeredInfo = registeredInfoCaptor.getValue();
         assertEquals(SERVICE_NAME, registeredInfo.getServiceName());
-        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceRegistrationSucceeded(
+                true /* isLegacy */, regId, 10L /* durationMs */);
 
         // Fail to register service.
         final RegistrationInfo registrationFailedInfo = new RegistrationInfo(
@@ -577,7 +574,8 @@
         eventListener.onServiceRegistrationStatus(registrationFailedInfo);
         verify(regListener, timeout(TIMEOUT_MS))
                 .onRegistrationFailed(any(), eq(FAILURE_INTERNAL_ERROR));
-        verify(mMetrics).reportServiceRegistrationFailed(regId, 20L /* durationMs */);
+        verify(mMetrics).reportServiceRegistrationFailed(
+                true /* isLegacy */, regId, 20L /* durationMs */);
     }
 
     @Test
@@ -593,7 +591,7 @@
         verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE), eq(IFACE_IDX_ANY));
         verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
         final int discId = discIdCaptor.getValue();
-        verify(mMetrics).reportServiceDiscoveryStarted(discId);
+        verify(mMetrics).reportServiceDiscoveryStarted(true /* isLegacy */, discId);
 
         // Fail to discover service.
         final DiscoveryInfo discoveryFailedInfo = new DiscoveryInfo(
@@ -608,7 +606,8 @@
         eventListener.onServiceDiscoveryStatus(discoveryFailedInfo);
         verify(discListener, timeout(TIMEOUT_MS))
                 .onStartDiscoveryFailed(SERVICE_TYPE, FAILURE_INTERNAL_ERROR);
-        verify(mMetrics).reportServiceDiscoveryFailed(discId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceDiscoveryFailed(
+                true /* isLegacy */, discId, 10L /* durationMs */);
     }
 
     @Test
@@ -642,7 +641,8 @@
         eventListener.onServiceResolutionStatus(resolutionFailedInfo);
         verify(resolveListener, timeout(TIMEOUT_MS))
                 .onResolveFailed(any(), eq(FAILURE_INTERNAL_ERROR));
-        verify(mMetrics).reportServiceResolutionFailed(resolvId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceResolutionFailed(
+                true /* isLegacy */, resolvId, 10L /* durationMs */);
     }
 
     @Test
@@ -692,7 +692,8 @@
         eventListener.onGettingServiceAddressStatus(gettingAddrFailedInfo);
         verify(resolveListener, timeout(TIMEOUT_MS))
                 .onResolveFailed(any(), eq(FAILURE_INTERNAL_ERROR));
-        verify(mMetrics).reportServiceResolutionFailed(getAddrId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceResolutionFailed(
+                true /* isLegacy */, getAddrId, 10L /* durationMs */);
     }
 
     @Test
@@ -737,7 +738,8 @@
         verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
-        verify(mMetrics).reportServiceResolutionStop(resolveId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceResolutionStop(
+                true /* isLegacy */, resolveId, 10L /* durationMs */);
     }
 
     @Test
@@ -808,7 +810,8 @@
         verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
-        verify(mMetrics).reportServiceResolutionStop(getAddrId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceResolutionStop(
+                true /* isLegacy */, getAddrId, 10L /* durationMs */);
     }
 
     private void verifyUpdatedServiceInfo(NsdServiceInfo info, String serviceName,
@@ -999,7 +1002,7 @@
 
         final MdnsListener listener = listenerCaptor.getValue();
         final int discId = listener.mTransactionId;
-        verify(mMetrics).reportServiceDiscoveryStarted(discId);
+        verify(mMetrics).reportServiceDiscoveryStarted(false /* isLegacy */, discId);
 
         // Callbacks for query sent.
         listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
@@ -1053,9 +1056,9 @@
         verify(mDiscoveryManager).unregisterListener(eq(serviceTypeWithLocalDomain), any());
         verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
         verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
-        verify(mMetrics).reportServiceDiscoveryStop(discId, 10L /* durationMs */,
-                1 /* foundCallbackCount */, 1 /* lostCallbackCount */, 1 /* servicesCount */,
-                3 /* sentQueryCount */);
+        verify(mMetrics).reportServiceDiscoveryStop(false /* isLegacy */, discId,
+                10L /* durationMs */, 1 /* foundCallbackCount */, 1 /* lostCallbackCount */,
+                1 /* servicesCount */, 3 /* sentQueryCount */);
     }
 
     @Test
@@ -1071,8 +1074,8 @@
         waitForIdle();
         verify(discListener, timeout(TIMEOUT_MS))
                 .onStartDiscoveryFailed(invalidServiceType, FAILURE_INTERNAL_ERROR);
-        verify(mMetrics, times(1))
-                .reportServiceDiscoveryFailed(NO_TRANSACTION, 0L /* durationMs */);
+        verify(mMetrics, times(1)).reportServiceDiscoveryFailed(
+                false /* isLegacy */, NO_TRANSACTION, 0L /* durationMs */);
 
         final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         client.discoverServices(
@@ -1080,8 +1083,8 @@
         waitForIdle();
         verify(discListener, timeout(TIMEOUT_MS))
                 .onStartDiscoveryFailed(serviceTypeWithLocalDomain, FAILURE_INTERNAL_ERROR);
-        verify(mMetrics, times(2))
-                .reportServiceDiscoveryFailed(NO_TRANSACTION, 0L /* durationMs */);
+        verify(mMetrics, times(2)).reportServiceDiscoveryFailed(
+                false /* isLegacy */, NO_TRANSACTION, 0L /* durationMs */);
 
         final String serviceTypeWithoutTcpOrUdpEnding = "_test._com";
         client.discoverServices(
@@ -1089,8 +1092,8 @@
         waitForIdle();
         verify(discListener, timeout(TIMEOUT_MS))
                 .onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
-        verify(mMetrics, times(3))
-                .reportServiceDiscoveryFailed(NO_TRANSACTION, 0L /* durationMs */);
+        verify(mMetrics, times(3)).reportServiceDiscoveryFailed(
+                false /* isLegacy */, NO_TRANSACTION, 0L /* durationMs */);
     }
 
     @Test
@@ -1167,8 +1170,8 @@
         final ArgumentCaptor<NsdServiceInfo> infoCaptor =
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
-        verify(mMetrics).reportServiceResolved(listener.mTransactionId, 10 /* durationMs */,
-                true /* isServiceFromCache */, 0 /* sendQueryCount */);
+        verify(mMetrics).reportServiceResolved(false /* isLegacy */, listener.mTransactionId,
+                10 /* durationMs */, true /* isServiceFromCache */, 0 /* sendQueryCount */);
 
         final NsdServiceInfo info = infoCaptor.getValue();
         assertEquals(SERVICE_NAME, info.getServiceName());
@@ -1222,6 +1225,8 @@
         verify(mMockMDnsM).stopOperation(legacyIdCaptor.getValue());
         verify(mAdvertiser, never()).removeService(anyInt());
 
+        doReturn(mock(MdnsAdvertiser.AdvertiserMetrics.class))
+                .when(mAdvertiser).getAdvertiserMetrics(anyInt());
         client.unregisterService(regListenerWithFeature);
         waitForIdle();
         verify(mAdvertiser).removeService(serviceIdCaptor.getValue());
@@ -1287,7 +1292,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1310,16 +1315,23 @@
 
         verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(argThat(info -> matches(info,
                 new NsdServiceInfo(regInfo.getServiceName(), null))));
-        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceRegistrationSucceeded(
+                false /* isLegacy */, regId, 10L /* durationMs */);
 
+        final MdnsAdvertiser.AdvertiserMetrics metrics = new MdnsAdvertiser.AdvertiserMetrics(
+                50 /* repliedRequestCount */, 100 /* sentPacketCount */,
+                3 /* conflictDuringProbingCount */, 2 /* conflictAfterProbingCount */);
         doReturn(TEST_TIME_MS + 100L).when(mClock).elapsedRealtime();
+        doReturn(metrics).when(mAdvertiser).getAdvertiserMetrics(regId);
         client.unregisterService(regListener);
         waitForIdle();
         verify(mAdvertiser).removeService(idCaptor.getValue());
         verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
                 argThat(info -> matches(info, regInfo)));
         verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
-        verify(mMetrics).reportServiceUnregistration(regId, 100L /* durationMs */);
+        verify(mMetrics).reportServiceUnregistration(false /* isLegacy */, regId,
+                100L /* durationMs */, 50 /* repliedRequestCount */, 100 /* sentPacketCount */,
+                3 /* conflictDuringProbingCount */, 2 /* conflictAfterProbingCount */);
     }
 
     @Test
@@ -1331,7 +1343,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, "invalid_type");
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1345,7 +1357,8 @@
 
         verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
                 argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
-        verify(mMetrics).reportServiceRegistrationFailed(NO_TRANSACTION, 0L /* durationMs */);
+        verify(mMetrics).reportServiceRegistrationFailed(
+                false /* isLegacy */, NO_TRANSACTION, 0L /* durationMs */);
     }
 
     @Test
@@ -1357,7 +1370,7 @@
         // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
         final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
                 ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
-        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any());
 
         final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
         regInfo.setHost(parseNumericAddress("192.0.2.123"));
@@ -1381,7 +1394,8 @@
 
         verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
                 argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
-        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceRegistrationSucceeded(
+                false /* isLegacy */, regId, 10L /* durationMs */);
     }
 
     @Test
@@ -1421,7 +1435,8 @@
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
         verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
-        verify(mMetrics).reportServiceResolutionStop(listener.mTransactionId, 10L /* durationMs */);
+        verify(mMetrics).reportServiceResolutionStop(
+                false /* isLegacy */, listener.mTransactionId, 10L /* durationMs */);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 4f0d46f..56346ad 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -2041,6 +2041,8 @@
 
         // Check if allowBypass is set or not.
         assertTrue(nacCaptor.getValue().isBypassableVpn());
+        // Check if extra info for VPN is set.
+        assertTrue(nacCaptor.getValue().getLegacyExtraInfo().contains(TEST_VPN_PKG));
         final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
         assertTrue(info.isBypassable());
         assertEquals(areLongLivedTcpConnectionsExpensive,
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 862a9ec..8eace1c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -33,7 +33,10 @@
 import com.android.testutils.waitForIdle
 import java.net.NetworkInterface
 import java.util.Objects
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
 import org.junit.After
+import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -143,6 +146,7 @@
     private val mockInterfaceAdvertiser1 = mock(MdnsInterfaceAdvertiser::class.java)
     private val mockInterfaceAdvertiser2 = mock(MdnsInterfaceAdvertiser::class.java)
     private val mockDeps = mock(MdnsAdvertiser.Dependencies::class.java)
+    private val flags = MdnsFeatureFlags.newBuilder().setIsMdnsOffloadFeatureEnabled(true).build()
 
     @Before
     fun setUp() {
@@ -180,7 +184,8 @@
 
     @Test
     fun testAddService_OneNetwork() {
-        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        val advertiser =
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
         postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
 
         val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
@@ -206,6 +211,18 @@
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
         verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
 
+        // Service is conflicted.
+        postSync { intAdvCbCaptor.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+
+        // Verify the metrics data
+        doReturn(25).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
+        doReturn(40).`when`(mockInterfaceAdvertiser1).getSentPacketCount(SERVICE_ID_1)
+        val metrics = postReturn { advertiser.getAdvertiserMetrics(SERVICE_ID_1) }
+        assertEquals(25, metrics.mRepliedRequestsCount)
+        assertEquals(40, metrics.mSentPacketCount)
+        assertEquals(0, metrics.mConflictDuringProbingCount)
+        assertEquals(1, metrics.mConflictAfterProbingCount)
+
         doReturn(TEST_OFFLOAD_PACKET2).`when`(mockInterfaceAdvertiser1)
             .getRawOffloadPayload(
                 SERVICE_ID_1
@@ -227,7 +244,8 @@
 
     @Test
     fun testAddService_AllNetworks() {
-        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        val advertiser =
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
         postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE) }
 
         val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
@@ -265,6 +283,22 @@
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
                 argThat { it.matches(ALL_NETWORKS_SERVICE) })
 
+        // Services are conflicted.
+        postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        postSync { intAdvCbCaptor1.value.onServiceConflict(mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        postSync { intAdvCbCaptor2.value.onServiceConflict(mockInterfaceAdvertiser2, SERVICE_ID_1) }
+
+        // Verify the metrics data
+        doReturn(10).`when`(mockInterfaceAdvertiser1).getServiceRepliedRequestsCount(SERVICE_ID_1)
+        doReturn(5).`when`(mockInterfaceAdvertiser2).getServiceRepliedRequestsCount(SERVICE_ID_1)
+        doReturn(22).`when`(mockInterfaceAdvertiser1).getSentPacketCount(SERVICE_ID_1)
+        doReturn(12).`when`(mockInterfaceAdvertiser2).getSentPacketCount(SERVICE_ID_1)
+        val metrics = postReturn { advertiser.getAdvertiserMetrics(SERVICE_ID_1) }
+        assertEquals(15, metrics.mRepliedRequestsCount)
+        assertEquals(34, metrics.mSentPacketCount)
+        assertEquals(2, metrics.mConflictDuringProbingCount)
+        assertEquals(1, metrics.mConflictAfterProbingCount)
+
         // Unregister the service
         postSync { advertiser.removeService(SERVICE_ID_1) }
         verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
@@ -281,7 +315,8 @@
 
     @Test
     fun testAddService_Conflicts() {
-        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        val advertiser =
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
         postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
 
         val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
@@ -365,7 +400,8 @@
 
     @Test
     fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
-        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        val advertiser =
+            MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
         verify(mockDeps, times(1)).generateHostname()
         postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
         postSync { advertiser.removeService(SERVICE_ID_1) }
@@ -376,6 +412,14 @@
         handler.post(r)
         handler.waitForIdle(TIMEOUT_MS)
     }
+
+    private fun <T> postReturn(r: (() -> T)): T {
+        val future = CompletableFuture<T>()
+        handler.post {
+            future.complete(r())
+        }
+        return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+    }
 }
 
 // NsdServiceInfo does not implement equals; this is useful to use in argument matchers
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 12faa50..c39ee1e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -253,7 +253,7 @@
 
         val captor = ArgumentCaptor.forClass(DatagramPacket::class.java)
         repeat(FIRST_ANNOUNCES_COUNT) { i ->
-            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, request)
+            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, request, 1 /* sentPacketCount */)
             verify(socket, atLeast(i + 1)).send(any())
             val now = SystemClock.elapsedRealtime()
             assertTrue(now > timeStart + startDelay + i * FIRST_ANNOUNCES_DELAY)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 5ca4dd6..f284819 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -92,7 +92,7 @@
 
     private fun assertProbesSent(probeInfo: TestProbeInfo, expectedHex: String) {
         repeat(probeInfo.numSends) { i ->
-            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, probeInfo)
+            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, probeInfo, 1 /* sentPacketCount */)
             // If the probe interval is short, more than (i+1) probes may have been sent already
             verify(socket, atLeast(i + 1)).send(any())
         }
@@ -190,7 +190,7 @@
         prober.startProbing(probeInfo)
 
         // Expect the initial probe
-        verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(0, probeInfo)
+        verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(0, probeInfo, 1 /* sentPacketCount */)
 
         // Stop probing
         val stopResult = CompletableFuture<Boolean>()
@@ -200,7 +200,7 @@
 
         // Wait for a bit (more than the probe delay) to ensure no more probes were sent
         Thread.sleep(SHORT_TIMEOUT_MS * 2)
-        verify(cb, never()).onSent(1, probeInfo)
+        verify(cb, never()).onSent(1, probeInfo, 1 /* sentPacketCount */)
         verify(cb, never()).onFinished(probeInfo)
 
         // Only one sent packet
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 56fbdf0..88fb66a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -155,7 +155,7 @@
 
         val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
         repository.onProbingSucceeded(probingInfo)
-        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
         assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
 
         repository.exitService(TEST_SERVICE_ID_1)
@@ -166,7 +166,7 @@
     fun testExitAnnouncements() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
 
         val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
         assertNotNull(exitAnnouncement)
@@ -182,7 +182,7 @@
                 MdnsPointerRecord(
                         arrayOf("_testservice", "_tcp", "local"),
                         0L /* receiptTimeMillis */,
-                        true /* cacheFlush */,
+                        false /* cacheFlush */,
                         0L /* ttlMillis */,
                         arrayOf("MyTestService", "_testservice", "_tcp", "local"))
         ), packet.answers)
@@ -195,7 +195,7 @@
     fun testExitAnnouncements_WithSubtype() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
-        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
 
         val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
         assertNotNull(exitAnnouncement)
@@ -211,13 +211,13 @@
                 MdnsPointerRecord(
                         arrayOf("_testservice", "_tcp", "local"),
                         0L /* receiptTimeMillis */,
-                        true /* cacheFlush */,
+                        false /* cacheFlush */,
                         0L /* ttlMillis */,
                         arrayOf("MyTestService", "_testservice", "_tcp", "local")),
                 MdnsPointerRecord(
                         arrayOf("_subtype", "_sub", "_testservice", "_tcp", "local"),
                         0L /* receiptTimeMillis */,
-                        true /* cacheFlush */,
+                        false /* cacheFlush */,
                         0L /* ttlMillis */,
                         arrayOf("MyTestService", "_testservice", "_tcp", "local")),
         ), packet.answers)
@@ -230,7 +230,7 @@
     fun testExitingServiceReAdded() {
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
-        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
         repository.exitService(TEST_SERVICE_ID_1)
 
         assertEquals(TEST_SERVICE_ID_1,
@@ -246,7 +246,7 @@
         val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
         val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
                 TEST_SUBTYPE)
-        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1, 2 /* sentPacketCount */)
         val packet = announcementInfo.getPacket(0)
 
         assertEquals(0x8400 /* response, authoritative */, packet.flags)
@@ -657,6 +657,34 @@
         // Above records are identical to the actual registrations: no conflict
         assertEquals(emptySet(), repository.getConflictingServices(packet))
     }
+
+    @Test
+    fun testGetServiceRepliedRequestsCount() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        // Verify that there is no packet replied.
+        assertEquals(MdnsConstants.NO_PACKET,
+                repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_1))
+
+        val questions = listOf(MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                // TTL and data is empty for a question
+                0L /* ttlMillis */,
+                null /* pointer */))
+        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
+                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+
+        // Reply to the question and verify there is one packet replied.
+        val reply = repository.getReply(query, src)
+        assertNotNull(reply)
+        assertEquals(1, repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_1))
+
+        // No package replied for unknown service.
+        assertEquals(MdnsConstants.NO_PACKET,
+                repository.getServiceRepliedRequestsCount(TEST_SERVICE_ID_2))
+    }
 }
 
 private fun MdnsRecordRepository.initWithService(
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index d71bea4..3fc656a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -17,10 +17,8 @@
 package com.android.server.connectivity.mdns;
 
 import static android.net.InetAddresses.parseNumericAddress;
-
 import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -343,9 +341,9 @@
         assertNotNull(parsedPacket);
 
         final Network network = mock(Network.class);
-        responses = decoder.augmentResponses(parsedPacket,
+        responses = new ArraySet<>(decoder.augmentResponses(parsedPacket,
                 /* existingResponses= */ Collections.emptyList(),
-                /* interfaceIndex= */ 10, network /* expireOnExit= */).first;
+                /* interfaceIndex= */ 10, network /* expireOnExit= */).first);
 
         assertEquals(responses.size(), 1);
         assertEquals(responses.valueAt(0).getInterfaceIndex(), 10);
@@ -641,8 +639,8 @@
         final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
         assertNotNull(parsedPacket);
 
-        return decoder.augmentResponses(parsedPacket,
+        return new ArraySet<>(decoder.augmentResponses(parsedPacket,
                 existingResponses,
-                MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class)).first;
+                MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class)).first);
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt
new file mode 100644
index 0000000..6f8ba6c
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBasicMethodsTest.kt
@@ -0,0 +1,35 @@
+@file:Suppress("DEPRECATION") // This file tests a bunch of deprecated methods : don't warn about it
+
+package com.android.server
+
+import android.net.ConnectivityManager
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.R)
+class CSBasicMethodsTest : CSTest() {
+    @Test
+    fun testNetworkTypes() {
+        // Ensure that mocks for the networkAttributes config variable work as expected. If they
+        // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
+        // will fail. Failing here is much easier to debug.
+        assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_WIFI))
+        assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE))
+        assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_MMS))
+        assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_FOTA))
+        assertFalse(cm.isNetworkSupported(ConnectivityManager.TYPE_PROXY))
+
+        // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
+        // mocks, this assert exercises the ConnectivityService code path that ensures that
+        // TYPE_ETHERNET is supported if the ethernet service is running.
+        assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_ETHERNET))
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
new file mode 100644
index 0000000..5ae9232
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -0,0 +1,134 @@
+package com.android.server
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.INetworkMonitor
+import android.net.INetworkMonitorCallbacks
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkProvider
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkTestResultParcelable
+import android.net.networkstack.NetworkStackClientBase
+import android.os.HandlerThread
+import com.android.modules.utils.build.SdkLevel
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.TestableNetworkCallback
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.verify
+import org.mockito.stubbing.Answer
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+private inline fun <reified T> ArgumentCaptor() = ArgumentCaptor.forClass(T::class.java)
+
+private val agentCounter = AtomicInteger(1)
+private fun nextAgentId() = agentCounter.getAndIncrement()
+
+/**
+ * A wrapper for network agents, for use with CSTest.
+ *
+ * This class knows how to interact with CSTest and has helpful methods to make fake agents
+ * that can be manipulated directly from a test.
+ */
+class CSAgentWrapper(
+        val context: Context,
+        csHandlerThread: HandlerThread,
+        networkStack: NetworkStackClientBase,
+        nac: NetworkAgentConfig,
+        val nc: NetworkCapabilities,
+        val lp: LinkProperties,
+        val score: FromS<NetworkScore>,
+        val provider: NetworkProvider?
+) : TestableNetworkCallback.HasNetwork {
+    private val TAG = "CSAgent${nextAgentId()}"
+    private val VALIDATION_RESULT_INVALID = 0
+    private val VALIDATION_TIMESTAMP = 1234L
+    private val agent: NetworkAgent
+    private val nmCallbacks: INetworkMonitorCallbacks
+    val networkMonitor = mock<INetworkMonitor>()
+
+    override val network: Network get() = agent.network!!
+
+    init {
+        // Capture network monitor callbacks and simulate network monitor
+        val validateAnswer = Answer {
+            CSTest.CSTestExecutor.execute { onValidationRequested() }
+            null
+        }
+        doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnected(any(), any())
+        doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnectedParcel(any())
+        doAnswer(validateAnswer).`when`(networkMonitor).forceReevaluation(anyInt())
+        val nmNetworkCaptor = ArgumentCaptor<Network>()
+        val nmCbCaptor = ArgumentCaptor<INetworkMonitorCallbacks>()
+        doNothing().`when`(networkStack).makeNetworkMonitor(
+                nmNetworkCaptor.capture(),
+                any() /* name */,
+                nmCbCaptor.capture())
+
+        // Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
+        if (SdkLevel.isAtLeastS()) {
+            agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
+                    nc, lp, score.value, nac, provider) {}
+        } else {
+            agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
+                    nc, lp, 50 /* score */, nac, provider) {}
+        }
+        agent.register()
+        assertEquals(agent.network!!.netId, nmNetworkCaptor.value.netId)
+        nmCallbacks = nmCbCaptor.value
+        nmCallbacks.onNetworkMonitorCreated(networkMonitor)
+    }
+
+    private fun onValidationRequested() {
+        if (SdkLevel.isAtLeastT()) {
+            verify(networkMonitor).notifyNetworkConnectedParcel(any())
+        } else {
+            verify(networkMonitor).notifyNetworkConnected(any(), any())
+        }
+        nmCallbacks.notifyProbeStatusChanged(0 /* completed */, 0 /* succeeded */)
+        val p = NetworkTestResultParcelable()
+        p.result = VALIDATION_RESULT_INVALID
+        p.probesAttempted = 0
+        p.probesSucceeded = 0
+        p.redirectUrl = null
+        p.timestampMillis = VALIDATION_TIMESTAMP
+        nmCallbacks.notifyNetworkTestedWithExtras(p)
+    }
+
+    fun connect() {
+        val mgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+        val request = NetworkRequest.Builder().clearCapabilities()
+                .addTransportType(nc.transportTypes[0])
+                .build()
+        val cb = TestableNetworkCallback()
+        mgr.registerNetworkCallback(request, cb)
+        agent.markConnected()
+        if (null == cb.poll { it is Available && agent.network == it.network }) {
+            if (!nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) &&
+                    nc.hasTransport(TRANSPORT_CELLULAR)) {
+                // ConnectivityService adds NOT_SUSPENDED by default to all non-cell agents. An
+                // agent without NOT_SUSPENDED will not connect, instead going into the SUSPENDED
+                // state, so this call will not terminate.
+                // Instead of forcefully adding NOT_SUSPENDED to all agents like older tools did,
+                // it's better to let the developer manage it as they see fit but help them
+                // debug if they forget.
+                fail("Could not connect the agent. Did you forget to add " +
+                        "NET_CAPABILITY_NOT_SUSPENDED ?")
+            }
+            fail("Could not connect the agent. Instrumentation failure ?")
+        }
+        mgr.unregisterNetworkCallback(cb)
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
new file mode 100644
index 0000000..68613a6
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -0,0 +1,239 @@
+package com.android.server
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.content.pm.UserInfo
+import android.content.res.Resources
+import android.net.ConnectivityManager
+import android.net.INetd
+import android.net.InetAddresses
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkPolicyManager
+import android.net.NetworkProvider
+import android.net.NetworkScore
+import android.net.PacProxyManager
+import android.net.RouteInfo
+import android.net.networkstack.NetworkStackClientBase
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.UserHandle
+import android.os.UserManager
+import android.telephony.TelephonyManager
+import android.testing.TestableContext
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.util.test.BroadcastInterceptingContext
+import com.android.modules.utils.build.SdkLevel
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator
+import com.android.server.connectivity.ClatCoordinator
+import com.android.server.connectivity.ConnectivityFlags
+import com.android.server.connectivity.MultinetworkPolicyTracker
+import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
+import com.android.server.connectivity.ProxyTracker
+import com.android.testutils.waitForIdle
+import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.util.concurrent.Executors
+import kotlin.test.fail
+
+internal const val HANDLER_TIMEOUT_MS = 2_000
+internal const val TEST_PACKAGE_NAME = "com.android.test.package"
+internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
+internal val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
+
+open class FromS<Type>(val value: Type)
+
+/**
+ * Base class for tests testing ConnectivityService and its satellites.
+ *
+ * This class sets up a ConnectivityService running locally in the test.
+ */
+// TODO (b/272685721) : make ConnectivityServiceTest smaller and faster by moving the setup
+// parts into this class and moving the individual tests to multiple separate classes.
+open class CSTest {
+    companion object {
+        val CSTestExecutor = Executors.newSingleThreadExecutor()
+    }
+
+    init {
+        if (!SdkLevel.isAtLeastS()) {
+            throw UnsupportedApiLevelException("CSTest subclasses must be annotated to only " +
+                    "run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)");
+        }
+    }
+
+    val instrumentationContext =
+            TestableContext(InstrumentationRegistry.getInstrumentation().context)
+    val context = CSContext(instrumentationContext)
+
+    // See constructor for default-enabled features. All queried features must be either enabled
+    // or disabled, because the test can't hold READ_DEVICE_CONFIG and device config utils query
+    // permissions using static contexts.
+    val enabledFeatures = HashMap<String, Boolean>().also {
+        it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
+        it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
+        it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
+    }
+    fun enableFeature(f: String) = enabledFeatures.set(f, true)
+    fun disableFeature(f: String) = enabledFeatures.set(f, false)
+
+    // When adding new members, consider if it's not better to build the object in CSTestHelpers
+    // to keep this file clean of implementation details. Generally, CSTestHelpers should only
+    // need changes when new details of instrumentation are needed.
+    val contentResolver = makeMockContentResolver(context)
+
+    val PRIMARY_USER = 0
+    val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+    val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
+    val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
+    val activityManager = makeActivityManager()
+
+    val networkStack = mock<NetworkStackClientBase>()
+    val csHandlerThread = HandlerThread("CSTestHandler")
+    val sysResources = mock<Resources>().also { initMockedResources(it) }
+    val packageManager = makeMockPackageManager()
+    val connResources = makeMockConnResources(sysResources, packageManager)
+
+    val bpfNetMaps = mock<BpfNetMaps>()
+    val clatCoordinator = mock<ClatCoordinator>()
+    val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
+    val alarmManager = makeMockAlarmManager()
+    val systemConfigManager = makeMockSystemConfigManager()
+    val telephonyManager = mock<TelephonyManager>().also {
+        doReturn(true).`when`(it).isDataCapable()
+    }
+
+    val deps = CSDeps()
+    val service = makeConnectivityService(context, deps).also { it.systemReadyInternal() }
+    val cm = ConnectivityManager(context, service)
+    val csHandler = Handler(csHandlerThread.looper)
+
+    inner class CSDeps : ConnectivityService.Dependencies() {
+        override fun getResources(ctx: Context) = connResources
+        override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
+        override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
+        override fun getNetworkStack() = this@CSTest.networkStack
+
+        override fun makeHandlerThread() = csHandlerThread
+        override fun makeProxyTracker(context: Context, connServiceHandler: Handler) = proxyTracker
+
+        override fun makeCarrierPrivilegeAuthenticator(context: Context, tm: TelephonyManager) =
+                if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
+
+        private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
+            override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
+                return isFeatureEnabled(context, name)
+            }
+        }
+        override fun makeAutomaticOnOffKeepaliveTracker(c: Context, h: Handler) =
+                AutomaticOnOffKeepaliveTracker(c, h, AOOKTDeps(c))
+
+        override fun makeMultinetworkPolicyTracker(c: Context, h: Handler, r: Runnable) =
+                MultinetworkPolicyTracker(c, h, r,
+                        MultinetworkPolicyTrackerTestDependencies(connResources.get()))
+
+        // All queried features must be mocked, because the test cannot hold the
+        // READ_DEVICE_CONFIG permission and device config utils use static methods for
+        // checking permissions.
+        override fun isFeatureEnabled(context: Context?, name: String?) =
+                enabledFeatures[name] ?: fail("Unmocked feature $name, see CSTest.enabledFeatures")
+    }
+
+    inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
+        val pacProxyManager = mock<PacProxyManager>()
+        val networkPolicyManager = mock<NetworkPolicyManager>()
+
+        override fun getPackageManager() = this@CSTest.packageManager
+        override fun getContentResolver() = this@CSTest.contentResolver
+
+        // TODO : buff up the capabilities of this permission scheme to allow checking for
+        // permission rejections
+        override fun checkPermission(permission: String, pid: Int, uid: Int) = PERMISSION_GRANTED
+        override fun checkCallingOrSelfPermission(permission: String) = PERMISSION_GRANTED
+
+        // Necessary for MultinetworkPolicyTracker, which tries to register a receiver for
+        // all users. The test can't do that since it doesn't hold INTERACT_ACROSS_USERS.
+        // TODO : ensure MultinetworkPolicyTracker's BroadcastReceiver is tested ; ideally,
+        // just returning null should not have tests pass
+        override fun registerReceiverForAllUsers(
+                receiver: BroadcastReceiver?,
+                filter: IntentFilter,
+                broadcastPermission: String?,
+                scheduler: Handler?
+        ): Intent? = null
+
+        // Create and cache user managers on the fly as necessary.
+        val userManagers = HashMap<UserHandle, UserManager>()
+        override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+            val asUser = mock(Context::class.java, delegatesTo<Any>(this))
+            doReturn(user).`when`(asUser).getUser()
+            doAnswer { userManagers.computeIfAbsent(user) {
+                mock(UserManager::class.java, delegatesTo<Any>(userManager)) }
+            }.`when`(asUser).getSystemService(Context.USER_SERVICE)
+            return asUser
+        }
+
+        // List of mocked services. Add additional services here or in subclasses.
+        override fun getSystemService(serviceName: String) = when (serviceName) {
+            Context.CONNECTIVITY_SERVICE -> cm
+            Context.PAC_PROXY_SERVICE -> pacProxyManager
+            Context.NETWORK_POLICY_SERVICE -> networkPolicyManager
+            Context.ALARM_SERVICE -> alarmManager
+            Context.USER_SERVICE -> userManager
+            Context.ACTIVITY_SERVICE -> activityManager
+            Context.SYSTEM_CONFIG_SERVICE -> systemConfigManager
+            Context.TELEPHONY_SERVICE -> telephonyManager
+            Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
+            else -> super.getSystemService(serviceName)
+        }
+    }
+
+    // Utility methods for subclasses to use
+    fun waitForIdle() = csHandlerThread.waitForIdle(HANDLER_TIMEOUT_MS)
+
+    private fun emptyAgentConfig() = NetworkAgentConfig.Builder().build()
+    private fun defaultNc() = NetworkCapabilities.Builder()
+            // Add sensible defaults for agents that don't want to care
+            .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+            .addCapability(NET_CAPABILITY_NOT_ROAMING)
+            .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+            .build()
+    private fun defaultScore() = FromS<NetworkScore>(NetworkScore.Builder().build())
+    private fun defaultLp() = LinkProperties().apply {
+        addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+        addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+    }
+
+    // Network agents. See CSAgentWrapper. This class contains utility methods to simplify
+    // creation.
+    fun Agent(
+            nac: NetworkAgentConfig = emptyAgentConfig(),
+            nc: NetworkCapabilities = defaultNc(),
+            lp: LinkProperties = defaultLp(),
+            score: FromS<NetworkScore> = defaultScore(),
+            provider: NetworkProvider? = null
+    ) = CSAgentWrapper(context, csHandlerThread, networkStack, nac, nc, lp, score, provider)
+
+    fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
+        val nc = NetworkCapabilities.Builder().apply {
+            transports.forEach {
+                addTransportType(it)
+            }
+        }.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .build()
+        return Agent(nc = nc, lp = lp)
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
new file mode 100644
index 0000000..b8f2151
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
@@ -0,0 +1,140 @@
+@file:JvmName("CsTestHelpers")
+
+package com.android.server
+
+import android.app.ActivityManager
+import android.app.AlarmManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_BLUETOOTH
+import android.content.pm.PackageManager.FEATURE_ETHERNET
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
+import android.content.pm.UserInfo
+import android.content.res.Resources
+import android.net.IDnsResolver
+import android.net.INetd
+import android.net.metrics.IpConnectivityLog
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.SystemClock
+import android.os.SystemConfigManager
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import android.test.mock.MockContentResolver
+import com.android.connectivity.resources.R
+import com.android.internal.util.WakeupMessage
+import com.android.internal.util.test.FakeSettingsProvider
+import com.android.modules.utils.build.SdkLevel
+import com.android.server.ConnectivityService.Dependencies
+import com.android.server.connectivity.ConnectivityResources
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.doNothing
+import kotlin.test.fail
+
+internal inline fun <reified T> mock() = Mockito.mock(T::class.java)
+internal inline fun <reified T> any() = any(T::class.java)
+
+internal fun makeMockContentResolver(context: Context) = MockContentResolver(context).apply {
+    addProvider(Settings.AUTHORITY, FakeSettingsProvider())
+}
+
+internal fun makeMockUserManager(info: UserInfo, handle: UserHandle) = mock<UserManager>().also {
+    doReturn(listOf(info)).`when`(it).getAliveUsers()
+    doReturn(listOf(handle)).`when`(it).getUserHandles(ArgumentMatchers.anyBoolean())
+}
+
+internal fun makeActivityManager() = mock<ActivityManager>().also {
+    if (SdkLevel.isAtLeastU()) {
+        doNothing().`when`(it).registerUidFrozenStateChangedCallback(any(), any())
+    }
+}
+
+internal fun makeMockPackageManager() = mock<PackageManager>().also { pm ->
+    val supported = listOf(FEATURE_WIFI, FEATURE_WIFI_DIRECT, FEATURE_BLUETOOTH, FEATURE_ETHERNET)
+    doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
+}
+
+internal fun makeMockConnResources(resources: Resources, pm: PackageManager) = mock<Context>().let {
+    doReturn(resources).`when`(it).resources
+    doReturn(pm).`when`(it).packageManager
+    ConnectivityResources.setResourcesContextForTest(it)
+    ConnectivityResources(it)
+}
+
+private val UNREASONABLY_LONG_ALARM_WAIT_MS = 1000
+internal fun makeMockAlarmManager() = mock<AlarmManager>().also { am ->
+    val alrmHdlr = HandlerThread("TestAlarmManager").also { it.start() }.threadHandler
+    doAnswer {
+        val (_, date, _, wakeupMsg, handler) = it.arguments
+        wakeupMsg as WakeupMessage
+        handler as Handler
+        val delayMs = ((date as Long) - SystemClock.elapsedRealtime()).coerceAtLeast(0)
+        if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
+            fail("Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
+                    "ms into the future : $delayMs")
+        }
+        alrmHdlr.postDelayed({ handler.post(wakeupMsg::onAlarm) }, wakeupMsg, delayMs)
+    }.`when`(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
+            any<WakeupMessage>(), any())
+    doAnswer {
+        alrmHdlr.removeCallbacksAndMessages(it.getArgument<WakeupMessage>(0))
+    }.`when`(am).cancel(any<WakeupMessage>())
+}
+
+internal fun makeMockSystemConfigManager() = mock<SystemConfigManager>().also {
+    doReturn(intArrayOf(0)).`when`(it).getSystemPermissionUids(anyString())
+}
+
+// Mocking resources used by ConnectivityService. Note these can't be defined to return the
+// value returned by the mocking, because a non-null method would mean the helper would also
+// return non-null and the compiler would check that, but mockito has no qualms returning null
+// from a @NonNull method when stubbing. Hence, mock() = doReturn().getString() would crash
+// at runtime, because getString() returns non-null String, therefore mock returns non-null String,
+// and kotlinc adds an intrinsics check for that, which crashes at runtime when mockito actually
+// returns null.
+private fun Resources.mock(r: Int, v: Boolean) { doReturn(v).`when`(this).getBoolean(r) }
+private fun Resources.mock(r: Int, v: Int) { doReturn(v).`when`(this).getInteger(r) }
+private fun Resources.mock(r: Int, v: String) { doReturn(v).`when`(this).getString(r) }
+private fun Resources.mock(r: Int, v: Array<String?>) { doReturn(v).`when`(this).getStringArray(r) }
+private fun Resources.mock(r: Int, v: IntArray) { doReturn(v).`when`(this).getIntArray(r) }
+
+internal fun initMockedResources(res: Resources) {
+    // Resources accessed through reflection need to return the id
+    doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(res)
+            .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
+    doReturn(R.array.network_switch_type_name).`when`(res)
+            .getIdentifier(eq("network_switch_type_name"), eq("array"), any())
+    // Mock the values themselves
+    res.mock(R.integer.config_networkTransitionTimeout, 60_000)
+    res.mock(R.string.config_networkCaptivePortalServerUrl, "")
+    res.mock(R.array.config_wakeonlan_supported_interfaces, arrayOf(WIFI_WOL_IFNAME))
+    res.mock(R.array.config_networkSupportedKeepaliveCount, arrayOf("0,1", "1,3"))
+    res.mock(R.array.config_networkNotifySwitches, arrayOfNulls<String>(size = 0))
+    res.mock(R.array.config_protectedNetworks, intArrayOf(10, 11, 12, 14, 15))
+    res.mock(R.array.network_switch_type_name, arrayOfNulls<String>(size = 0))
+    res.mock(R.integer.config_networkAvoidBadWifi, 1)
+    res.mock(R.integer.config_activelyPreferBadWifi, 0)
+    res.mock(R.bool.config_cellular_radio_timesharing_capable, true)
+}
+
+private val TEST_LINGER_DELAY_MS = 400
+private val TEST_NASCENT_DELAY_MS = 300
+internal fun makeConnectivityService(context: Context, deps: Dependencies) = ConnectivityService(
+        context,
+        mock<IDnsResolver>(),
+        mock<IpConnectivityLog>(),
+        mock<INetd>(),
+        deps).also {
+    it.mLingerDelayMs = TEST_LINGER_DELAY_MS
+    it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
+}