Merge changes Ib5f541ed,Iff6a3cb1,I2ebd5c7a into main

* changes:
  [remoteauth] Add UwbRangingSession
  [remoteauth] Implement RangingSession
  [remoteauth] Implement RangingManager
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index d3b01ea..bb3dc24 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -110,7 +110,6 @@
     ],
     apps: [
         "ServiceConnectivityResources",
-        "HalfSheetUX",
     ],
     prebuilts: ["current_sdkinfo"],
     manifest: "manifest.json",
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index bb09d0d..eadba58 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -283,6 +283,7 @@
     private List<TetheredClient> mDhcpLeases = Collections.emptyList();
 
     private int mLastIPv6UpstreamIfindex = 0;
+    private boolean mUpstreamSupportsBpf = false;
 
     private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
         public void accept(NeighborEvent e) {
@@ -779,15 +780,15 @@
 
         // If v6only is null, we pass in null to setRaParams(), which handles
         // deprecation of any existing RA data.
-
         setRaParams(params);
-        // Be aware that updateIpv6ForwardingRules use mLastIPv6LinkProperties, so this line should
-        // be eariler than updateIpv6ForwardingRules.
-        // TODO: avoid this dependencies and move this logic into BpfCoordinator.
-        mLastIPv6LinkProperties = v6only;
 
-        updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null);
+        // Not support BPF on virtual upstream interface
+        final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
+        updateIpv6ForwardingRules(
+                mLastIPv6UpstreamIfindex, upstreamIfIndex, upstreamSupportsBpf, null);
+        mLastIPv6LinkProperties = v6only;
         mLastIPv6UpstreamIfindex = upstreamIfIndex;
+        mUpstreamSupportsBpf = upstreamSupportsBpf;
         if (mDadProxy != null) {
             mDadProxy.setUpstreamIface(upstreamIfaceParams);
         }
@@ -921,20 +922,14 @@
         mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex);
     }
 
-    private boolean isIpv6VcnNetworkInterface() {
-        if (mLastIPv6LinkProperties == null) return false;
-
-        return isVcnInterface(mLastIPv6LinkProperties.getInterfaceName());
-    }
-
     // 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,
-            NeighborEvent e) {
-        // If no longer have an upstream or it is virtual network, clear forwarding rules and do
+            boolean upstreamSupportsBpf, NeighborEvent e) {
+        // If no longer have an upstream or upstream not supports BPF, clear forwarding rules and do
         // nothing else.
         // TODO: Rather than always clear rules, ensure whether ipv6 ever enable first.
-        if (upstreamIfindex == 0 || isIpv6VcnNetworkInterface()) {
+        if (upstreamIfindex == 0 || !upstreamSupportsBpf) {
             clearIpv6ForwardingRules();
             return;
         }
@@ -995,7 +990,8 @@
         if (mInterfaceParams != null
                 && mInterfaceParams.index == e.ifindex
                 && mInterfaceParams.hasMacAddress) {
-            updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+            updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex,
+                    mUpstreamSupportsBpf, e);
             updateClientInfoIpv4(e);
         }
     }
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 9f5c743..e2e6d02 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -92,6 +92,8 @@
 DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE)
 DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
 DEFINE_BPF_MAP_RW_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
+                       INGRESS_DISCARD_MAP_SIZE)
 
 /* never actually used from ebpf */
 DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
@@ -343,6 +345,35 @@
     return *config;
 }
 
+static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb,
+                                                          const unsigned kver) {
+    // Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which
+    // provides relative to L3 header reads.  Without that we could fetch the wrong bytes.
+    // Additionally earlier bpf verifiers are much harder to please.
+    if (kver < KVER(4, 19, 0)) return false;
+
+    IngressDiscardKey k = {};
+    if (skb->protocol == htons(ETH_P_IP)) {
+        k.daddr.s6_addr32[2] = htonl(0xFFFF);
+        (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(daddr), &k.daddr.s6_addr32[3], 4, kver);
+    } else if (skb->protocol == htons(ETH_P_IPV6)) {
+        (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(daddr), &k.daddr, sizeof(k.daddr), kver);
+    } else {
+        return false; // non IPv4/IPv6, so no IP to match on
+    }
+
+    // we didn't check for load success, because destination bytes will be zeroed if
+    // bpf_skb_load_bytes_net() fails, instead we rely on daddr of '::' and '::ffff:0.0.0.0'
+    // never being present in the map itself
+
+    IngressDiscardValue* v = bpf_ingress_discard_map_lookup_elem(&k);
+    if (!v) return false;  // lookup failure -> no protection in place -> allow
+    // if (skb->ifindex == 1) return false;  // allow 'lo', but can't happen - see callsite
+    if (skb->ifindex == v->iif[0]) return false;  // allowed interface
+    if (skb->ifindex == v->iif[1]) return false;  // allowed interface
+    return true;  // disallowed interface
+}
+
 // DROP_IF_SET is set of rules that DROP if rule is globally enabled, and per-uid bit is set
 #define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)
 // DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
@@ -368,6 +399,7 @@
     if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return DROP;
 
     if (!egress && skb->ifindex != 1) {
+        if (ingress_should_discard(skb, kver)) return DROP;
         if (uidRules & IIF_MATCH) {
             if (allowed_iif && skb->ifindex != allowed_iif) {
                 // Drops packets not coming from lo nor the allowed interface
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index dcf6d6a..836e998 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -122,6 +122,7 @@
 static const int IFACE_STATS_MAP_SIZE = 1000;
 static const int CONFIGURATION_MAP_SIZE = 2;
 static const int UID_OWNER_MAP_SIZE = 4000;
+static const int INGRESS_DISCARD_MAP_SIZE = 100;
 static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
 
 #ifdef __cplusplus
@@ -166,6 +167,7 @@
 #define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
 #define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
 #define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map"
 #define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
 #define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
 
@@ -214,6 +216,18 @@
 } UidOwnerValue;
 STRUCT_SIZE(UidOwnerValue, 2 * 4);  // 8
 
+typedef struct {
+    // The destination ip of the incoming packet.  IPv4 uses IPv4-mapped IPv6 address format.
+    struct in6_addr daddr;
+} IngressDiscardKey;
+STRUCT_SIZE(IngressDiscardKey, 16);  // 16
+
+typedef struct {
+    // Allowed interface indexes.  Use same value multiple times if you just want to match 1 value.
+    uint32_t iif[2];
+} IngressDiscardValue;
+STRUCT_SIZE(IngressDiscardValue, 2 * 4);  // 8
+
 // Entry in the configuration map that stores which UID rules are enabled.
 #define UID_RULES_CONFIGURATION_KEY 0
 // Entry in the configuration map that stores which stats map is currently in use.
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 42c83d8..5a8d47b 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -207,43 +207,3 @@
 
 }
 
-package android.remoteauth {
-
-  public interface DeviceDiscoveryCallback {
-    method public void onDeviceUpdate(@NonNull android.remoteauth.RemoteDevice, int);
-    method public void onTimeout();
-    field public static final int STATE_LOST = 0; // 0x0
-    field public static final int STATE_SEEN = 1; // 0x1
-  }
-
-  public final class RemoteAuthFrameworkInitializer {
-    method public static void registerServiceWrappers();
-  }
-
-  public class RemoteAuthManager {
-    method public boolean isRemoteAuthSupported();
-    method public boolean startDiscovery(int, @NonNull java.util.concurrent.Executor, @NonNull android.remoteauth.DeviceDiscoveryCallback);
-    method public void stopDiscovery(@NonNull android.remoteauth.DeviceDiscoveryCallback);
-  }
-
-  public final class RemoteDevice implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public int getConnectionId();
-    method @Nullable public String getName();
-    method public int getRegistrationState();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.remoteauth.RemoteDevice> CREATOR;
-    field public static final int STATE_NOT_REGISTERED = 0; // 0x0
-    field public static final int STATE_REGISTERED = 1; // 0x1
-  }
-
-  public static final class RemoteDevice.Builder {
-    ctor public RemoteDevice.Builder(int);
-    method @NonNull public android.remoteauth.RemoteDevice build();
-    method @NonNull public android.remoteauth.RemoteDevice.Builder setConnectionId(int);
-    method @NonNull public android.remoteauth.RemoteDevice.Builder setName(@Nullable String);
-    method @NonNull public android.remoteauth.RemoteDevice.Builder setRegistrationState(int);
-  }
-
-}
-
diff --git a/framework/Android.bp b/framework/Android.bp
index e663764..e577e6d 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -80,6 +80,7 @@
     impl_only_libs: [
         // TODO: figure out why just using "framework-tethering" uses the stubs, even though both
         // framework-connectivity and framework-tethering are in the same APEX.
+        "framework-location.stubs.module_lib",
         "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
     ],
@@ -127,6 +128,7 @@
         // to generate the connectivity stubs. That would create a circular dependency
         // because the tethering impl depend on the connectivity stubs (e.g.,
         // TetheringRequest depends on LinkAddress).
+        "framework-location.stubs.module_lib",
         "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
     ],
diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING
index d68bcc9..dbaca33 100644
--- a/nearby/TEST_MAPPING
+++ b/nearby/TEST_MAPPING
@@ -8,9 +8,6 @@
     },
     {
       "name": "NearbyIntegrationUntrustedTests"
-    },
-    {
-      "name": "NearbyIntegrationUiTests"
     }
   ],
   "postsubmit": [
diff --git a/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
index f53e2dc..1414f7e 100644
--- a/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
+++ b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
@@ -16,10 +16,7 @@
 
 package android.remoteauth;
 
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import androidx.annotation.IntDef;
 
@@ -31,7 +28,7 @@
  *
  * @hide
  */
-@SystemApi(client = MODULE_LIBRARIES)
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
 public interface DeviceDiscoveryCallback {
     /** The device is no longer seen in the discovery process. */
     int STATE_LOST = 0;
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
index dfd7726..112ffa8 100644
--- a/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
@@ -16,7 +16,6 @@
 
 package android.remoteauth;
 
-import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
 
@@ -25,7 +24,7 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
 public final class RemoteAuthFrameworkInitializer {
     private RemoteAuthFrameworkInitializer() {}
 
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
index c025a55..038af2a 100644
--- a/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
@@ -16,14 +16,12 @@
 
 package android.remoteauth;
 
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.remoteauth.DeviceDiscoveryCallback.STATE_LOST;
 import static android.remoteauth.DeviceDiscoveryCallback.STATE_SEEN;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserIdInt;
 import android.content.Context;
@@ -47,7 +45,7 @@
  *
  * @hide
  */
-@SystemApi(client = MODULE_LIBRARIES)
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
 // TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375
 // is automerges from aosp-main to udc-mainline-prod
 @SystemService(RemoteAuthManager.REMOTE_AUTH_SERVICE)
@@ -79,7 +77,7 @@
      * @return true if this device can be enrolled
      * @hide
      */
-    @SystemApi(client = MODULE_LIBRARIES)
+    // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
     // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
     public boolean isRemoteAuthSupported() {
         try {
@@ -100,7 +98,7 @@
      * @return {@code true} if discovery began successfully, {@code false} otherwise
      * @hide
      */
-    @SystemApi(client = MODULE_LIBRARIES)
+    // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
     // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
     public boolean startDiscovery(
             int timeoutMs,
@@ -149,7 +147,7 @@
     // Suppressed lint: Registration methods should have overload that accepts delivery Executor.
     // Already have executor in startDiscovery() method.
     @SuppressLint("ExecutorRegistration")
-    @SystemApi(client = MODULE_LIBRARIES)
+    // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
     // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
     public void stopDiscovery(@NonNull DeviceDiscoveryCallback callback) {
         Preconditions.checkNotNull(callback, "invalid null scanCallback");
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.java b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
index 4cd2399..b6ede2e 100644
--- a/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
+++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
@@ -16,12 +16,9 @@
 
 package android.remoteauth;
 
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,7 +32,7 @@
  * @hide
  */
 // TODO(b/295407748) Change to use @DataClass
-@SystemApi(client = MODULE_LIBRARIES)
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
 public final class RemoteDevice implements Parcelable {
     /** The remote device is not registered as remote authenticator. */
     public static final int STATE_NOT_REGISTERED = 0;
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index c7e5419..dba8b75 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"],
     defaults: [
         "framework-system-server-module-defaults",
     ],
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
new file mode 100644
index 0000000..f79ec7e
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jni;
+
+/**
+ * Interface defining a proxy between Rust and Java implementation of RemoteAuth protocol.
+ *
+ * @hide
+ */
+public interface INativeRemoteAuthService {
+    /**
+     * Interface for RemoteAuth PAL
+     *
+     * @hide
+     */
+    interface IPlatform {
+        /**
+         * Sends message to the remote authenticator
+         *
+         * @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
+         *
+         * @hide
+         */
+        void sendRequest(int connectionId, byte[] request, ResponseCallback callback);
+
+        /**
+         * Interface for a callback to send a response back.
+         *
+         * @hide
+         */
+        interface ResponseCallback {
+            /**
+             * Invoked when message sending succeeds.
+             *
+             * @param response contains response
+             *
+             * @hide
+             */
+            void onSuccess(byte[] response);
+
+            /**
+             * 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
new file mode 100644
index 0000000..39c2a74
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.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.server.remoteauth.jni;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.remoteauth.jni.INativeRemoteAuthService.IPlatform;
+
+/**
+ * A service providing a proxy between Rust implementation and {@link
+ * com.android.server.remoteauth.RemoteAuthService}.
+ *
+ * @hide
+ */
+public class NativeRemoteAuthService {
+    private static final String TAG = NativeRemoteAuthService.class.getSimpleName();
+
+    private IPlatform mPlatform;
+    public final Object mNativeLock = new Object();
+
+    // Constructor should receive pointers to:
+    // ConnectivityManager, RangingManager and DB
+    public NativeRemoteAuthService() {
+        System.loadLibrary("remoteauth_jni_rust");
+        synchronized (mNativeLock) {
+            native_init();
+        }
+    }
+
+    public void setDeviceListener(final IPlatform platform) {
+        mPlatform = platform;
+    }
+
+    /**
+     * Sends message to the remote authenticator
+     *
+     * @param connectionId connection ID of the {@link android.remoteauth.RemoteAuthenticator}
+     * @param request payload of the request
+     * @param responseHandle a handle associated with the request, used to pass the response to the
+     *     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) {
+        mPlatform.sendRequest(
+                connectionId,
+                request,
+                new IPlatform.ResponseCallback() {
+                    @Override
+                    public void onSuccess(byte[] response) {
+                        synchronized (mNativeLock) {
+                            native_on_send_request_success(
+                                    response, platformHandle, responseHandle);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(int errorCode) {
+                        synchronized (mNativeLock) {
+                            native_on_send_request_error(errorCode, platformHandle, responseHandle);
+                        }
+                    }
+                });
+    }
+
+    /* Native functions implemented in JNI */
+    // This function should be implemented in remoteauth_jni_android_protocol
+    private native boolean native_init();
+
+    private native void native_on_send_request_success(
+            byte[] appResponse, long platformHandle, long responseHandle);
+
+    private native void native_on_send_request_error(
+            int errorCode, long platformHandle, long responseHandle);
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
new file mode 100644
index 0000000..3ae9838
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/**
+ * Represents an unrecoverable error (invalid handle) that has occurred during accessing the
+ * platform.
+ */
+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}.
+ *
+ * @hide
+ */
+@Keep
+public class PlatformBadHandleException extends Exception {
+    public PlatformBadHandleException(final String message) {
+        super(message);
+    }
+
+    public PlatformBadHandleException(final Exception e) {
+        super(e);
+    }
+
+    public PlatformBadHandleException(final String message, final Exception e) {
+        super(message, e);
+    }
+}
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
new file mode 100644
index 0000000..e6e8a43
--- /dev/null
+++ b/remoteauth/service/jni/Android.bp
@@ -0,0 +1,76 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libremoteauth_jni_rust_defaults",
+    crate_name: "remoteauth_jni_rust",
+    lints: "android",
+    clippy_lints: "android",
+    min_sdk_version: "35",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+        "libjni",
+        "liblazy_static",
+        "liblog_rust",
+        "liblogger",
+        "libnum_traits",
+        "libthiserror",
+        "libtokio",
+        "libanyhow",
+    ],
+    proc_macros: [
+        "libasync_trait",
+    ],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.remoteauth",
+    ],
+    host_supported: true,
+}
+
+rust_test {
+    name: "libremoteauth_jni_rust_tests",
+    defaults: ["libremoteauth_jni_rust_defaults"],
+    rustlibs: [
+    ],
+    target: {
+        android: {
+            test_suites: [
+                "general-tests",
+            ],
+            test_config_template: "remoteauth_rust_test_config_template.xml",
+        },
+        host: {
+            test_suites: [
+                "general-tests",
+            ],
+            data_libs: [
+                "libandroid_runtime_lazy",
+                "libbase",
+                "libbinder",
+                "libbinder_ndk",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+    // Support multilib variants (using different suffix per sub-architecture), which is needed on
+    // build targets with secondary architectures, as the MTS test suite packaging logic flattens
+    // all test artifacts into a single `testcases` directory.
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    auto_gen_config: true,
+}
diff --git a/remoteauth/service/jni/remoteauth_rust_test_config_template.xml b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml
new file mode 100644
index 0000000..673b451
--- /dev/null
+++ b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="Configuration for {MODULE} Rust tests">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+        <option name="test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="{MODULE}" />
+    </test>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.remoteauth" />
+    </object>
+</configuration>
\ No newline at end of file
diff --git a/remoteauth/service/jni/src/jnames.rs b/remoteauth/service/jni/src/jnames.rs
new file mode 100644
index 0000000..d7cc908
--- /dev/null
+++ b/remoteauth/service/jni/src/jnames.rs
@@ -0,0 +1,17 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Name of java classes and methods for RemoteAuth platform:
+pub(crate) const SEND_REQUEST_MNAME: &str = "sendRequest";
+pub(crate) const SEND_REQUEST_MSIG: &str = "(I[BII)V";
diff --git a/remoteauth/service/jni/src/lib.rs b/remoteauth/service/jni/src/lib.rs
new file mode 100644
index 0000000..a816c94
--- /dev/null
+++ b/remoteauth/service/jni/src/lib.rs
@@ -0,0 +1,25 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! New rust RemoteAuth JNI library.
+//!
+//! This library takes the JNI calls from RemoteAuthService to the remoteauth protocol library
+//! and from protocol library to platform (Java interface)
+
+mod jnames;
+mod unique_jvm;
+mod utils;
+
+pub mod remoteauth_jni_android_platform;
+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
new file mode 100644
index 0000000..f3cf3ea
--- /dev/null
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -0,0 +1,303 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::jnames::{SEND_REQUEST_MNAME, SEND_REQUEST_MSIG};
+use crate::unique_jvm;
+use anyhow::anyhow;
+use async_trait::async_trait;
+use jni::errors::Error as JNIError;
+use jni::objects::{GlobalRef, JMethodID, JObject, JValue};
+use jni::signature::TypeSignature;
+use jni::sys::{jbyteArray, jint, jlong, jvalue};
+use jni::{JNIEnv, JavaVM};
+use lazy_static::lazy_static;
+use log::{debug, error, info};
+use std::collections::HashMap;
+use std::sync::{
+    atomic::{AtomicI64, Ordering},
+    Arc,
+};
+use tokio::{
+    runtime::Runtime,
+    sync::{mpsc, Mutex},
+};
+
+/// Macro capturing the name of the function calling this macro.
+///
+/// function_name()! -> &'static str
+/// Returns the function name as 'static reference.
+macro_rules! function_name {
+    () => {{
+        // Declares function f inside current function.
+        fn f() {}
+        fn type_name_of<T>(_: T) -> &'static str {
+            std::any::type_name::<T>()
+        }
+        // type name of f is struct_or_crate_name::calling_function_name::f
+        let name = type_name_of(f);
+        // Find and cut the rest of the path:
+        // Third to last character, up to the first semicolon: is calling_function_name
+        match &name[..name.len() - 3].rfind(':') {
+            Some(pos) => &name[pos + 1..name.len() - 3],
+            None => &name[..name.len() - 3],
+        }
+    }};
+}
+
+lazy_static! {
+    static ref HANDLE_MAPPING: Mutex<HashMap<i64, Arc<Mutex<JavaPlatform>>>> =
+        Mutex::new(HashMap::new());
+    static ref HANDLE_RN: AtomicI64 = AtomicI64::new(0);
+}
+
+fn generate_platform_handle() -> i64 {
+    HANDLE_RN.fetch_add(1, Ordering::SeqCst)
+}
+
+async fn insert_platform_handle(handle: i64, item: Arc<Mutex<JavaPlatform>>) {
+    if 0 == handle {
+        // Init once
+        logger::init(
+            logger::Config::default()
+                .with_tag_on_device("remoteauth")
+                .with_min_level(log::Level::Trace)
+                .with_filter("trace,jni=info"),
+        );
+    }
+    HANDLE_MAPPING.lock().await.insert(handle, Arc::clone(&item));
+}
+
+#[async_trait]
+pub trait Platform {
+    /// Send a binary message to the remote with the given connection id and return the response.
+    async fn send_request(&mut self, connection_id: i32, request: &[u8])
+        -> anyhow::Result<Vec<u8>>;
+}
+//////////////////////////////////
+
+pub struct JavaPlatform {
+    platform_handle: i64,
+    vm: &'static Arc<JavaVM>,
+    platform_native_obj: GlobalRef,
+    send_request_method_id: JMethodID,
+    map_futures: Mutex<HashMap<i64, mpsc::Sender<Vec<u8>>>>,
+    atomic_handle: AtomicI64,
+}
+
+impl JavaPlatform {
+    // Method to create JavaPlatform
+    pub async fn create(
+        java_platform_native: JObject<'_>,
+    ) -> Result<Arc<Mutex<impl Platform>>, JNIError> {
+        let platform_handle = generate_platform_handle();
+        let platform = Arc::new(Mutex::new(JavaPlatform::new(
+            platform_handle,
+            unique_jvm::get_static_ref().ok_or(JNIError::InvalidCtorReturn)?,
+            java_platform_native,
+        )?));
+        insert_platform_handle(platform_handle, Arc::clone(&platform)).await;
+        Ok(Arc::clone(&platform))
+    }
+
+    fn new(
+        platform_handle: i64,
+        vm: &'static Arc<JavaVM>,
+        java_platform_native: JObject,
+    ) -> Result<JavaPlatform, JNIError> {
+        vm.attach_current_thread().and_then(|env| {
+            let platform_class = env.get_object_class(java_platform_native)?;
+            let platform_native_obj = env.new_global_ref(java_platform_native)?;
+            let send_request_method: JMethodID =
+                env.get_method_id(platform_class, SEND_REQUEST_MNAME, SEND_REQUEST_MSIG)?;
+
+            Ok(Self {
+                platform_handle,
+                vm,
+                platform_native_obj,
+                send_request_method_id: send_request_method,
+                map_futures: Mutex::new(HashMap::new()),
+                atomic_handle: AtomicI64::new(0),
+            })
+        })
+    }
+}
+
+#[async_trait]
+impl Platform for JavaPlatform {
+    async fn send_request(
+        &mut self,
+        connection_id: i32,
+        request: &[u8],
+    ) -> anyhow::Result<Vec<u8>> {
+        let type_signature = TypeSignature::from_str(SEND_REQUEST_MSIG)
+            .map_err(|e| anyhow!("JNI: Invalid type signature: {:?}", e))?;
+
+        let (tx, mut rx) = mpsc::channel(1);
+        let response_handle = self.atomic_handle.fetch_add(1, Ordering::SeqCst);
+        self.map_futures.lock().await.insert(response_handle, tx);
+        self.vm
+            .attach_current_thread()
+            .and_then(|env| {
+                let request_jbytearray = env.byte_array_from_slice(request)?;
+                // Safety: request_jbytearray is safely instantiated above.
+                let request_jobject = unsafe { JObject::from_raw(request_jbytearray) };
+
+                let _ = env.call_method_unchecked(
+                    self.platform_native_obj.as_obj(),
+                    self.send_request_method_id,
+                    type_signature.ret,
+                    &[
+                        jvalue::from(JValue::Int(connection_id)),
+                        jvalue::from(JValue::Object(request_jobject)),
+                        jvalue::from(JValue::Long(response_handle)),
+                        jvalue::from(JValue::Long(self.platform_handle)),
+                    ],
+                );
+                Ok(info!(
+                    "{} successfully sent-message, waiting for response {}:{}",
+                    function_name!(),
+                    self.platform_handle,
+                    response_handle
+                ))
+            })
+            .map_err(|e| anyhow!("JNI: Failed to attach current thread: {:?}", e))?;
+
+        rx.recv().await.ok_or(anyhow!("{} failed in awaiting for a result", function_name!()))
+    }
+}
+
+impl JavaPlatform {
+    async fn on_send_request_success(&mut self, response: &[u8], response_handle: i64) {
+        info!(
+            "{} completed successfully {}:{}",
+            function_name!(),
+            self.platform_handle,
+            response_handle
+        );
+        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
+            let _ = tx.send(response.to_vec()).await;
+        } else {
+            error!(
+                "Failed to find TX for {} and {}:{}",
+                function_name!(),
+                self.platform_handle,
+                response_handle
+            );
+        }
+    }
+
+    async fn on_send_request_error(&self, error_code: i32, response_handle: i64) {
+        error!(
+            "{} completed with error {} {}:{}",
+            function_name!(),
+            error_code,
+            self.platform_handle,
+            response_handle
+        );
+        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
+            // `rx.recv()` ends with `Err`
+            drop(tx);
+        } else {
+            error!(
+                "Failed to find TX for {} and {}:{}",
+                function_name!(),
+                self.platform_handle,
+                response_handle
+            );
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_success(
+    env: JNIEnv,
+    _: JObject,
+    app_response: jbyteArray,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    debug!("{}: enter", function_name!());
+    Runtime::new().unwrap().block_on(native_on_send_request_success(
+        env,
+        app_response,
+        platform_handle,
+        response_handle,
+    ));
+}
+
+async fn native_on_send_request_success(
+    env: JNIEnv<'_>,
+    app_response: jbyteArray,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
+        let response =
+            env.convert_byte_array(app_response).map_err(|_| JNIError::InvalidCtorReturn).unwrap();
+        let mut platform = (*platform).lock().await;
+        platform.on_send_request_success(&response, response_handle).await;
+    } else {
+        let _ = env.throw_new(
+            "com/android/server/remoteauth/jni/BadHandleException",
+            format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()),
+        );
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_error(
+    env: JNIEnv,
+    _: JObject,
+    error_code: jint,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    debug!("{}: enter", function_name!());
+    Runtime::new().unwrap().block_on(native_on_send_request_error(
+        env,
+        error_code,
+        platform_handle,
+        response_handle,
+    ));
+}
+
+async fn native_on_send_request_error(
+    env: JNIEnv<'_>,
+    error_code: jint,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
+        let platform = (*platform).lock().await;
+        platform.on_send_request_error(error_code, response_handle).await;
+    } else {
+        let _ = env.throw_new(
+            "com/android/server/remoteauth/jni/BadHandleException",
+            format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()),
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    //use super::*;
+
+    //use tokio::runtime::Builder;
+
+    /// Checks validity of the function_name! macro.
+    #[test]
+    fn test_function_name() {
+        assert_eq!(function_name!(), "test_function_name");
+    }
+}
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
new file mode 100644
index 0000000..1f73207
--- /dev/null
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+use crate::unique_jvm;
+use crate::utils::get_boolean_result;
+use jni::objects::JObject;
+use jni::sys::jboolean;
+use jni::JNIEnv;
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_init(
+    env: JNIEnv,
+    _: JObject,
+) -> jboolean {
+    logger::init(
+        logger::Config::default()
+            .with_tag_on_device("remoteauth")
+            .with_min_level(log::Level::Trace)
+            .with_filter("trace,jni=info"),
+    );
+    get_boolean_result(native_init(env), "native_init")
+}
+
+fn native_init(env: JNIEnv) -> anyhow::Result<()> {
+    let jvm = env.get_java_vm()?;
+    unique_jvm::set_once(jvm)
+}
diff --git a/remoteauth/service/jni/src/unique_jvm.rs b/remoteauth/service/jni/src/unique_jvm.rs
new file mode 100644
index 0000000..46cc361
--- /dev/null
+++ b/remoteauth/service/jni/src/unique_jvm.rs
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+//! takes a JavaVM to a static reference.
+//!
+//! JavaVM is shared as multiple JavaVM within a single process is not allowed
+//! per [JNI spec](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html)
+//! The unique JavaVM need to be shared over (potentially) different threads.
+
+use std::sync::{Arc, Once};
+
+use anyhow::Result;
+use jni::JavaVM;
+
+static mut JVM: Option<Arc<JavaVM>> = None;
+static INIT: Once = Once::new();
+/// set_once sets the unique JavaVM that can be then accessed using get_static_ref()
+///
+/// The function shall only be called once.
+pub(crate) fn set_once(jvm: JavaVM) -> Result<()> {
+    // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html).
+    // Modification to static mut is nested inside call_once.
+    unsafe {
+        INIT.call_once(|| {
+            JVM = Some(Arc::new(jvm));
+        });
+    }
+    Ok(())
+}
+/// Gets a 'static reference to the unique JavaVM. Returns None if set_once() was never called.
+pub(crate) fn get_static_ref() -> Option<&'static Arc<JavaVM>> {
+    // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html).
+    // Modification to static mut is nested inside call_once.
+    unsafe { JVM.as_ref() }
+}
diff --git a/remoteauth/service/jni/src/utils.rs b/remoteauth/service/jni/src/utils.rs
new file mode 100644
index 0000000..e61b895
--- /dev/null
+++ b/remoteauth/service/jni/src/utils.rs
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+use jni::sys::jboolean;
+use log::error;
+
+pub(crate) fn get_boolean_result<T>(result: anyhow::Result<T>, error_msg: &str) -> jboolean {
+    match result {
+        Ok(_) => true,
+        Err(e) => {
+            error!("{} failed with {:?}", error_msg, &e);
+            false
+        }
+    }
+    .into()
+}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 442d69f..15263cc 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -95,6 +95,7 @@
     NETD "map_netd_cookie_tag_map",
     NETD "map_netd_iface_index_name_map",
     NETD "map_netd_iface_stats_map",
+    NETD "map_netd_ingress_discard_map",
     NETD "map_netd_stats_map_A",
     NETD "map_netd_stats_map_B",
     NETD "map_netd_uid_counterset_map",
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 9b99b81..f8e3166 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -424,7 +424,6 @@
 import com.android.testutils.FunctionalUtils.ThrowingRunnable;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TestableNetworkCallback;
 import com.android.testutils.TestableNetworkOfferCallback;
 
@@ -7430,7 +7429,6 @@
         assertPinnedToWifiWithCellDefault();
     }
 
-    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testNetworkCallbackMaximum() throws Exception {
         final int MAX_REQUESTS = 100;
@@ -7549,6 +7547,19 @@
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.requestNetwork(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
+            // While requestNetwork increases the count synchronously, unregister decreases it
+            // asynchronously on a handler, so unregistering doesn't immediately free up
+            // a slot : calling unregister-register when max requests are registered throws.
+            // Potential fix : ConnectivityService catches TooManyRequestsException once when
+            // creating NetworkRequestInfo and waits for handler thread (see
+            // https://r.android.com/2707373 for impl). However, this complexity is not equal to
+            // the issue ; the purpose of having "max requests" is only to help apps detect leaks.
+            // Apps relying on exact enforcement or rapid request registration should reconsider.
+            //
+            // In this test, test thread registering all before handler thread decrements can cause
+            // flakes. A single waitForIdle at (e.g.) MAX_REQUESTS / 2 processes decrements up to
+            // that point, fixing the flake.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
         waitForIdle();
 
@@ -7556,6 +7567,8 @@
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerNetworkCallback(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
+            // See comment above for the reasons for this wait.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
         waitForIdle();
 
@@ -7563,6 +7576,8 @@
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerDefaultNetworkCallback(networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
+            // See comment above for the reasons for this wait.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
         waitForIdle();
 
@@ -7570,6 +7585,8 @@
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerDefaultNetworkCallback(networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
+            // See comment above for the reasons for this wait.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
         waitForIdle();
 
@@ -7579,6 +7596,8 @@
                 mCm.registerDefaultNetworkCallbackForUid(1000000 + i, networkCallback,
                         new Handler(ConnectivityThread.getInstanceLooper()));
                 mCm.unregisterNetworkCallback(networkCallback);
+                // See comment above for the reasons for this wait.
+                if (MAX_REQUESTS / 2 == i) waitForIdle();
             }
         });
         waitForIdle();
@@ -7588,6 +7607,8 @@
                     mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE);
             mCm.requestNetwork(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
+            // See comment above for the reasons for this wait.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
         waitForIdle();
 
@@ -7596,6 +7617,8 @@
                     mContext, 0 /* requestCode */, new Intent("f" + i), FLAG_IMMUTABLE);
             mCm.registerNetworkCallback(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
+            // See comment above for the reasons for this wait.
+            if (MAX_REQUESTS / 2 == i) waitForIdle();
         }
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index b943bfc..4f0d46f 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -57,6 +57,7 @@
 import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
 import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
 import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
 import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -2862,15 +2863,34 @@
         // Verify MOBIKE is triggered
         verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
                 0 /* retryIndex */);
+        // Validation failure on VPN network should trigger a re-evaluation request for the
+        // underlying network.
+        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
 
         reset(mIkev2SessionCreator);
+        reset(mExecutor);
 
         // Send validation status update.
         // Recovered and get network validated. It should not trigger the ike session reset.
         ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_VALID);
+        // Verify that the retry count is reset. The mValidationFailRetryCount will not be reset
+        // until the executor finishes the execute() call, so wait until the all tasks are executed.
+        waitForIdleSerialExecutor(mExecutor, TEST_TIMEOUT_MS);
+        assertEquals(0,
+                ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).mValidationFailRetryCount);
         verify(mIkev2SessionCreator, never()).createIkeSession(
                 any(), any(), any(), any(), any(), any());
+
+        reset(mIkeSessionWrapper);
+        reset(mExecutor);
+
+        // Another validation fail should trigger another reportNetworkConnectivity
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                0 /* retryIndex */);
+        verify(mConnectivityManager, times(2)).reportNetworkConnectivity(TEST_NETWORK, false);
     }
 
     @Test
@@ -2884,7 +2904,9 @@
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
         verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
                 retry++);
-
+        // Validation failure on VPN network should trigger a re-evaluation request for the
+        // underlying network.
+        verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
         reset(mIkev2SessionCreator);
 
         // Second validation status update.
@@ -2892,6 +2914,8 @@
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
         verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
                 retry++);
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
 
         // Use real delay to verify reset session will not be performed if there is an existing
         // recovery for resetting the session.
@@ -2908,6 +2932,8 @@
                 eq(TimeUnit.MILLISECONDS));
         final List<Long> delays = delayCaptor.getAllValues();
         assertEquals(expectedDelay, (long) delays.get(delays.size() - 1));
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
 
         // Another invalid status reported should not trigger other scheduled recovery.
         expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
@@ -2919,6 +2945,8 @@
         // Verify that session being reset
         verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay))
                 .createIkeSession(any(), any(), any(), any(), any(), any());
+        // Call to reportNetworkConnectivity should only happen once. No further interaction.
+        verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
     }
 
     @Test