Merge "Add Qos Callback support"

This commit is contained in:
Chalard Jean
2021-01-22 13:09:24 +00:00
committed by Gerrit Code Review
18 changed files with 2020 additions and 49 deletions

View File

@@ -19,6 +19,7 @@ import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.REQUEST;
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -4849,4 +4850,118 @@ public class ConnectivityManager {
Log.d(TAG, "setOemNetworkPreference called with preference: "
+ preference.toString());
}
@NonNull
private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
/**
* Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will
* receive available QoS events related to the {@link Network} and local ip + port
* specified within socketInfo.
* <p/>
* The same {@link QosCallback} must be unregistered before being registered a second time,
* otherwise {@link QosCallbackRegistrationException} is thrown.
* <p/>
* This API does not, in itself, require any permission if called with a network that is not
* restricted. However, the underlying implementation currently only supports the IMS network,
* which is always restricted. That means non-preinstalled callers can't possibly find this API
* useful, because they'd never be called back on networks that they would have access to.
*
* @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is
* missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
* @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered.
* @throws RuntimeException if the app already has too many callbacks registered.
*
* Exceptions after the time of registration is passed through
* {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}.
*
* @param socketInfo the socket information used to match QoS events
* @param callback receives qos events that satisfy socketInfo
* @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.
*
* @hide
*/
@SystemApi
public void registerQosCallback(@NonNull final QosSocketInfo socketInfo,
@NonNull final QosCallback callback,
@CallbackExecutor @NonNull final Executor executor) {
Objects.requireNonNull(socketInfo, "socketInfo must be non-null");
Objects.requireNonNull(callback, "callback must be non-null");
Objects.requireNonNull(executor, "executor must be non-null");
try {
synchronized (mQosCallbackConnections) {
if (getQosCallbackConnection(callback) == null) {
final QosCallbackConnection connection =
new QosCallbackConnection(this, callback, executor);
mQosCallbackConnections.add(connection);
mService.registerQosSocketCallback(socketInfo, connection);
} else {
Log.e(TAG, "registerQosCallback: Callback already registered");
throw new QosCallbackRegistrationException();
}
}
} catch (final RemoteException e) {
Log.e(TAG, "registerQosCallback: Error while registering ", e);
// The same unregister method method is called for consistency even though nothing
// will be sent to the ConnectivityService since the callback was never successfully
// registered.
unregisterQosCallback(callback);
e.rethrowFromSystemServer();
} catch (final ServiceSpecificException e) {
Log.e(TAG, "registerQosCallback: Error while registering ", e);
unregisterQosCallback(callback);
throw convertServiceException(e);
}
}
/**
* Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive
* events once unregistered and can be registered a second time.
* <p/>
* If the {@link QosCallback} does not have an active registration, it is a no-op.
*
* @param callback the callback being unregistered
*
* @hide
*/
@SystemApi
public void unregisterQosCallback(@NonNull final QosCallback callback) {
Objects.requireNonNull(callback, "The callback must be non-null");
try {
synchronized (mQosCallbackConnections) {
final QosCallbackConnection connection = getQosCallbackConnection(callback);
if (connection != null) {
connection.stopReceivingMessages();
mService.unregisterQosCallback(connection);
mQosCallbackConnections.remove(connection);
} else {
Log.d(TAG, "unregisterQosCallback: Callback not registered");
}
}
} catch (final RemoteException e) {
Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e);
e.rethrowFromSystemServer();
}
}
/**
* Gets the connection related to the callback.
*
* @param callback the callback to look up
* @return the related connection
*/
@Nullable
private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) {
for (final QosCallbackConnection connection : mQosCallbackConnections) {
// Checking by reference here is intentional
if (connection.getCallback() == callback) {
return connection;
}
}
return null;
}
}

View File

@@ -20,6 +20,8 @@ import android.app.PendingIntent;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager;
import android.net.IConnectivityDiagnosticsCallback;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgentConfig;
@@ -27,9 +29,9 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.ISocketKeepaliveCallback;
import android.net.ProxyInfo;
import android.net.UidRange;
import android.net.QosSocketInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.INetworkActivityListener;
@@ -239,4 +241,7 @@ interface IConnectivityManager
void unregisterNetworkActivityListener(in INetworkActivityListener l);
boolean isDefaultNetworkActive();
void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
void unregisterQosCallback(in IQosCallback callback);
}

View File

@@ -30,6 +30,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.util.Log;
import com.android.connectivity.aidl.INetworkAgent;
@@ -227,7 +228,7 @@ public abstract class NetworkAgent {
*/
public static final String REDIRECT_URL_KEY = "redirect URL";
/**
/**
* Sent by the NetworkAgent to ConnectivityService to indicate this network was
* explicitly selected. This should be sent before the NetworkInfo is marked
* CONNECTED so it can be given special treatment at that time.
@@ -341,6 +342,24 @@ public abstract class NetworkAgent {
*/
private static final int EVENT_AGENT_DISCONNECTED = BASE + 19;
/**
* Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with
* callback.
*
* arg1 = QoS agent callback ID
* obj = {@link QosFilter}
* @hide
*/
public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20;
/**
* Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback.
*
* arg1 = QoS agent callback ID
* @hide
*/
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
// The subtype can be changed with (TODO) setLegacySubtype, but it starts
// with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
@@ -520,6 +539,17 @@ public abstract class NetworkAgent {
onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
break;
}
case CMD_REGISTER_QOS_CALLBACK: {
onQosCallbackRegistered(
msg.arg1 /* QoS callback id */,
(QosFilter) msg.obj /* QoS filter */);
break;
}
case CMD_UNREGISTER_QOS_CALLBACK: {
onQosCallbackUnregistered(
msg.arg1 /* QoS callback id */);
break;
}
}
}
}
@@ -553,6 +583,8 @@ public abstract class NetworkAgent {
}
private static class NetworkAgentBinder extends INetworkAgent.Stub {
private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName();
private final Handler mHandler;
private NetworkAgentBinder(Handler handler) {
@@ -639,6 +671,25 @@ public abstract class NetworkAgent {
mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER,
slot, 0));
}
@Override
public void onQosFilterCallbackRegistered(final int qosCallbackId,
final QosFilterParcelable qosFilterParcelable) {
if (qosFilterParcelable.getQosFilter() != null) {
mHandler.sendMessage(
mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0,
qosFilterParcelable.getQosFilter()));
return;
}
Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null.");
}
@Override
public void onQosCallbackUnregistered(final int qosCallbackId) {
mHandler.sendMessage(mHandler.obtainMessage(
CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null));
}
}
/**
@@ -1067,8 +1118,68 @@ public abstract class NetworkAgent {
protected void preventAutomaticReconnect() {
}
/**
* Called when a qos callback is registered with a filter.
* @param qosCallbackId the id for the callback registered
* @param filter the filter being registered
*/
public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
}
/**
* Called when a qos callback is registered with a filter.
* <p/>
* Any QoS events that are sent with the same callback id after this method is called
* are a no-op.
*
* @param qosCallbackId the id for the callback being unregistered
*/
public void onQosCallbackUnregistered(final int qosCallbackId) {
}
/**
* Sends the attributes of Eps Bearer Qos Session back to the Application
*
* @param qosCallbackId the callback id that the session belongs to
* @param sessionId the unique session id across all Eps Bearer Qos Sessions
* @param attributes the attributes of the Eps Qos Session
*/
public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId,
@NonNull final EpsBearerQosSessionAttributes attributes) {
Objects.requireNonNull(attributes, "The attributes must be non-null");
queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
attributes));
}
/**
* Sends event that the Eps Qos Session was lost.
*
* @param qosCallbackId the callback id that the session belongs to
* @param sessionId the unique session id across all Eps Bearer Qos Sessions
*/
public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) {
queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_EPS_BEARER)));
}
/**
* Sends the exception type back to the application.
*
* The NetworkAgent should not send anymore messages with this id.
*
* @param qosCallbackId the callback id this exception belongs to
* @param exceptionType the type of exception
*/
public final void sendQosCallbackError(final int qosCallbackId,
@QosCallbackException.ExceptionType final int exceptionType) {
queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
}
/** @hide */
protected void log(String s) {
protected void log(final String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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;
/**
* 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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 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 java.io.FileDescriptor;
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();
}
}

View File

@@ -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];
}
};
}