Merge "Add ConnectivityServiceIntegrationTest"
am: 324d724fd7
Change-Id: Ib82c5e5aee9e830a0efe8ea6335933237bb33856
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2a2dc3d..18a8148 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2165,7 +2165,11 @@
}
}
- void systemReady() {
+ /**
+ * Called when the system is ready and ConnectivityService can initialize remaining components.
+ */
+ @VisibleForTesting
+ public void systemReady() {
mProxyTracker.loadGlobalProxy();
registerNetdEventCallback();
mTethering.systemReady();
diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp
index 16a68d7..7d9b7b7 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -14,6 +14,39 @@
// limitations under the License.
//
+android_test {
+ name: "FrameworksNetIntegrationTests",
+ platform_apis: true,
+ certificate: "platform",
+ srcs: [
+ "src/**/*.kt",
+ "src/**/*.aidl",
+ ],
+ libs: [
+ "android.test.mock",
+ ],
+ static_libs: [
+ "TestNetworkStackLib",
+ "androidx.test.ext.junit",
+ "frameworks-net-integration-testutils",
+ "kotlin-reflect",
+ "mockito-target-extended-minus-junit4",
+ "net-tests-utils",
+ "services.core",
+ "services.net",
+ "testables",
+ ],
+ use_embedded_native_libs: true,
+ jni_libs: [
+ // For mockito extended
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ // android_library does not include JNI libs: include NetworkStack dependencies here
+ "libnativehelper_compat_libc++",
+ "libnetworkstackutilsjni",
+ ],
+}
+
// Utilities for testing framework code both in integration and unit tests.
java_library {
name: "frameworks-net-integration-testutils",
diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml
new file mode 100644
index 0000000..91b3cd9
--- /dev/null
+++ b/tests/net/integration/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.server.net.integrationtests">
+
+ <!-- For ConnectivityService registerReceiverAsUser (receiving broadcasts) -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <!-- PermissionMonitor sets network permissions for each user -->
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <!-- ConnectivityService sends notifications to BatteryStats -->
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+
+ <!-- This manifest is merged with the base manifest of the real NetworkStack app.
+ Remove the NetworkStackService from the base (real) manifest, and replace with a test
+ service that responds to the same intent -->
+ <service android:name="com.android.server.NetworkStackService" tools:node="remove"/>
+ <service android:name=".TestNetworkStackService"
+ android:process="com.android.server.net.integrationtests.testnetworkstack">
+ <intent-filter>
+ <action android:name="android.net.INetworkStackConnector.Test"/>
+ </intent-filter>
+ </service>
+ <service android:name=".NetworkStackInstrumentationService"
+ android:process="com.android.server.net.integrationtests.testnetworkstack">
+ <intent-filter>
+ <action android:name=".INetworkStackInstrumentation"/>
+ </intent-filter>
+ </service>
+ <service tools:replace="android:process"
+ android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+ android:process="com.android.server.net.integrationtests.testnetworkstack"/>
+
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.net.integrationtests"
+ android:label="Frameworks Net Integration Tests" />
+
+</manifest>
diff --git a/tests/net/integration/res/values/config.xml b/tests/net/integration/res/values/config.xml
new file mode 100644
index 0000000..2c8046f
--- /dev/null
+++ b/tests/net/integration/res/values/config.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!--
+ Override configuration for testing. The below settings use the config_ variants, which are
+ normally used by RROs to override the setting with highest priority. -->
+ <integer name="config_captive_portal_dns_probe_timeout">12500</integer>
+ <string name="config_captive_portal_http_url" translatable="false">http://test.android.com</string>
+ <string name="config_captive_portal_https_url" translatable="false">https://secure.test.android.com</string>
+ <string-array name="config_captive_portal_fallback_urls" translatable="false">
+ <item>http://fallback1.android.com</item>
+ <item>http://fallback2.android.com</item>
+ </string-array>
+ <string-array name="config_captive_portal_fallback_probe_specs" translatable="false">
+ </string-array>
+</resources>
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
new file mode 100644
index 0000000..01eb514
--- /dev/null
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -0,0 +1,80 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import com.android.server.net.integrationtests.TestNetworkStackService
+import org.mockito.Mockito.any
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import kotlin.test.fail
+
+const val TEST_ACTION_SUFFIX = ".Test"
+
+class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+ // TODO: consider switching to TrackRecord for more expressive checks
+ private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+
+ private class TestDependencies(private val context: Context) : Dependencies {
+ override fun addToServiceManager(service: IBinder) = Unit
+ override fun checkCallerUid() = Unit
+
+ override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
+ return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
+ getNetworkStackIntent(inSystemProcess)
+ }.also { it.init(context) }
+ }
+
+ private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
+ // Simulate out-of-system-process config: in-process service not found (null intent)
+ if (inSystemProcess) return null
+ val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
+ val serviceName = TestNetworkStackService::class.qualifiedName
+ ?: fail("TestNetworkStackService name not found")
+ intent.component = ComponentName(context.packageName, serviceName)
+ return intent
+ }
+ }
+
+ // base may be an instance of an inaccessible subclass, so non-spyable.
+ // Use a known open class that delegates to the original instance for all methods except
+ // asBinder. asBinder needs to use its own non-delegated implementation as otherwise it would
+ // return a binder token to a class that is not spied on.
+ open class NetworkMonitorCallbacksWrapper(private val base: INetworkMonitorCallbacks) :
+ INetworkMonitorCallbacks.Stub(), INetworkMonitorCallbacks by base {
+ // asBinder is implemented by both base class and delegate: specify explicitly
+ override fun asBinder(): IBinder {
+ return super.asBinder()
+ }
+ }
+
+ override fun makeNetworkMonitor(network: Network, name: String?, cb: INetworkMonitorCallbacks) {
+ val cbSpy = spy(NetworkMonitorCallbacksWrapper(cb))
+ lastCallbacks[network] = cbSpy
+ super.makeNetworkMonitor(network, name, cbSpy)
+ }
+
+ fun verifyNetworkMonitorCreated(network: Network, timeoutMs: Long) {
+ val cb = lastCallbacks[network]
+ ?: fail("NetworkMonitor for network $network not requested")
+ verify(cb, timeout(timeoutMs)).onNetworkMonitorCreated(any())
+ }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
new file mode 100644
index 0000000..334b26d
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.server.net.integrationtests
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Context.BIND_IMPORTANT
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.ConnectivityManager
+import android.net.IDnsResolver
+import android.net.INetd
+import android.net.INetworkPolicyManager
+import android.net.INetworkStatsService
+import android.net.LinkProperties
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkRequest
+import android.net.TestNetworkStackClient
+import android.net.metrics.IpConnectivityLog
+import android.os.ConditionVariable
+import android.os.IBinder
+import android.os.INetworkManagementService
+import android.testing.TestableContext
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.ConnectivityService
+import com.android.server.LocalServices
+import com.android.server.NetworkAgentWrapper
+import com.android.server.TestNetIdManager
+import com.android.server.connectivity.DefaultNetworkMetrics
+import com.android.server.connectivity.IpConnectivityMetrics
+import com.android.server.connectivity.MockableSystemProperties
+import com.android.server.connectivity.ProxyTracker
+import com.android.server.connectivity.Tethering
+import com.android.server.net.NetworkPolicyManagerInternal
+import com.android.testutils.TestableNetworkCallback
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.Spy
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+const val SERVICE_BIND_TIMEOUT_MS = 5_000L
+const val TEST_TIMEOUT_MS = 1_000L
+
+/**
+ * Test that exercises an instrumented version of ConnectivityService against an instrumented
+ * NetworkStack in a different test process.
+ */
+@RunWith(AndroidJUnit4::class)
+class ConnectivityServiceIntegrationTest {
+ // lateinit used here for mocks as they need to be reinitialized between each test and the test
+ // should crash if they are used before being initialized.
+ @Mock
+ private lateinit var netManager: INetworkManagementService
+ @Mock
+ private lateinit var statsService: INetworkStatsService
+ @Mock
+ private lateinit var policyManager: INetworkPolicyManager
+ @Mock
+ private lateinit var log: IpConnectivityLog
+ @Mock
+ private lateinit var netd: INetd
+ @Mock
+ private lateinit var dnsResolver: IDnsResolver
+ @Mock
+ private lateinit var metricsLogger: IpConnectivityMetrics.Logger
+ @Mock
+ private lateinit var defaultMetrics: DefaultNetworkMetrics
+ @Spy
+ private var context = TestableContext(realContext)
+
+ // lateinit for these three classes under test, as they should be reset to a different instance
+ // for every test but should always be initialized before use (or the test should crash).
+ private lateinit var networkStackClient: TestNetworkStackClient
+ private lateinit var service: ConnectivityService
+ private lateinit var cm: ConnectivityManager
+
+ companion object {
+ // lateinit for this binder token, as it must be initialized before any test code is run
+ // and use of it before init should crash the test.
+ private lateinit var nsInstrumentation: INetworkStackInstrumentation
+ private val bindingCondition = ConditionVariable(false)
+
+ private val realContext get() = InstrumentationRegistry.getInstrumentation().context
+
+ private class InstrumentationServiceConnection : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ Log.i("TestNetworkStack", "Service connected")
+ try {
+ if (service == null) fail("Error binding to NetworkStack instrumentation")
+ if (::nsInstrumentation.isInitialized) fail("Service already connected")
+ nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service)
+ } finally {
+ bindingCondition.open()
+ }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) = Unit
+ }
+
+ @BeforeClass
+ @JvmStatic
+ fun setUpClass() {
+ val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
+ intent.action = INetworkStackInstrumentation::class.qualifiedName
+ assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(),
+ BIND_AUTO_CREATE or BIND_IMPORTANT),
+ "Error binding to instrumentation service")
+ assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
+ "Timed out binding to instrumentation service " +
+ "after $SERVICE_BIND_TIMEOUT_MS ms")
+ }
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ doReturn(defaultMetrics).`when`(metricsLogger).defaultNetworkMetrics()
+ doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any())
+
+ networkStackClient = TestNetworkStackClient(realContext)
+ networkStackClient.init()
+ networkStackClient.start()
+
+ LocalServices.removeServiceForTest(NetworkPolicyManagerInternal::class.java)
+ LocalServices.addService(NetworkPolicyManagerInternal::class.java,
+ mock(NetworkPolicyManagerInternal::class.java))
+
+ service = TestConnectivityService(makeDependencies())
+ cm = ConnectivityManager(context, service)
+ context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm)
+
+ service.systemReady()
+ }
+
+ private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
+ context, netManager, statsService, policyManager, dnsResolver, log, netd, deps)
+
+ private fun makeDependencies(): ConnectivityService.Dependencies {
+ val deps = spy(ConnectivityService.Dependencies())
+ doReturn(networkStackClient).`when`(deps).networkStack
+ doReturn(metricsLogger).`when`(deps).metricsLogger
+ doReturn(mock(Tethering::class.java)).`when`(deps).makeTethering(
+ any(), any(), any(), any(), any())
+ doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
+ doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
+ doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
+ return deps
+ }
+
+ @After
+ fun tearDown() {
+ nsInstrumentation.clearAllState()
+ }
+
+ @Test
+ fun testValidation() {
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ val testCallback = TestableNetworkCallback()
+
+ cm.registerNetworkCallback(request, testCallback)
+ nsInstrumentation.addHttpResponse(HttpResponse(
+ "http://test.android.com",
+ responseCode = 204, contentLength = 42, redirectUrl = null))
+ nsInstrumentation.addHttpResponse(HttpResponse(
+ "https://secure.test.android.com",
+ responseCode = 204, contentLength = 42, redirectUrl = null))
+
+ val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
+ networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
+
+ na.addCapability(NET_CAPABILITY_INTERNET)
+ na.connect()
+
+ testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
+ assertEquals(2, nsInstrumentation.getRequestUrls().size)
+ }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
new file mode 100644
index 0000000..9a2bcfe
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.server.net.integrationtests;
+
+parcelable HttpResponse;
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
new file mode 100644
index 0000000..45073d8
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.server.net.integrationtests
+
+import android.os.Parcel
+import android.os.Parcelable
+
+data class HttpResponse(
+ val requestUrl: String,
+ val responseCode: Int,
+ val contentLength: Long,
+ val redirectUrl: String?
+) : Parcelable {
+ constructor(p: Parcel): this(p.readString(), p.readInt(), p.readLong(), p.readString())
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ with(dest) {
+ writeString(requestUrl)
+ writeInt(responseCode)
+ writeLong(contentLength)
+ writeString(redirectUrl)
+ }
+ }
+
+ override fun describeContents() = 0
+ companion object CREATOR : Parcelable.Creator<HttpResponse> {
+ override fun createFromParcel(source: Parcel) = HttpResponse(source)
+ override fun newArray(size: Int) = arrayOfNulls<HttpResponse?>(size)
+ }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
new file mode 100644
index 0000000..efc58ad
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.server.net.integrationtests;
+
+import com.android.server.net.integrationtests.HttpResponse;
+
+interface INetworkStackInstrumentation {
+ void clearAllState();
+ void addHttpResponse(in HttpResponse response);
+ List<String> getRequestUrls();
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
new file mode 100644
index 0000000..4827d29
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.server.net.integrationtests
+
+import android.app.Service
+import android.content.Intent
+import java.net.URL
+import java.util.Collections
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedQueue
+import kotlin.collections.ArrayList
+import kotlin.test.fail
+
+/**
+ * An instrumentation interface for the NetworkStack that allows controlling behavior to
+ * facilitate integration tests.
+ */
+class NetworkStackInstrumentationService : Service() {
+ override fun onBind(intent: Intent) = InstrumentationConnector.asBinder()
+
+ object InstrumentationConnector : INetworkStackInstrumentation.Stub() {
+ private val httpResponses = ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpResponse>>()
+ .run {
+ withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } }
+ }
+ private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>())
+
+ /**
+ * Called when an HTTP request is being processed by NetworkMonitor. Returns the response
+ * that should be simulated.
+ */
+ fun processRequest(url: URL): HttpResponse {
+ val strUrl = url.toString()
+ httpRequestUrls.add(strUrl)
+ return httpResponses[strUrl]?.poll()
+ ?: fail("No mocked response for request: $strUrl. " +
+ "Mocked URL keys are: ${httpResponses.keys}")
+ }
+
+ /**
+ * Clear all state of this connector. This is intended for use between two tests, so all
+ * state should be reset as if the connector was just created.
+ */
+ override fun clearAllState() {
+ httpResponses.clear()
+ httpRequestUrls.clear()
+ }
+
+ /**
+ * Add a response to a future HTTP request.
+ *
+ * <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
+ * used to mock the query response.
+ */
+ override fun addHttpResponse(response: HttpResponse) {
+ httpResponses.getValue(response.requestUrl).add(response)
+ }
+
+ /**
+ * Get the ordered list of request URLs that have been sent by NetworkMonitor, and were
+ * answered based on mock responses.
+ */
+ override fun getRequestUrls(): List<String> {
+ return ArrayList(httpRequestUrls)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
new file mode 100644
index 0000000..8e4a9dd
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.server.net.integrationtests
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.INetworkMonitorCallbacks
+import android.net.Network
+import android.net.metrics.IpConnectivityLog
+import android.net.util.SharedLog
+import android.os.IBinder
+import com.android.networkstack.metrics.DataStallStatsUtils
+import com.android.server.NetworkStackService.NetworkMonitorConnector
+import com.android.server.NetworkStackService.NetworkStackConnector
+import com.android.server.connectivity.NetworkMonitor
+import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import java.net.HttpURLConnection
+import java.net.URL
+import java.net.URLConnection
+
+private const val TEST_NETID = 42
+
+/**
+ * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented
+ * through [NetworkStackInstrumentationService].
+ * Useful in tests to create test instrumented NetworkStack components that can receive
+ * instrumentation commands through [NetworkStackInstrumentationService].
+ */
+class TestNetworkStackService : Service() {
+ override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext())
+
+ private fun makeTestContext() = spy(applicationContext).also {
+ doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
+ }
+
+ private class TestPermissionChecker : NetworkStackConnector.PermissionChecker() {
+ override fun enforceNetworkStackCallingPermission() = Unit
+ }
+
+ private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
+ NetworkMonitor.Dependencies() {
+ override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+ override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
+ }
+
+ private inner class TestNetworkStackConnector(context: Context) :
+ NetworkStackConnector(context, TestPermissionChecker()) {
+
+ private val network = Network(TEST_NETID)
+ private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
+
+ private inner class TestNetwork(netId: Int) : Network(netId) {
+ override fun openConnection(url: URL): URLConnection {
+ val response = InstrumentationConnector.processRequest(url)
+
+ val connection = mock(HttpURLConnection::class.java)
+ doReturn(response.responseCode).`when`(connection).responseCode
+ doReturn(response.contentLength).`when`(connection).contentLengthLong
+ doReturn(response.redirectUrl).`when`(connection).getHeaderField("location")
+ return connection
+ }
+ }
+
+ override fun makeNetworkMonitor(
+ network: Network,
+ name: String?,
+ cb: INetworkMonitorCallbacks
+ ) {
+ val nm = NetworkMonitor(this@TestNetworkStackService, cb,
+ this.network,
+ mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+ NetworkMonitorDeps(privateDnsBypassNetwork),
+ mock(DataStallStatsUtils::class.java))
+ cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker()))
+ }
+ }
+}
\ No newline at end of file