Move QoS API classes to Connectivity
The classes were added in S and are used to back ConnectivityManager
APIs. Add them to the connectivity boundary as they belong together with
ConnectivityManager.
Bug: 181512874
Test: m
Change-Id: I03b5978949b200a72813d1ebc4812d851fe3df37
diff --git a/framework/aidl-export/android/net/QosFilterParcelable.aidl b/framework/aidl-export/android/net/QosFilterParcelable.aidl
new file mode 100644
index 0000000..312d635
--- /dev/null
+++ b/framework/aidl-export/android/net/QosFilterParcelable.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** Copyright (C) 2020 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+parcelable QosFilterParcelable;
+
diff --git a/framework/aidl-export/android/net/QosSession.aidl b/framework/aidl-export/android/net/QosSession.aidl
new file mode 100644
index 0000000..c2cf366
--- /dev/null
+++ b/framework/aidl-export/android/net/QosSession.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** Copyright (C) 2020 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+parcelable QosSession;
+
diff --git a/framework/aidl-export/android/net/QosSocketInfo.aidl b/framework/aidl-export/android/net/QosSocketInfo.aidl
new file mode 100644
index 0000000..476c090
--- /dev/null
+++ b/framework/aidl-export/android/net/QosSocketInfo.aidl
@@ -0,0 +1,21 @@
+/*
+**
+** Copyright (C) 2020 The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.net;
+
+parcelable QosSocketInfo;
+
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 41ebc57..40c2cd1 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -308,6 +308,9 @@
field public static final int ID_NONE = -1; // 0xffffffff
}
+ public class NetworkReleasedException extends java.lang.Exception {
+ }
+
public class NetworkRequest implements android.os.Parcelable {
method @Nullable public String getRequestorPackageName();
method public int getRequestorUid();
@@ -317,6 +320,47 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
+ public abstract class QosCallback {
+ ctor public QosCallback();
+ method public void onError(@NonNull android.net.QosCallbackException);
+ method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes);
+ method public void onQosSessionLost(@NonNull android.net.QosSession);
+ }
+
+ public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException {
+ }
+
+ public final class QosCallbackException extends java.lang.Exception {
+ }
+
+ public abstract class QosFilter {
+ method @NonNull public abstract android.net.Network getNetwork();
+ method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int);
+ }
+
+ public final class QosSession implements android.os.Parcelable {
+ ctor public QosSession(int, int);
+ method public int describeContents();
+ method public int getSessionId();
+ method public int getSessionType();
+ method public long getUniqueId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR;
+ field public static final int TYPE_EPS_BEARER = 1; // 0x1
+ }
+
+ public interface QosSessionAttributes {
+ }
+
+ public final class QosSocketInfo implements android.os.Parcelable {
+ ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException;
+ method public int describeContents();
+ method @NonNull public java.net.InetSocketAddress getLocalSocketAddress();
+ method @NonNull public android.net.Network getNetwork();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR;
+ }
+
public final class RouteInfo implements android.os.Parcelable {
ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
@@ -331,6 +375,12 @@
field public static final int SUCCESS = 0; // 0x0
}
+ public class SocketLocalAddressChangedException extends java.lang.Exception {
+ }
+
+ public class SocketNotBoundException extends java.lang.Exception {
+ }
+
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/IQosCallback.aidl b/framework/src/android/net/IQosCallback.aidl
new file mode 100644
index 0000000..91c7575
--- /dev/null
+++ b/framework/src/android/net/IQosCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Bundle;
+import android.net.QosSession;
+import android.telephony.data.EpsBearerQosSessionAttributes;
+
+/**
+ * AIDL interface for QosCallback
+ *
+ * @hide
+ */
+oneway interface IQosCallback
+{
+ void onQosEpsBearerSessionAvailable(in QosSession session,
+ in EpsBearerQosSessionAttributes attributes);
+ void onQosSessionLost(in QosSession session);
+ void onError(in int type);
+}
diff --git a/framework/src/android/net/NetworkReleasedException.java b/framework/src/android/net/NetworkReleasedException.java
new file mode 100644
index 0000000..0629b75
--- /dev/null
+++ b/framework/src/android/net/NetworkReleasedException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+
+/**
+ * Indicates that the {@link Network} was released and is no longer available.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkReleasedException extends Exception {
+ /** @hide */
+ public NetworkReleasedException() {
+ super("The network was released and is no longer available");
+ }
+}
diff --git a/framework/src/android/net/QosCallback.java b/framework/src/android/net/QosCallback.java
new file mode 100644
index 0000000..22f06bc
--- /dev/null
+++ b/framework/src/android/net/QosCallback.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Receives Qos information given a {@link Network}. The callback is registered with
+ * {@link ConnectivityManager#registerQosCallback}.
+ *
+ * <p>
+ * <br/>
+ * The callback will no longer receive calls if any of the following takes place:
+ * <ol>
+ * <li>{@link ConnectivityManager#unregisterQosCallback(QosCallback)} is called with the same
+ * callback instance.</li>
+ * <li>{@link QosCallback#onError(QosCallbackException)} is called.</li>
+ * <li>A network specific issue occurs. eg. Congestion on a carrier network.</li>
+ * <li>The network registered with the callback has no associated QoS providers</li>
+ * </ul>
+ * {@hide}
+ */
+@SystemApi
+public abstract class QosCallback {
+ /**
+ * Invoked after an error occurs on a registered callback. Once called, the callback is
+ * automatically unregistered and the callback will no longer receive calls.
+ *
+ * <p>The underlying exception can either be a runtime exception or a custom exception made for
+ * {@link QosCallback}. see: {@link QosCallbackException}.
+ *
+ * @param exception wraps the underlying cause
+ */
+ public void onError(@NonNull final QosCallbackException exception) {
+ }
+
+ /**
+ * Called when a Qos Session first becomes available to the callback or if its attributes have
+ * changed.
+ * <p>
+ * Note: The callback may be called multiple times with the same attributes.
+ *
+ * @param session the available session
+ * @param sessionAttributes the attributes of the session
+ */
+ public void onQosSessionAvailable(@NonNull final QosSession session,
+ @NonNull final QosSessionAttributes sessionAttributes) {
+ }
+
+ /**
+ * Called after a Qos Session is lost.
+ * <p>
+ * At least one call to
+ * {@link QosCallback#onQosSessionAvailable(QosSession, QosSessionAttributes)}
+ * with the same {@link QosSession} will precede a call to lost.
+ *
+ * @param session the lost session
+ */
+ public void onQosSessionLost(@NonNull final QosSession session) {
+ }
+
+ /**
+ * Thrown when there is a problem registering {@link QosCallback} with
+ * {@link ConnectivityManager#registerQosCallback(QosSocketInfo, QosCallback, Executor)}.
+ */
+ public static class QosCallbackRegistrationException extends RuntimeException {
+ /**
+ * @hide
+ */
+ public QosCallbackRegistrationException() {
+ super();
+ }
+ }
+}
diff --git a/framework/src/android/net/QosCallbackConnection.java b/framework/src/android/net/QosCallbackConnection.java
new file mode 100644
index 0000000..bdb4ad6
--- /dev/null
+++ b/framework/src/android/net/QosCallbackConnection.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.data.EpsBearerQosSessionAttributes;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Sends messages from {@link com.android.server.ConnectivityService} to the registered
+ * {@link QosCallback}.
+ * <p/>
+ * This is a satellite class of {@link ConnectivityManager} and not meant
+ * to be used in other contexts.
+ *
+ * @hide
+ */
+class QosCallbackConnection extends android.net.IQosCallback.Stub {
+
+ @NonNull private final ConnectivityManager mConnectivityManager;
+ @Nullable private volatile QosCallback mCallback;
+ @NonNull private final Executor mExecutor;
+
+ @VisibleForTesting
+ @Nullable
+ public QosCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * The constructor for the connection
+ *
+ * @param connectivityManager the mgr that created this connection
+ * @param callback the callback to send messages back to
+ * @param executor The executor on which the callback will be invoked. The provided
+ * {@link Executor} must run callback sequentially, otherwise the order of
+ * callbacks cannot be guaranteed.
+ */
+ QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
+ @NonNull final QosCallback callback,
+ @NonNull final Executor executor) {
+ mConnectivityManager = Objects.requireNonNull(connectivityManager,
+ "connectivityManager must be non-null");
+ mCallback = Objects.requireNonNull(callback, "callback must be non-null");
+ mExecutor = Objects.requireNonNull(executor, "executor must be non-null");
+ }
+
+ /**
+ * Called when either the {@link EpsBearerQosSessionAttributes} has changed or on the first time
+ * the attributes have become available.
+ *
+ * @param session the session that is now available
+ * @param attributes the corresponding attributes of session
+ */
+ @Override
+ public void onQosEpsBearerSessionAvailable(@NonNull final QosSession session,
+ @NonNull final EpsBearerQosSessionAttributes attributes) {
+
+ mExecutor.execute(() -> {
+ final QosCallback callback = mCallback;
+ if (callback != null) {
+ callback.onQosSessionAvailable(session, attributes);
+ }
+ });
+ }
+
+ /**
+ * Called when the session is lost.
+ *
+ * @param session the session that was lost
+ */
+ @Override
+ public void onQosSessionLost(@NonNull final QosSession session) {
+ mExecutor.execute(() -> {
+ final QosCallback callback = mCallback;
+ if (callback != null) {
+ callback.onQosSessionLost(session);
+ }
+ });
+ }
+
+ /**
+ * Called when there is an error on the registered callback.
+ *
+ * @param errorType the type of error
+ */
+ @Override
+ public void onError(@QosCallbackException.ExceptionType final int errorType) {
+ mExecutor.execute(() -> {
+ final QosCallback callback = mCallback;
+ if (callback != null) {
+ // Messages no longer need to be received since there was an error.
+ stopReceivingMessages();
+ mConnectivityManager.unregisterQosCallback(callback);
+ callback.onError(QosCallbackException.createException(errorType));
+ }
+ });
+ }
+
+ /**
+ * The callback will stop receiving messages.
+ * <p/>
+ * There are no synchronization guarantees on exactly when the callback will stop receiving
+ * messages.
+ */
+ void stopReceivingMessages() {
+ mCallback = null;
+ }
+}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
new file mode 100644
index 0000000..7fd9a52
--- /dev/null
+++ b/framework/src/android/net/QosCallbackException.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This is the exception type passed back through the onError method on {@link QosCallback}.
+ * {@link QosCallbackException#getCause()} contains the actual error that caused this exception.
+ *
+ * The possible exception types as causes are:
+ * 1. {@link NetworkReleasedException}
+ * 2. {@link SocketNotBoundException}
+ * 3. {@link UnsupportedOperationException}
+ * 4. {@link SocketLocalAddressChangedException}
+ *
+ * @hide
+ */
+@SystemApi
+public final class QosCallbackException extends Exception {
+
+ /** @hide */
+ @IntDef(prefix = {"EX_TYPE_"}, value = {
+ EX_TYPE_FILTER_NONE,
+ EX_TYPE_FILTER_NETWORK_RELEASED,
+ EX_TYPE_FILTER_SOCKET_NOT_BOUND,
+ EX_TYPE_FILTER_NOT_SUPPORTED,
+ EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExceptionType {}
+
+ private static final String TAG = "QosCallbackException";
+
+ // Types of exceptions supported //
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_NONE = 0;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_NETWORK_RELEASED = 1;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
+
+ /**
+ * Creates exception based off of a type and message. Not all types of exceptions accept a
+ * custom message.
+ *
+ * {@hide}
+ */
+ @NonNull
+ static QosCallbackException createException(@ExceptionType final int type) {
+ switch (type) {
+ case EX_TYPE_FILTER_NETWORK_RELEASED:
+ return new QosCallbackException(new NetworkReleasedException());
+ case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
+ return new QosCallbackException(new SocketNotBoundException());
+ case EX_TYPE_FILTER_NOT_SUPPORTED:
+ return new QosCallbackException(new UnsupportedOperationException(
+ "This device does not support the specified filter"));
+ case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
+ return new QosCallbackException(
+ new SocketLocalAddressChangedException());
+ default:
+ Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
+ return new QosCallbackException(
+ new RuntimeException("Unknown exception code: " + type));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public QosCallbackException(@NonNull final String message) {
+ super(message);
+ }
+
+ /**
+ * @hide
+ */
+ public QosCallbackException(@NonNull final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
new file mode 100644
index 0000000..ab55002
--- /dev/null
+++ b/framework/src/android/net/QosFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.net.InetAddress;
+
+/**
+ * Provides the related filtering logic to the {@link NetworkAgent} to match {@link QosSession}s
+ * to their related {@link QosCallback}.
+ *
+ * Used by the {@link com.android.server.ConnectivityService} to validate a {@link QosCallback}
+ * is still able to receive a {@link QosSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class QosFilter {
+
+ /**
+ * The constructor is kept hidden from outside this package to ensure that all derived types
+ * are known and properly handled when being passed to and from {@link NetworkAgent}.
+ *
+ * @hide
+ */
+ QosFilter() {
+ }
+
+ /**
+ * The network used with this filter.
+ *
+ * @return the registered {@link Network}
+ */
+ @NonNull
+ public abstract Network getNetwork();
+
+ /**
+ * Validates that conditions have not changed such that no further {@link QosSession}s should
+ * be passed back to the {@link QosCallback} associated to this filter.
+ *
+ * @return the error code when present, otherwise the filter is valid
+ *
+ * @hide
+ */
+ @QosCallbackException.ExceptionType
+ public abstract int validate();
+
+ /**
+ * Determines whether or not the parameters is a match for the filter.
+ *
+ * @param address the local address
+ * @param startPort the start of the port range
+ * @param endPort the end of the port range
+ * @return whether the parameters match the local address of the filter
+ */
+ public abstract boolean matchesLocalAddress(@NonNull InetAddress address,
+ int startPort, int endPort);
+}
+
diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java
new file mode 100644
index 0000000..da3b2cf
--- /dev/null
+++ b/framework/src/android/net/QosFilterParcelable.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Aware of how to parcel different types of {@link QosFilter}s. Any new type of qos filter must
+ * have a specialized case written here.
+ * <p/>
+ * Specifically leveraged when transferring {@link QosFilter} from
+ * {@link com.android.server.ConnectivityService} to {@link NetworkAgent} when the filter is first
+ * registered.
+ * <p/>
+ * This is not meant to be used in other contexts.
+ *
+ * @hide
+ */
+public final class QosFilterParcelable implements Parcelable {
+
+ private static final String LOG_TAG = QosFilterParcelable.class.getSimpleName();
+
+ // Indicates that the filter was not successfully written to the parcel.
+ private static final int NO_FILTER_PRESENT = 0;
+
+ // The parcel is of type qos socket filter.
+ private static final int QOS_SOCKET_FILTER = 1;
+
+ private final QosFilter mQosFilter;
+
+ /**
+ * The underlying qos filter.
+ * <p/>
+ * Null only in the case parceling failed.
+ */
+ @Nullable
+ public QosFilter getQosFilter() {
+ return mQosFilter;
+ }
+
+ public QosFilterParcelable(@NonNull final QosFilter qosFilter) {
+ Objects.requireNonNull(qosFilter, "qosFilter must be non-null");
+
+ // NOTE: Normally a type check would belong here, but doing so breaks unit tests that rely
+ // on mocking qos filter.
+ mQosFilter = qosFilter;
+ }
+
+ private QosFilterParcelable(final Parcel in) {
+ final int filterParcelType = in.readInt();
+
+ switch (filterParcelType) {
+ case QOS_SOCKET_FILTER: {
+ mQosFilter = new QosSocketFilter(QosSocketInfo.CREATOR.createFromParcel(in));
+ break;
+ }
+
+ case NO_FILTER_PRESENT:
+ default: {
+ mQosFilter = null;
+ }
+ }
+ }
+
+ public static final Creator<QosFilterParcelable> CREATOR = new Creator<QosFilterParcelable>() {
+ @Override
+ public QosFilterParcelable createFromParcel(final Parcel in) {
+ return new QosFilterParcelable(in);
+ }
+
+ @Override
+ public QosFilterParcelable[] newArray(final int size) {
+ return new QosFilterParcelable[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ if (mQosFilter instanceof QosSocketFilter) {
+ dest.writeInt(QOS_SOCKET_FILTER);
+ final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
+ qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
+ return;
+ }
+ dest.writeInt(NO_FILTER_PRESENT);
+ Log.e(LOG_TAG, "Parceling failed, unknown type of filter present: " + mQosFilter);
+ }
+}
diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java
new file mode 100644
index 0000000..4f3bb77
--- /dev/null
+++ b/framework/src/android/net/QosSession.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides identifying information of a QoS session. Sent to an application through
+ * {@link QosCallback}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class QosSession implements Parcelable {
+
+ /**
+ * The {@link QosSession} is a LTE EPS Session.
+ */
+ public static final int TYPE_EPS_BEARER = 1;
+
+ private final int mSessionId;
+
+ private final int mSessionType;
+
+ /**
+ * Gets the unique id of the session that is used to differentiate sessions across different
+ * types.
+ * <p/>
+ * Note: Different qos sessions can be provided by different actors.
+ *
+ * @return the unique id
+ */
+ public long getUniqueId() {
+ return (long) mSessionType << 32 | mSessionId;
+ }
+
+ /**
+ * Gets the session id that is unique within that type.
+ * <p/>
+ * Note: The session id is set by the actor providing the qos. It can be either manufactured by
+ * the actor, but also may have a particular meaning within that type. For example, using the
+ * bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes}
+ * is a straight forward way to keep the sessions unique from one another within that type.
+ *
+ * @return the id of the session
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * Gets the type of session.
+ */
+ @QosSessionType
+ public int getSessionType() {
+ return mSessionType;
+ }
+
+ /**
+ * Creates a {@link QosSession}.
+ *
+ * @param sessionId uniquely identifies the session across all sessions of the same type
+ * @param sessionType the type of session
+ */
+ public QosSession(final int sessionId, @QosSessionType final int sessionType) {
+ //Ensures the session id is unique across types of sessions
+ mSessionId = sessionId;
+ mSessionType = sessionType;
+ }
+
+
+ @Override
+ public String toString() {
+ return "QosSession{"
+ + "mSessionId=" + mSessionId
+ + ", mSessionType=" + mSessionType
+ + '}';
+ }
+
+ /**
+ * Annotations for types of qos sessions.
+ */
+ @IntDef(value = {
+ TYPE_EPS_BEARER,
+ })
+ @interface QosSessionType {}
+
+ private QosSession(final Parcel in) {
+ mSessionId = in.readInt();
+ mSessionType = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<QosSession> CREATOR = new Creator<QosSession>() {
+ @NonNull
+ @Override
+ public QosSession createFromParcel(@NonNull final Parcel in) {
+ return new QosSession(in);
+ }
+
+ @NonNull
+ @Override
+ public QosSession[] newArray(final int size) {
+ return new QosSession[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ dest.writeInt(mSessionId);
+ dest.writeInt(mSessionType);
+ }
+}
diff --git a/framework/src/android/net/QosSessionAttributes.java b/framework/src/android/net/QosSessionAttributes.java
new file mode 100644
index 0000000..7a88594
--- /dev/null
+++ b/framework/src/android/net/QosSessionAttributes.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+
+/**
+ * Implemented by classes that encapsulate Qos related attributes that describe a Qos Session.
+ *
+ * Use the instanceof keyword to determine the underlying type.
+ *
+ * @hide
+ */
+@SystemApi
+public interface QosSessionAttributes {
+}
diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java
new file mode 100644
index 0000000..2080e68
--- /dev/null
+++ b/framework/src/android/net/QosSocketFilter.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Objects;
+
+/**
+ * Filters a {@link QosSession} according to the binding on the provided {@link Socket}.
+ *
+ * @hide
+ */
+public class QosSocketFilter extends QosFilter {
+
+ private static final String TAG = QosSocketFilter.class.getSimpleName();
+
+ @NonNull
+ private final QosSocketInfo mQosSocketInfo;
+
+ /**
+ * Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}.
+ *
+ * @param qosSocketInfo the information required to filter and validate
+ */
+ public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) {
+ Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null");
+ mQosSocketInfo = qosSocketInfo;
+ }
+
+ /**
+ * Gets the parcelable qos socket info that was used to create the filter.
+ */
+ @NonNull
+ public QosSocketInfo getQosSocketInfo() {
+ return mQosSocketInfo;
+ }
+
+ /**
+ * Performs two validations:
+ * 1. If the socket is not bound, then return
+ * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected
+ * by checking the local address on the filter which becomes null when the socket is no
+ * longer bound.
+ * 2. In the scenario that the socket is now bound to a different local address, which can
+ * happen in the case of UDP, then
+ * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
+ * @return validation error code
+ */
+ @Override
+ public int validate() {
+ final InetSocketAddress sa = getAddressFromFileDescriptor();
+ if (sa == null) {
+ return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+ }
+
+ if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
+ return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+ }
+
+ return EX_TYPE_FILTER_NONE;
+ }
+
+ /**
+ * The local address of the socket's binding.
+ *
+ * Note: If the socket is no longer bound, null is returned.
+ *
+ * @return the local address
+ */
+ @Nullable
+ private InetSocketAddress getAddressFromFileDescriptor() {
+ final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
+ if (parcelFileDescriptor == null) return null;
+
+ final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
+ if (fd == null) return null;
+
+ final SocketAddress address;
+ try {
+ address = Os.getsockname(fd);
+ } catch (final ErrnoException e) {
+ Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
+ return null;
+ }
+ if (address instanceof InetSocketAddress) {
+ return (InetSocketAddress) address;
+ }
+ return null;
+ }
+
+ /**
+ * The network used with this filter.
+ *
+ * @return the registered {@link Network}
+ */
+ @NonNull
+ @Override
+ public Network getNetwork() {
+ return mQosSocketInfo.getNetwork();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort,
+ final int endPort) {
+ if (mQosSocketInfo.getLocalSocketAddress() == null) {
+ return false;
+ }
+
+ return matchesLocalAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort,
+ endPort);
+ }
+
+ /**
+ * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)} with the
+ * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
+ * <p>
+ * This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked
+ * due to being final.
+ *
+ * @param filterSocketAddress the socket address of the filter
+ * @param address the address to compare the filterSocketAddressWith
+ * @param startPort the start of the port range to check
+ * @param endPort the end of the port range to check
+ */
+ @VisibleForTesting
+ public static boolean matchesLocalAddress(@NonNull final InetSocketAddress filterSocketAddress,
+ @NonNull final InetAddress address,
+ final int startPort, final int endPort) {
+ return startPort <= filterSocketAddress.getPort()
+ && endPort >= filterSocketAddress.getPort()
+ && filterSocketAddress.getAddress().equals(address);
+ }
+}
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
new file mode 100644
index 0000000..d37c469
--- /dev/null
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * Used in conjunction with
+ * {@link ConnectivityManager#registerQosCallback}
+ * in order to receive Qos Sessions related to the local address and port of a bound {@link Socket}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class QosSocketInfo implements Parcelable {
+
+ @NonNull
+ private final Network mNetwork;
+
+ @NonNull
+ private final ParcelFileDescriptor mParcelFileDescriptor;
+
+ @NonNull
+ private final InetSocketAddress mLocalSocketAddress;
+
+ /**
+ * The {@link Network} the socket is on.
+ *
+ * @return the registered {@link Network}
+ */
+ @NonNull
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ /**
+ * The parcel file descriptor wrapped around the socket's file descriptor.
+ *
+ * @return the parcel file descriptor of the socket
+ */
+ @NonNull
+ ParcelFileDescriptor getParcelFileDescriptor() {
+ return mParcelFileDescriptor;
+ }
+
+ /**
+ * The local address of the socket passed into {@link QosSocketInfo(Network, Socket)}.
+ * The value does not reflect any changes that occur to the socket after it is first set
+ * in the constructor.
+ *
+ * @return the local address of the socket
+ */
+ @NonNull
+ public InetSocketAddress getLocalSocketAddress() {
+ return mLocalSocketAddress;
+ }
+
+ /**
+ * Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The
+ * {@link Socket} must remain bound in order to receive {@link QosSession}s.
+ *
+ * @param network the network
+ * @param socket the bound {@link Socket}
+ */
+ public QosSocketInfo(@NonNull final Network network, @NonNull final Socket socket)
+ throws IOException {
+ Objects.requireNonNull(socket, "socket cannot be null");
+
+ mNetwork = Objects.requireNonNull(network, "network cannot be null");
+ mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$());
+ mLocalSocketAddress =
+ new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort());
+ }
+
+ /* Parcelable methods */
+ private QosSocketInfo(final Parcel in) {
+ mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
+ mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+
+ final int addressLength = in.readInt();
+ mLocalSocketAddress = readSocketAddress(in, addressLength);
+ }
+
+ private InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
+ final byte[] address = new byte[addressLength];
+ in.readByteArray(address);
+ final int port = in.readInt();
+
+ try {
+ return new InetSocketAddress(InetAddress.getByAddress(address), port);
+ } catch (final UnknownHostException e) {
+ /* The catch block was purposely left empty. UnknownHostException will never be thrown
+ since the address provided is numeric and non-null. */
+ }
+ return new InetSocketAddress();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ mNetwork.writeToParcel(dest, 0);
+ mParcelFileDescriptor.writeToParcel(dest, 0);
+
+ final byte[] address = mLocalSocketAddress.getAddress().getAddress();
+ dest.writeInt(address.length);
+ dest.writeByteArray(address);
+ dest.writeInt(mLocalSocketAddress.getPort());
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<QosSocketInfo> CREATOR =
+ new Parcelable.Creator<QosSocketInfo>() {
+ @NonNull
+ @Override
+ public QosSocketInfo createFromParcel(final Parcel in) {
+ return new QosSocketInfo(in);
+ }
+
+ @NonNull
+ @Override
+ public QosSocketInfo[] newArray(final int size) {
+ return new QosSocketInfo[size];
+ }
+ };
+}
diff --git a/framework/src/android/net/SocketLocalAddressChangedException.java b/framework/src/android/net/SocketLocalAddressChangedException.java
new file mode 100644
index 0000000..9daad83
--- /dev/null
+++ b/framework/src/android/net/SocketLocalAddressChangedException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+
+/**
+ * Thrown when the local address of the socket has changed.
+ *
+ * @hide
+ */
+@SystemApi
+public class SocketLocalAddressChangedException extends Exception {
+ /** @hide */
+ public SocketLocalAddressChangedException() {
+ super("The local address of the socket changed");
+ }
+}
diff --git a/framework/src/android/net/SocketNotBoundException.java b/framework/src/android/net/SocketNotBoundException.java
new file mode 100644
index 0000000..b1d7026
--- /dev/null
+++ b/framework/src/android/net/SocketNotBoundException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+
+/**
+ * Thrown when a previously bound socket becomes unbound.
+ *
+ * @hide
+ */
+@SystemApi
+public class SocketNotBoundException extends Exception {
+ /** @hide */
+ public SocketNotBoundException() {
+ super("The socket is unbound");
+ }
+}