diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index f1298ce946..8b3519727b 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -433,6 +433,7 @@ package android.net { public abstract class QosFilter { method @NonNull public abstract android.net.Network getNetwork(); method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); + method public boolean matchesProtocol(int); method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int); } @@ -453,6 +454,7 @@ package android.net { public final class QosSocketInfo implements android.os.Parcelable { ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; + ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException; method public int describeContents(); method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); method @NonNull public android.net.Network getNetwork(); @@ -480,6 +482,14 @@ package android.net { ctor public SocketNotBoundException(); } + public class SocketNotConnectedException extends java.lang.Exception { + ctor public SocketNotConnectedException(); + } + + public class SocketRemoteAddressChangedException extends java.lang.Exception { + ctor public SocketRemoteAddressChangedException(); + } + public final class StaticIpConfiguration implements android.os.Parcelable { ctor public StaticIpConfiguration(); ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java index 01dc4bbabd..a731b23c10 100644 --- a/framework/src/android/net/QosFilter.java +++ b/framework/src/android/net/QosFilter.java @@ -97,10 +97,15 @@ public abstract class QosFilter { * Determines whether or not the parameter will be matched with this filter. * * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS - * flow assigned on {@link Network}. + * flow assigned on {@link Network}. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. * @return whether the parameters match the socket type of the filter - * @hide */ - public abstract boolean matchesProtocol(int protocol); + // Since this method is added in U, it's required to be default method for binary compatibility + // with existing @SystemApi. + // IPPROTO_* are not compile-time constants, so they are not annotated with @IntDef. + public boolean matchesProtocol(int protocol) { + return false; + } } diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java index da9b356050..1c3db23cad 100644 --- a/framework/src/android/net/QosSocketInfo.java +++ b/framework/src/android/net/QosSocketInfo.java @@ -144,7 +144,6 @@ public final class QosSocketInfo implements Parcelable { * * @param network the network * @param socket the bound {@link DatagramSocket} - * @hide */ public QosSocketInfo(@NonNull final Network network, @NonNull final DatagramSocket socket) throws IOException { diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java index fa2a615101..a6357c7e48 100644 --- a/framework/src/android/net/SocketNotConnectedException.java +++ b/framework/src/android/net/SocketNotConnectedException.java @@ -16,13 +16,18 @@ package android.net; +import android.annotation.SystemApi; + +import com.android.internal.annotations.VisibleForTesting; + /** * Thrown when a previously bound socket becomes unbound. * * @hide */ +@SystemApi public class SocketNotConnectedException extends Exception { - /** @hide */ + @VisibleForTesting public SocketNotConnectedException() { super("The socket is not connected"); } diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java index ecaeebc208..e13d5ed588 100644 --- a/framework/src/android/net/SocketRemoteAddressChangedException.java +++ b/framework/src/android/net/SocketRemoteAddressChangedException.java @@ -16,13 +16,18 @@ package android.net; +import android.annotation.SystemApi; + +import com.android.internal.annotations.VisibleForTesting; + /** * Thrown when the local address of the socket has changed. * * @hide */ +@SystemApi public class SocketRemoteAddressChangedException extends Exception { - /** @hide */ + @VisibleForTesting public SocketRemoteAddressChangedException() { super("The remote address of the socket changed"); } diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index d4f3d579a6..d2cd7aaad7 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -73,6 +73,8 @@ import android.os.HandlerThread import android.os.Message import android.os.SystemClock import android.platform.test.annotations.AppModeFull +import android.system.OsConstants.IPPROTO_TCP +import android.system.OsConstants.IPPROTO_UDP import android.telephony.TelephonyManager import android.telephony.data.EpsBearerQosSessionAttributes import android.util.DebugUtils.valueToString @@ -117,6 +119,7 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import org.mockito.Mockito.timeout import org.mockito.Mockito.verify +import java.io.Closeable import java.io.IOException import java.net.DatagramSocket import java.net.InetAddress @@ -174,7 +177,7 @@ class NetworkAgentTest { private val mFakeConnectivityService = FakeConnectivityService() private val agentsToCleanUp = mutableListOf() private val callbacksToCleanUp = mutableListOf() - private var qosTestSocket: Socket? = null + private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket @Before fun setUp() { @@ -930,34 +933,49 @@ class NetworkAgentTest { } } - private fun setupForQosCallbackTesting(): Pair { - val request = NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(TRANSPORT_TEST) - .build() + private fun setupForQosCallbackTest(creator: (TestableNetworkAgent) -> T) = + createConnectedNetworkAgent().first.let { Pair(it, creator(it)) } - val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) - requestNetwork(request, callback) - val (agent, _) = createConnectedNetworkAgent() + private fun setupForQosSocket() = setupForQosCallbackTest { + agent: TestableNetworkAgent -> Socket() + .also { assertNotNull(agent.network?.bindSocket(it)) + it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) } + } - qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also { - it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) - } - return Pair(agent, qosTestSocket!!) + private fun setupForQosDatagram() = setupForQosCallbackTest { + agent: TestableNetworkAgent -> DatagramSocket( + InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) + .also { assertNotNull(agent.network?.bindSocket(it)) } } @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test - fun testQosCallbackRegisterWithUnregister() { - val (agent, socket) = setupForQosCallbackTesting() + fun testQosCallbackRegisterAndUnregister() { + validateQosCallbackRegisterAndUnregister(IPPROTO_TCP) + } + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") + @Test + fun testQosCallbackRegisterAndUnregisterWithDatagramSocket() { + validateQosCallbackRegisterAndUnregister(IPPROTO_UDP) + } + + private fun validateQosCallbackRegisterAndUnregister(proto: Int) { + val (agent, qosTestSocket) = when (proto) { + IPPROTO_TCP -> setupForQosSocket() + IPPROTO_UDP -> setupForQosDatagram() + else -> fail("unsupported protocol") + } val qosCallback = TestableQosCallback() var callbackId = -1 Executors.newSingleThreadExecutor().let { executor -> try { - val info = QosSocketInfo(agent.network!!, socket) + val info = QosSocketInfo(agent, qosTestSocket) mCM.registerQosCallback(info, executor, qosCallback) - callbackId = agent.expectCallback().callbackId + agent.expectCallback().let { + callbackId = it.callbackId + assertTrue(it.filter.matchesProtocol(proto)) + } assertFailsWith( "The same callback cannot be " + @@ -965,7 +983,7 @@ class NetworkAgentTest { mCM.registerQosCallback(info, executor, qosCallback) } } finally { - socket.close() + qosTestSocket.close() mCM.unregisterQosCallback(qosCallback) agent.expectCallback { it.callbackId == callbackId } executor.shutdown() @@ -976,11 +994,31 @@ class NetworkAgentTest { @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackOnQosSession() { - val (agent, socket) = setupForQosCallbackTesting() + validateQosCallbackOnQosSession(IPPROTO_TCP) + } + + @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") + @Test + fun testQosCallbackOnQosSessionWithDatagramSocket() { + validateQosCallbackOnQosSession(IPPROTO_UDP) + } + + fun QosSocketInfo(agent: NetworkAgent, socket: Closeable) = when (socket) { + is Socket -> QosSocketInfo(agent.network, socket) + is DatagramSocket -> QosSocketInfo(agent.network, socket) + else -> fail("unexpected socket type") + } + + private fun validateQosCallbackOnQosSession(proto: Int) { + val (agent, qosTestSocket) = when (proto) { + IPPROTO_TCP -> setupForQosSocket() + IPPROTO_UDP -> setupForQosDatagram() + else -> fail("unsupported protocol") + } val qosCallback = TestableQosCallback() Executors.newSingleThreadExecutor().let { executor -> try { - val info = QosSocketInfo(agent.network!!, socket) + val info = QosSocketInfo(agent, qosTestSocket) assertEquals(agent.network, info.getNetwork()) mCM.registerQosCallback(info, executor, qosCallback) val callbackId = agent.expectCallback().callbackId @@ -1009,8 +1047,7 @@ class NetworkAgentTest { agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER) qosCallback.assertNoCallback() } finally { - socket.close() - + qosTestSocket.close() // safety precaution mCM.unregisterQosCallback(qosCallback) @@ -1022,11 +1059,11 @@ class NetworkAgentTest { @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackOnError() { - val (agent, socket) = setupForQosCallbackTesting() + val (agent, qosTestSocket) = setupForQosSocket() val qosCallback = TestableQosCallback() Executors.newSingleThreadExecutor().let { executor -> try { - val info = QosSocketInfo(agent.network!!, socket) + val info = QosSocketInfo(agent.network!!, qosTestSocket) mCM.registerQosCallback(info, executor, qosCallback) val callbackId = agent.expectCallback().callbackId @@ -1048,7 +1085,7 @@ class NetworkAgentTest { agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER) qosCallback.assertNoCallback() } finally { - socket.close() + qosTestSocket.close() // Make sure that the callback is fully unregistered mCM.unregisterQosCallback(qosCallback) @@ -1061,12 +1098,12 @@ class NetworkAgentTest { @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackIdsAreMappedCorrectly() { - val (agent, socket) = setupForQosCallbackTesting() + val (agent, qosTestSocket) = setupForQosSocket() val qosCallback1 = TestableQosCallback() val qosCallback2 = TestableQosCallback() Executors.newSingleThreadExecutor().let { executor -> try { - val info = QosSocketInfo(agent.network!!, socket) + val info = QosSocketInfo(agent.network!!, qosTestSocket) mCM.registerQosCallback(info, executor, qosCallback1) val callbackId1 = agent.expectCallback().callbackId @@ -1088,7 +1125,7 @@ class NetworkAgentTest { qosCallback1.assertNoCallback() qosCallback2.expectCallback { sessId2 == it.sess.sessionId } } finally { - socket.close() + qosTestSocket.close() // Make sure that the callback is fully unregistered mCM.unregisterQosCallback(qosCallback1) @@ -1102,13 +1139,13 @@ class NetworkAgentTest { @AppModeFull(reason = "Instant apps don't have permission to bind sockets.") @Test fun testQosCallbackWhenNetworkReleased() { - val (agent, socket) = setupForQosCallbackTesting() + val (agent, qosTestSocket) = setupForQosSocket() Executors.newSingleThreadExecutor().let { executor -> try { val qosCallback1 = TestableQosCallback() val qosCallback2 = TestableQosCallback() try { - val info = QosSocketInfo(agent.network!!, socket) + val info = QosSocketInfo(agent.network!!, qosTestSocket) mCM.registerQosCallback(info, executor, qosCallback1) mCM.registerQosCallback(info, executor, qosCallback2) agent.unregister() @@ -1121,12 +1158,12 @@ class NetworkAgentTest { it.ex.cause is NetworkReleasedException } } finally { - socket.close() + qosTestSocket.close() mCM.unregisterQosCallback(qosCallback1) mCM.unregisterQosCallback(qosCallback2) } } finally { - socket.close() + qosTestSocket.close() executor.shutdown() } } diff --git a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java index cd43a34b5e..ffb34e60ea 100644 --- a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java +++ b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java @@ -17,6 +17,7 @@ package android.net.cts; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -24,6 +25,8 @@ import android.net.NetworkReleasedException; import android.net.QosCallbackException; import android.net.SocketLocalAddressChangedException; import android.net.SocketNotBoundException; +import android.net.SocketNotConnectedException; +import android.net.SocketRemoteAddressChangedException; import android.os.Build; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -36,12 +39,6 @@ import org.junit.runner.RunWith; @IgnoreUpTo(Build.VERSION_CODES.R) public class QosCallbackExceptionTest { private static final String ERROR_MESSAGE = "Test Error Message"; - private static final String ERROR_MSG_SOCK_NOT_BOUND = "The socket is unbound"; - private static final String ERROR_MSG_NET_RELEASED = - "The network was released and is no longer available"; - private static final String ERROR_MSG_SOCK_ADDR_CHANGED = - "The local address of the socket changed"; - @Test public void testQosCallbackException() throws Exception { @@ -57,33 +54,65 @@ public class QosCallbackExceptionTest { public void testNetworkReleasedExceptions() throws Exception { final Throwable netReleasedException = new NetworkReleasedException(); final QosCallbackException exception = new QosCallbackException(netReleasedException); - - assertTrue(exception.getCause() instanceof NetworkReleasedException); - assertEquals(netReleasedException, exception.getCause()); - assertTrue(exception.getMessage().contains(ERROR_MSG_NET_RELEASED)); - assertThrowableMessageContains(exception, ERROR_MSG_NET_RELEASED); + validateQosCallbackException( + exception, netReleasedException, NetworkReleasedException.class); } @Test public void testSocketNotBoundExceptions() throws Exception { final Throwable sockNotBoundException = new SocketNotBoundException(); final QosCallbackException exception = new QosCallbackException(sockNotBoundException); - - assertTrue(exception.getCause() instanceof SocketNotBoundException); - assertEquals(sockNotBoundException, exception.getCause()); - assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_NOT_BOUND)); - assertThrowableMessageContains(exception, ERROR_MSG_SOCK_NOT_BOUND); + validateQosCallbackException( + exception, sockNotBoundException, SocketNotBoundException.class); } @Test public void testSocketLocalAddressChangedExceptions() throws Exception { - final Throwable localAddrChangedException = new SocketLocalAddressChangedException(); - final QosCallbackException exception = new QosCallbackException(localAddrChangedException); + final Throwable localAddressChangedException = new SocketLocalAddressChangedException(); + final QosCallbackException exception = + new QosCallbackException(localAddressChangedException); + validateQosCallbackException( + exception, localAddressChangedException, SocketLocalAddressChangedException.class); + } - assertTrue(exception.getCause() instanceof SocketLocalAddressChangedException); - assertEquals(localAddrChangedException, exception.getCause()); - assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_ADDR_CHANGED)); - assertThrowableMessageContains(exception, ERROR_MSG_SOCK_ADDR_CHANGED); + @Test + @IgnoreUpTo(Build.VERSION_CODES.S) + public void testSocketNotConnectedExceptions() throws Exception { + final Throwable sockNotConnectedException = new SocketNotConnectedException(); + final QosCallbackException exception = new QosCallbackException(sockNotConnectedException); + validateQosCallbackException( + exception, sockNotConnectedException, SocketNotConnectedException.class); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S) + public void testSocketRemoteAddressChangedExceptions() throws Exception { + final Throwable remoteAddressChangedException = new SocketRemoteAddressChangedException(); + final QosCallbackException exception = + new QosCallbackException(remoteAddressChangedException); + validateQosCallbackException( + exception, remoteAddressChangedException, + SocketRemoteAddressChangedException.class); + } + + private void validateQosCallbackException( + QosCallbackException e, Throwable cause, Class c) throws Exception { + if (c == SocketNotConnectedException.class) { + assertTrue(e.getCause() instanceof SocketNotConnectedException); + } else if (c == SocketRemoteAddressChangedException.class) { + assertTrue(e.getCause() instanceof SocketRemoteAddressChangedException); + } else if (c == SocketLocalAddressChangedException.class) { + assertTrue(e.getCause() instanceof SocketLocalAddressChangedException); + } else if (c == SocketNotBoundException.class) { + assertTrue(e.getCause() instanceof SocketNotBoundException); + } else if (c == NetworkReleasedException.class) { + assertTrue(e.getCause() instanceof NetworkReleasedException); + } else { + fail("unexpected error msg."); + } + assertEquals(cause, e.getCause()); + assertFalse(e.getMessage().isEmpty()); + assertThrowableMessageContains(e, e.getMessage()); } private void assertThrowableMessageContains(QosCallbackException exception, String errorMsg)