Merge "Reconnect to wifi if supported" into main
diff --git a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
index 310dbc9..c7ed911 100644
--- a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
@@ -26,12 +26,13 @@
  */
 public class TimerFdUtils {
     static {
-        if (Process.myUid() == Process.SYSTEM_UID) {
+        final String jniLibName = JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage());
+        if (jniLibName.equals("android_net_connectivity_com_android_net_module_util_jni")) {
             // This library is part of service-connectivity.jar when in the system server,
             // so libservice-connectivity.so is the library to load.
             System.loadLibrary("service-connectivity");
         } else {
-            System.loadLibrary(JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage()));
+            System.loadLibrary(jniLibName);
         }
     }
 
diff --git a/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
new file mode 100644
index 0000000..5a16de6
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
@@ -0,0 +1,199 @@
+/*
+ * 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 static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+
+/**
+ * Represents a Timer file descriptor object used for scheduling tasks with precise delays.
+ * Compared to {@link Handler#postDelayed}, this class offers enhanced accuracy for delayed
+ * callbacks by accounting for periods when the device is in deep sleep.
+ *
+ *  <p> This class is designed for use exclusively from the handler thread.
+ *
+ * **Usage Examples:**
+ *
+ * ** Scheduling recurring tasks with the same TimerFileDescriptor **
+ *
+ * ```java
+ * // Create a TimerFileDescriptor
+ * final TimerFileDescriptor timerFd = new TimerFileDescriptor(handler);
+ *
+ * // Schedule a new task with a delay.
+ * timerFd.setDelayedTask(() -> taskToExecute(), delayTime);
+ *
+ * // Once the delay has elapsed, and the task is running, schedule another task.
+ * timerFd.setDelayedTask(() -> anotherTaskToExecute(), anotherDelayTime);
+ *
+ * // Remember to close the TimerFileDescriptor after all tasks have finished running.
+ * timerFd.close();
+ * ```
+ */
+public class TimerFileDescriptor {
+    private static final String TAG = TimerFileDescriptor.class.getSimpleName();
+    // EVENT_ERROR may be generated even if not specified, as per its javadoc.
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private final CloseGuard mGuard = new CloseGuard();
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final MessageQueue mQueue;
+    @NonNull
+    private final ParcelFileDescriptor mParcelFileDescriptor;
+    private final int mFdInt;
+    @Nullable
+    private Runnable mTask;
+
+    /**
+     * TimerFileDescriptor constructor
+     *
+     * Note: The constructor is currently safe to call on another thread because it only sets final
+     * members and registers the event to be called on the handler.
+     */
+    public TimerFileDescriptor(@NonNull Handler handler) {
+        mFdInt = TimerFdUtils.createTimerFileDescriptor();
+        mParcelFileDescriptor = ParcelFileDescriptor.adoptFd(mFdInt);
+        mHandler = handler;
+        mQueue = handler.getLooper().getQueue();
+        registerFdEventListener();
+
+        mGuard.open("close");
+    }
+
+    /**
+     * Set a task to be executed after a specified delay.
+     *
+     * <p> A task can only be scheduled once at a time. Cancel previous scheduled task before the
+     *     new task is scheduled.
+     *
+     * @param task the task to be executed
+     * @param delayMs the delay time in milliseconds
+     * @throws IllegalArgumentException if try to replace the current scheduled task
+     * @throws IllegalArgumentException if the delay time is less than 0
+     */
+    public void setDelayedTask(@NonNull Runnable task, long delayMs) {
+        ensureRunningOnCorrectThread();
+        if (mTask != null) {
+            throw new IllegalArgumentException("task is already scheduled");
+        }
+        if (delayMs <= 0L) {
+            mHandler.post(task);
+            return;
+        }
+
+        if (TimerFdUtils.setExpirationTime(mFdInt, delayMs)) {
+            mTask = task;
+        }
+    }
+
+    /**
+     * Cancel the scheduled task.
+     */
+    public void cancelTask() {
+        ensureRunningOnCorrectThread();
+        if (mTask == null) return;
+
+        TimerFdUtils.setExpirationTime(mFdInt, 0 /* delayMs */);
+        mTask = null;
+    }
+
+    /**
+     * Check if there is a scheduled task.
+     */
+    public boolean hasDelayedTask() {
+        ensureRunningOnCorrectThread();
+        return mTask != null;
+    }
+
+    /**
+     * Close the TimerFileDescriptor. This implementation closes the underlying
+     * OS resources allocated to represent this stream.
+     */
+    public void close() {
+        ensureRunningOnCorrectThread();
+        unregisterAndDestroyFd();
+    }
+
+    private void registerFdEventListener() {
+        mQueue.addOnFileDescriptorEventListener(
+                mParcelFileDescriptor.getFileDescriptor(),
+                FD_EVENTS,
+                (fd, events) -> {
+                    if (!isRunning()) {
+                        return 0;
+                    }
+                    if ((events & EVENT_INPUT) != 0) {
+                        handleExpiration();
+                    }
+                    return FD_EVENTS;
+                });
+    }
+
+    private boolean isRunning() {
+        return mParcelFileDescriptor.getFileDescriptor().valid();
+    }
+
+    private void handleExpiration() {
+        // Execute the task
+        if (mTask != null) {
+            mTask.run();
+            mTask = null;
+        }
+    }
+
+    private void unregisterAndDestroyFd() {
+        if (mGuard != null) {
+            mGuard.close();
+        }
+
+        mQueue.removeOnFileDescriptorEventListener(mParcelFileDescriptor.getFileDescriptor());
+        try {
+            mParcelFileDescriptor.close();
+        } catch (IOException exception) {
+            Log.e(TAG, "close ParcelFileDescriptor failed. ", exception);
+        }
+    }
+
+    private void ensureRunningOnCorrectThread() {
+        if (mHandler.getLooper() != Looper.myLooper()) {
+            throw new IllegalStateException(
+                    "Not running on Handler thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        if (mGuard != null) {
+            mGuard.warnIfOpen();
+        }
+        super.finalize();
+    }
+}
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() }
+        }
+    }
+}
diff --git a/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
index f6168af..a99359b 100644
--- a/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -18,12 +18,15 @@
 
 import com.android.ddmlib.testrunner.TestResult
 import com.android.tradefed.config.Option
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey
 import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.log.LogUtil.CLog
 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
+import java.io.File
 
 private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
 private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
@@ -48,7 +51,41 @@
  * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
  */
 open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
-    private val installer = SuiteApkInstaller()
+    private val installer = ApkInstaller()
+
+    private class ApkInstaller : SuiteApkInstaller() {
+        override fun getLocalPathForFilename(
+            testInfo: TestInformation,
+            apkFileName: String
+        ): File {
+            if (apkFileName == CONNECTIVITY_CHECKER_APK) {
+                // For the connectivity checker APK, explicitly look for it in the directory of the
+                // host-side preparer.
+                // This preparer is part of the net-tests-utils-host-common library, which includes
+                // the checker APK via device_common_data in its build rule. Both need to be at the
+                // same version so that the preparer calls the right test methods in the checker
+                // APK.
+                // The default strategy for finding test files is to do a recursive search in test
+                // directories, which may find wrong files in wrong directories. In particular,
+                // if some MTS test includes the checker APK, and that test is linked to a module
+                // that boards the train at a version different from this target preparer, there
+                // could be a version difference between the APK and the host-side preparer.
+                // Explicitly looking for the APK in the host-side preparer directory ensures that
+                // it uses the version that was packaged together with the host-side preparer.
+                val testsDir = testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)
+                val f = File(testsDir, "net-tests-utils-host-common/$CONNECTIVITY_CHECKER_APK")
+                if (f.isFile) {
+                    return f
+                }
+                // When running locally via atest, device_common_data does cause the APK to be put
+                // into the test temp directory, so recursive search is still used to find it in the
+                // directory of the test module that is being run. This is fine because atest runs
+                // are on local trees that do not have versioning problems.
+                CLog.i("APK not found at $f, falling back to recursive search")
+            }
+            return super.getLocalPathForFilename(testInfo, apkFileName)
+        }
+    }
 
     @Option(
         name = IGNORE_WIFI_CHECK,
@@ -179,7 +216,7 @@
                 .contains(":deny")
         }
 
-    private fun refreshTime(testInfo: TestInformation,) {
+    private fun refreshTime(testInfo: TestInformation) {
         // Forces a synchronous time refresh using the network. Time is fetched synchronously but
         // this does not guarantee that system time is updated when it returns.
         // This avoids flakes where the system clock rolls back, for example when using test
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 30d5a02..c55096b 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -351,14 +351,14 @@
         }
 
         otDaemon.initialize(
-                mTunIfController.getTunFd(),
                 shouldEnableThread(),
                 newOtDaemonConfig(mPersistentSettings.getConfiguration()),
+                mTunIfController.getTunFd(),
                 mNsdPublisher,
                 getMeshcopTxtAttributes(mResources.get()),
-                mOtDaemonCallbackProxy,
                 mCountryCodeSupplier.get(),
-                FeatureFlags.isTrelEnabled());
+                FeatureFlags.isTrelEnabled(),
+                mOtDaemonCallbackProxy);
         otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
         mOtDaemon = otDaemon;
         mHandler.post(mNat64CidrController::maybeUpdateNat64Cidr);
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index aeeed65..875a4ad 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -16,7 +16,6 @@
 
 package android.net.thread;
 
-import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
 import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
@@ -38,7 +37,6 @@
 
 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.testutils.TestPermissionUtil.runAsShell;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -56,7 +54,6 @@
 import android.net.RouteInfo;
 import android.net.thread.utils.FullThreadDevice;
 import android.net.thread.utils.InfraNetworkDevice;
-import android.net.thread.utils.IntegrationTestUtils;
 import android.net.thread.utils.OtDaemonController;
 import android.net.thread.utils.TestTunNetworkUtils;
 import android.net.thread.utils.ThreadFeatureCheckerRule;