Build the timerfd wrapper class

Build a wrapper class that allows users to easily use the timerfd
object and trigger delayed callbacks through this class.

Bug: 366373064
Test: atest NetworkStaticLibTests
Change-Id: I8081d29e4874df8b9e59f7fc845b20cbd29aec40
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 8c54e6a..9d1d291 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -55,6 +55,7 @@
         // For mockito extended
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
+        "libcom_android_net_moduletests_util_jni",
     ],
     jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
diff --git a/staticlibs/tests/unit/jni/Android.bp b/staticlibs/tests/unit/jni/Android.bp
new file mode 100644
index 0000000..e456471
--- /dev/null
+++ b/staticlibs/tests/unit/jni/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES 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_team: "trendy_team_fwk_core_networking",
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libcom_android_net_moduletests_util_jni",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+    srcs: [
+        "com_android_net_moduletests_util/onload.cpp",
+    ],
+    static_libs: [
+        "libnet_utils_device_common_timerfdjni",
+    ],
+    shared_libs: [
+        "liblog",
+        "libnativehelper",
+    ],
+}
diff --git a/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
new file mode 100644
index 0000000..a035540
--- /dev/null
+++ b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+
+#define LOG_TAG "NetworkStaticLibTestsJni"
+#include <android/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+                                                      char const *class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM *vm, void *) {
+  JNIEnv *env;
+  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
+    __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "ERROR: GetEnv failed");
+    return JNI_ERR;
+  }
+
+  if (register_com_android_net_module_util_TimerFdUtils(
+          env, "com/android/net/moduletests/util/TimerFdUtils") < 0)
+    return JNI_ERR;
+
+  return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt
new file mode 100644
index 0000000..2018902
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/TimerFileDescriptorTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os.Build
+import android.os.ConditionVariable
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.tryTest
+import com.android.testutils.visibleOnHandlerThread
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.time.Duration
+import java.time.Instant
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class TimerFileDescriptorTest {
+    private val thread = HandlerThread(TimerFileDescriptorTest::class.simpleName).apply { start() }
+    private val handler by lazy { Handler(thread.looper) }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    @Test
+    fun testSetDelayedTask() {
+        val delayTime = 10L
+        val timerFd = TimerFileDescriptor(handler)
+        val cv = ConditionVariable()
+        val startTime = Instant.now()
+        tryTest {
+            handler.post { timerFd.setDelayedTask({ cv.open() }, delayTime) }
+            assertTrue(cv.block(100L /* timeoutMs*/))
+            // Verify that the delay time has actually passed.
+            val duration = Duration.between(startTime, Instant.now())
+            assertTrue(duration.toMillis() >= delayTime)
+        } cleanup {
+            visibleOnHandlerThread(handler) { timerFd.close() }
+        }
+    }
+
+    @Test
+    fun testCancelTask() {
+        // The task is posted and canceled within the same handler loop, so the short delay used
+        // here won't cause flakes.
+        val delayTime = 10L
+        val timerFd = TimerFileDescriptor(handler)
+        val cv = ConditionVariable()
+        tryTest {
+            handler.post {
+                timerFd.setDelayedTask({ cv.open() }, delayTime)
+                assertTrue(timerFd.hasDelayedTask())
+                timerFd.cancelTask()
+                assertFalse(timerFd.hasDelayedTask())
+            }
+            assertFalse(cv.block(20L /* timeoutMs*/))
+        } cleanup {
+            visibleOnHandlerThread(handler) { timerFd.close() }
+        }
+    }
+}