Merge "gn2bp: Convert cc_objects to cc_static_library"
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index d260694..945b220 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -50,8 +50,6 @@
],
static_libs: [
"androidx.test.ext.junit",
- "androidx.test.rules",
- "androidx.core_core",
"ctstestrunner-axt",
"ctstestserver",
"junit",
@@ -59,9 +57,7 @@
"kotlin-test",
],
libs: [
- "android.test.runner",
"android.test.base",
- "android.test.mock",
"androidx.annotation_annotation",
"framework-tethering",
"org.apache.http.legacy",
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
new file mode 100644
index 0000000..13c220d
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.net.http.cts
+
+import android.content.Context
+import android.net.http.BidirectionalStream
+import android.net.http.HttpEngine
+import android.net.http.cts.util.TestBidirectionalStreamCallback
+import android.net.http.cts.util.TestBidirectionalStreamCallback.ResponseStep
+import android.net.http.cts.util.assumeOKStatusCode
+import android.net.http.cts.util.skipIfNoInternetConnection
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.hamcrest.MatcherAssert
+import org.hamcrest.Matchers
+import org.junit.After
+import org.junit.Before
+import org.junit.runner.RunWith
+
+private const val URL = "https://source.android.com"
+
+/**
+ * This tests uses a non-hermetic server. Instead of asserting, assume the next callback. This way,
+ * if the request were to fail, the test would just be skipped instead of failing.
+ */
+@RunWith(AndroidJUnit4::class)
+class BidirectionalStreamTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val callback = TestBidirectionalStreamCallback()
+ private val httpEngine = HttpEngine.Builder(context).build()
+ private var stream: BidirectionalStream? = null
+
+ @Before
+ fun setUp() {
+ skipIfNoInternetConnection(context)
+ }
+
+ @After
+ @Throws(Exception::class)
+ fun tearDown() {
+ // cancel active requests to enable engine shutdown.
+ stream?.let {
+ it.cancel()
+ callback.blockForDone()
+ }
+ httpEngine.shutdown()
+ }
+
+ private fun createBidirectionalStreamBuilder(url: String): BidirectionalStream.Builder {
+ return httpEngine.newBidirectionalStreamBuilder(url, callback, callback.executor)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
+ stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
+ stream!!.start()
+ callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+ val info = callback.mResponseInfo
+ assumeOKStatusCode(info)
+ MatcherAssert.assertThat(
+ "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L))
+ assertEquals("h2", info.negotiatedProtocol)
+ }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 8663a67..45d27bf 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -32,8 +32,8 @@
import android.net.http.cts.util.TestUrlRequestCallback;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -52,7 +52,7 @@
@Before
public void setUp() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Context context = ApplicationProvider.getApplicationContext();
skipIfNoInternetConnection(context);
mEngineBuilder = new HttpEngine.Builder(context);
mCallback = new TestUrlRequestCallback();
diff --git a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
index a2611e4..dd07a41 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
@@ -20,12 +20,15 @@
import android.net.http.NetworkException
import android.net.http.cts.util.TestUrlRequestCallback
import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertSame
import kotlin.test.assertTrue
import org.junit.Test
+import org.junit.runner.RunWith
+@RunWith(AndroidJUnit4::class)
class NetworkExceptionTest {
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
index 5256bae..2bec9e6 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -35,8 +35,8 @@
import android.net.http.cts.util.TestUrlRequestCallback;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -51,7 +51,7 @@
@Before
public void setUp() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Context context = ApplicationProvider.getApplicationContext();
skipIfNoInternetConnection(context);
HttpEngine.Builder builder = new HttpEngine.Builder(context);
mHttpEngine = builder.build();
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
new file mode 100644
index 0000000..e82b24d
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
@@ -0,0 +1,484 @@
+/*
+ * 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 android.net.http.cts.util;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.net.http.BidirectionalStream;
+import android.net.http.HttpException;
+import android.net.http.UrlResponseInfo;
+import android.os.ConditionVariable;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Callback that tracks information from different callbacks and has a method to block thread until
+ * the stream completes on another thread. Allows to cancel, block stream or throw an exception from
+ * an arbitrary step.
+ */
+public class TestBidirectionalStreamCallback extends BidirectionalStream.Callback {
+ private static final int TIMEOUT_MS = 12_000;
+ public UrlResponseInfo mResponseInfo;
+ public HttpException mError;
+
+ public ResponseStep mResponseStep = ResponseStep.NOTHING;
+
+ public boolean mOnErrorCalled;
+ public boolean mOnCanceledCalled;
+
+ public int mHttpResponseDataLength;
+ public String mResponseAsString = "";
+
+ public UrlResponseInfo.HeaderBlock mTrailers;
+
+ private static final int READ_BUFFER_SIZE = 32 * 1024;
+
+ // When false, the consumer is responsible for all calls into the stream
+ // that advance it.
+ private boolean mAutoAdvance = true;
+
+ // Conditionally fail on certain steps.
+ private FailureType mFailureType = FailureType.NONE;
+ private ResponseStep mFailureStep = ResponseStep.NOTHING;
+
+ // Signals when the stream is done either successfully or not.
+ private final ConditionVariable mDone = new ConditionVariable();
+
+ // Signaled on each step when mAutoAdvance is false.
+ private final ConditionVariable mReadStepBlock = new ConditionVariable();
+ private final ConditionVariable mWriteStepBlock = new ConditionVariable();
+
+ // Executor Service for Cronet callbacks.
+ private final ExecutorService mExecutorService =
+ Executors.newSingleThreadExecutor(new ExecutorThreadFactory());
+ private Thread mExecutorThread;
+
+ // position() of ByteBuffer prior to read() call.
+ private int mBufferPositionBeforeRead;
+
+ // Data to write.
+ private final ArrayList<WriteBuffer> mWriteBuffers = new ArrayList<WriteBuffer>();
+
+ // Buffers that we yet to receive the corresponding onWriteCompleted callback.
+ private final ArrayList<WriteBuffer> mWriteBuffersToBeAcked = new ArrayList<WriteBuffer>();
+
+ // Whether to use a direct executor.
+ private final boolean mUseDirectExecutor;
+ private final DirectExecutor mDirectExecutor;
+
+ private class ExecutorThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable r) {
+ mExecutorThread = new Thread(r);
+ return mExecutorThread;
+ }
+ }
+
+ private static class WriteBuffer {
+ final ByteBuffer mBuffer;
+ final boolean mFlush;
+
+ WriteBuffer(ByteBuffer buffer, boolean flush) {
+ mBuffer = buffer;
+ mFlush = flush;
+ }
+ }
+
+ private static class DirectExecutor implements Executor {
+ @Override
+ public void execute(Runnable task) {
+ task.run();
+ }
+ }
+
+ public enum ResponseStep {
+ NOTHING,
+ ON_STREAM_READY,
+ ON_RESPONSE_STARTED,
+ ON_READ_COMPLETED,
+ ON_WRITE_COMPLETED,
+ ON_TRAILERS,
+ ON_CANCELED,
+ ON_FAILED,
+ ON_SUCCEEDED,
+ }
+
+ public enum FailureType {
+ NONE,
+ CANCEL_SYNC,
+ CANCEL_ASYNC,
+ // Same as above, but continues to advance the stream after posting
+ // the cancellation task.
+ CANCEL_ASYNC_WITHOUT_PAUSE,
+ THROW_SYNC
+ }
+
+ private boolean isTerminalCallback(ResponseStep step) {
+ switch (step) {
+ case ON_SUCCEEDED:
+ case ON_CANCELED:
+ case ON_FAILED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public TestBidirectionalStreamCallback() {
+ mUseDirectExecutor = false;
+ mDirectExecutor = null;
+ }
+
+ public TestBidirectionalStreamCallback(boolean useDirectExecutor) {
+ mUseDirectExecutor = useDirectExecutor;
+ mDirectExecutor = new DirectExecutor();
+ }
+
+ public void setAutoAdvance(boolean autoAdvance) {
+ mAutoAdvance = autoAdvance;
+ }
+
+ public void setFailure(FailureType failureType, ResponseStep failureStep) {
+ mFailureStep = failureStep;
+ mFailureType = failureType;
+ }
+
+ public boolean blockForDone() {
+ return mDone.block(TIMEOUT_MS);
+ }
+
+ /**
+ * Waits for a terminal callback to complete execution before failing if the callback is not the
+ * expected one
+ *
+ * @param expectedStep the expected callback step
+ */
+ public void expectCallback(ResponseStep expectedStep) {
+ if (isTerminalCallback(expectedStep)) {
+ assertTrue(String.format(
+ "Request timed out. Expected %s callback. Current callback is %s",
+ expectedStep, mResponseStep),
+ blockForDone());
+ }
+ assertSame(expectedStep, mResponseStep);
+ }
+
+ /**
+ * Waits for a terminal callback to complete execution before skipping the test if the callback
+ * is not the expected one
+ *
+ * @param expectedStep the expected callback step
+ */
+ public void assumeCallback(ResponseStep expectedStep) {
+ if (isTerminalCallback(expectedStep)) {
+ assumeTrue(
+ String.format(
+ "Request timed out. Expected %s callback. Current callback is %s",
+ expectedStep, mResponseStep),
+ blockForDone());
+ }
+ assumeThat(expectedStep, equalTo(mResponseStep));
+ }
+
+ public void waitForNextReadStep() {
+ mReadStepBlock.block();
+ mReadStepBlock.close();
+ }
+
+ public void waitForNextWriteStep() {
+ mWriteStepBlock.block();
+ mWriteStepBlock.close();
+ }
+
+ public Executor getExecutor() {
+ if (mUseDirectExecutor) {
+ return mDirectExecutor;
+ }
+ return mExecutorService;
+ }
+
+ public void shutdownExecutor() {
+ if (mUseDirectExecutor) {
+ throw new UnsupportedOperationException("DirectExecutor doesn't support shutdown");
+ }
+ mExecutorService.shutdown();
+ }
+
+ public void addWriteData(byte[] data) {
+ addWriteData(data, true);
+ }
+
+ public void addWriteData(byte[] data, boolean flush) {
+ ByteBuffer writeBuffer = ByteBuffer.allocateDirect(data.length);
+ writeBuffer.put(data);
+ writeBuffer.flip();
+ mWriteBuffers.add(new WriteBuffer(writeBuffer, flush));
+ mWriteBuffersToBeAcked.add(new WriteBuffer(writeBuffer, flush));
+ }
+
+ @Override
+ public void onStreamReady(BidirectionalStream stream) {
+ checkOnValidThread();
+ assertFalse(stream.isDone());
+ assertEquals(ResponseStep.NOTHING, mResponseStep);
+ assertNull(mError);
+ mResponseStep = ResponseStep.ON_STREAM_READY;
+ if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+ return;
+ }
+ startNextWrite(stream);
+ }
+
+ @Override
+ public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInfo info) {
+ checkOnValidThread();
+ assertFalse(stream.isDone());
+ assertTrue(
+ mResponseStep == ResponseStep.NOTHING
+ || mResponseStep == ResponseStep.ON_STREAM_READY
+ || mResponseStep == ResponseStep.ON_WRITE_COMPLETED);
+ assertNull(mError);
+
+ mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
+ mResponseInfo = info;
+ if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+ return;
+ }
+ startNextRead(stream);
+ }
+
+ @Override
+ public void onReadCompleted(
+ BidirectionalStream stream,
+ UrlResponseInfo info,
+ ByteBuffer byteBuffer,
+ boolean endOfStream) {
+ checkOnValidThread();
+ assertFalse(stream.isDone());
+ assertTrue(
+ mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+ || mResponseStep == ResponseStep.ON_READ_COMPLETED
+ || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+ || mResponseStep == ResponseStep.ON_TRAILERS);
+ assertNull(mError);
+
+ mResponseStep = ResponseStep.ON_READ_COMPLETED;
+ mResponseInfo = info;
+
+ final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
+ mHttpResponseDataLength += bytesRead;
+ final byte[] lastDataReceivedAsBytes = new byte[bytesRead];
+ // Rewind byteBuffer.position() to pre-read() position.
+ byteBuffer.position(mBufferPositionBeforeRead);
+ // This restores byteBuffer.position() to its value on entrance to
+ // this function.
+ byteBuffer.get(lastDataReceivedAsBytes);
+
+ mResponseAsString += new String(lastDataReceivedAsBytes);
+
+ if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+ return;
+ }
+ // Do not read if EOF has been reached.
+ if (!endOfStream) {
+ startNextRead(stream);
+ }
+ }
+
+ @Override
+ public void onWriteCompleted(
+ BidirectionalStream stream,
+ UrlResponseInfo info,
+ ByteBuffer buffer,
+ boolean endOfStream) {
+ checkOnValidThread();
+ assertFalse(stream.isDone());
+ assertNull(mError);
+ mResponseStep = ResponseStep.ON_WRITE_COMPLETED;
+ mResponseInfo = info;
+ if (!mWriteBuffersToBeAcked.isEmpty()) {
+ assertEquals(buffer, mWriteBuffersToBeAcked.get(0).mBuffer);
+ mWriteBuffersToBeAcked.remove(0);
+ }
+ if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+ return;
+ }
+ startNextWrite(stream);
+ }
+
+ @Override
+ public void onResponseTrailersReceived(
+ BidirectionalStream stream,
+ UrlResponseInfo info,
+ UrlResponseInfo.HeaderBlock trailers) {
+ checkOnValidThread();
+ assertFalse(stream.isDone());
+ assertNull(mError);
+ mResponseStep = ResponseStep.ON_TRAILERS;
+ mResponseInfo = info;
+ mTrailers = trailers;
+ if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+ return;
+ }
+ }
+
+ @Override
+ public void onSucceeded(BidirectionalStream stream, UrlResponseInfo info) {
+ checkOnValidThread();
+ assertTrue(stream.isDone());
+ assertTrue(
+ mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+ || mResponseStep == ResponseStep.ON_READ_COMPLETED
+ || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+ || mResponseStep == ResponseStep.ON_TRAILERS);
+ assertFalse(mOnErrorCalled);
+ assertFalse(mOnCanceledCalled);
+ assertNull(mError);
+ assertEquals(0, mWriteBuffers.size());
+ assertEquals(0, mWriteBuffersToBeAcked.size());
+
+ mResponseStep = ResponseStep.ON_SUCCEEDED;
+ mResponseInfo = info;
+ openDone();
+ maybeThrowCancelOrPause(stream, mReadStepBlock);
+ }
+
+ @Override
+ public void onFailed(BidirectionalStream stream, UrlResponseInfo info, HttpException error) {
+ checkOnValidThread();
+ assertTrue(stream.isDone());
+ // Shouldn't happen after success.
+ assertTrue(mResponseStep != ResponseStep.ON_SUCCEEDED);
+ // Should happen at most once for a single stream.
+ assertFalse(mOnErrorCalled);
+ assertFalse(mOnCanceledCalled);
+ assertNull(mError);
+ mResponseStep = ResponseStep.ON_FAILED;
+ mResponseInfo = info;
+
+ mOnErrorCalled = true;
+ mError = error;
+ openDone();
+ maybeThrowCancelOrPause(stream, mReadStepBlock);
+ }
+
+ @Override
+ public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {
+ checkOnValidThread();
+ assertTrue(stream.isDone());
+ // Should happen at most once for a single stream.
+ assertFalse(mOnCanceledCalled);
+ assertFalse(mOnErrorCalled);
+ assertNull(mError);
+ mResponseStep = ResponseStep.ON_CANCELED;
+ mResponseInfo = info;
+
+ mOnCanceledCalled = true;
+ openDone();
+ maybeThrowCancelOrPause(stream, mReadStepBlock);
+ }
+
+ public void startNextRead(BidirectionalStream stream) {
+ startNextRead(stream, ByteBuffer.allocateDirect(READ_BUFFER_SIZE));
+ }
+
+ public void startNextRead(BidirectionalStream stream, ByteBuffer buffer) {
+ mBufferPositionBeforeRead = buffer.position();
+ stream.read(buffer);
+ }
+
+ public void startNextWrite(BidirectionalStream stream) {
+ if (!mWriteBuffers.isEmpty()) {
+ Iterator<WriteBuffer> iterator = mWriteBuffers.iterator();
+ while (iterator.hasNext()) {
+ WriteBuffer b = iterator.next();
+ stream.write(b.mBuffer, !iterator.hasNext());
+ iterator.remove();
+ if (b.mFlush) {
+ stream.flush();
+ break;
+ }
+ }
+ }
+ }
+
+ public boolean isDone() {
+ // It's not mentioned by the Android docs, but block(0) seems to block
+ // indefinitely, so have to block for one millisecond to get state
+ // without blocking.
+ return mDone.block(1);
+ }
+
+ /** Returns the number of pending Writes. */
+ public int numPendingWrites() {
+ return mWriteBuffers.size();
+ }
+
+ protected void openDone() {
+ mDone.open();
+ }
+
+ /** Returns {@code false} if the callback should continue to advance the stream. */
+ private boolean maybeThrowCancelOrPause(
+ final BidirectionalStream stream, ConditionVariable stepBlock) {
+ if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
+ if (!mAutoAdvance) {
+ stepBlock.open();
+ return true;
+ }
+ return false;
+ }
+
+ if (mFailureType == FailureType.THROW_SYNC) {
+ throw new IllegalStateException("Callback Exception.");
+ }
+ Runnable task =
+ new Runnable() {
+ @Override
+ public void run() {
+ stream.cancel();
+ }
+ };
+ if (mFailureType == FailureType.CANCEL_ASYNC
+ || mFailureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE) {
+ getExecutor().execute(task);
+ } else {
+ task.run();
+ }
+ return mFailureType != FailureType.CANCEL_ASYNC_WITHOUT_PAUSE;
+ }
+
+ /** Checks whether callback methods are invoked on the correct thread. */
+ private void checkOnValidThread() {
+ if (!mUseDirectExecutor) {
+ assertEquals(mExecutorThread, Thread.currentThread());
+ }
+ }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
index 23ec2c8..7fc005a 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
@@ -27,7 +27,8 @@
fun skipIfNoInternetConnection(context: Context) {
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
assumeNotNull(
- "This test requires a working Internet connection", connectivityManager.getActiveNetwork())
+ "This test requires a working Internet connection", connectivityManager!!.activeNetwork
+ )
}
fun assertOKStatusCode(info: UrlResponseInfo) {
@@ -35,5 +36,5 @@
}
fun assumeOKStatusCode(info: UrlResponseInfo) {
- assumeThat("Status code must be 200 OK", info.getHttpStatusCode(), equalTo(200))
+ assumeThat("Status code must be 200 OK", info.httpStatusCode, equalTo(200))
}
diff --git a/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
index 64256a0..2acf8cd 100644
--- a/Cronet/tools/import/copy.bara.sky
+++ b/Cronet/tools/import/copy.bara.sky
@@ -17,8 +17,8 @@
"**/Android.bp",
"**/Android.mk",
- # Exclude existing OWNERS files
- "**/OWNERS",
+ # Exclude existing *OWNERS files
+ "**/*OWNERS",
]
cronet_origin_files = glob(
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 34646e2..4d3ecdf 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,6 +58,17 @@
]
},
{
+ "name": "CtsNetTestCasesMaxTargetSdk33",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
"name": "bpf_existence_test"
},
{
@@ -143,6 +154,17 @@
}
]
},
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk33[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
diff --git a/Tethering/tests/mts/AndroidManifest.xml b/Tethering/tests/mts/AndroidManifest.xml
index 6d2abca..42f2da9 100644
--- a/Tethering/tests/mts/AndroidManifest.xml
+++ b/Tethering/tests/mts/AndroidManifest.xml
@@ -27,8 +27,6 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.tethering.mts"
android:label="MTS tests of android.tethering">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
</manifest>
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
index b64fbdb..0585756 100644
--- a/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -111,17 +111,18 @@
final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
final TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
- boolean hasCarrierPrivileges;
- final long token = Binder.clearCallingIdentity();
+ final boolean hasCarrierPrivileges;
+ final boolean isDeviceOwner;
+ long token = Binder.clearCallingIdentity();
try {
hasCarrierPrivileges = tm != null
&& tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
}
- final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
final int appId = UserHandle.getAppId(callingUid);
final boolean isNetworkStack = context.checkPermission(
@@ -135,15 +136,20 @@
return NetworkStatsAccess.Level.DEVICE;
}
- boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage);
+ final boolean hasAppOpsPermission =
+ hasAppOpsPermission(context, callingUid, callingPackage);
if (hasAppOpsPermission || context.checkCallingOrSelfPermission(
READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) {
return NetworkStatsAccess.Level.DEVICESUMMARY;
}
- //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
- boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
- || mDpm.isDeviceOwnerApp(callingPackage));
+ final boolean isProfileOwner;
+ token = Binder.clearCallingIdentity();
+ try {
+ isProfileOwner = mDpm != null && mDpm.isProfileOwnerApp(callingPackage);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
if (isProfileOwner) {
// Apps with the AppOps permission, profile owners, and apps with the privileged
// permission can access data usage for all apps in this user/profile.
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 547b7e2..672e3e2 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -360,6 +360,7 @@
field public static final int TRANSPORT_CELLULAR = 0; // 0x0
field public static final int TRANSPORT_ETHERNET = 3; // 0x3
field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+ field public static final int TRANSPORT_THREAD = 9; // 0x9
field public static final int TRANSPORT_USB = 8; // 0x8
field public static final int TRANSPORT_VPN = 4; // 0x4
field public static final int TRANSPORT_WIFI = 1; // 0x1
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index e70d75d..427eac1 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1110,6 +1110,7 @@
TRANSPORT_LOWPAN,
TRANSPORT_TEST,
TRANSPORT_USB,
+ TRANSPORT_THREAD,
})
public @interface Transport { }
@@ -1161,10 +1162,15 @@
*/
public static final int TRANSPORT_USB = 8;
+ /**
+ * Indicates this network uses a Thread transport.
+ */
+ public static final int TRANSPORT_THREAD = 9;
+
/** @hide */
public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
/** @hide */
- public static final int MAX_TRANSPORT = TRANSPORT_USB;
+ public static final int MAX_TRANSPORT = TRANSPORT_THREAD;
private static final int ALL_VALID_TRANSPORTS;
static {
@@ -1189,7 +1195,8 @@
"WIFI_AWARE",
"LOWPAN",
"TEST",
- "USB"
+ "USB",
+ "THREAD",
};
/**
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index a166675..2cfda9e 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -62,6 +62,17 @@
// This was a platform change ID with value 191844585L before T
public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
+ /**
+ * The self certified capabilities check should be enabled after android 13.
+ *
+ * <p> See {@link android.net.NetworkCapabilities} for more details.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION = 266524688;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml
index 812e0e8..b0dfe20 100644
--- a/nearby/halfsheet/res/values-ky/strings.xml
+++ b/nearby/halfsheet/res/values-ky/strings.xml
@@ -25,5 +25,5 @@
<string name="paring_action_save" msgid="6259357442067880136">"Сактоо"</string>
<string name="paring_action_connect" msgid="4801102939608129181">"Туташуу"</string>
<string name="paring_action_launch" msgid="8940808384126591230">"Жөндөө"</string>
- <string name="paring_action_settings" msgid="424875657242864302">"Жөндөөлөр"</string>
+ <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
</resources>
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 7950ff7..2b773c9 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -93,7 +93,7 @@
// cgroup if the program is pinned properly.
// TODO: delete the if statement once all devices should support cgroup
// socket filter (ie. the minimum kernel version required is 4.14).
- if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+ if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
RETURN_IF_NOT_OK(
attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 4ad39e1..8e06fde 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -317,7 +317,7 @@
if (!mIsMonitoringSocketsStarted) return;
if (isAnyRequestActive()) return;
- mMdnsSocketProvider.stopMonitoringSockets();
+ mMdnsSocketProvider.requestStopWhenInactive();
mIsMonitoringSocketsStarted = false;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index fdd1478..c472a8f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -60,13 +60,21 @@
}
}
+ @NonNull
private final WeakReference<MdnsSocketClientBase> weakRequestSender;
+ @NonNull
private final MdnsPacketWriter packetWriter;
+ @NonNull
private final String[] serviceTypeLabels;
+ @NonNull
private final List<String> subtypes;
private final boolean expectUnicastResponse;
private final int transactionId;
+ @Nullable
private final Network network;
+ private final boolean sendDiscoveryQueries;
+ @NonNull
+ private final List<MdnsResponse> servicesToResolve;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
@@ -75,7 +83,9 @@
@NonNull Collection<String> subtypes,
boolean expectUnicastResponse,
int transactionId,
- @Nullable Network network) {
+ @Nullable Network network,
+ boolean sendDiscoveryQueries,
+ @NonNull Collection<MdnsResponse> servicesToResolve) {
weakRequestSender = new WeakReference<>(requestSender);
this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -83,6 +93,8 @@
this.expectUnicastResponse = expectUnicastResponse;
this.transactionId = transactionId;
this.network = network;
+ this.sendDiscoveryQueries = sendDiscoveryQueries;
+ this.servicesToResolve = new ArrayList<>(servicesToResolve);
}
// Incompatible return type for override of Callable#call().
@@ -96,9 +108,48 @@
return null;
}
- int numQuestions = 1;
- if (!subtypes.isEmpty()) {
- numQuestions += subtypes.size();
+ int numQuestions = 0;
+
+ if (sendDiscoveryQueries) {
+ numQuestions++; // Base service type
+ if (!subtypes.isEmpty()) {
+ numQuestions += subtypes.size();
+ }
+ }
+
+ // List of (name, type) to query
+ final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
+ for (MdnsResponse response : servicesToResolve) {
+ // In practice responses should always have at least one pointer record, since the
+ // record is added after creation in MdnsResponseDecoder. All PTR records point to
+ // the same instance name, since addPointerRecord is only called on instances
+ // obtained through MdnsResponseDecoder.findResponseWithPointer.
+ // TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
+ // if remaining TTL is more than half the original one, so send the queries if half
+ // the TTL has passed).
+ if (!response.hasPointerRecords() || response.isComplete()) continue;
+ final String[] instanceName = response.getPointerRecords().get(0).getPointer();
+ if (instanceName == null) continue;
+ if (!response.hasTextRecord()) {
+ missingKnownAnswerRecords.add(new Pair<>(instanceName, MdnsRecord.TYPE_TXT));
+ }
+ if (!response.hasServiceRecord()) {
+ missingKnownAnswerRecords.add(new Pair<>(instanceName, MdnsRecord.TYPE_SRV));
+ // The hostname is not yet known, so queries for address records will be sent
+ // the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
+ // contain them. In practice, advertisers should include the address records
+ // when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
+ } else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
+ final String[] host = response.getServiceRecord().getServiceHost();
+ missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
+ missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
+ }
+ }
+ numQuestions += missingKnownAnswerRecords.size();
+
+ if (numQuestions == 0) {
+ // No query to send
+ return null;
}
// Header.
@@ -109,28 +160,25 @@
packetWriter.writeUInt16(0); // number of authority entries
packetWriter.writeUInt16(0); // number of additional records
- // Question(s). There will be one question for each (fqdn+subtype, recordType)
- // combination,
- // as well as one for each (fqdn, recordType) combination.
-
- for (String subtype : subtypes) {
- String[] labels = new String[serviceTypeLabels.length + 2];
- labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
- labels[1] = MdnsConstants.SUBTYPE_LABEL;
- System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
-
- packetWriter.writeLabels(labels);
- packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
- packetWriter.writeUInt16(
- MdnsConstants.QCLASS_INTERNET
- | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+ // Question(s) for missing records on known answers
+ for (Pair<String[], Integer> question : missingKnownAnswerRecords) {
+ writeQuestion(question.first, question.second);
}
- packetWriter.writeLabels(serviceTypeLabels);
- packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
- packetWriter.writeUInt16(
- MdnsConstants.QCLASS_INTERNET
- | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+ // Question(s) for discovering other services with the type. There will be one question
+ // for each (fqdn+subtype, recordType) combination, as well as one for each (fqdn,
+ // recordType) combination.
+ if (sendDiscoveryQueries) {
+ for (String subtype : subtypes) {
+ String[] labels = new String[serviceTypeLabels.length + 2];
+ labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
+ labels[1] = MdnsConstants.SUBTYPE_LABEL;
+ System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
+
+ writeQuestion(labels, MdnsRecord.TYPE_PTR);
+ }
+ writeQuestion(serviceTypeLabels, MdnsRecord.TYPE_PTR);
+ }
if (requestSender instanceof MdnsMultinetworkSocketClient) {
sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, network);
@@ -159,6 +207,14 @@
}
}
+ private void writeQuestion(String[] labels, int type) throws IOException {
+ packetWriter.writeLabels(labels);
+ packetWriter.writeUInt16(type);
+ packetWriter.writeUInt16(
+ MdnsConstants.QCLASS_INTERNET
+ | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+ }
+
private void sendPacketTo(MdnsSocketClientBase requestSender, InetSocketAddress address)
throws IOException {
DatagramPacket packet = packetWriter.getPacket(address);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index cc6b98b..67059e7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -19,6 +19,7 @@
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.net.Network;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -27,7 +28,6 @@
import com.android.server.connectivity.mdns.util.MdnsLogger;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Map;
/**
@@ -119,22 +119,10 @@
}
@Override
- public synchronized void onResponseReceived(@NonNull MdnsResponse response) {
- String[] name =
- response.getPointerRecords().isEmpty()
- ? null
- : response.getPointerRecords().get(0).getName();
- if (name != null) {
- for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
- String[] serviceType = serviceTypeClient.getServiceTypeLabels();
- if ((Arrays.equals(name, serviceType)
- || ((name.length == (serviceType.length + 2))
- && name[1].equals(MdnsConstants.SUBTYPE_LABEL)
- && MdnsRecord.labelsAreSuffix(serviceType, name)))) {
- serviceTypeClient.processResponse(response);
- return;
- }
- }
+ public synchronized void onResponseReceived(@NonNull MdnsPacket packet,
+ int interfaceIndex, Network network) {
+ for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
+ serviceTypeClient.processResponse(packet, interfaceIndex, network);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index d959065..93972d9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -33,7 +33,6 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -48,7 +47,6 @@
@NonNull private final Handler mHandler;
@NonNull private final MdnsSocketProvider mSocketProvider;
- @NonNull private final MdnsResponseDecoder mResponseDecoder;
private final Map<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
new ArrayMap<>();
@@ -62,8 +60,6 @@
@NonNull MdnsSocketProvider provider) {
mHandler = new Handler(looper);
mSocketProvider = provider;
- mResponseDecoder = new MdnsResponseDecoder(
- new MdnsResponseDecoder.Clock(), null /* serviceType */);
}
private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -170,19 +166,21 @@
@NonNull Network network) {
int packetNumber = ++mReceivedPacketNumber;
- final List<MdnsResponse> responses = new ArrayList<>();
- final int errorCode = mResponseDecoder.decode(
- recvbuf, length, responses, interfaceIndex, network);
- if (errorCode == MdnsResponseDecoder.SUCCESS) {
- for (MdnsResponse response : responses) {
+ final MdnsPacket response;
+ try {
+ response = MdnsResponseDecoder.parseResponse(recvbuf, length);
+ } catch (MdnsPacket.ParseException e) {
+ if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+ Log.e(TAG, e.getMessage(), e);
if (mCallback != null) {
- mCallback.onResponseReceived(response);
+ mCallback.onFailedToParseMdnsResponse(packetNumber, e.code);
}
}
- } else if (errorCode != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
- if (mCallback != null) {
- mCallback.onFailedToParseMdnsResponse(packetNumber, errorCode);
- }
+ return;
+ }
+
+ if (mCallback != null) {
+ mCallback.onResponseReceived(response, interfaceIndex, network);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 82da2e4..31527c0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -24,7 +24,6 @@
import com.android.server.connectivity.mdns.util.MdnsLogger;
import java.io.EOFException;
-import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -82,34 +81,16 @@
/**
* Decodes all mDNS responses for the desired service type from a packet. The class does not
- * check
- * the responses for completeness; the caller should do that.
- *
- * @param packet The packet to read from.
- * @param interfaceIndex the network interface index (or {@link
- * MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
- * @param network the network at which the packet was received, or null if it is unknown.
- * @return A list of mDNS responses, or null if the packet contained no appropriate responses.
- */
- public int decode(@NonNull DatagramPacket packet, @NonNull List<MdnsResponse> responses,
- int interfaceIndex, @Nullable Network network) {
- return decode(packet.getData(), packet.getLength(), responses, interfaceIndex, network);
- }
-
- /**
- * Decodes all mDNS responses for the desired service type from a packet. The class does not
- * check
- * the responses for completeness; the caller should do that.
+ * check the responses for completeness; the caller should do that.
*
* @param recvbuf The received data buffer to read from.
* @param length The length of received data buffer.
- * @param interfaceIndex the network interface index (or {@link
- * MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
- * @param network the network at which the packet was received, or null if it is unknown.
- * @return A list of mDNS responses, or null if the packet contained no appropriate responses.
+ * @return A decoded {@link MdnsPacket}.
+ * @throws MdnsPacket.ParseException if a response packet could not be parsed.
*/
- public int decode(@NonNull byte[] recvbuf, int length, @NonNull List<MdnsResponse> responses,
- int interfaceIndex, @Nullable Network network) {
+ @NonNull
+ public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length)
+ throws MdnsPacket.ParseException {
MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length);
final MdnsPacket mdnsPacket;
@@ -117,21 +98,35 @@
reader.readUInt16(); // transaction ID (not used)
int flags = reader.readUInt16();
if ((flags & MdnsConstants.FLAGS_RESPONSE_MASK) != MdnsConstants.FLAGS_RESPONSE) {
- return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
+ throw new MdnsPacket.ParseException(
+ MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE, "Not a response", null);
}
mdnsPacket = MdnsPacket.parseRecordsSection(reader, flags);
if (mdnsPacket.answers.size() < 1) {
- return MdnsResponseErrorCode.ERROR_NO_ANSWERS;
+ throw new MdnsPacket.ParseException(
+ MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE, "Response has no answers",
+ null);
}
+ return mdnsPacket;
} catch (EOFException e) {
- LOGGER.e("Reached the end of the mDNS response unexpectedly.", e);
- return MdnsResponseErrorCode.ERROR_END_OF_FILE;
- } catch (MdnsPacket.ParseException e) {
- LOGGER.e(e.getMessage(), e);
- return e.code;
+ throw new MdnsPacket.ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+ "Reached the end of the mDNS response unexpectedly.", e);
}
+ }
+ /**
+ * Builds mDNS responses for the desired service type from a packet.
+ *
+ * The class does not check the responses for completeness; the caller should do that.
+ *
+ * @param mdnsPacket The packet to read from.
+ * @param interfaceIndex the network interface index (or {@link
+ * MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received.
+ * @param network the network at which the packet was received, or null if it is unknown.
+ */
+ public List<MdnsResponse> buildResponses(@NonNull MdnsPacket mdnsPacket, int interfaceIndex,
+ @Nullable Network network) {
final ArrayList<MdnsRecord> records = new ArrayList<>(
mdnsPacket.questions.size() + mdnsPacket.answers.size()
+ mdnsPacket.authorityRecords.size() + mdnsPacket.additionalRecords.size());
@@ -139,6 +134,8 @@
records.addAll(mdnsPacket.authorityRecords);
records.addAll(mdnsPacket.additionalRecords);
+ final ArrayList<MdnsResponse> responses = new ArrayList<>();
+
// The response records are structured in a hierarchy, where some records reference
// others, as follows:
//
@@ -224,8 +221,7 @@
}
}
}
-
- return SUCCESS;
+ return responses;
}
private static void assignInetRecord(MdnsResponse response, MdnsInetAddressRecord inetRecord) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 583c4a9..3da6bd0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -46,7 +46,8 @@
public MdnsSearchOptions createFromParcel(Parcel source) {
return new MdnsSearchOptions(source.createStringArrayList(),
source.readBoolean(), source.readBoolean(),
- source.readParcelable(null));
+ source.readParcelable(null),
+ source.readString());
}
@Override
@@ -56,6 +57,8 @@
};
private static MdnsSearchOptions defaultOptions;
private final List<String> subtypes;
+ @Nullable
+ private final String resolveInstanceName;
private final boolean isPassiveMode;
private final boolean removeExpiredService;
@@ -64,7 +67,7 @@
/** Parcelable constructs for a {@link MdnsSearchOptions}. */
MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
- @Nullable Network network) {
+ @Nullable Network network, @Nullable String resolveInstanceName) {
this.subtypes = new ArrayList<>();
if (subtypes != null) {
this.subtypes.addAll(subtypes);
@@ -72,6 +75,7 @@
this.isPassiveMode = isPassiveMode;
this.removeExpiredService = removeExpiredService;
mNetwork = network;
+ this.resolveInstanceName = resolveInstanceName;
}
/** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
@@ -115,6 +119,15 @@
return mNetwork;
}
+ /**
+ * If non-null, queries should try to resolve all records of this specific service, rather than
+ * discovering all services.
+ */
+ @Nullable
+ public String getResolveInstanceName() {
+ return resolveInstanceName;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -126,6 +139,7 @@
out.writeBoolean(isPassiveMode);
out.writeBoolean(removeExpiredService);
out.writeParcelable(mNetwork, 0);
+ out.writeString(resolveInstanceName);
}
/** A builder to create {@link MdnsSearchOptions}. */
@@ -134,6 +148,7 @@
private boolean isPassiveMode = true;
private boolean removeExpiredService;
private Network mNetwork;
+ private String resolveInstanceName;
private Builder() {
subtypes = new ArraySet<>();
@@ -194,10 +209,22 @@
return this;
}
+ /**
+ * Set the instance name to resolve.
+ *
+ * If non-null, queries should try to resolve all records of this specific service,
+ * rather than discovering all services.
+ * @param name The instance name.
+ */
+ public Builder setResolveInstanceName(String name) {
+ resolveInstanceName = name;
+ return this;
+ }
+
/** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
public MdnsSearchOptions build() {
return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
- removeExpiredService, mNetwork);
+ removeExpiredService, mNetwork, resolveInstanceName);
}
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index d26fbdb..707a57e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -21,9 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
-import android.os.SystemClock;
import android.text.TextUtils;
-import android.util.ArraySet;
+import android.util.ArrayMap;
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
@@ -33,12 +32,12 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
@@ -54,15 +53,19 @@
private final String serviceType;
private final String[] serviceTypeLabels;
private final MdnsSocketClientBase socketClient;
+ private final MdnsResponseDecoder responseDecoder;
private final ScheduledExecutorService executor;
private final Object lock = new Object();
- private final Set<MdnsServiceBrowserListener> listeners = new ArraySet<>();
+ private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
+ new ArrayMap<>();
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
private final boolean allowSearchOptionsToRemoveExpiredService =
MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
+ private final MdnsResponseDecoder.Clock clock;
+
@Nullable private MdnsSearchOptions searchOptions;
// The session ID increases when startSendAndReceive() is called where we schedule a
@@ -84,10 +87,21 @@
@NonNull String serviceType,
@NonNull MdnsSocketClientBase socketClient,
@NonNull ScheduledExecutorService executor) {
+ this(serviceType, socketClient, executor, new MdnsResponseDecoder.Clock());
+ }
+
+ @VisibleForTesting
+ public MdnsServiceTypeClient(
+ @NonNull String serviceType,
+ @NonNull MdnsSocketClientBase socketClient,
+ @NonNull ScheduledExecutorService executor,
+ @NonNull MdnsResponseDecoder.Clock clock) {
this.serviceType = serviceType;
this.socketClient = socketClient;
this.executor = executor;
- serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+ this.serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+ this.responseDecoder = new MdnsResponseDecoder(clock, serviceTypeLabels);
+ this.clock = clock;
}
private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
@@ -148,7 +162,7 @@
@NonNull MdnsSearchOptions searchOptions) {
synchronized (lock) {
this.searchOptions = searchOptions;
- if (listeners.add(listener)) {
+ if (listeners.put(listener, searchOptions) == null) {
for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
final MdnsServiceInfo info =
buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
@@ -197,30 +211,38 @@
return serviceTypeLabels;
}
- public synchronized void processResponse(@NonNull MdnsResponse response) {
- if (shouldRemoveServiceAfterTtlExpires()) {
- // Because {@link QueryTask} and {@link processResponse} are running in different
- // threads. We need to synchronize {@link lock} to protect
- // {@link instanceNameToResponse} won’t be modified at the same time.
- synchronized (lock) {
+ /**
+ * Process an incoming response packet.
+ */
+ public synchronized void processResponse(@NonNull MdnsPacket packet, int interfaceIndex,
+ Network network) {
+ final List<MdnsResponse> responses = responseDecoder.buildResponses(packet, interfaceIndex,
+ network);
+ for (MdnsResponse response : responses) {
+ if (shouldRemoveServiceAfterTtlExpires()) {
+ // Because {@link QueryTask} and {@link processResponse} are running in different
+ // threads. We need to synchronize {@link lock} to protect
+ // {@link instanceNameToResponse} won’t be modified at the same time.
+ synchronized (lock) {
+ if (response.isGoodbye()) {
+ onGoodbyeReceived(response.getServiceInstanceName());
+ } else {
+ onResponseReceived(response);
+ }
+ }
+ } else {
if (response.isGoodbye()) {
onGoodbyeReceived(response.getServiceInstanceName());
} else {
onResponseReceived(response);
}
}
- } else {
- if (response.isGoodbye()) {
- onGoodbyeReceived(response.getServiceInstanceName());
- } else {
- onResponseReceived(response);
- }
}
}
public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
- for (MdnsServiceBrowserListener listener : listeners) {
- listener.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.keyAt(i).onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
}
}
@@ -250,7 +272,8 @@
MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(currentResponse, serviceTypeLabels);
- for (MdnsServiceBrowserListener listener : listeners) {
+ for (int i = 0; i < listeners.size(); i++) {
+ final MdnsServiceBrowserListener listener = listeners.keyAt(i);
if (newServiceFound) {
listener.onServiceNameDiscovered(serviceInfo);
}
@@ -270,7 +293,8 @@
if (response == null) {
return;
}
- for (MdnsServiceBrowserListener listener : listeners) {
+ for (int i = 0; i < listeners.size(); i++) {
+ final MdnsServiceBrowserListener listener = listeners.keyAt(i);
final MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
if (response.isComplete()) {
@@ -400,6 +424,36 @@
@Override
public void run() {
+ final List<MdnsResponse> servicesToResolve = new ArrayList<>();
+ boolean sendDiscoveryQueries = false;
+ synchronized (lock) {
+ for (int i = 0; i < listeners.size(); i++) {
+ final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+ if (resolveName == null) {
+ sendDiscoveryQueries = true;
+ continue;
+ }
+ MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
+ if (knownResponse == null) {
+ // The listener is requesting to resolve a service that has no info in
+ // cache. Use the provided name to generate a minimal response with just a
+ // PTR record, so other records are queried to complete it.
+ // Only the names are used to know which queries to send, other
+ // parameters do not matter.
+ knownResponse = new MdnsResponse(
+ 0L /* now */, 0 /* interfaceIndex */, config.network);
+ final ArrayList<String> instanceFullName = new ArrayList<>(
+ serviceTypeLabels.length + 1);
+ instanceFullName.add(resolveName);
+ instanceFullName.addAll(Arrays.asList(serviceTypeLabels));
+ knownResponse.addPointerRecord(new MdnsPointerRecord(
+ serviceTypeLabels, 0L /* receiptTimeMillis */,
+ false /* cacheFlush */, 0L /* ttlMillis */,
+ instanceFullName.toArray(new String[0])));
+ }
+ servicesToResolve.add(knownResponse);
+ }
+ }
Pair<Integer, List<String>> result;
try {
result =
@@ -410,7 +464,9 @@
config.subtypes,
config.expectUnicastResponse,
config.transactionId,
- config.network)
+ config.network,
+ sendDiscoveryQueries,
+ servicesToResolve)
.call();
} catch (RuntimeException e) {
LOGGER.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
@@ -435,8 +491,8 @@
}
}
if ((result != null)) {
- for (MdnsServiceBrowserListener listener : listeners) {
- listener.onDiscoveryQuerySent(result.second, result.first);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.keyAt(i).onDiscoveryQuerySent(result.second, result.first);
}
}
if (shouldRemoveServiceAfterTtlExpires()) {
@@ -446,10 +502,11 @@
if (existingResponse.hasServiceRecord()
&& existingResponse
.getServiceRecord()
- .getRemainingTTL(SystemClock.elapsedRealtime())
+ .getRemainingTTL(clock.elapsedRealtime())
== 0) {
iter.remove();
- for (MdnsServiceBrowserListener listener : listeners) {
+ for (int i = 0; i < listeners.size(); i++) {
+ final MdnsServiceBrowserListener listener = listeners.keyAt(i);
String serviceInstanceName =
existingResponse.getServiceInstanceName();
if (serviceInstanceName != null) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 907687e..c03e6aa 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -16,8 +16,6 @@
package com.android.server.connectivity.mdns;
-import static com.android.server.connectivity.mdns.MdnsSocketClientBase.Callback;
-
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,7 +33,6 @@
import java.net.DatagramPacket;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
@@ -73,7 +70,6 @@
private final Context context;
private final byte[] multicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
@Nullable private final byte[] unicastReceiverBuffer;
- private final MdnsResponseDecoder responseDecoder;
private final MulticastLock multicastLock;
private final boolean useSeparateSocketForUnicast =
MdnsConfigs.useSeparateSocketToSendUnicastQuery();
@@ -110,7 +106,6 @@
public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock) {
this.context = context;
this.multicastLock = multicastLock;
- responseDecoder = new MdnsResponseDecoder(new MdnsResponseDecoder.Clock(), null);
if (useSeparateSocketForUnicast) {
unicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
} else {
@@ -421,37 +416,27 @@
int interfaceIndex, @Nullable Network network) {
int packetNumber = ++receivedPacketNumber;
- List<MdnsResponse> responses = new LinkedList<>();
- int errorCode = responseDecoder.decode(packet, responses, interfaceIndex, network);
- if (errorCode == MdnsResponseDecoder.SUCCESS) {
- if (responseType.equals(MULTICAST_TYPE)) {
- receivedMulticastResponse = true;
- if (cannotReceiveMulticastResponse.getAndSet(false)) {
- // If we are already in the bad state, receiving a multicast response means
- // we are recovered.
- LOGGER.e(
- "Recovered from the state where the phone can't receive any multicast"
- + " response");
- }
- } else {
- receivedUnicastResponse = true;
- }
- for (MdnsResponse response : responses) {
- String serviceInstanceName = response.getServiceInstanceName();
- LOGGER.log("mDNS %s response received: %s at ifIndex %d", responseType,
- serviceInstanceName, interfaceIndex);
- if (callback != null) {
- callback.onResponseReceived(response);
- }
- }
- } else if (errorCode != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+ final MdnsPacket response;
+ try {
+ response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
+ } catch (MdnsPacket.ParseException e) {
LOGGER.w(String.format("Error while decoding %s packet (%d): %d",
- responseType, packetNumber, errorCode));
+ responseType, packetNumber, e.code));
if (callback != null) {
- callback.onFailedToParseMdnsResponse(packetNumber, errorCode);
+ callback.onFailedToParseMdnsResponse(packetNumber, e.code);
}
+ return e.code;
}
- return errorCode;
+
+ if (response == null) {
+ return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
+ }
+
+ if (callback != null) {
+ callback.onResponseReceived(response, interfaceIndex, network);
+ }
+
+ return MdnsResponseErrorCode.SUCCESS;
}
@VisibleForTesting
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
index 23504a0..796dc83 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -72,7 +72,8 @@
/*** Callback for mdns response */
interface Callback {
/*** Receive a mdns response */
- void onResponseReceived(@NonNull MdnsResponse response);
+ void onResponseReceived(@NonNull MdnsPacket packet, int interfaceIndex,
+ @Nullable Network network);
/*** Parse a mdns response failed */
void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 9298852..743f946 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -82,6 +82,7 @@
private final List<String> mTetheredInterfaces = new ArrayList<>();
private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
private boolean mMonitoringSockets = false;
+ private boolean mRequestStop = false;
public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper) {
this(context, looper, new Dependencies());
@@ -179,6 +180,7 @@
/*** Start monitoring sockets by listening callbacks for sockets creation or removal */
public void startMonitoringSockets() {
ensureRunningOnHandlerThread(mHandler);
+ mRequestStop = false; // Reset stop request flag.
if (mMonitoringSockets) {
Log.d(TAG, "Already monitoring sockets.");
return;
@@ -195,22 +197,34 @@
mMonitoringSockets = true;
}
- /*** Stop monitoring sockets and unregister callbacks */
- public void stopMonitoringSockets() {
+ private void maybeStopMonitoringSockets() {
+ if (!mMonitoringSockets) return; // Already unregistered.
+ if (!mRequestStop) return; // No stop request.
+
+ // Only unregister the network callback if there is no socket request.
+ if (mCallbacksToRequestedNetworks.isEmpty()) {
+ mContext.getSystemService(ConnectivityManager.class)
+ .unregisterNetworkCallback(mNetworkCallback);
+
+ final TetheringManager tetheringManager = mContext.getSystemService(
+ TetheringManager.class);
+ tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
+
+ mHandler.post(mNetlinkMonitor::stop);
+ mMonitoringSockets = false;
+ }
+ }
+
+ /*** Request to stop monitoring sockets and unregister callbacks */
+ public void requestStopWhenInactive() {
ensureRunningOnHandlerThread(mHandler);
if (!mMonitoringSockets) {
Log.d(TAG, "Monitoring sockets hasn't been started.");
return;
}
- if (DBG) Log.d(TAG, "Stop monitoring sockets.");
- mContext.getSystemService(ConnectivityManager.class)
- .unregisterNetworkCallback(mNetworkCallback);
-
- final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
- tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
-
- mHandler.post(mNetlinkMonitor::stop);
- mMonitoringSockets = false;
+ if (DBG) Log.d(TAG, "Try to stop monitoring sockets.");
+ mRequestStop = true;
+ maybeStopMonitoringSockets();
}
/*** Check whether the target network is matched current network */
@@ -450,6 +464,9 @@
cb.onInterfaceDestroyed(new Network(INetd.LOCAL_NET_ID), info.mSocket);
}
mTetherInterfaceSockets.clear();
+
+ // Try to unregister network callback.
+ maybeStopMonitoringSockets();
}
/*** Callbacks for listening socket changes */
diff --git a/service/Android.bp b/service/Android.bp
index c8d2fdd..1523af9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -171,7 +171,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V9-java",
+ "dnsresolver_aidl_interface-V11-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-bpf",
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index 9b39fd3..13f9eb4 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -36,7 +36,7 @@
<item msgid="3004933964374161223">"datu-konexioa"</item>
<item msgid="5624324321165953608">"Wifia"</item>
<item msgid="5667906231066981731">"Bluetootha"</item>
- <item msgid="346574747471703768">"Ethernet-a"</item>
+ <item msgid="346574747471703768">"Etherneta"</item>
<item msgid="5734728378097476003">"VPNa"</item>
</string-array>
<string name="network_switch_type_name_unknown" msgid="5116448402191972082">"sare mota ezezaguna"</string>
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 48b8316..8104632 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -284,3 +284,66 @@
// The latency of selection issued in milli-second
optional int32 selection_issued_latency_milli = 5;
}
+
+message NetworkSliceRequestCountSample {
+ // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+ // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int64 slice_id = 1;
+
+ // Bitfield representing the network's enterprise capability identifier
+ // (e.g. NET_ENTERPRISE_ID_1), defined in
+ // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int32 enterprise_id = 2;
+
+ // number of request for this slice
+ optional int32 request_count = 3;
+
+ // number of apps with outstanding request(s) for this slice
+ optional int32 distinct_app_count = 4;
+}
+
+message NetworkSliceSessionEnded {
+ // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+ // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int64 slice_id = 1;
+
+ // Bitfield representing the network's enterprise capability identifier
+ // (e.g. NET_ENTERPRISE_ID_1), defined in
+ // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int32 enterprise_id = 2;
+
+ // Number of bytes received at the device on this slice id
+ optional int64 rx_bytes = 3;
+
+ // Number of bytes transmitted by the device on this slice id
+ optional int64 tx_bytes = 4;
+
+ // Number of apps that have used this slice
+ optional int32 number_of_apps = 5;
+
+ // How long(in seconds) this slice has been connected
+ optional int32 slice_connection_duration_sec = 6;
+}
+
+message NetworkSliceDailyDataUsageReported {
+ // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+ // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int64 slice_id = 1;
+
+ // Bitfield representing the network's enterprise capability identifier
+ // (e.g. NET_ENTERPRISE_ID_1), defined in
+ // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+ optional int32 enterprise_id = 2;
+
+ // Number of bytes received at the device on this slice id
+ optional int64 rx_bytes = 3;
+
+ // Number of bytes transmitted by the device on this slice id
+ optional int64 tx_bytes = 4;
+
+ // Number of apps that have used this slice
+ optional int32 number_of_apps = 5;
+
+ // How long(in seconds) this slice has been connected
+ optional int32 slice_connection_duration_sec = 6;
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e32ea8f..58310bb 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -113,6 +113,7 @@
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -121,6 +122,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.net.CaptivePortal;
import android.net.CaptivePortalData;
@@ -195,6 +197,7 @@
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netd.aidl.NativeUidRangeConfig;
@@ -269,6 +272,7 @@
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.BroadcastOptionsShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
import com.android.server.connectivity.AutodestructReference;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
@@ -279,6 +283,7 @@
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InvalidTagException;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
@@ -300,6 +305,8 @@
import libcore.io.IoUtils;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -902,6 +909,13 @@
// Only the handler thread is allowed to access this field.
private long mIngressRateLimit = -1;
+ // This is the cache for the packageName -> ApplicationSelfCertifiedNetworkCapabilities. This
+ // value can be accessed from both handler thread and any random binder thread. Therefore,
+ // accessing this value requires holding a lock.
+ @GuardedBy("mSelfCertifiedCapabilityCache")
+ private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
+ mSelfCertifiedCapabilityCache = new HashMap<>();
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -1452,6 +1466,20 @@
public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
return BroadcastOptionsShimImpl.newInstance(options);
}
+
+ /**
+ * Wrapper method for
+ * {@link android.app.compat.CompatChanges#isChangeEnabled(long, String, UserHandle)}.
+ *
+ * @param changeId The ID of the compatibility change in question.
+ * @param packageName The package name of the app in question.
+ * @param user The user that the operation is done for.
+ * @return {@code true} if the change is enabled for the specified package.
+ */
+ public boolean isChangeEnabled(long changeId, @NonNull final String packageName,
+ @NonNull final UserHandle user) {
+ return CompatChanges.isChangeEnabled(changeId, packageName, user);
+ }
}
public ConnectivityService(Context context) {
@@ -4427,7 +4455,7 @@
mQosCallbackTracker.handleNetworkReleased(nai.network);
for (String iface : nai.linkProperties.getAllInterfaceNames()) {
// Disable wakeup packet monitoring for each interface.
- wakeupModifyInterface(iface, nai.networkCapabilities, false);
+ wakeupModifyInterface(iface, nai, false);
}
nai.networkMonitor().notifyNetworkDisconnected();
mNetworkAgentInfos.remove(nai);
@@ -5024,9 +5052,6 @@
@Override
public void setTestLowTcpPollingTimerForKeepalive(long timeMs) {
enforceSettingsPermission();
- if (!Build.isDebuggable()) {
- throw new IllegalStateException("Is not supported in non-debuggable build");
- }
if (timeMs > System.currentTimeMillis() + MAX_TEST_LOW_TCP_POLLING_UNTIL_MS) {
throw new IllegalArgumentException("Argument should not exceed "
@@ -6319,6 +6344,11 @@
if (isMappedInOemNetworkPreference(packageName)) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
+
+ // Invalidates cache entry when the package is updated.
+ synchronized (mSelfCertifiedCapabilityCache) {
+ mSelfCertifiedCapabilityCache.remove(packageName);
+ }
}
private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -6947,8 +6977,69 @@
asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
}
+ private boolean shouldCheckCapabilitiesDeclaration(
+ @NonNull final NetworkCapabilities networkCapabilities, final int callingUid,
+ @NonNull final String callingPackageName) {
+ final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+ // Only run the check if the change is enabled.
+ if (!mDeps.isChangeEnabled(
+ ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
+ callingPackageName, user)) {
+ return false;
+ }
+
+ return networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ || networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+ }
+
+ private void enforceRequestCapabilitiesDeclaration(@NonNull final String callerPackageName,
+ @NonNull final NetworkCapabilities networkCapabilities) {
+ // This check is added to fix the linter error for "current min is 30", which is not going
+ // to happen because Connectivity service always run in S+.
+ if (!SdkLevel.isAtLeastS()) {
+ Log.wtf(TAG, "Connectivity service should always run in at least SDK S");
+ return;
+ }
+ ApplicationSelfCertifiedNetworkCapabilities applicationNetworkCapabilities;
+ try {
+ synchronized (mSelfCertifiedCapabilityCache) {
+ applicationNetworkCapabilities = mSelfCertifiedCapabilityCache.get(
+ callerPackageName);
+ if (applicationNetworkCapabilities == null) {
+ final PackageManager packageManager = mContext.getPackageManager();
+ final PackageManager.Property networkSliceProperty = packageManager.getProperty(
+ ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+ callerPackageName
+ );
+ final XmlResourceParser parser = packageManager
+ .getResourcesForApplication(callerPackageName)
+ .getXml(networkSliceProperty.getResourceId());
+ applicationNetworkCapabilities =
+ ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser);
+ mSelfCertifiedCapabilityCache.put(callerPackageName,
+ applicationNetworkCapabilities);
+ }
+
+ }
+ } catch (PackageManager.NameNotFoundException ne) {
+ throw new SecurityException(
+ "Cannot find " + ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES
+ + " property");
+ } catch (XmlPullParserException | IOException | InvalidTagException e) {
+ throw new SecurityException(e.getMessage());
+ }
+
+ applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
+ networkCapabilities);
+ }
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag, final int callingUid) {
+ if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
+ callingPackageName)) {
+ enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities);
+ }
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
// For T+ devices, callers with carrier privilege could request with CBS capabilities.
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
@@ -7706,7 +7797,7 @@
// the LinkProperties for the network are accurate.
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
- updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+ updateInterfaces(newLp, oldLp, netId, networkAgent);
// update filtering rules, need to happen after the interface update so netd knows about the
// new interface (the interface name -> index map becomes initialized)
@@ -7814,10 +7905,16 @@
return captivePortalBuilder.build();
}
- private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
+ private String makeNflogPrefix(String iface, long networkHandle) {
+ // This needs to be kept in sync and backwards compatible with the decoding logic in
+ // NetdEventListenerService, which is non-mainline code.
+ return SdkLevel.isAtLeastU() ? (networkHandle + ":" + iface) : ("iface:" + iface);
+ }
+
+ private void wakeupModifyInterface(String iface, NetworkAgentInfo nai, boolean add) {
// Marks are only available on WiFi interfaces. Checking for
// marks on unsupported interfaces is harmless.
- if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ if (!nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return;
}
@@ -7830,7 +7927,7 @@
return;
}
- final String prefix = "iface:" + iface;
+ final String prefix = makeNflogPrefix(iface, nai.network.getNetworkHandle());
try {
if (add) {
mNetd.wakeupAddInterface(iface, prefix, mark, mask);
@@ -7840,12 +7937,11 @@
} catch (Exception e) {
loge("Exception modifying wakeup packet monitoring: " + e);
}
-
}
private void updateInterfaces(final @NonNull LinkProperties newLp,
final @Nullable LinkProperties oldLp, final int netId,
- final @NonNull NetworkCapabilities caps) {
+ final @NonNull NetworkAgentInfo nai) {
final CompareResult<String> interfaceDiff = new CompareResult<>(
oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp.getAllInterfaceNames());
if (!interfaceDiff.added.isEmpty()) {
@@ -7853,9 +7949,9 @@
try {
if (DBG) log("Adding iface " + iface + " to network " + netId);
mNetd.networkAddInterface(netId, iface);
- wakeupModifyInterface(iface, caps, true);
+ wakeupModifyInterface(iface, nai, true);
mDeps.reportNetworkInterfaceForTransports(mContext, iface,
- caps.getTransportTypes());
+ nai.networkCapabilities.getTransportTypes());
} catch (Exception e) {
logw("Exception adding interface: " + e);
}
@@ -7864,7 +7960,7 @@
for (final String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
- wakeupModifyInterface(iface, caps, false);
+ wakeupModifyInterface(iface, nai, false);
mNetd.networkRemoveInterface(netId, iface);
} catch (Exception e) {
loge("Exception removing interface: " + e);
diff --git a/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
new file mode 100644
index 0000000..76e966f
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
@@ -0,0 +1,209 @@
+/*
+ * 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.connectivity;
+
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+
+/**
+ * The class for parsing and checking the self-declared application network capabilities.
+ *
+ * ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
+ * can parse the self-declared application network capabilities in the application resources. The
+ * class also provides a helper method to check whether the requested network capabilities
+ * already self-declared.
+ */
+public final class ApplicationSelfCertifiedNetworkCapabilities {
+
+ public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
+ public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
+
+ private static final String TAG =
+ ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
+ private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
+ "network-capabilities-declaration";
+ private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
+ private static final String NAME_TAG = "name";
+
+ private long mRequestedNetworkCapabilities = 0;
+
+ /**
+ * Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
+ *
+ * <p> Here is an example of the xml syntax:
+ *
+ * <pre>
+ * {@code
+ * <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ * <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ * <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+ * </network-capabilities-declaration>
+ * }
+ * </pre>
+ * <p>
+ *
+ * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
+ * @return An ApplicationSelfCertifiedNetworkCapabilities object.
+ * @throws InvalidTagException if the capabilities in xml config contains invalid tag.
+ * @throws XmlPullParserException if xml parsing failed.
+ * @throws IOException if unable to read the xml file properly.
+ */
+ @NonNull
+ public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
+ @NonNull final XmlPullParser xmlParser)
+ throws InvalidTagException, XmlPullParserException, IOException {
+ return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
+ }
+
+ private static long parseXml(@NonNull final XmlPullParser xmlParser)
+ throws InvalidTagException, XmlPullParserException, IOException {
+ long requestedNetworkCapabilities = 0;
+ final ArrayDeque<String> openTags = new ArrayDeque<>();
+
+ while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ // Validates the tag is "network-capabilities-declaration"
+ if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
+ throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
+ }
+
+ checkedNextTag(xmlParser, openTags);
+ int eventType = xmlParser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ // USES_NETWORK_CAPABILITY_TAG should directly be declared under
+ // NETWORK_CAPABILITIES_DECLARATION_TAG.
+ if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
+ && openTags.size() == 1) {
+ int capability = parseDeclarationTag(xmlParser);
+ if (capability >= 0) {
+ requestedNetworkCapabilities |= 1L << capability;
+ }
+ } else {
+ Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
+ + openTags.size());
+ }
+ break;
+ default:
+ break;
+ }
+ eventType = checkedNextTag(xmlParser, openTags);
+ }
+ // Checks all the tags are parsed.
+ if (!openTags.isEmpty()) {
+ throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
+ }
+ return requestedNetworkCapabilities;
+ }
+
+ private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
+ String name = null;
+ for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
+ final String attrName = xmlParser.getAttributeName(i);
+ if (attrName.equals(NAME_TAG)) {
+ name = xmlParser.getAttributeValue(i);
+ } else {
+ Log.w(TAG, "Unknown attribute name: " + attrName);
+ }
+ }
+ if (name != null) {
+ switch (name) {
+ case PRIORITIZE_BANDWIDTH:
+ return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+ case PRIORITIZE_LATENCY:
+ return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+ default:
+ Log.w(TAG, "Unknown capability declaration name: " + name);
+ }
+ } else {
+ Log.w(TAG, "uses-network-capability name must be specified");
+ }
+ // Invalid capability
+ return -1;
+ }
+
+ private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
+ @NonNull final ArrayDeque<String> openTags)
+ throws XmlPullParserException, IOException, InvalidTagException {
+ if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
+ openTags.addFirst(xmlParser.getName());
+ } else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
+ if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
+ openTags.removeFirst();
+ } else {
+ throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
+ }
+ }
+ return xmlParser.next();
+ }
+
+ private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
+ mRequestedNetworkCapabilities = requestedNetworkCapabilities;
+ }
+
+ /**
+ * Enforces self-certified capabilities are declared.
+ *
+ * @param networkCapabilities the input NetworkCapabilities to check against.
+ * @throws SecurityException if the capabilities are not properly self-declared.
+ */
+ public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
+ @NonNull final NetworkCapabilities networkCapabilities) {
+ if (networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ && !hasPrioritizeBandwidth()) {
+ throw new SecurityException(
+ "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+ + " declaration");
+ }
+ if (networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ && !hasPrioritizeLatency()) {
+ throw new SecurityException(
+ "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+ + " declaration");
+ }
+ }
+
+ /**
+ * Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
+ */
+ private boolean hasPrioritizeLatency() {
+ return (mRequestedNetworkCapabilities & (1L
+ << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
+ }
+
+ /**
+ * Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
+ */
+ private boolean hasPrioritizeBandwidth() {
+ return (mRequestedNetworkCapabilities & (1L
+ << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
+ }
+}
diff --git a/service/src/com/android/server/connectivity/InvalidTagException.java b/service/src/com/android/server/connectivity/InvalidTagException.java
new file mode 100644
index 0000000..b924d27
--- /dev/null
+++ b/service/src/com/android/server/connectivity/InvalidTagException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.connectivity;
+
+
+/**
+ * An exception thrown when a Tag is not valid in self_certified_network_capabilities.xml.
+ */
+public class InvalidTagException extends Exception {
+
+ public InvalidTagException(String message) {
+ super(message);
+ }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index ff9bd31..891c2dd 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -47,6 +47,9 @@
data: [
":CtsHostsideNetworkTestsApp",
":CtsHostsideNetworkTestsApp2",
+ ":CtsHostsideNetworkCapTestsAppWithoutProperty",
+ ":CtsHostsideNetworkCapTestsAppWithProperty",
+ ":CtsHostsideNetworkCapTestsAppSdk33",
] + next_app_data,
per_testcase_directory: true,
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
new file mode 100644
index 0000000..2aa3f69
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -0,0 +1,65 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "CtsHostsideNetworkCapTestsAppDefaults",
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "modules-utils-build",
+ "cts-net-utils",
+ ],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults"
+ ],
+ manifest: "AndroidManifestWithoutProperty.xml",
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkCapTestsAppWithProperty",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults"
+ ],
+ manifest: "AndroidManifestWithProperty.xml",
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkCapTestsAppSdk33",
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkCapTestsAppDefaults"
+ ],
+ target_sdk_version: "33",
+ manifest: "AndroidManifestWithoutProperty.xml",
+}
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
new file mode 100644
index 0000000..3ef0376
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.net.hostside.networkslicingtestapp">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <property android:name="android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"
+ android:resource="@xml/self_certified_network_capabilities_both" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
new file mode 100644
index 0000000..fe66684
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.net.hostside.networkslicingtestapp">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
new file mode 100644
index 0000000..39792fc
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cts.net.hostside.networkslicingtestapp;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkSelfDeclaredCapabilitiesTest {
+
+ @Rule
+ public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void requestNetwork_withoutRequestCapabilities() {
+ final ConnectivityManager cm =
+ (ConnectivityManager)
+ InstrumentationRegistry.getInstrumentation()
+ .getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkRequest request =
+ new NetworkRequest.Builder().build();
+ final ConnectivityManager.NetworkCallback callback =
+ new ConnectivityManager.NetworkCallback();
+ cm.requestNetwork(request, callback);
+ cm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void requestNetwork_withSelfDeclaredCapabilities() {
+ final ConnectivityManager cm =
+ (ConnectivityManager)
+ InstrumentationRegistry.getInstrumentation()
+ .getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkRequest request =
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .build();
+ final ConnectivityManager.NetworkCallback callback =
+ new ConnectivityManager.NetworkCallback();
+ cm.requestNetwork(request, callback);
+ cm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void requestNetwork_lackingRequiredSelfDeclaredCapabilities() {
+ final ConnectivityManager cm =
+ (ConnectivityManager)
+ InstrumentationRegistry.getInstrumentation()
+ .getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkRequest request =
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .build();
+ final ConnectivityManager.NetworkCallback callback =
+ new ConnectivityManager.NetworkCallback();
+ assertThrows(
+ SecurityException.class,
+ () -> cm.requestNetwork(request, callback));
+ }
+
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
new file mode 100644
index 0000000..4c2985d
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.cts.net;
+
+public class HostsideSelfDeclaredNetworkCapabilitiesCheckTest extends HostsideNetworkTestCase {
+
+ private static final String TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK =
+ "CtsHostsideNetworkCapTestsAppWithProperty.apk";
+ private static final String TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK =
+ "CtsHostsideNetworkCapTestsAppWithoutProperty.apk";
+ private static final String TEST_IN_SDK_33_APK =
+ "CtsHostsideNetworkCapTestsAppSdk33.apk";
+ private static final String TEST_APP_PKG =
+ "com.android.cts.net.hostside.networkslicingtestapp";
+ private static final String TEST_CLASS_NAME = ".NetworkSelfDeclaredCapabilitiesTest";
+ private static final String WITH_SELF_DECLARED_CAPABILITIES_METHOD =
+ "requestNetwork_withSelfDeclaredCapabilities";
+ private static final String LACKING_SELF_DECLARED_CAPABILITIES_METHOD =
+ "requestNetwork_lackingRequiredSelfDeclaredCapabilities";
+ private static final String WITHOUT_REQUEST_CAPABILITIES_METHOD =
+ "requestNetwork_withoutRequestCapabilities";
+
+
+ public void testRequestNetworkInCurrentSdkWithProperty() throws Exception {
+ uninstallPackage(TEST_APP_PKG, false);
+ installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+ // If the self-declared capabilities are defined,
+ // the ConnectivityManager.requestNetwork() call should always pass.
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITHOUT_REQUEST_CAPABILITIES_METHOD);
+ uninstallPackage(TEST_APP_PKG, true);
+ }
+
+ public void testRequestNetworkInCurrentSdkWithoutProperty() throws Exception {
+ uninstallPackage(TEST_APP_PKG, false);
+ installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+ // If the self-declared capabilities are not defined,
+ // the ConnectivityManager.requestNetwork() call will fail if the properly is not declared.
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITHOUT_REQUEST_CAPABILITIES_METHOD);
+ uninstallPackage(TEST_APP_PKG, true);
+ }
+
+ public void testRequestNetworkInSdk33() throws Exception {
+ uninstallPackage(TEST_APP_PKG, false);
+ installPackage(TEST_IN_SDK_33_APK);
+ // In Sdk33, the ConnectivityManager.requestNetwork() call should always pass.
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITHOUT_REQUEST_CAPABILITIES_METHOD);
+ uninstallPackage(TEST_APP_PKG, true);
+ }
+
+ public void testReinstallPackageWillUpdateProperty() throws Exception {
+ uninstallPackage(TEST_APP_PKG, false);
+ installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+ uninstallPackage(TEST_APP_PKG, true);
+
+
+ // Updates package.
+ installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+ runDeviceTests(TEST_APP_PKG,
+ TEST_APP_PKG + TEST_CLASS_NAME,
+ WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+ uninstallPackage(TEST_APP_PKG, true);
+
+ }
+}
+
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 23cb15c..f9fe5b0 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -114,34 +114,39 @@
],
}
-android_test {
- name: "CtsNetTestCasesMaxTargetSdk31", // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+java_defaults {
+ name: "CtsNetTestCasesMaxTargetSdkDefaults",
defaults: [
"CtsNetTestCasesDefaults",
"CtsNetTestCasesApiStableDefaults",
],
- target_sdk_version: "31",
- package_name: "android.net.cts.maxtargetsdk31", // CTS package names must be unique.
- instrumentation_target_package: "android.net.cts.maxtargetsdk31",
test_suites: [
"cts",
"general-tests",
- "mts-networking",
+ "mts-tethering",
],
}
android_test {
+ name: "CtsNetTestCasesMaxTargetSdk33", // Must match CtsNetTestCasesMaxTargetSdk33 annotation.
+ defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+ target_sdk_version: "33",
+ package_name: "android.net.cts.maxtargetsdk33",
+ instrumentation_target_package: "android.net.cts.maxtargetsdk33",
+}
+
+android_test {
+ name: "CtsNetTestCasesMaxTargetSdk31", // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+ defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+ target_sdk_version: "31",
+ package_name: "android.net.cts.maxtargetsdk31", // CTS package names must be unique.
+ instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+}
+
+android_test {
name: "CtsNetTestCasesMaxTargetSdk30", // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
- defaults: [
- "CtsNetTestCasesDefaults",
- "CtsNetTestCasesApiStableDefaults",
- ],
+ defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
target_sdk_version: "30",
package_name: "android.net.cts.maxtargetsdk30", // CTS package names must be unique.
instrumentation_target_package: "android.net.cts.maxtargetsdk30",
- test_suites: [
- "cts",
- "general-tests",
- "mts-networking",
- ],
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 4fa0080..9c30811 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -395,7 +395,7 @@
private static class StatsChecker {
private static final double ERROR_MARGIN_BYTES = 1.05;
private static final double ERROR_MARGIN_PKTS = 1.05;
- private static final int MAX_WAIT_TIME_MILLIS = 1000;
+ private static final int MAX_WAIT_TIME_MILLIS = 3000;
private static long uidTxBytes;
private static long uidRxBytes;
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 093c7f8..de5e46f 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -20,6 +20,8 @@
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.LinkProperties
+import android.net.LocalSocket
+import android.net.LocalSocketAddress
import android.net.Network
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
@@ -63,6 +65,8 @@
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.PropertyUtil
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
import com.android.networkstack.apishim.NsdShimImpl
@@ -72,14 +76,17 @@
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import com.android.testutils.waitForIdle
import java.io.File
+import java.io.IOException
import java.net.ServerSocket
import java.nio.charset.StandardCharsets
import java.util.Random
import java.util.concurrent.Executor
+import kotlin.math.min
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
@@ -763,6 +770,69 @@
}
}
+ private fun checkConnectSocketToMdnsd(shouldFail: Boolean) {
+ val discoveryRecord = NsdDiscoveryRecord()
+ val localSocket = LocalSocket()
+ tryTest {
+ // Discover any service from NsdManager to enforce NsdService to start the mdnsd.
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+
+ // Checks the /dev/socket/mdnsd is created.
+ val socket = File("/dev/socket/mdnsd")
+ val doesSocketExist = PollingCheck.waitFor(
+ TIMEOUT_MS,
+ {
+ socket.exists()
+ },
+ { doesSocketExist ->
+ doesSocketExist
+ },
+ )
+
+ // If the socket is not created, then no need to check the access.
+ if (doesSocketExist) {
+ // Create a LocalSocket and try to connect to mdnsd.
+ assertFalse("LocalSocket is connected.", localSocket.isConnected)
+ val address = LocalSocketAddress("mdnsd", LocalSocketAddress.Namespace.RESERVED)
+ if (shouldFail) {
+ assertFailsWith<IOException>("Expect fail but socket connected") {
+ localSocket.connect(address)
+ }
+ } else {
+ localSocket.connect(address)
+ assertTrue("LocalSocket is not connected.", localSocket.isConnected)
+ }
+ }
+ } cleanup {
+ localSocket.close()
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ }
+ }
+
+ /**
+ * Starting from Android U, the access to the /dev/socket/mdnsd is blocked by the
+ * sepolicy(b/265364111).
+ */
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun testCannotConnectSocketToMdnsd() {
+ val targetSdkVersion = context.packageManager
+ .getTargetSdkVersion(context.applicationInfo.packageName)
+ assumeTrue(targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+ val firstApiLevel = min(PropertyUtil.getFirstApiLevel(), PropertyUtil.getVendorApiLevel())
+ // The sepolicy is implemented in the vendor image, so the access may not be blocked if
+ // the vendor image is not update to date.
+ assumeTrue(firstApiLevel > Build.VERSION_CODES.TIRAMISU)
+ checkConnectSocketToMdnsd(shouldFail = true)
+ }
+
+ @Test @CtsNetTestCasesMaxTargetSdk33("mdnsd socket is accessible up to target SDK 33")
+ fun testCanConnectSocketToMdnsd() {
+ checkConnectSocketToMdnsd(shouldFail = false)
+ }
+
@Test @CtsNetTestCasesMaxTargetSdk30("Socket is started with the service up to target SDK 30")
fun testManagerCreatesLegacySocket() {
nsdManager // Ensure the lazy-init member is initialized, so NsdManager is created
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index 9203f8f..cca0b66 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -104,7 +104,7 @@
// Check valid customization generates expected array.
val validRes = arrayOf("0,3", "1,0", "4,4")
- val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0)
+ val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0)
val mockContext = getMockedContextWithStringArrayRes(
R.array.config_networkSupportedKeepaliveCount,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 5b98237..1a358b2 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -183,6 +183,8 @@
import static com.android.testutils.RecorderCallback.CallbackEntry.UNAVAILABLE;
import static com.android.testutils.TestPermissionUtil.runAsShell;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -228,6 +230,7 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -312,6 +315,7 @@
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
import android.net.metrics.IpConnectivityLog;
import android.net.netd.aidl.NativeUidRangeConfig;
import android.net.networkstack.NetworkStackClientBase;
@@ -379,6 +383,7 @@
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
@@ -406,6 +411,9 @@
import com.android.testutils.TestableNetworkCallback;
import com.android.testutils.TestableNetworkOfferCallback;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -475,6 +483,9 @@
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+ @Rule
+ public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final int TIMEOUT_MS = 2_000;
// Broadcasts can take a long time to be delivered. The test will not wait for that long unless
// there is a failure, so use a long timeout.
@@ -2105,6 +2116,37 @@
reset(mBroadcastOptionsShim);
return mBroadcastOptionsShim;
}
+
+ @GuardedBy("this")
+ private boolean mForceDisableCompatChangeCheck = true;
+
+ /**
+ * By default, the {@link #isChangeEnabled(long, String, UserHandle)} will always return
+ * true as the mForceDisableCompatChangeCheck is true and compat change check logic is
+ * never executed. The compat change check logic can be turned on by calling this method.
+ * If this method is called, the
+ * {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} or
+ * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges} must be
+ * used to turn on/off the compat change flag.
+ */
+ private void enableCompatChangeCheck() {
+ synchronized (this) {
+ mForceDisableCompatChangeCheck = false;
+ }
+ }
+
+ @Override
+ public boolean isChangeEnabled(long changeId,
+ @NonNull final String packageName,
+ @NonNull final UserHandle user) {
+ synchronized (this) {
+ if (mForceDisableCompatChangeCheck) {
+ return false;
+ } else {
+ return super.isChangeEnabled(changeId, packageName, user);
+ }
+ }
+ }
}
private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -6310,6 +6352,142 @@
}
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @DisableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void testSelfCertifiedCapabilitiesDisabled()
+ throws Exception {
+ mDeps.enableCompatChangeCheck();
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(networkRequest, cb);
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ /** Set the networkSliceResourceId to 0 will result in NameNotFoundException be thrown. */
+ private void setupMockForNetworkCapabilitiesResources(int networkSliceResourceId)
+ throws PackageManager.NameNotFoundException {
+ if (networkSliceResourceId == 0) {
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getProperty(
+ ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+ mContext.getPackageName());
+ } else {
+ final PackageManager.Property property = new PackageManager.Property(
+ ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+ networkSliceResourceId,
+ true /* isResource */,
+ mContext.getPackageName(),
+ "dummyClass"
+ );
+ doReturn(property).when(mPackageManager).getProperty(
+ ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+ mContext.getPackageName());
+ doReturn(mContext.getResources()).when(mPackageManager).getResourcesForApplication(
+ mContext.getPackageName());
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void requestNetwork_withoutPrioritizeBandwidthDeclaration_shouldThrowException()
+ throws Exception {
+ mDeps.enableCompatChangeCheck();
+ setupMockForNetworkCapabilitiesResources(
+ com.android.frameworks.tests.net.R.xml.self_certified_capabilities_latency);
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ final Exception e = assertThrows(SecurityException.class,
+ () -> mCm.requestNetwork(networkRequest, cb));
+ assertThat(e.getMessage(),
+ containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void requestNetwork_withoutPrioritizeLatencyDeclaration_shouldThrowException()
+ throws Exception {
+ mDeps.enableCompatChangeCheck();
+ setupMockForNetworkCapabilitiesResources(
+ com.android.frameworks.tests.net.R.xml.self_certified_capabilities_bandwidth);
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ final Exception e = assertThrows(SecurityException.class,
+ () -> mCm.requestNetwork(networkRequest, cb));
+ assertThat(e.getMessage(),
+ containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void requestNetwork_withoutNetworkSliceProperty_shouldThrowException() throws Exception {
+ mDeps.enableCompatChangeCheck();
+ setupMockForNetworkCapabilitiesResources(0 /* networkSliceResourceId */);
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ final Exception e = assertThrows(SecurityException.class,
+ () -> mCm.requestNetwork(networkRequest, cb));
+ assertThat(e.getMessage(),
+ containsString(ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void requestNetwork_withNetworkSliceDeclaration_shouldSucceed() throws Exception {
+ mDeps.enableCompatChangeCheck();
+ setupMockForNetworkCapabilitiesResources(
+ com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(networkRequest, cb);
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+ public void requestNetwork_withNetworkSliceDeclaration_shouldUseCache() throws Exception {
+ mDeps.enableCompatChangeCheck();
+ setupMockForNetworkCapabilitiesResources(
+ com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ .build();
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(networkRequest, cb);
+ mCm.unregisterNetworkCallback(cb);
+
+ // Second call should use caches
+ mCm.requestNetwork(networkRequest, cb);
+ mCm.unregisterNetworkCallback(cb);
+
+ // PackageManager's API only called once because the second call is using cache.
+ verify(mPackageManager, times(1)).getProperty(
+ ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+ mContext.getPackageName());
+ verify(mPackageManager, times(1)).getResourcesForApplication(
+ mContext.getPackageName());
+ }
+
/**
* Validate the service throws if request with CBS but without carrier privilege.
*/
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 0680772..8fc9252 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -932,7 +932,7 @@
waitForIdle();
verify(mDiscoveryManager).unregisterListener(eq(serviceTypeWithLocalDomain), any());
verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
- verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
+ verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
}
@Test
@@ -1016,7 +1016,7 @@
// Verify the listener has been unregistered.
verify(mDiscoveryManager, timeout(TIMEOUT_MS))
.unregisterListener(eq(constructedServiceType), any());
- verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
+ verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
}
@Test
@@ -1090,7 +1090,7 @@
verify(mAdvertiser).removeService(idCaptor.getValue());
verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
argThat(info -> matches(info, regInfo)));
- verify(mSocketProvider, timeout(TIMEOUT_MS)).stopMonitoringSockets();
+ verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
new file mode 100644
index 0000000..f2d7aaa
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.connectivity
+
+import android.net.NetworkCapabilities
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class ApplicationSelfCertifiedNetworkCapabilitiesTest {
+ private val mResource = InstrumentationRegistry.getContext().getResources()
+ private val bandwidthCapability = NetworkCapabilities.Builder().apply {
+ addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ }.build()
+ private val latencyCapability = NetworkCapabilities.Builder().apply {
+ addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ }.build()
+ private val emptyCapability = NetworkCapabilities.Builder().build()
+ private val bothCapabilities = NetworkCapabilities.Builder().apply {
+ addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ }.build()
+
+ @Test
+ fun parseXmlWithWrongTag_shouldIgnoreWrongTag() {
+ val parser = mResource.getXml(
+ R.xml.self_certified_capabilities_wrong_tag
+ )
+ val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+ }
+
+ @Test
+ fun parseXmlWithWrongDeclaration_shouldThrowException() {
+ val parser = mResource.getXml(
+ R.xml.self_certified_capabilities_wrong_declaration
+ )
+ val exception = assertFailsWith<InvalidTagException> {
+ ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+ }
+ assertThat(exception.message).contains("network-capabilities-declaration1")
+ }
+
+ @Test
+ fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldThrowExceptionWhenNoDeclaration() {
+ val parser = mResource.getXml(R.xml.self_certified_capabilities_other)
+ val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+ val exception1 = assertFailsWith<SecurityException> {
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+ }
+ assertThat(exception1.message).contains(
+ ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+ )
+ val exception2 = assertFailsWith<SecurityException> {
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+ }
+ assertThat(exception2.message).contains(
+ ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+ )
+ }
+
+ @Test
+ fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldPassIfDeclarationExist() {
+ val parser = mResource.getXml(R.xml.self_certified_capabilities_both)
+ val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bothCapabilities)
+ selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(emptyCapability)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 719314a..5881a8e 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -80,6 +80,7 @@
private static final byte[] MAC_ADDR =
{(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
+ private static final long NET_HANDLE = new Network(4291).getNetworkHandle();
@Mock Context mCtx;
@Mock IIpConnectivityMetrics mMockService;
@@ -607,7 +608,7 @@
void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
String dstIp, int sport, int dport, long now) throws Exception {
- String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ String prefix = NET_HANDLE + ":" + iface;
mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
}
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 7d6c3ae..f4b6464 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -60,6 +60,8 @@
private static final String EXAMPLE_IPV4 = "192.0.2.1";
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
+ private static final long NET_HANDLE = new Network(5391).getNetworkHandle();
+
private static final byte[] MAC_ADDR =
{(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
@@ -498,7 +500,7 @@
void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
String dstIp, int sport, int dport, long now) throws Exception {
- String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ String prefix = NET_HANDLE + ":" + iface;
mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 83e7696..e6b8326 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.net.Network;
import android.text.TextUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -35,7 +36,10 @@
import org.mockito.MockitoAnnotations;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
/** Tests for {@link MdnsDiscoveryManager}. */
@RunWith(DevSdkIgnoreRunner.class)
@@ -111,25 +115,38 @@
discoveryManager.registerListener(
SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
- MdnsResponse responseForServiceTypeOne = createMockResponse(SERVICE_TYPE_1);
- discoveryManager.onResponseReceived(responseForServiceTypeOne);
- verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne);
+ MdnsPacket responseForServiceTypeOne = createMdnsPacket(SERVICE_TYPE_1);
+ final int ifIndex = 1;
+ final Network network = mock(Network.class);
+ discoveryManager.onResponseReceived(responseForServiceTypeOne, ifIndex, network);
+ verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne, ifIndex,
+ network);
- MdnsResponse responseForServiceTypeTwo = createMockResponse(SERVICE_TYPE_2);
- discoveryManager.onResponseReceived(responseForServiceTypeTwo);
- verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo);
+ MdnsPacket responseForServiceTypeTwo = createMdnsPacket(SERVICE_TYPE_2);
+ discoveryManager.onResponseReceived(responseForServiceTypeTwo, ifIndex, network);
+ verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo, ifIndex,
+ network);
- MdnsResponse responseForSubtype = createMockResponse("subtype._sub._googlecast._tcp.local");
- discoveryManager.onResponseReceived(responseForSubtype);
- verify(mockServiceTypeClientOne).processResponse(responseForSubtype);
+ MdnsPacket responseForSubtype = createMdnsPacket("subtype._sub._googlecast._tcp.local");
+ discoveryManager.onResponseReceived(responseForSubtype, ifIndex, network);
+ verify(mockServiceTypeClientOne).processResponse(responseForSubtype, ifIndex, network);
}
- private MdnsResponse createMockResponse(String serviceType) {
- MdnsPointerRecord mockPointerRecord = mock(MdnsPointerRecord.class);
- MdnsResponse mockResponse = mock(MdnsResponse.class);
- when(mockResponse.getPointerRecords())
- .thenReturn(Collections.singletonList(mockPointerRecord));
- when(mockPointerRecord.getName()).thenReturn(TextUtils.split(serviceType, "\\."));
- return mockResponse;
+ private MdnsPacket createMdnsPacket(String serviceType) {
+ final String[] type = TextUtils.split(serviceType, "\\.");
+ final ArrayList<String> name = new ArrayList<>(type.length + 1);
+ name.add("TestName");
+ name.addAll(Arrays.asList(type));
+ return new MdnsPacket(0 /* flags */,
+ Collections.emptyList() /* questions */,
+ List.of(new MdnsPointerRecord(
+ type,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000 /* ttlMillis */,
+ name.toArray(new String[0])
+ )) /* answers */,
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
}
}
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 9d42a65..1e322e4 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -19,9 +19,9 @@
import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.timeout;
@@ -146,16 +146,23 @@
// Send the data and verify the received records.
final PacketHandler handler = handlerCaptor.getValue();
handler.handlePacket(data, data.length, null /* src */);
- final ArgumentCaptor<MdnsResponse> responseCaptor =
- ArgumentCaptor.forClass(MdnsResponse.class);
- verify(mCallback).onResponseReceived(responseCaptor.capture());
- final MdnsResponse response = responseCaptor.getValue();
- assertTrue(response.hasPointerRecords());
- assertArrayEquals("_testtype._tcp.local".split("\\."),
- response.getPointerRecords().get(0).getName());
- assertTrue(response.hasServiceRecord());
- assertEquals("testservice", response.getServiceRecord().getServiceInstanceName());
- assertEquals("Android.local".split("\\."),
- response.getServiceRecord().getServiceHost());
+ final ArgumentCaptor<MdnsPacket> responseCaptor =
+ ArgumentCaptor.forClass(MdnsPacket.class);
+ verify(mCallback).onResponseReceived(responseCaptor.capture(), anyInt(), any());
+ final MdnsPacket response = responseCaptor.getValue();
+ assertEquals(0, response.questions.size());
+ assertEquals(0, response.additionalRecords.size());
+ assertEquals(0, response.authorityRecords.size());
+
+ final String[] serviceName = "testservice._testtype._tcp.local".split("\\.");
+ assertEquals(List.of(
+ new MdnsPointerRecord("_testtype._tcp.local".split("\\."),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 4500000 /* ttlMillis */,
+ serviceName),
+ new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+ false /* cacheFlush */, 4500000 /* ttlMillis */, 0 /* servicePriority */,
+ 0 /* serviceWeight */, 31234 /* servicePort */,
+ new String[] { "Android", "local" } /* serviceHost */)
+ ), response.answers);
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 4cae447..aaef048 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -43,7 +43,6 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
-import java.util.LinkedList;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@@ -154,35 +153,22 @@
private static final String[] MATTER_SERVICE_TYPE =
new String[] {MATTER_SERVICE_NAME, "_tcp", "local"};
- private final List<MdnsResponse> responses = new LinkedList<>();
+ private List<MdnsResponse> responses;
private final Clock mClock = mock(Clock.class);
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
assertNotNull(data);
- DatagramPacket packet = new DatagramPacket(data, data.length);
- packet.setSocketAddress(
- new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
- responses.clear();
- int errorCode = decoder.decode(
- packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
- assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+ responses = decode(decoder, data);
assertEquals(1, responses.size());
}
@Test
- public void testDecodeWithNullServiceType() {
+ public void testDecodeWithNullServiceType() throws Exception {
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
- assertNotNull(data);
- DatagramPacket packet = new DatagramPacket(data, data.length);
- packet.setSocketAddress(
- new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
- responses.clear();
- int errorCode = decoder.decode(
- packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
- assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+ responses = decode(decoder, data);
assertEquals(2, responses.size());
}
@@ -235,15 +221,9 @@
public void testDecodeIPv6AnswerPacket() throws IOException {
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
assertNotNull(data6);
- DatagramPacket packet = new DatagramPacket(data6, data6.length);
- packet.setSocketAddress(
- new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
- responses.clear();
- int errorCode = decoder.decode(
- packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
- assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
-
+ responses = decode(decoder, data6);
+ assertEquals(1, responses.size());
MdnsResponse response = responses.get(0);
assertTrue(response.isComplete());
@@ -283,39 +263,33 @@
}
@Test
- public void decode_withInterfaceIndex_populatesInterfaceIndex() {
+ public void decode_withInterfaceIndex_populatesInterfaceIndex() throws Exception {
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
assertNotNull(data6);
DatagramPacket packet = new DatagramPacket(data6, data6.length);
packet.setSocketAddress(
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
- responses.clear();
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data6, data6.length);
+ assertNotNull(parsedPacket);
+
final Network network = mock(Network.class);
- int errorCode = decoder.decode(
- packet, responses, /* interfaceIndex= */ 10, network);
- assertEquals(errorCode, MdnsResponseDecoder.SUCCESS);
+ responses = decoder.buildResponses(parsedPacket,
+ /* interfaceIndex= */ 10, network /* expireOnExit= */);
+
assertEquals(responses.size(), 1);
assertEquals(responses.get(0).getInterfaceIndex(), 10);
assertEquals(network, responses.get(0).getNetwork());
}
@Test
- public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses() {
+ public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses()
+ throws Exception {
//MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(true);
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
assertNotNull(matterDuplicateHostname);
- DatagramPacket packet =
- new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
- packet.setSocketAddress(
- new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
- responses.clear();
- int errorCode = decoder.decode(
- packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
- assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+ responses = decode(decoder, matterDuplicateHostname);
// This should emit two records:
assertEquals(2, responses.size());
@@ -336,21 +310,13 @@
@Test
@Ignore("MdnsConfigs is not configurable currently.")
- public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse() {
+ public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse()
+ throws Exception {
//MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(false);
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
assertNotNull(matterDuplicateHostname);
- DatagramPacket packet =
- new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
- packet.setSocketAddress(
- new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
- responses.clear();
- int errorCode = decoder.decode(
- packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
- assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+ responses = decode(decoder, matterDuplicateHostname);
// This should emit only two records:
assertEquals(2, responses.size());
@@ -359,4 +325,14 @@
assertTrue(responses.get(0).isComplete());
assertFalse(responses.get(1).isComplete());
}
+
+
+ private List<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data)
+ throws MdnsPacket.ParseException {
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
+ assertNotNull(parsedPacket);
+
+ return decoder.buildResponses(parsedPacket,
+ MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
+ }
}
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index a45ca68..d266b3d 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -24,13 +24,10 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,8 +57,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -85,6 +81,9 @@
private static final InetSocketAddress IPV6_ADDRESS = new InetSocketAddress(
MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
+ private static final long TEST_TTL = 120000L;
+ private static final long TEST_ELAPSED_REALTIME = 123L;
+
@Mock
private MdnsServiceBrowserListener mockListenerOne;
@Mock
@@ -95,6 +94,8 @@
private MdnsMultinetworkSocketClient mockSocketClient;
@Mock
private Network mockNetwork;
+ @Mock
+ private MdnsResponseDecoder.Clock mockDecoderClock;
@Captor
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
@@ -111,6 +112,7 @@
@SuppressWarnings("DoNotMock")
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
+ doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
expectedIPv4Packets = new DatagramPacket[16];
expectedIPv6Packets = new DatagramPacket[16];
@@ -160,7 +162,8 @@
.thenReturn(expectedIPv6Packets[15]);
client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+ new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -424,6 +427,7 @@
assertEquals(port, serviceInfo.getPort());
assertEquals(subTypes, serviceInfo.getSubtypes());
for (String key : attributes.keySet()) {
+ assertTrue(attributes.containsKey(key));
assertEquals(attributes.get(key), serviceInfo.getAttributeByKey(key));
}
assertEquals(interfaceIndex, serviceInfo.getInterfaceIndex());
@@ -434,22 +438,19 @@
public void processResponse_incompleteResponse() {
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
- MdnsResponse response = mock(MdnsResponse.class);
- when(response.getServiceInstanceName()).thenReturn("service-instance-1");
- doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
- doReturn(mockNetwork).when(response).getNetwork();
- when(response.isComplete()).thenReturn(false);
-
- client.processResponse(response);
+ client.processResponse(createResponse(
+ "service-instance-1", null /* host */, 0 /* port */,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
"service-instance-1",
SERVICE_TYPE_LABELS,
- null /* ipv4Address */,
- null /* ipv6Address */,
- 0 /* port */,
- List.of() /* subTypes */,
- Collections.singletonMap("key", null) /* attributes */,
+ /* ipv4Address= */ null,
+ /* ipv6Address= */ null,
+ /* port= */ 0,
+ /* subTypes= */ List.of(),
+ Collections.emptyMap(),
INTERFACE_INDEX,
mockNetwork);
@@ -463,28 +464,17 @@
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
// Process the initial response.
- MdnsResponse initialResponse =
- createResponse(
- "service-instance-1",
- ipV4Address,
- 5353,
- /* subtype= */ "ABCDE",
- Collections.emptyMap(),
- /* interfaceIndex= */ 20,
- mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ "service-instance-1", ipV4Address, 5353,
+ /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
// Process a second response with a different port and updated text attributes.
- MdnsResponse secondResponse =
- createResponse(
- "service-instance-1",
- ipV4Address,
- 5354,
- /* subtype= */ "ABCDE",
- Collections.singletonMap("key", "value"),
- /* interfaceIndex= */ 20,
- mockNetwork);
- client.processResponse(secondResponse);
+ client.processResponse(createResponse(
+ "service-instance-1", ipV4Address, 5354,
+ /* subtype= */ "ABCDE",
+ Collections.singletonMap("key", "value"), TEST_TTL),
+ /* interfaceIndex= */ 20, mockNetwork);
// Verify onServiceNameDiscovered was called once for the initial response.
verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
@@ -529,31 +519,17 @@
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
// Process the initial response.
- MdnsResponse initialResponse =
- createResponse(
- "service-instance-1",
- ipV6Address,
- 5353,
- /* subtype= */ "ABCDE",
- Collections.emptyMap(),
- /* interfaceIndex= */ 20,
- mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ "service-instance-1", ipV6Address, 5353,
+ /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
// Process a second response with a different port and updated text attributes.
- MdnsResponse secondResponse =
- createResponse(
- "service-instance-1",
- ipV6Address,
- 5354,
- /* subtype= */ "ABCDE",
- Collections.singletonMap("key", "value"),
- /* interfaceIndex= */ 20,
- mockNetwork);
- client.processResponse(secondResponse);
-
- System.out.println("secondResponses ip"
- + secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
+ client.processResponse(createResponse(
+ "service-instance-1", ipV6Address, 5354,
+ /* subtype= */ "ABCDE",
+ Collections.singletonMap("key", "value"), TEST_TTL),
+ /* interfaceIndex= */ 20, mockNetwork);
// Verify onServiceNameDiscovered was called once for the initial response.
verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
@@ -619,29 +595,25 @@
final String serviceName = "service-instance-1";
final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
// Process the initial response.
- final MdnsResponse initialResponse =
- createResponse(
- serviceName,
- ipV6Address,
- 5353 /* port */,
- /* subtype= */ "ABCDE",
- Collections.emptyMap(),
- INTERFACE_INDEX,
- mockNetwork);
- client.processResponse(initialResponse);
- MdnsResponse response = mock(MdnsResponse.class);
- doReturn("goodbye-service").when(response).getServiceInstanceName();
- doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
- doReturn(mockNetwork).when(response).getNetwork();
- doReturn(true).when(response).isGoodbye();
- client.processResponse(response);
+ client.processResponse(createResponse(
+ serviceName, ipV6Address, 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
+
+ client.processResponse(createResponse(
+ "goodbye-service", ipV6Address, 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
+
// Verify removed callback won't be called if the service is not existed.
verifyServiceRemovedNoCallback(mockListenerOne);
verifyServiceRemovedNoCallback(mockListenerTwo);
// Verify removed callback would be called.
- doReturn(serviceName).when(response).getServiceInstanceName();
- client.processResponse(response);
+ client.processResponse(createResponse(
+ serviceName, ipV6Address, 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), 0L), INTERFACE_INDEX, mockNetwork);
verifyServiceRemovedCallback(
mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, mockNetwork);
verifyServiceRemovedCallback(
@@ -651,16 +623,10 @@
@Test
public void reportExistingServiceToNewlyRegisteredListeners() throws Exception {
// Process the initial response.
- MdnsResponse initialResponse =
- createResponse(
- "service-instance-1",
- "192.168.1.1",
- 5353,
- /* subtype= */ "ABCDE",
- Collections.emptyMap(),
- INTERFACE_INDEX,
- mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ "service-instance-1", "192.168.1.1", 5353,
+ /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
@@ -687,10 +653,10 @@
assertNull(existingServiceInfo.getAttributeByKey("key"));
// Process a goodbye message for the existing response.
- MdnsResponse goodByeResponse = mock(MdnsResponse.class);
- when(goodByeResponse.getServiceInstanceName()).thenReturn("service-instance-1");
- when(goodByeResponse.isGoodbye()).thenReturn(true);
- client.processResponse(goodByeResponse);
+ client.processResponse(createResponse(
+ "service-instance-1", "192.168.1.1", 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
@@ -709,17 +675,15 @@
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
- MdnsResponse initialResponse =
- createMockResponse(
- serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), INTERFACE_INDEX, mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Clear the scheduled runnable.
currentThreadExecutor.getAndClearLastScheduledRunnable();
// Simulate the case where the response is after TTL.
- when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+ doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was not called.
@@ -733,7 +697,8 @@
//MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
final String serviceInstanceName = "service-instance-1";
client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+ new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -743,24 +708,22 @@
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
- MdnsResponse initialResponse =
- createMockResponse(
- serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), INTERFACE_INDEX, mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Clear the scheduled runnable.
currentThreadExecutor.getAndClearLastScheduledRunnable();
// Simulate the case where the response is under TTL.
- when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
+ doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was not called.
verifyServiceRemovedNoCallback(mockListenerOne);
// Simulate the case where the response is after TTL.
- when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+ doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was called.
@@ -773,7 +736,8 @@
throws Exception {
final String serviceInstanceName = "service-instance-1";
client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+ new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -783,17 +747,15 @@
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
- MdnsResponse initialResponse =
- createMockResponse(
- serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), INTERFACE_INDEX, mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Clear the scheduled runnable.
currentThreadExecutor.getAndClearLastScheduledRunnable();
// Simulate the case where the response is after TTL.
- when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+ doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was not called.
@@ -807,7 +769,8 @@
//MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
final String serviceInstanceName = "service-instance-1";
client =
- new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+ new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock) {
@Override
MdnsPacketWriter createMdnsPacketWriter() {
return mockPacketWriter;
@@ -817,17 +780,15 @@
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
- MdnsResponse initialResponse =
- createMockResponse(
- serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), INTERFACE_INDEX, mockNetwork);
- client.processResponse(initialResponse);
+ client.processResponse(createResponse(
+ serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Clear the scheduled runnable.
currentThreadExecutor.getAndClearLastScheduledRunnable();
// Simulate the case where the response is after TTL.
- when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+ doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was called.
@@ -844,46 +805,26 @@
InOrder inOrder = inOrder(mockListenerOne);
// Process the initial response which is incomplete.
- final MdnsResponse initialResponse =
- createResponse(
- serviceName,
- null,
- 5353,
- "ABCDE" /* subtype */,
- Collections.emptyMap(),
- INTERFACE_INDEX,
- mockNetwork);
- client.processResponse(initialResponse);
+ final String subtype = "ABCDE";
+ client.processResponse(createResponse(
+ serviceName, null, 5353, subtype,
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Process a second response which has ip address to make response become complete.
- final MdnsResponse secondResponse =
- createResponse(
- serviceName,
- ipV4Address,
- 5353,
- "ABCDE" /* subtype */,
- Collections.emptyMap(),
- INTERFACE_INDEX,
- mockNetwork);
- client.processResponse(secondResponse);
+ client.processResponse(createResponse(
+ serviceName, ipV4Address, 5353, subtype,
+ Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Process a third response with a different ip address, port and updated text attributes.
- final MdnsResponse thirdResponse =
- createResponse(
- serviceName,
- ipV6Address,
- 5354,
- "ABCDE" /* subtype */,
- Collections.singletonMap("key", "value"),
- INTERFACE_INDEX,
- mockNetwork);
- client.processResponse(thirdResponse);
+ client.processResponse(createResponse(
+ serviceName, ipV6Address, 5354, subtype,
+ Collections.singletonMap("key", "value"), TEST_TTL), INTERFACE_INDEX, mockNetwork);
- // Process the last response which is goodbye message.
- final MdnsResponse lastResponse = mock(MdnsResponse.class);
- doReturn(serviceName).when(lastResponse).getServiceInstanceName();
- doReturn(true).when(lastResponse).isGoodbye();
- client.processResponse(lastResponse);
+ // Process the last response which is goodbye message (with the main type, not subtype).
+ client.processResponse(createResponse(
+ serviceName, ipV6Address, 5354, SERVICE_TYPE_LABELS,
+ Collections.singletonMap("key", "value"), /* ptrTtlMillis= */ 0L),
+ INTERFACE_INDEX, mockNetwork);
// Verify onServiceNameDiscovered was first called for the initial response.
inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
@@ -893,7 +834,7 @@
null /* ipv4Address */,
null /* ipv6Address */,
5353 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(subtype) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
INTERFACE_INDEX,
mockNetwork);
@@ -906,7 +847,7 @@
ipV4Address /* ipv4Address */,
null /* ipv6Address */,
5353 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(subtype) /* subTypes */,
Collections.singletonMap("key", null) /* attributes */,
INTERFACE_INDEX,
mockNetwork);
@@ -919,7 +860,7 @@
ipV4Address /* ipv4Address */,
ipV6Address /* ipv6Address */,
5354 /* port */,
- Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonList(subtype) /* subTypes */,
Collections.singletonMap("key", "value") /* attributes */,
INTERFACE_INDEX,
mockNetwork);
@@ -1029,106 +970,68 @@
}
}
- // Creates a mock mDNS response.
- private MdnsResponse createMockResponse(
- @NonNull String serviceInstanceName,
- @NonNull String host,
- int port,
- @NonNull List<String> subtypes,
- @NonNull Map<String, String> textAttributes,
- int interfaceIndex,
- Network network)
- throws Exception {
- String[] hostName = new String[]{"hostname"};
- MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
- when(serviceRecord.getServiceHost()).thenReturn(hostName);
- when(serviceRecord.getServicePort()).thenReturn(port);
-
- MdnsResponse response = spy(new MdnsResponse(0, interfaceIndex, network));
-
- MdnsInetAddressRecord inetAddressRecord = mock(MdnsInetAddressRecord.class);
- if (host.contains(":")) {
- when(inetAddressRecord.getInet6Address())
- .thenReturn((Inet6Address) Inet6Address.getByName(host));
- response.setInet6AddressRecord(inetAddressRecord);
- } else {
- when(inetAddressRecord.getInet4Address())
- .thenReturn((Inet4Address) Inet4Address.getByName(host));
- response.setInet4AddressRecord(inetAddressRecord);
- }
-
- MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
- List<String> textStrings = new ArrayList<>();
- List<TextEntry> textEntries = new ArrayList<>();
- for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
- textStrings.add(kv.getKey() + "=" + kv.getValue());
- textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
- }
- when(textRecord.getStrings()).thenReturn(textStrings);
- when(textRecord.getEntries()).thenReturn(textEntries);
-
- response.setServiceRecord(serviceRecord);
- response.setTextRecord(textRecord);
-
- doReturn(false).when(response).isGoodbye();
- doReturn(true).when(response).isComplete();
- doReturn(serviceInstanceName).when(response).getServiceInstanceName();
- doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
- return response;
- }
-
- // Creates a mDNS response.
- private MdnsResponse createResponse(
+ private MdnsPacket createResponse(
@NonNull String serviceInstanceName,
@Nullable String host,
int port,
@NonNull String subtype,
@NonNull Map<String, String> textAttributes,
- int interfaceIndex,
- Network network)
+ long ptrTtlMillis)
throws Exception {
- MdnsResponse response = new MdnsResponse(0, interfaceIndex, network);
+ final ArrayList<String> type = new ArrayList<>();
+ type.add(subtype);
+ type.add(MdnsConstants.SUBTYPE_LABEL);
+ type.addAll(Arrays.asList(SERVICE_TYPE_LABELS));
+ return createResponse(serviceInstanceName, host, port, type.toArray(new String[0]),
+ textAttributes, ptrTtlMillis);
+ }
+
+ // Creates a mDNS response.
+ private MdnsPacket createResponse(
+ @NonNull String serviceInstanceName,
+ @Nullable String host,
+ int port,
+ @NonNull String[] type,
+ @NonNull Map<String, String> textAttributes,
+ long ptrTtlMillis) {
+
+ final ArrayList<MdnsRecord> answerRecords = new ArrayList<>();
// Set PTR record
+ final ArrayList<String> serviceNameList = new ArrayList<>();
+ serviceNameList.add(serviceInstanceName);
+ serviceNameList.addAll(Arrays.asList(type));
+ final String[] serviceName = serviceNameList.toArray(new String[0]);
final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
- new String[]{subtype, MdnsConstants.SUBTYPE_LABEL, "test"} /* name */,
- 0L /* receiptTimeMillis */,
+ type,
+ TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
false /* cacheFlush */,
- 120000L /* ttlMillis */,
- new String[]{serviceInstanceName});
- response.addPointerRecord(pointerRecord);
+ ptrTtlMillis,
+ serviceName);
+ answerRecords.add(pointerRecord);
// Set SRV record.
final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
- new String[] {"service"} /* name */,
- 0L /* receiptTimeMillis */,
+ serviceName,
+ TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
false /* cacheFlush */,
- 120000L /* ttlMillis */,
+ TEST_TTL,
0 /* servicePriority */,
0 /* serviceWeight */,
port,
new String[]{"hostname"});
- response.setServiceRecord(serviceRecord);
+ answerRecords.add(serviceRecord);
// Set A/AAAA record.
if (host != null) {
- if (InetAddresses.parseNumericAddress(host) instanceof Inet6Address) {
- final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
- new String[] {"address"} /* name */,
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- 120000L /* ttlMillis */,
- Inet6Address.getByName(host));
- response.setInet6AddressRecord(inetAddressRecord);
- } else {
- final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
- new String[] {"address"} /* name */,
- 0L /* receiptTimeMillis */,
- false /* cacheFlush */,
- 120000L /* ttlMillis */,
- Inet4Address.getByName(host));
- response.setInet4AddressRecord(inetAddressRecord);
- }
+ final InetAddress addr = InetAddresses.parseNumericAddress(host);
+ final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+ new String[] {"hostname"} /* name */,
+ TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ TEST_TTL,
+ addr);
+ answerRecords.add(inetAddressRecord);
}
// Set TXT record.
@@ -1137,12 +1040,18 @@
textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
}
final MdnsTextRecord textRecord = new MdnsTextRecord(
- new String[] {"text"} /* name */,
- 0L /* receiptTimeMillis */,
+ serviceName,
+ TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
false /* cacheFlush */,
- 120000L /* ttlMillis */,
+ TEST_TTL,
textEntries);
- response.setTextRecord(textRecord);
- return response;
+ answerRecords.add(textRecord);
+ return new MdnsPacket(
+ 0 /* flags */,
+ Collections.emptyList() /* questions */,
+ answerRecords,
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */
+ );
}
}
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1d61cd3..9048686 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -18,13 +18,11 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
@@ -48,7 +46,6 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -373,7 +370,7 @@
mdnsClient.startDiscovery();
verify(mockCallback, timeout(TIMEOUT).atLeast(1))
- .onResponseReceived(any(MdnsResponse.class));
+ .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
}
@Test
@@ -382,7 +379,7 @@
mdnsClient.startDiscovery();
verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
- .onResponseReceived(any(MdnsResponse.class));
+ .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
mdnsClient.stopDiscovery();
}
@@ -514,7 +511,7 @@
mdnsClient.startDiscovery();
verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
- .onResponseReceived(argThat(response -> response.getInterfaceIndex() == 21));
+ .onResponseReceived(any(), eq(21), any());
}
@Test
@@ -536,11 +533,7 @@
mdnsClient.setCallback(mockCallback);
mdnsClient.startDiscovery();
- ArgumentCaptor<MdnsResponse> mdnsResponseCaptor =
- ArgumentCaptor.forClass(MdnsResponse.class);
verify(mockMulticastSocket, never()).getInterfaceIndex();
- verify(mockCallback, timeout(TIMEOUT).atLeast(1))
- .onResponseReceived(mdnsResponseCaptor.capture());
- assertEquals(-1, mdnsResponseCaptor.getValue().getInterfaceIndex());
+ verify(mockCallback, timeout(TIMEOUT).atLeast(1)).onResponseReceived(any(), eq(-1), any());
}
}
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 635b296..b9cb255 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -109,12 +110,15 @@
final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
thread.start();
mHandler = new Handler(thread.getLooper());
+ mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+ }
+ private void startMonitoringSockets() {
final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
ArgumentCaptor.forClass(TetheringEventCallback.class);
- mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+
mHandler.post(mSocketProvider::startMonitoringSockets);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
@@ -205,6 +209,8 @@
@Test
public void testSocketRequestAndUnrequestSocket() {
+ startMonitoringSockets();
+
final TestSocketCallback testCallback1 = new TestSocketCallback();
mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -275,6 +281,8 @@
@Test
public void testAddressesChanged() throws Exception {
+ startMonitoringSockets();
+
final TestSocketCallback testCallback = new TestSocketCallback();
mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -297,4 +305,53 @@
testCallback.expectedAddressesChangedForNetwork(
TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
}
+
+ @Test
+ public void testStartAndStopMonitoringSockets() {
+ // Stop monitoring sockets before start. Should not unregister any network callback.
+ mHandler.post(mSocketProvider::requestStopWhenInactive);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+ verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+ // Start sockets monitoring.
+ startMonitoringSockets();
+ // Request a socket then unrequest it. Expect no network callback unregistration.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback.expectedNoCallback();
+ mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+ verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+ // Request stop and it should unregister network callback immediately because there is no
+ // socket request.
+ mHandler.post(mSocketProvider::requestStopWhenInactive);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+ verify(mTm, times(1)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+ // Start sockets monitoring and request a socket again.
+ mHandler.post(mSocketProvider::startMonitoringSockets);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
+ verify(mTm, times(2)).registerTetheringEventCallback(
+ any(), any(TetheringEventCallback.class));
+ final TestSocketCallback testCallback2 = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback2.expectedNoCallback();
+ // Try to stop monitoring sockets but should be ignored and wait until all socket are
+ // unrequested.
+ mHandler.post(mSocketProvider::requestStopWhenInactive);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+ verify(mTm, times(1)).unregisterTetheringEventCallback(any());
+ // Unrequest the socket then network callbacks should be unregistered.
+ mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback2));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
+ verify(mTm, times(2)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+ }
}
diff --git a/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
new file mode 100644
index 0000000..01fdca1
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_both.xml b/tests/unit/res/xml/self_certified_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_latency.xml b/tests/unit/res/xml/self_certified_capabilities_latency.xml
new file mode 100644
index 0000000..1c4a0e0
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_latency.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_other.xml b/tests/unit/res/xml/self_certified_capabilities_other.xml
new file mode 100644
index 0000000..5b6649c
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_other.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="other"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
new file mode 100644
index 0000000..6082356
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration1 xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration1>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
new file mode 100644
index 0000000..c9ecc0b
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability1 android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>