Add Qos Callback support
* Provide App Developers Qos related info associated to a bound socket through ConnectivityManager * Qos sessions are generated and filtered by Network Agents and sent back through the Connectivity Service to the API consumer. * The structure of the code within com.android.server is designed to support different types of filters in the the future. * The first type of Qos Attributes are related to EPS Bearers in order support RCS. Bug: 155176305 Test: Added to cts/NetworkAgentTest Test: Added to ConnectivityServiceTest Change-Id: I145dd065d9deeee449eb9695ab3f6c8556ee7c09
This commit is contained in:
@@ -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;
|
||||
@@ -4848,4 +4849,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
91
core/java/android/net/QosCallback.java
Normal file
91
core/java/android/net/QosCallback.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
128
core/java/android/net/QosCallbackConnection.java
Normal file
128
core/java/android/net/QosCallbackConnection.java
Normal 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;
|
||||
}
|
||||
}
|
||||
110
core/java/android/net/QosCallbackException.java
Normal file
110
core/java/android/net/QosCallbackException.java
Normal 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);
|
||||
}
|
||||
}
|
||||
62
core/java/android/net/QosFilter.java
Normal file
62
core/java/android/net/QosFilter.java
Normal 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();
|
||||
}
|
||||
|
||||
113
core/java/android/net/QosFilterParcelable.java
Normal file
113
core/java/android/net/QosFilterParcelable.java
Normal 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);
|
||||
}
|
||||
}
|
||||
136
core/java/android/net/QosSession.java
Normal file
136
core/java/android/net/QosSession.java
Normal 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);
|
||||
}
|
||||
}
|
||||
128
core/java/android/net/QosSocketFilter.java
Normal file
128
core/java/android/net/QosSocketFilter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
154
core/java/android/net/QosSocketInfo.java
Normal file
154
core/java/android/net/QosSocketInfo.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -94,6 +94,7 @@ import android.net.INetworkMonitor;
|
||||
import android.net.INetworkMonitorCallbacks;
|
||||
import android.net.INetworkPolicyListener;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.IQosCallback;
|
||||
import android.net.ISocketKeepaliveCallback;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpMemoryStore;
|
||||
@@ -121,6 +122,10 @@ import android.net.NetworkUtils;
|
||||
import android.net.NetworkWatchlistManager;
|
||||
import android.net.PrivateDnsConfigParcel;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.QosCallbackException;
|
||||
import android.net.QosFilter;
|
||||
import android.net.QosSocketFilter;
|
||||
import android.net.QosSocketInfo;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.RouteInfoParcel;
|
||||
import android.net.SocketKeepalive;
|
||||
@@ -204,6 +209,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy
|
||||
import com.android.server.connectivity.NetworkRanker;
|
||||
import com.android.server.connectivity.PermissionMonitor;
|
||||
import com.android.server.connectivity.ProxyTracker;
|
||||
import com.android.server.connectivity.QosCallbackTracker;
|
||||
import com.android.server.connectivity.Vpn;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
import com.android.server.net.LockdownVpnTracker;
|
||||
@@ -279,6 +285,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
// Default to 30s linger time-out. Modifiable only for testing.
|
||||
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
|
||||
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
|
||||
|
||||
// The maximum number of network request allowed per uid before an exception is thrown.
|
||||
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
|
||||
|
||||
@VisibleForTesting
|
||||
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
|
||||
|
||||
@@ -291,6 +301,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
@VisibleForTesting
|
||||
protected final PermissionMonitor mPermissionMonitor;
|
||||
|
||||
private final PerUidCounter mNetworkRequestCounter;
|
||||
|
||||
private KeyStore mKeyStore;
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -614,6 +626,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
private final LocationPermissionChecker mLocationPermissionChecker;
|
||||
|
||||
private KeepaliveTracker mKeepaliveTracker;
|
||||
private QosCallbackTracker mQosCallbackTracker;
|
||||
private NetworkNotificationManager mNotifier;
|
||||
private LingerMonitor mLingerMonitor;
|
||||
|
||||
@@ -857,6 +870,66 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Keeps track of the number of requests made under different uids.
|
||||
*/
|
||||
public static class PerUidCounter {
|
||||
private final int mMaxCountPerUid;
|
||||
|
||||
// Map from UID to number of NetworkRequests that UID has filed.
|
||||
@GuardedBy("mUidToNetworkRequestCount")
|
||||
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param maxCountPerUid the maximum count per uid allowed
|
||||
*/
|
||||
public PerUidCounter(final int maxCountPerUid) {
|
||||
mMaxCountPerUid = maxCountPerUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the request count of the given uid. Throws an exception if the number
|
||||
* of open requests for the uid exceeds the value of maxCounterPerUid which is the value
|
||||
* passed into the constructor. see: {@link #PerUidCounter(int)}.
|
||||
*
|
||||
* @throws ServiceSpecificException with
|
||||
* {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for
|
||||
* the uid exceed the allowed number.
|
||||
*
|
||||
* @param uid the uid that the request was made under
|
||||
*/
|
||||
public void incrementCountOrThrow(final int uid) {
|
||||
synchronized (mUidToNetworkRequestCount) {
|
||||
final int networkRequests = mUidToNetworkRequestCount.get(uid, 0) + 1;
|
||||
if (networkRequests >= mMaxCountPerUid) {
|
||||
throw new ServiceSpecificException(
|
||||
ConnectivityManager.Errors.TOO_MANY_REQUESTS);
|
||||
}
|
||||
mUidToNetworkRequestCount.put(uid, networkRequests);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the request count of the given uid.
|
||||
*
|
||||
* @param uid the uid that the request was made under
|
||||
*/
|
||||
public void decrementCount(final int uid) {
|
||||
synchronized (mUidToNetworkRequestCount) {
|
||||
final int requests = mUidToNetworkRequestCount.get(uid, 0);
|
||||
if (requests < 1) {
|
||||
logwtf("BUG: too small request count " + requests + " for UID " + uid);
|
||||
} else if (requests == 1) {
|
||||
mUidToNetworkRequestCount.delete(uid);
|
||||
} else {
|
||||
mUidToNetworkRequestCount.put(uid, requests - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependencies of ConnectivityService, for injection in tests.
|
||||
*/
|
||||
@@ -945,6 +1018,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
mSystemProperties = mDeps.getSystemProperties();
|
||||
mNetIdManager = mDeps.makeNetIdManager();
|
||||
mContext = Objects.requireNonNull(context, "missing Context");
|
||||
mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
|
||||
|
||||
mMetricsLog = logger;
|
||||
mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
|
||||
@@ -1125,6 +1199,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
|
||||
mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
|
||||
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
|
||||
mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
|
||||
|
||||
final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
|
||||
@@ -2777,6 +2852,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
updateCapabilitiesForNetwork(nai);
|
||||
notifyIfacesChangedForNetworkStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3338,6 +3414,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
// of rematchAllNetworksAndRequests
|
||||
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
|
||||
mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
|
||||
|
||||
mQosCallbackTracker.handleNetworkReleased(nai.network);
|
||||
for (String iface : nai.linkProperties.getAllInterfaceNames()) {
|
||||
// Disable wakeup packet monitoring for each interface.
|
||||
wakeupModifyInterface(iface, nai.networkCapabilities, false);
|
||||
@@ -3607,7 +3685,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
nri.unlinkDeathRecipient();
|
||||
mNetworkRequests.remove(nri.request);
|
||||
|
||||
decrementNetworkRequestPerUidCount(nri);
|
||||
mNetworkRequestCounter.decrementCount(nri.mUid);
|
||||
|
||||
mNetworkRequestInfoLogs.log("RELEASE " + nri);
|
||||
if (nri.request.isRequest()) {
|
||||
@@ -3680,19 +3758,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) {
|
||||
synchronized (mUidToNetworkRequestCount) {
|
||||
final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
|
||||
if (requests < 1) {
|
||||
Log.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid);
|
||||
} else if (requests == 1) {
|
||||
mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid));
|
||||
} else {
|
||||
mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
|
||||
enforceNetworkStackSettingsOrSetup();
|
||||
@@ -4519,6 +4584,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
Log.w(TAG, s);
|
||||
}
|
||||
|
||||
private static void logwtf(String s) {
|
||||
Log.wtf(TAG, s);
|
||||
}
|
||||
|
||||
private static void loge(String s) {
|
||||
Log.e(TAG, s);
|
||||
}
|
||||
@@ -5261,11 +5330,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
|
||||
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
|
||||
|
||||
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
|
||||
// Map from UID to number of NetworkRequests that UID has filed.
|
||||
@GuardedBy("mUidToNetworkRequestCount")
|
||||
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
|
||||
|
||||
private static class NetworkProviderInfo {
|
||||
public final String name;
|
||||
public final Messenger messenger;
|
||||
@@ -5379,7 +5443,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
mBinder = null;
|
||||
mPid = getCallingPid();
|
||||
mUid = mDeps.getCallingUid();
|
||||
enforceRequestCountLimit();
|
||||
mNetworkRequestCounter.incrementCountOrThrow(mUid);
|
||||
}
|
||||
|
||||
NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
|
||||
@@ -5392,7 +5456,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
mPid = getCallingPid();
|
||||
mUid = mDeps.getCallingUid();
|
||||
mPendingIntent = null;
|
||||
enforceRequestCountLimit();
|
||||
mNetworkRequestCounter.incrementCountOrThrow(mUid);
|
||||
|
||||
try {
|
||||
mBinder.linkToDeath(this, 0);
|
||||
@@ -5429,17 +5493,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
private void enforceRequestCountLimit() {
|
||||
synchronized (mUidToNetworkRequestCount) {
|
||||
int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
|
||||
if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
|
||||
throw new ServiceSpecificException(
|
||||
ConnectivityManager.Errors.TOO_MANY_REQUESTS);
|
||||
}
|
||||
mUidToNetworkRequestCount.put(mUid, networkRequests);
|
||||
}
|
||||
}
|
||||
|
||||
void unlinkDeathRecipient() {
|
||||
if (mBinder != null) {
|
||||
mBinder.unlinkToDeath(this, 0);
|
||||
@@ -5998,7 +6051,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
|
||||
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
|
||||
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
|
||||
this, mNetd, mDnsResolver, mNMS, providerId, uid);
|
||||
this, mNetd, mDnsResolver, mNMS, providerId, uid, mQosCallbackTracker);
|
||||
|
||||
// Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
|
||||
processCapabilitiesFromAgent(nai, nc);
|
||||
@@ -8278,7 +8331,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
// Decrement the reference count for this NetworkRequestInfo. The reference count is
|
||||
// incremented when the NetworkRequestInfo is created as part of
|
||||
// enforceRequestCountLimit().
|
||||
decrementNetworkRequestPerUidCount(nri);
|
||||
mNetworkRequestCounter.decrementCount(nri.mUid);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8344,7 +8397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
// Decrement the reference count for this NetworkRequestInfo. The reference count is
|
||||
// incremented when the NetworkRequestInfo is created as part of
|
||||
// enforceRequestCountLimit().
|
||||
decrementNetworkRequestPerUidCount(nri);
|
||||
mNetworkRequestCounter.decrementCount(nri.mUid);
|
||||
|
||||
iCb.unlinkToDeath(cbInfo, 0);
|
||||
}
|
||||
@@ -8565,7 +8618,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
private final INetworkManagementService mNMS;
|
||||
|
||||
LegacyNetworkActivityTracker(@NonNull Context context,
|
||||
@NonNull INetworkManagementService nms) {
|
||||
@NonNull INetworkManagementService nms) {
|
||||
mContext = context;
|
||||
mNMS = nms;
|
||||
try {
|
||||
@@ -8584,7 +8637,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active,
|
||||
tsNanos);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// This is deprecated and only to support legacy use cases.
|
||||
private int transportTypeToLegacyType(int type) {
|
||||
@@ -8694,4 +8747,53 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers {@link QosSocketFilter} with {@link IQosCallback}.
|
||||
*
|
||||
* @param socketInfo the socket information
|
||||
* @param callback the callback to register
|
||||
*/
|
||||
@Override
|
||||
public void registerQosSocketCallback(@NonNull final QosSocketInfo socketInfo,
|
||||
@NonNull final IQosCallback callback) {
|
||||
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(socketInfo.getNetwork());
|
||||
if (nai == null || nai.networkCapabilities == null) {
|
||||
try {
|
||||
callback.onError(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
|
||||
} catch (final RemoteException ex) {
|
||||
loge("registerQosCallbackInternal: RemoteException", ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerQosCallbackInternal(new QosSocketFilter(socketInfo), callback, nai);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link IQosCallback} with base {@link QosFilter}.
|
||||
*
|
||||
* @param filter the filter to register
|
||||
* @param callback the callback to register
|
||||
* @param nai the agent information related to the filter's network
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void registerQosCallbackInternal(@NonNull final QosFilter filter,
|
||||
@NonNull final IQosCallback callback, @NonNull final NetworkAgentInfo nai) {
|
||||
if (filter == null) throw new IllegalArgumentException("filter must be non-null");
|
||||
if (callback == null) throw new IllegalArgumentException("callback must be non-null");
|
||||
|
||||
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
|
||||
enforceConnectivityRestrictedNetworksPermission();
|
||||
}
|
||||
mQosCallbackTracker.registerCallback(callback, filter, nai);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given callback.
|
||||
*
|
||||
* @param callback the callback to unregister
|
||||
*/
|
||||
@Override
|
||||
public void unregisterQosCallback(@NonNull final IQosCallback callback) {
|
||||
mQosCallbackTracker.unregisterCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,17 @@ import android.net.NetworkInfo;
|
||||
import android.net.NetworkMonitorManager;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkState;
|
||||
import android.net.QosCallbackException;
|
||||
import android.net.QosFilter;
|
||||
import android.net.QosFilterParcelable;
|
||||
import android.net.QosSession;
|
||||
import android.net.TcpKeepalivePacketData;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
@@ -321,18 +326,20 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
private final ConnectivityService mConnService;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final QosCallbackTracker mQosCallbackTracker;
|
||||
|
||||
public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info,
|
||||
LinkProperties lp, NetworkCapabilities nc, int score, Context context,
|
||||
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
|
||||
IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber,
|
||||
int creatorUid) {
|
||||
int creatorUid, QosCallbackTracker qosCallbackTracker) {
|
||||
Objects.requireNonNull(net);
|
||||
Objects.requireNonNull(info);
|
||||
Objects.requireNonNull(lp);
|
||||
Objects.requireNonNull(nc);
|
||||
Objects.requireNonNull(context);
|
||||
Objects.requireNonNull(config);
|
||||
Objects.requireNonNull(qosCallbackTracker);
|
||||
networkAgent = na;
|
||||
network = net;
|
||||
networkInfo = info;
|
||||
@@ -346,6 +353,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
networkAgentConfig = config;
|
||||
this.factorySerialNumber = factorySerialNumber;
|
||||
this.creatorUid = creatorUid;
|
||||
mQosCallbackTracker = qosCallbackTracker;
|
||||
}
|
||||
|
||||
private class AgentDeathMonitor implements IBinder.DeathRecipient {
|
||||
@@ -531,6 +539,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the NetworkAgent that the qos filter should be registered against the given qos
|
||||
* callback id.
|
||||
*/
|
||||
public void onQosFilterCallbackRegistered(final int qosCallbackId,
|
||||
final QosFilter qosFilter) {
|
||||
try {
|
||||
networkAgent.onQosFilterCallbackRegistered(qosCallbackId,
|
||||
new QosFilterParcelable(qosFilter));
|
||||
} catch (final RemoteException e) {
|
||||
Log.e(TAG, "Error registering a qos callback id against a qos filter", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the NetworkAgent that the given qos callback id should be unregistered.
|
||||
*/
|
||||
public void onQosCallbackUnregistered(final int qosCallbackId) {
|
||||
try {
|
||||
networkAgent.onQosCallbackUnregistered(qosCallbackId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error unregistering a qos callback id", e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider moving out of NetworkAgentInfo into its own class
|
||||
private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub {
|
||||
private final Handler mHandler;
|
||||
@@ -584,6 +617,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
mHandler.obtainMessage(NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED,
|
||||
new Pair<>(NetworkAgentInfo.this, networks)).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session,
|
||||
final EpsBearerQosSessionAttributes attributes) {
|
||||
mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendQosSessionLost(final int qosCallbackId, final QosSession session) {
|
||||
mQosCallbackTracker.sendEventQosSessionLost(qosCallbackId, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendQosCallbackError(final int qosCallbackId,
|
||||
@QosCallbackException.ExceptionType final int exceptionType) {
|
||||
mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -16,5 +16,177 @@
|
||||
|
||||
package com.android.server.connectivity;
|
||||
|
||||
class QosCallbackAgentConnection {
|
||||
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.IQosCallback;
|
||||
import android.net.Network;
|
||||
import android.net.QosCallbackException;
|
||||
import android.net.QosFilter;
|
||||
import android.net.QosSession;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Wraps callback related information and sends messages between network agent and the application.
|
||||
* <p/>
|
||||
* This is a satellite class of {@link com.android.server.ConnectivityService} and not meant
|
||||
* to be used in other contexts.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
class QosCallbackAgentConnection implements IBinder.DeathRecipient {
|
||||
private static final String TAG = QosCallbackAgentConnection.class.getSimpleName();
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private final int mAgentCallbackId;
|
||||
@NonNull private final QosCallbackTracker mQosCallbackTracker;
|
||||
@NonNull private final IQosCallback mCallback;
|
||||
@NonNull private final IBinder mBinder;
|
||||
@NonNull private final QosFilter mFilter;
|
||||
@NonNull private final NetworkAgentInfo mNetworkAgentInfo;
|
||||
|
||||
private final int mUid;
|
||||
|
||||
/**
|
||||
* Gets the uid
|
||||
* @return uid
|
||||
*/
|
||||
int getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the binder
|
||||
* @return binder
|
||||
*/
|
||||
@NonNull
|
||||
IBinder getBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback id
|
||||
*
|
||||
* @return callback id
|
||||
*/
|
||||
int getAgentCallbackId() {
|
||||
return mAgentCallbackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the network tied to the callback of this connection
|
||||
*
|
||||
* @return network
|
||||
*/
|
||||
@NonNull
|
||||
Network getNetwork() {
|
||||
return mFilter.getNetwork();
|
||||
}
|
||||
|
||||
QosCallbackAgentConnection(@NonNull final QosCallbackTracker qosCallbackTracker,
|
||||
final int agentCallbackId,
|
||||
@NonNull final IQosCallback callback,
|
||||
@NonNull final QosFilter filter,
|
||||
final int uid,
|
||||
@NonNull final NetworkAgentInfo networkAgentInfo) {
|
||||
Objects.requireNonNull(qosCallbackTracker, "qosCallbackTracker must be non-null");
|
||||
Objects.requireNonNull(callback, "callback must be non-null");
|
||||
Objects.requireNonNull(filter, "filter must be non-null");
|
||||
Objects.requireNonNull(networkAgentInfo, "networkAgentInfo must be non-null");
|
||||
|
||||
mQosCallbackTracker = qosCallbackTracker;
|
||||
mAgentCallbackId = agentCallbackId;
|
||||
mCallback = callback;
|
||||
mFilter = filter;
|
||||
mUid = uid;
|
||||
mBinder = mCallback.asBinder();
|
||||
mNetworkAgentInfo = networkAgentInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
logw("binderDied: binder died with callback id: " + mAgentCallbackId);
|
||||
mQosCallbackTracker.unregisterCallback(mCallback);
|
||||
}
|
||||
|
||||
void unlinkToDeathRecipient() {
|
||||
mBinder.unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
// Returns false if the NetworkAgent was never notified.
|
||||
boolean sendCmdRegisterCallback() {
|
||||
final int exceptionType = mFilter.validate();
|
||||
if (exceptionType != EX_TYPE_FILTER_NONE) {
|
||||
try {
|
||||
if (DBG) log("sendCmdRegisterCallback: filter validation failed");
|
||||
mCallback.onError(exceptionType);
|
||||
} catch (final RemoteException e) {
|
||||
loge("sendCmdRegisterCallback:", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
mBinder.linkToDeath(this, 0);
|
||||
} catch (final RemoteException e) {
|
||||
loge("failed linking to death recipient", e);
|
||||
return false;
|
||||
}
|
||||
mNetworkAgentInfo.onQosFilterCallbackRegistered(mAgentCallbackId, mFilter);
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendCmdUnregisterCallback() {
|
||||
if (DBG) log("sendCmdUnregisterCallback: unregistering");
|
||||
mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId);
|
||||
}
|
||||
|
||||
void sendEventQosSessionAvailable(final QosSession session,
|
||||
final EpsBearerQosSessionAttributes attributes) {
|
||||
try {
|
||||
if (DBG) log("sendEventQosSessionAvailable: sending...");
|
||||
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
|
||||
} catch (final RemoteException e) {
|
||||
loge("sendEventQosSessionAvailable: remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
void sendEventQosSessionLost(@NonNull final QosSession session) {
|
||||
try {
|
||||
if (DBG) log("sendEventQosSessionLost: sending...");
|
||||
mCallback.onQosSessionLost(session);
|
||||
} catch (final RemoteException e) {
|
||||
loge("sendEventQosSessionLost: remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
void sendEventQosCallbackError(@QosCallbackException.ExceptionType final int exceptionType) {
|
||||
try {
|
||||
if (DBG) log("sendEventQosCallbackError: sending...");
|
||||
mCallback.onError(exceptionType);
|
||||
} catch (final RemoteException e) {
|
||||
loge("sendEventQosCallbackError: remote exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void log(@NonNull final String msg) {
|
||||
Slog.d(TAG, msg);
|
||||
}
|
||||
|
||||
private static void logw(@NonNull final String msg) {
|
||||
Slog.w(TAG, msg);
|
||||
}
|
||||
|
||||
private static void loge(@NonNull final String msg, final Throwable t) {
|
||||
Slog.e(TAG, msg, t);
|
||||
}
|
||||
|
||||
private static void logwtf(@NonNull final String msg) {
|
||||
Slog.wtf(TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -16,5 +16,262 @@
|
||||
|
||||
package com.android.server.connectivity;
|
||||
|
||||
class QosCallbackTracker {
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.IQosCallback;
|
||||
import android.net.Network;
|
||||
import android.net.QosCallbackException;
|
||||
import android.net.QosFilter;
|
||||
import android.net.QosSession;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.server.ConnectivityService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tracks qos callbacks and handles the communication between the network agent and application.
|
||||
* <p/>
|
||||
* Any method prefixed by handle must be called from the
|
||||
* {@link com.android.server.ConnectivityService} handler thread.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class QosCallbackTracker {
|
||||
private static final String TAG = QosCallbackTracker.class.getSimpleName();
|
||||
private static final boolean DBG = true;
|
||||
|
||||
@NonNull
|
||||
private final Handler mConnectivityServiceHandler;
|
||||
|
||||
@NonNull
|
||||
private final ConnectivityService.PerUidCounter mNetworkRequestCounter;
|
||||
|
||||
/**
|
||||
* Each agent gets a unique callback id that is used to proxy messages back to the original
|
||||
* callback.
|
||||
* <p/>
|
||||
* Note: The fact that this is initialized to 0 is to ensure that the thread running
|
||||
* {@link #handleRegisterCallback(IQosCallback, QosFilter, int, NetworkAgentInfo)} sees the
|
||||
* initialized value. This would not necessarily be the case if the value was initialized to
|
||||
* the non-default value.
|
||||
* <p/>
|
||||
* Note: The term previous does not apply to the first callback id that is assigned.
|
||||
*/
|
||||
private int mPreviousAgentCallbackId = 0;
|
||||
|
||||
@NonNull
|
||||
private final List<QosCallbackAgentConnection> mConnections = new ArrayList<>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param connectivityServiceHandler must be the same handler used with
|
||||
* {@link com.android.server.ConnectivityService}
|
||||
* @param networkRequestCounter keeps track of the number of open requests under a given
|
||||
* uid
|
||||
*/
|
||||
public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler,
|
||||
final ConnectivityService.PerUidCounter networkRequestCounter) {
|
||||
mConnectivityServiceHandler = connectivityServiceHandler;
|
||||
mNetworkRequestCounter = networkRequestCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the callback with the tracker
|
||||
*
|
||||
* @param callback the callback to register
|
||||
* @param filter the filter being registered alongside the callback
|
||||
*/
|
||||
public void registerCallback(@NonNull final IQosCallback callback,
|
||||
@NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo) {
|
||||
final int uid = Binder.getCallingUid();
|
||||
|
||||
// Enforce that the number of requests under this uid has exceeded the allowed number
|
||||
mNetworkRequestCounter.incrementCountOrThrow(uid);
|
||||
|
||||
mConnectivityServiceHandler.post(
|
||||
() -> handleRegisterCallback(callback, filter, uid, networkAgentInfo));
|
||||
}
|
||||
|
||||
private void handleRegisterCallback(@NonNull final IQosCallback callback,
|
||||
@NonNull final QosFilter filter, final int uid,
|
||||
@NonNull final NetworkAgentInfo networkAgentInfo) {
|
||||
final QosCallbackAgentConnection ac =
|
||||
handleRegisterCallbackInternal(callback, filter, uid, networkAgentInfo);
|
||||
if (ac != null) {
|
||||
if (DBG) log("handleRegisterCallback: added callback " + ac.getAgentCallbackId());
|
||||
mConnections.add(ac);
|
||||
} else {
|
||||
mNetworkRequestCounter.decrementCount(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private QosCallbackAgentConnection handleRegisterCallbackInternal(
|
||||
@NonNull final IQosCallback callback,
|
||||
@NonNull final QosFilter filter, final int uid,
|
||||
@NonNull final NetworkAgentInfo networkAgentInfo) {
|
||||
final IBinder binder = callback.asBinder();
|
||||
if (CollectionUtils.any(mConnections, c -> c.getBinder().equals(binder))) {
|
||||
// A duplicate registration would have only made this far due to a programming error.
|
||||
logwtf("handleRegisterCallback: Callbacks can only be register once.");
|
||||
return null;
|
||||
}
|
||||
|
||||
mPreviousAgentCallbackId = mPreviousAgentCallbackId + 1;
|
||||
final int newCallbackId = mPreviousAgentCallbackId;
|
||||
|
||||
final QosCallbackAgentConnection ac =
|
||||
new QosCallbackAgentConnection(this, newCallbackId, callback,
|
||||
filter, uid, networkAgentInfo);
|
||||
|
||||
final int exceptionType = filter.validate();
|
||||
if (exceptionType != QosCallbackException.EX_TYPE_FILTER_NONE) {
|
||||
ac.sendEventQosCallbackError(exceptionType);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only add to the callback maps if the NetworkAgent successfully registered it
|
||||
if (!ac.sendCmdRegisterCallback()) {
|
||||
// There was an issue when registering the agent
|
||||
if (DBG) log("handleRegisterCallback: error sending register callback");
|
||||
mNetworkRequestCounter.decrementCount(uid);
|
||||
return null;
|
||||
}
|
||||
return ac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters callback
|
||||
* @param callback callback to unregister
|
||||
*/
|
||||
public void unregisterCallback(@NonNull final IQosCallback callback) {
|
||||
mConnectivityServiceHandler.post(() -> handleUnregisterCallback(callback.asBinder(), true));
|
||||
}
|
||||
|
||||
private void handleUnregisterCallback(@NonNull final IBinder binder,
|
||||
final boolean sendToNetworkAgent) {
|
||||
final QosCallbackAgentConnection agentConnection =
|
||||
CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder));
|
||||
if (agentConnection == null) {
|
||||
logw("handleUnregisterCallback: agentConnection is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
log("handleUnregisterCallback: unregister "
|
||||
+ agentConnection.getAgentCallbackId());
|
||||
}
|
||||
|
||||
mNetworkRequestCounter.decrementCount(agentConnection.getUid());
|
||||
mConnections.remove(agentConnection);
|
||||
|
||||
if (sendToNetworkAgent) {
|
||||
agentConnection.sendCmdUnregisterCallback();
|
||||
}
|
||||
agentConnection.unlinkToDeathRecipient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the NetworkAgent sends the qos session available event
|
||||
*
|
||||
* @param qosCallbackId the callback id that the qos session is now available to
|
||||
* @param session the qos session that is now available
|
||||
* @param attributes the qos attributes that are now available on the qos session
|
||||
*/
|
||||
public void sendEventQosSessionAvailable(final int qosCallbackId,
|
||||
final QosSession session,
|
||||
final EpsBearerQosSessionAttributes attributes) {
|
||||
runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ",
|
||||
ac -> ac.sendEventQosSessionAvailable(session, attributes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the NetworkAgent sends the qos session lost event
|
||||
*
|
||||
* @param qosCallbackId the callback id that lost the qos session
|
||||
* @param session the corresponding qos session
|
||||
*/
|
||||
public void sendEventQosSessionLost(final int qosCallbackId,
|
||||
final QosSession session) {
|
||||
runOnAgentConnection(qosCallbackId, "sendEventQosSessionLost: ",
|
||||
ac -> ac.sendEventQosSessionLost(session));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the NetworkAgent sends the qos session on error event
|
||||
*
|
||||
* @param qosCallbackId the callback id that should receive the exception
|
||||
* @param exceptionType the type of exception that caused the callback to error
|
||||
*/
|
||||
public void sendEventQosCallbackError(final int qosCallbackId,
|
||||
@QosCallbackException.ExceptionType final int exceptionType) {
|
||||
runOnAgentConnection(qosCallbackId, "sendEventQosCallbackError: ",
|
||||
ac -> {
|
||||
ac.sendEventQosCallbackError(exceptionType);
|
||||
handleUnregisterCallback(ac.getBinder(), false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all callbacks associated to this network agent
|
||||
*
|
||||
* Note: Must be called on the connectivity service handler thread
|
||||
*
|
||||
* @param network the network that was released
|
||||
*/
|
||||
public void handleNetworkReleased(@Nullable final Network network) {
|
||||
final List<QosCallbackAgentConnection> connections =
|
||||
CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network));
|
||||
|
||||
for (final QosCallbackAgentConnection agentConnection : connections) {
|
||||
agentConnection.sendEventQosCallbackError(
|
||||
QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
|
||||
|
||||
// Call unregister workflow w\o sending anything to agent since it is disconnected.
|
||||
handleUnregisterCallback(agentConnection.getBinder(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private interface AgentConnectionAction {
|
||||
void execute(@NonNull QosCallbackAgentConnection agentConnection);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private void runOnAgentConnection(final int qosCallbackId,
|
||||
@NonNull final String logPrefix,
|
||||
@NonNull final AgentConnectionAction action) {
|
||||
mConnectivityServiceHandler.post(() -> {
|
||||
final QosCallbackAgentConnection ac =
|
||||
CollectionUtils.find(mConnections,
|
||||
c -> c.getAgentCallbackId() == qosCallbackId);
|
||||
if (ac == null) {
|
||||
loge(logPrefix + ": " + qosCallbackId + " missing callback id");
|
||||
return;
|
||||
}
|
||||
|
||||
action.execute(ac);
|
||||
});
|
||||
}
|
||||
|
||||
private static void log(final String msg) {
|
||||
Slog.d(TAG, msg);
|
||||
}
|
||||
|
||||
private static void logw(final String msg) {
|
||||
Slog.w(TAG, msg);
|
||||
}
|
||||
|
||||
private static void loge(final String msg) {
|
||||
Slog.e(TAG, msg);
|
||||
}
|
||||
|
||||
private static void logwtf(final String msg) {
|
||||
Slog.wtf(TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkProperties;
|
||||
@@ -40,6 +41,7 @@ import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkSpecifier;
|
||||
import android.net.QosFilter;
|
||||
import android.net.SocketKeepalive;
|
||||
import android.net.UidRange;
|
||||
import android.os.ConditionVariable;
|
||||
@@ -47,10 +49,12 @@ import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.net.module.util.ArrayTrackRecord;
|
||||
import com.android.server.connectivity.ConnectivityConstants;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
import com.android.testutils.TestableNetworkCallback;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -71,6 +75,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
// start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem.
|
||||
private long mKeepaliveResponseDelay = 0L;
|
||||
private Integer mExpectedKeepaliveSlot = null;
|
||||
private final ArrayTrackRecord<CallbackType>.ReadHead mCallbackHistory =
|
||||
new ArrayTrackRecord<CallbackType>().newReadHead();
|
||||
|
||||
public NetworkAgentWrapper(int transport, LinkProperties linkProperties,
|
||||
NetworkCapabilities ncTemplate, Context context) throws Exception {
|
||||
@@ -156,6 +162,20 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
mWrapper.mKeepaliveResponseDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQosCallbackRegistered(final int qosCallbackId,
|
||||
final @NonNull QosFilter filter) {
|
||||
Log.i(mWrapper.mLogTag, "onQosCallbackRegistered");
|
||||
mWrapper.mCallbackHistory.add(
|
||||
new CallbackType.OnQosCallbackRegister(qosCallbackId, filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQosCallbackUnregistered(final int qosCallbackId) {
|
||||
Log.i(mWrapper.mLogTag, "onQosCallbackUnregistered");
|
||||
mWrapper.mCallbackHistory.add(new CallbackType.OnQosCallbackUnregister(qosCallbackId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preventAutomaticReconnect() {
|
||||
mWrapper.mPreventReconnectReceived.open();
|
||||
@@ -279,7 +299,60 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
return mNetworkCapabilities;
|
||||
}
|
||||
|
||||
public @NonNull ArrayTrackRecord<CallbackType>.ReadHead getCallbackHistory() {
|
||||
return mCallbackHistory;
|
||||
}
|
||||
|
||||
public void waitForIdle(long timeoutMs) {
|
||||
HandlerUtils.waitForIdle(mHandlerThread, timeoutMs);
|
||||
}
|
||||
|
||||
abstract static class CallbackType {
|
||||
final int mQosCallbackId;
|
||||
|
||||
protected CallbackType(final int qosCallbackId) {
|
||||
mQosCallbackId = qosCallbackId;
|
||||
}
|
||||
|
||||
static class OnQosCallbackRegister extends CallbackType {
|
||||
final QosFilter mFilter;
|
||||
OnQosCallbackRegister(final int qosCallbackId, final QosFilter filter) {
|
||||
super(qosCallbackId);
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final OnQosCallbackRegister that = (OnQosCallbackRegister) o;
|
||||
return mQosCallbackId == that.mQosCallbackId
|
||||
&& Objects.equals(mFilter, that.mFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mQosCallbackId, mFilter);
|
||||
}
|
||||
}
|
||||
|
||||
static class OnQosCallbackUnregister extends CallbackType {
|
||||
OnQosCallbackUnregister(final int qosCallbackId) {
|
||||
super(qosCallbackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final OnQosCallbackUnregister that = (OnQosCallbackUnregister) o;
|
||||
return mQosCallbackId == that.mQosCallbackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mQosCallbackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ import android.net.INetworkMonitor;
|
||||
import android.net.INetworkMonitorCallbacks;
|
||||
import android.net.INetworkPolicyListener;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.IQosCallback;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.InterfaceConfigurationParcel;
|
||||
import android.net.IpPrefix;
|
||||
@@ -190,6 +191,9 @@ import android.net.NetworkStackClient;
|
||||
import android.net.NetworkState;
|
||||
import android.net.NetworkTestResultParcelable;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.QosCallbackException;
|
||||
import android.net.QosFilter;
|
||||
import android.net.QosSession;
|
||||
import android.net.ResolverParamsParcel;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.RouteInfoParcel;
|
||||
@@ -218,6 +222,7 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@@ -226,10 +231,12 @@ import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.system.Os;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@@ -251,6 +258,7 @@ import com.android.server.connectivity.Nat464Xlat;
|
||||
import com.android.server.connectivity.NetworkAgentInfo;
|
||||
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
|
||||
import com.android.server.connectivity.ProxyTracker;
|
||||
import com.android.server.connectivity.QosCallbackTracker;
|
||||
import com.android.server.connectivity.Vpn;
|
||||
import com.android.server.net.NetworkPinner;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
@@ -368,6 +376,8 @@ public class ConnectivityServiceTest {
|
||||
private WrappedMultinetworkPolicyTracker mPolicyTracker;
|
||||
private HandlerThread mAlarmManagerThread;
|
||||
private TestNetIdManager mNetIdManager;
|
||||
private QosCallbackMockHelper mQosCallbackMockHelper;
|
||||
private QosCallbackTracker mQosCallbackTracker;
|
||||
|
||||
@Mock DeviceIdleInternal mDeviceIdleInternal;
|
||||
@Mock INetworkManagementService mNetworkManagementService;
|
||||
@@ -1395,6 +1405,7 @@ public class ConnectivityServiceTest {
|
||||
mService.systemReadyInternal();
|
||||
mockVpn(Process.myUid());
|
||||
mCm.bindProcessToNetwork(null);
|
||||
mQosCallbackTracker = mock(QosCallbackTracker.class);
|
||||
|
||||
// Ensure that the default setting for Captive Portals is used for most tests
|
||||
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
|
||||
@@ -1470,6 +1481,11 @@ public class ConnectivityServiceTest {
|
||||
mEthernetNetworkAgent.disconnect();
|
||||
mEthernetNetworkAgent = null;
|
||||
}
|
||||
|
||||
if (mQosCallbackMockHelper != null) {
|
||||
mQosCallbackMockHelper.tearDown();
|
||||
mQosCallbackMockHelper = null;
|
||||
}
|
||||
mMockVpn.disconnect();
|
||||
waitForIdle();
|
||||
|
||||
@@ -4379,7 +4395,7 @@ public class ConnectivityServiceTest {
|
||||
}
|
||||
|
||||
private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception {
|
||||
// Ensure the network is disconnected before we do anything.
|
||||
// Ensure the network is disconnected before anything else occurs
|
||||
if (mWiFiNetworkAgent != null) {
|
||||
assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
|
||||
}
|
||||
@@ -8512,7 +8528,7 @@ public class ConnectivityServiceTest {
|
||||
TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE));
|
||||
return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(),
|
||||
nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, null,
|
||||
0, INVALID_UID);
|
||||
0, INVALID_UID, mQosCallbackTracker);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -8890,7 +8906,7 @@ public class ConnectivityServiceTest {
|
||||
|
||||
@Test
|
||||
public void testInvalidRequestTypes() {
|
||||
final int[] invalidReqTypeInts = new int[] {-1, NetworkRequest.Type.NONE.ordinal(),
|
||||
final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(),
|
||||
NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length};
|
||||
final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
|
||||
|
||||
@@ -8903,4 +8919,151 @@ public class ConnectivityServiceTest {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private class QosCallbackMockHelper {
|
||||
@NonNull public final QosFilter mFilter;
|
||||
@NonNull public final IQosCallback mCallback;
|
||||
@NonNull public final TestNetworkAgentWrapper mAgentWrapper;
|
||||
@NonNull private final List<IQosCallback> mCallbacks = new ArrayList();
|
||||
|
||||
QosCallbackMockHelper() throws Exception {
|
||||
Log.d(TAG, "QosCallbackMockHelper: ");
|
||||
mFilter = mock(QosFilter.class);
|
||||
|
||||
// Ensure the network is disconnected before anything else occurs
|
||||
assertNull(mCellNetworkAgent);
|
||||
|
||||
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
|
||||
mCellNetworkAgent.connect(true);
|
||||
|
||||
verifyActiveNetwork(TRANSPORT_CELLULAR);
|
||||
waitForIdle();
|
||||
final Network network = mCellNetworkAgent.getNetwork();
|
||||
|
||||
final Pair<IQosCallback, IBinder> pair = createQosCallback();
|
||||
mCallback = pair.first;
|
||||
|
||||
when(mFilter.getNetwork()).thenReturn(network);
|
||||
when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
|
||||
mAgentWrapper = mCellNetworkAgent;
|
||||
}
|
||||
|
||||
void registerQosCallback(@NonNull final QosFilter filter,
|
||||
@NonNull final IQosCallback callback) {
|
||||
mCallbacks.add(callback);
|
||||
final NetworkAgentInfo nai =
|
||||
mService.getNetworkAgentInfoForNetwork(filter.getNetwork());
|
||||
mService.registerQosCallbackInternal(filter, callback, nai);
|
||||
}
|
||||
|
||||
void tearDown() {
|
||||
for (int i = 0; i < mCallbacks.size(); i++) {
|
||||
mService.unregisterQosCallback(mCallbacks.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<IQosCallback, IBinder> createQosCallback() {
|
||||
final IQosCallback callback = mock(IQosCallback.class);
|
||||
final IBinder binder = mock(Binder.class);
|
||||
when(callback.asBinder()).thenReturn(binder);
|
||||
when(binder.isBinderAlive()).thenReturn(true);
|
||||
return new Pair<>(callback, binder);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testQosCallbackRegistration() throws Exception {
|
||||
mQosCallbackMockHelper = new QosCallbackMockHelper();
|
||||
final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
|
||||
|
||||
when(mQosCallbackMockHelper.mFilter.validate())
|
||||
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
|
||||
mQosCallbackMockHelper.registerQosCallback(
|
||||
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
|
||||
|
||||
final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
|
||||
(NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
|
||||
wrapper.getCallbackHistory().poll(1000, x -> true);
|
||||
assertNotNull(cbRegister1);
|
||||
|
||||
final int registerCallbackId = cbRegister1.mQosCallbackId;
|
||||
mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
|
||||
final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
|
||||
cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
|
||||
wrapper.getCallbackHistory().poll(1000, x -> true);
|
||||
assertNotNull(cbUnregister);
|
||||
assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
|
||||
assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQosCallbackNoRegistrationOnValidationError() throws Exception {
|
||||
mQosCallbackMockHelper = new QosCallbackMockHelper();
|
||||
|
||||
when(mQosCallbackMockHelper.mFilter.validate())
|
||||
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
|
||||
mQosCallbackMockHelper.registerQosCallback(
|
||||
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
|
||||
waitForIdle();
|
||||
verify(mQosCallbackMockHelper.mCallback)
|
||||
.onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQosCallbackAvailableAndLost() throws Exception {
|
||||
mQosCallbackMockHelper = new QosCallbackMockHelper();
|
||||
final int sessionId = 10;
|
||||
final int qosCallbackId = 1;
|
||||
|
||||
when(mQosCallbackMockHelper.mFilter.validate())
|
||||
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
|
||||
mQosCallbackMockHelper.registerQosCallback(
|
||||
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
|
||||
waitForIdle();
|
||||
|
||||
final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
|
||||
1, 2, 3, 4, 5, new ArrayList<>());
|
||||
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
|
||||
.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
|
||||
waitForIdle();
|
||||
|
||||
verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
|
||||
session.getSessionId() == sessionId
|
||||
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
|
||||
|
||||
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
|
||||
.sendQosSessionLost(qosCallbackId, sessionId);
|
||||
waitForIdle();
|
||||
verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
|
||||
session.getSessionId() == sessionId
|
||||
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQosCallbackTooManyRequests() throws Exception {
|
||||
mQosCallbackMockHelper = new QosCallbackMockHelper();
|
||||
|
||||
when(mQosCallbackMockHelper.mFilter.validate())
|
||||
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final Pair<IQosCallback, IBinder> pair = createQosCallback();
|
||||
|
||||
try {
|
||||
mQosCallbackMockHelper.registerQosCallback(
|
||||
mQosCallbackMockHelper.mFilter, pair.first);
|
||||
} catch (ServiceSpecificException e) {
|
||||
assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS);
|
||||
if (i < 50) {
|
||||
fail("TOO_MANY_REQUESTS thrown too early, the count is " + i);
|
||||
}
|
||||
|
||||
// As long as there is at least 50 requests, it is safe to assume it works.
|
||||
// Note: The count isn't being tested precisely against 100 because the counter
|
||||
// is shared with request network.
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("TOO_MANY_REQUESTS never thrown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ public class LingerMonitorTest {
|
||||
@Mock Context mCtx;
|
||||
@Mock NetworkNotificationManager mNotifier;
|
||||
@Mock Resources mResources;
|
||||
@Mock QosCallbackTracker mQosCallbackTracker;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -358,7 +359,7 @@ public class LingerMonitorTest {
|
||||
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
|
||||
new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */,
|
||||
mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE,
|
||||
Binder.getCallingUid());
|
||||
Binder.getCallingUid(), mQosCallbackTracker);
|
||||
nai.everValidated = true;
|
||||
return nai;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user