Add TapPacketReaderRule

This test rule allows tests to easily create a tap interface, a
TapPacketReader on it, and tear them down after the test.

Bug: 168868607
Test: atest NetworkStackIntegrationTests
Change-Id: Ibc167f5c9a8a1b295e8f6c8384a55a1e3410fcdc
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
new file mode 100644
index 0000000..3319fbe
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.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.
+ */
+class TapPacketReaderRule @JvmOverloads constructor(
+    private val maxPacketSize: Int = 1500
+) : 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, 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 in the unlikely event
+    // that the members are referenced before they could be initialized.
+    lateinit var iface: TestNetworkInterface
+    lateinit var reader: TapPacketReader
+
+    // The reader runs on its own handlerThread created locally, but this is not an actual
+    // requirement: any handler could be used for this rule. If using a specific handler is needed,
+    // a method could be added to start the TapPacketReader manually on a given handler.
+    private val handlerThread = HandlerThread(TapPacketReaderRule::class.java.simpleName)
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return TapReaderStatement(base)
+    }
+
+    private inner class TapReaderStatement(private val base: Statement) : Statement() {
+        override fun evaluate() {
+            val ctx: android.content.Context = InstrumentationRegistry.getInstrumentation().context
+            iface = runAsShell(MANAGE_TEST_NETWORKS) {
+                val tnm = ctx.getSystemService(TestNetworkManager::class.java)
+                        ?: fail("Could not obtain the TestNetworkManager")
+                tnm.createTapInterface()
+            }
+
+            handlerThread.start()
+            reader = TapPacketReader(handlerThread.threadHandler,
+                    iface.fileDescriptor.fileDescriptor, maxPacketSize)
+            reader.startAsyncForTest()
+
+            try {
+                base.evaluate()
+            } finally {
+                handlerThread.threadHandler.post(reader::stop)
+                handlerThread.quitSafely()
+                handlerThread.join(HANDLER_TIMEOUT_MS)
+                assertFalse(handlerThread.isAlive,
+                        "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
+                iface.fileDescriptor.close()
+            }
+        }
+    }
+}
\ No newline at end of file