Merge changes I0afdda02,I1c47f616 into main
* changes: Implement ConnectivityStateMetrics sample Add base classes for common ConnectivityService tests.
This commit is contained in:
@@ -259,6 +259,19 @@ public final class NetworkCapabilities implements Parcelable {
|
||||
*/
|
||||
private int mEnterpriseId;
|
||||
|
||||
/**
|
||||
* Gets the enterprise IDs as an int. Internal callers only.
|
||||
*
|
||||
* DO NOT USE THIS if not immediately collapsing back into a scalar. Instead,
|
||||
* prefer getEnterpriseIds/hasEnterpriseId.
|
||||
*
|
||||
* @return the internal, version-dependent int representing enterprise ids
|
||||
* @hide
|
||||
*/
|
||||
public int getEnterpriseIdsInternal() {
|
||||
return mEnterpriseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enteprise identifiers set.
|
||||
*
|
||||
@@ -741,8 +754,10 @@ public final class NetworkCapabilities implements Parcelable {
|
||||
|
||||
/**
|
||||
* Capabilities that are managed by ConnectivityService.
|
||||
* @hide
|
||||
*/
|
||||
private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
|
||||
@VisibleForTesting
|
||||
public static final long CONNECTIVITY_MANAGED_CAPABILITIES =
|
||||
BitUtils.packBitList(
|
||||
NET_CAPABILITY_VALIDATED,
|
||||
NET_CAPABILITY_CAPTIVE_PORTAL,
|
||||
@@ -858,6 +873,19 @@ public final class NetworkCapabilities implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the capabilities as an int. Internal callers only.
|
||||
*
|
||||
* DO NOT USE THIS if not immediately collapsing back into a scalar. Instead,
|
||||
* prefer getCapabilities/hasCapability.
|
||||
*
|
||||
* @return an internal, version-dependent int representing the capabilities
|
||||
* @hide
|
||||
*/
|
||||
public long getCapabilitiesInternal() {
|
||||
return mNetworkCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the capabilities set on this {@code NetworkCapability} instance.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.metrics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.StatsManager;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.util.StatsEvent;
|
||||
|
||||
import com.android.modules.utils.HandlerExecutor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A class to register, sample and send connectivity state metrics.
|
||||
*/
|
||||
public class ConnectivitySampleMetricsHelper implements StatsManager.StatsPullAtomCallback {
|
||||
private static final String TAG = ConnectivitySampleMetricsHelper.class.getSimpleName();
|
||||
|
||||
final Supplier<StatsEvent> mDelegate;
|
||||
|
||||
/**
|
||||
* Start collecting metrics.
|
||||
* @param context some context to get services
|
||||
* @param connectivityServiceHandler the connectivity service handler
|
||||
* @param atomTag the tag to collect metrics from
|
||||
* @param delegate a method returning data when called on the handler thread
|
||||
*/
|
||||
// Unfortunately it seems essentially impossible to unit test this method. The only thing
|
||||
// to test is that there is a call to setPullAtomCallback, but StatsManager is final and
|
||||
// can't be mocked without mockito-extended. Using mockito-extended in FrameworksNetTests
|
||||
// would have a very large impact on performance, while splitting the unit test for this
|
||||
// class in a separate target would make testing very hard to manage. Therefore, there
|
||||
// can unfortunately be no unit tests for this method, but at least it is very simple.
|
||||
public static void start(@NonNull final Context context,
|
||||
@NonNull final Handler connectivityServiceHandler,
|
||||
final int atomTag,
|
||||
@NonNull final Supplier<StatsEvent> delegate) {
|
||||
final ConnectivitySampleMetricsHelper metrics =
|
||||
new ConnectivitySampleMetricsHelper(delegate);
|
||||
final StatsManager mgr = context.getSystemService(StatsManager.class);
|
||||
if (null == mgr) return; // No metrics for you
|
||||
mgr.setPullAtomCallback(atomTag, null /* metadata */,
|
||||
new HandlerExecutor(connectivityServiceHandler), metrics);
|
||||
}
|
||||
|
||||
public ConnectivitySampleMetricsHelper(@NonNull final Supplier<StatsEvent> delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onPullAtom(final int atomTag, final List<StatsEvent> data) {
|
||||
Log.d(TAG, "Sampling data for atom : " + atomTag);
|
||||
data.add(mDelegate.get());
|
||||
return StatsManager.PULL_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
|
||||
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
|
||||
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
|
||||
@@ -102,6 +103,7 @@ import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
|
||||
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
|
||||
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
|
||||
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
|
||||
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
|
||||
|
||||
import static java.util.Map.Entry;
|
||||
|
||||
@@ -235,6 +237,9 @@ import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.stats.connectivity.MeteredState;
|
||||
import android.stats.connectivity.RequestType;
|
||||
import android.stats.connectivity.ValidatedState;
|
||||
import android.sysprop.NetworkProperties;
|
||||
import android.system.ErrnoException;
|
||||
import android.telephony.TelephonyManager;
|
||||
@@ -247,6 +252,7 @@ import android.util.Pair;
|
||||
import android.util.Range;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.StatsEvent;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@@ -255,6 +261,16 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.MessageUtils;
|
||||
import com.android.metrics.ConnectionDurationForTransports;
|
||||
import com.android.metrics.ConnectionDurationPerTransports;
|
||||
import com.android.metrics.ConnectivitySampleMetricsHelper;
|
||||
import com.android.metrics.ConnectivityStateSample;
|
||||
import com.android.metrics.NetworkCountForTransports;
|
||||
import com.android.metrics.NetworkCountPerTransports;
|
||||
import com.android.metrics.NetworkDescription;
|
||||
import com.android.metrics.NetworkList;
|
||||
import com.android.metrics.NetworkRequestCount;
|
||||
import com.android.metrics.RequestCountForType;
|
||||
import com.android.modules.utils.BasicShellCommandHandler;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||
@@ -337,6 +353,7 @@ import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@@ -2340,6 +2357,134 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
return out;
|
||||
}
|
||||
|
||||
// Because StatsEvent is not usable in tests (everything inside it is hidden), this
|
||||
// method is used to convert a ConnectivityStateSample into a StatsEvent, so that tests
|
||||
// can call sampleConnectivityState and make the checks on it.
|
||||
@NonNull
|
||||
private StatsEvent sampleConnectivityStateToStatsEvent() {
|
||||
final ConnectivityStateSample sample = sampleConnectivityState();
|
||||
return ConnectivityStatsLog.buildStatsEvent(
|
||||
ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE,
|
||||
sample.getNetworkCountPerTransports().toByteArray(),
|
||||
sample.getConnectionDurationPerTransports().toByteArray(),
|
||||
sample.getNetworkRequestCount().toByteArray(),
|
||||
sample.getNetworks().toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather and return a snapshot of the current connectivity state, to be used as a sample.
|
||||
*
|
||||
* This is used for metrics. These snapshots will be sampled and constitute a base for
|
||||
* statistics about connectivity state of devices.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
public ConnectivityStateSample sampleConnectivityState() {
|
||||
ensureRunningOnConnectivityServiceThread();
|
||||
final ConnectivityStateSample.Builder builder = ConnectivityStateSample.newBuilder();
|
||||
builder.setNetworkCountPerTransports(sampleNetworkCount(mNetworkAgentInfos));
|
||||
builder.setConnectionDurationPerTransports(sampleConnectionDuration(mNetworkAgentInfos));
|
||||
builder.setNetworkRequestCount(sampleNetworkRequestCount(mNetworkRequests.values()));
|
||||
builder.setNetworks(sampleNetworks(mNetworkAgentInfos));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static NetworkCountPerTransports sampleNetworkCount(
|
||||
@NonNull final ArraySet<NetworkAgentInfo> nais) {
|
||||
final SparseIntArray countPerTransports = new SparseIntArray();
|
||||
for (final NetworkAgentInfo nai : nais) {
|
||||
int transports = (int) nai.networkCapabilities.getTransportTypesInternal();
|
||||
countPerTransports.put(transports, 1 + countPerTransports.get(transports, 0));
|
||||
}
|
||||
final NetworkCountPerTransports.Builder builder = NetworkCountPerTransports.newBuilder();
|
||||
for (int i = countPerTransports.size() - 1; i >= 0; --i) {
|
||||
final NetworkCountForTransports.Builder c = NetworkCountForTransports.newBuilder();
|
||||
c.setTransportTypes(countPerTransports.keyAt(i));
|
||||
c.setNetworkCount(countPerTransports.valueAt(i));
|
||||
builder.addNetworkCountForTransports(c);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static ConnectionDurationPerTransports sampleConnectionDuration(
|
||||
@NonNull final ArraySet<NetworkAgentInfo> nais) {
|
||||
final ConnectionDurationPerTransports.Builder builder =
|
||||
ConnectionDurationPerTransports.newBuilder();
|
||||
for (final NetworkAgentInfo nai : nais) {
|
||||
final ConnectionDurationForTransports.Builder c =
|
||||
ConnectionDurationForTransports.newBuilder();
|
||||
c.setTransportTypes((int) nai.networkCapabilities.getTransportTypesInternal());
|
||||
final long durationMillis = SystemClock.elapsedRealtime() - nai.getConnectedTime();
|
||||
final long millisPerSecond = TimeUnit.SECONDS.toMillis(1);
|
||||
// Add millisPerSecond/2 to round up or down to the nearest value
|
||||
c.setDurationSec((int) ((durationMillis + millisPerSecond / 2) / millisPerSecond));
|
||||
builder.addConnectionDurationForTransports(c);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static NetworkRequestCount sampleNetworkRequestCount(
|
||||
@NonNull final Collection<NetworkRequestInfo> nris) {
|
||||
final NetworkRequestCount.Builder builder = NetworkRequestCount.newBuilder();
|
||||
final SparseIntArray countPerType = new SparseIntArray();
|
||||
for (final NetworkRequestInfo nri : nris) {
|
||||
final int type;
|
||||
if (Process.SYSTEM_UID == nri.mAsUid) {
|
||||
// The request is filed "as" the system, so it's the system on its own behalf.
|
||||
type = RequestType.RT_SYSTEM.getNumber();
|
||||
} else if (Process.SYSTEM_UID == nri.mUid) {
|
||||
// The request is filed by the system as some other app, so it's the system on
|
||||
// behalf of an app.
|
||||
type = RequestType.RT_SYSTEM_ON_BEHALF_OF_APP.getNumber();
|
||||
} else {
|
||||
// Not the system, so it's an app requesting on its own behalf.
|
||||
type = RequestType.RT_APP.getNumber();
|
||||
}
|
||||
countPerType.put(type, countPerType.get(type, 0));
|
||||
}
|
||||
for (int i = countPerType.size() - 1; i >= 0; --i) {
|
||||
final RequestCountForType.Builder r = RequestCountForType.newBuilder();
|
||||
r.setRequestType(RequestType.forNumber(countPerType.keyAt(i)));
|
||||
r.setRequestCount(countPerType.valueAt(i));
|
||||
builder.addRequestCountForType(r);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static NetworkList sampleNetworks(@NonNull final ArraySet<NetworkAgentInfo> nais) {
|
||||
final NetworkList.Builder builder = NetworkList.newBuilder();
|
||||
for (final NetworkAgentInfo nai : nais) {
|
||||
final NetworkCapabilities nc = nai.networkCapabilities;
|
||||
final NetworkDescription.Builder d = NetworkDescription.newBuilder();
|
||||
d.setTransportTypes((int) nc.getTransportTypesInternal());
|
||||
final MeteredState meteredState;
|
||||
if (nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
|
||||
meteredState = MeteredState.METERED_TEMPORARILY_UNMETERED;
|
||||
} else if (nc.hasCapability(NET_CAPABILITY_NOT_METERED)) {
|
||||
meteredState = MeteredState.METERED_NO;
|
||||
} else {
|
||||
meteredState = MeteredState.METERED_YES;
|
||||
}
|
||||
d.setMeteredState(meteredState);
|
||||
final ValidatedState validatedState;
|
||||
if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
|
||||
validatedState = ValidatedState.VS_PORTAL;
|
||||
} else if (nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
|
||||
validatedState = ValidatedState.VS_PARTIAL;
|
||||
} else if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
|
||||
validatedState = ValidatedState.VS_VALID;
|
||||
} else {
|
||||
validatedState = ValidatedState.VS_INVALID;
|
||||
}
|
||||
d.setValidatedState(validatedState);
|
||||
d.setScorePolicies(nai.getScore().getPoliciesInternal());
|
||||
d.setCapabilities(nc.getCapabilitiesInternal());
|
||||
d.setEnterpriseId(nc.getEnterpriseIdsInternal());
|
||||
builder.addNetworkDescription(d);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNetworkSupported(int networkType) {
|
||||
enforceAccessPermission();
|
||||
@@ -3453,6 +3598,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
if (mDeps.isAtLeastT()) {
|
||||
mBpfNetMaps.setPullAtomCallback(mContext);
|
||||
}
|
||||
ConnectivitySampleMetricsHelper.start(mContext, mHandler,
|
||||
CONNECTIVITY_STATE_SAMPLE, this::sampleConnectivityStateToStatsEvent);
|
||||
// Wait PermissionMonitor to finish the permission update. Then MultipathPolicyTracker won't
|
||||
// have permission problem. While CV#block() is unbounded in time and can in principle block
|
||||
// forever, this replaces a synchronous call to PermissionMonitor#startMonitoring, which
|
||||
|
||||
@@ -124,7 +124,7 @@ public class FullScore {
|
||||
new Class[]{FullScore.class, NetworkScore.class}, new String[]{"POLICY_"});
|
||||
|
||||
@VisibleForTesting
|
||||
static @NonNull String policyNameOf(final int policy) {
|
||||
public static @NonNull String policyNameOf(final int policy) {
|
||||
final String name = sMessageNames.get(policy);
|
||||
if (name == null) {
|
||||
// Don't throw here because name might be null due to proguard stripping out the
|
||||
@@ -303,6 +303,18 @@ public class FullScore {
|
||||
return new FullScore(mPolicies | (1L << POLICY_IS_VALIDATED), mKeepConnectedReason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the policies as an long. Internal callers only.
|
||||
*
|
||||
* DO NOT USE if not immediately collapsing back into a scalar. Instead, use
|
||||
* {@link #hasPolicy}.
|
||||
* @return the internal, version-dependent int representing the policies.
|
||||
* @hide
|
||||
*/
|
||||
public long getPoliciesInternal() {
|
||||
return mPolicies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this score has a particular policy.
|
||||
*/
|
||||
|
||||
@@ -1105,6 +1105,11 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
||||
* already present.
|
||||
*/
|
||||
public boolean addRequest(NetworkRequest networkRequest) {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on ConnectivityService thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
NetworkRequest existing = mNetworkRequests.get(networkRequest.requestId);
|
||||
if (existing == networkRequest) return false;
|
||||
if (existing != null) {
|
||||
@@ -1123,6 +1128,11 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
||||
* Remove the specified request from this network.
|
||||
*/
|
||||
public void removeRequest(int requestId) {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on ConnectivityService thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
NetworkRequest existing = mNetworkRequests.get(requestId);
|
||||
if (existing == null) return;
|
||||
updateRequestCounts(REMOVE, existing);
|
||||
@@ -1144,6 +1154,11 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
||||
* network.
|
||||
*/
|
||||
public NetworkRequest requestAt(int index) {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on ConnectivityService thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
return mNetworkRequests.valueAt(index);
|
||||
}
|
||||
|
||||
@@ -1174,6 +1189,11 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
||||
* Returns the number of requests of any type currently satisfied by this network.
|
||||
*/
|
||||
public int numNetworkRequests() {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on ConnectivityService thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
return mNetworkRequests.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
private final NetworkCapabilities mNetworkCapabilities;
|
||||
private final HandlerThread mHandlerThread;
|
||||
private final Context mContext;
|
||||
@@ -468,4 +471,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
public boolean isBypassableVpn() {
|
||||
return mNetworkAgentConfig.isBypassableVpn();
|
||||
}
|
||||
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.android.metrics
|
||||
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.CONNECTIVITY_MANAGED_CAPABILITIES
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_IMS
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||
import android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1
|
||||
import android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3
|
||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||
import android.net.NetworkScore
|
||||
import android.net.NetworkScore.POLICY_EXITING
|
||||
import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.stats.connectivity.MeteredState
|
||||
import android.stats.connectivity.ValidatedState
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.net.module.util.BitUtils
|
||||
import com.android.server.CSTest
|
||||
import com.android.server.FromS
|
||||
import com.android.server.connectivity.FullScore
|
||||
import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
private fun <T> Handler.onHandler(f: () -> T): T {
|
||||
val future = CompletableFuture<T>()
|
||||
post { future.complete(f()) }
|
||||
return future.get()
|
||||
}
|
||||
|
||||
private fun flags(vararg flags: Int) = flags.fold(0L) { acc, it -> acc or (1L shl it) }
|
||||
|
||||
private fun Number.toTransportsString() = StringBuilder().also { sb ->
|
||||
BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
|
||||
{ NetworkCapabilities.transportNameOf(it) }, "|") }.toString()
|
||||
|
||||
private fun Number.toCapsString() = StringBuilder().also { sb ->
|
||||
BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
|
||||
{ NetworkCapabilities.capabilityNameOf(it) }, "&") }.toString()
|
||||
|
||||
private fun Number.toPolicyString() = StringBuilder().also {sb ->
|
||||
BitUtils.appendStringRepresentationOfBitMaskToStringBuilder(sb, this.toLong(),
|
||||
{ FullScore.policyNameOf(it) }, "|") }.toString()
|
||||
|
||||
private fun Number.exceptCSManaged() = this.toLong() and CONNECTIVITY_MANAGED_CAPABILITIES.inv()
|
||||
|
||||
private val NetworkCapabilities.meteredState get() = when {
|
||||
hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED) ->
|
||||
MeteredState.METERED_TEMPORARILY_UNMETERED
|
||||
hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ->
|
||||
MeteredState.METERED_NO
|
||||
else ->
|
||||
MeteredState.METERED_YES
|
||||
}
|
||||
|
||||
private val NetworkCapabilities.validatedState get() = when {
|
||||
hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) -> ValidatedState.VS_PORTAL
|
||||
hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY) -> ValidatedState.VS_PARTIAL
|
||||
hasCapability(NET_CAPABILITY_VALIDATED) -> ValidatedState.VS_VALID
|
||||
else -> ValidatedState.VS_INVALID
|
||||
}
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@SmallTest
|
||||
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
|
||||
class ConnectivitySampleMetricsTest : CSTest() {
|
||||
@Test
|
||||
fun testSampleConnectivityState() {
|
||||
val wifi1Caps = NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_WIFI)
|
||||
.addCapability(NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_ROAMING)
|
||||
.build()
|
||||
val wifi1Score = NetworkScore.Builder().setExiting(true).build()
|
||||
val agentWifi1 = Agent(nc = wifi1Caps, score = FromS(wifi1Score)).also { it.connect() }
|
||||
|
||||
val wifi2Caps = NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_WIFI)
|
||||
.addCapability(NET_CAPABILITY_ENTERPRISE)
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_ROAMING)
|
||||
.addEnterpriseId(NET_ENTERPRISE_ID_3)
|
||||
.build()
|
||||
val wifi2Score = NetworkScore.Builder().setTransportPrimary(true).build()
|
||||
val agentWifi2 = Agent(nc = wifi2Caps, score = FromS(wifi2Score)).also { it.connect() }
|
||||
|
||||
val cellCaps = NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_CELLULAR)
|
||||
.addCapability(NET_CAPABILITY_IMS)
|
||||
.addCapability(NET_CAPABILITY_ENTERPRISE)
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_ROAMING)
|
||||
.addEnterpriseId(NET_ENTERPRISE_ID_1)
|
||||
.build()
|
||||
val cellScore = NetworkScore.Builder().build()
|
||||
val agentCell = Agent(nc = cellCaps, score = FromS(cellScore)).also { it.connect() }
|
||||
|
||||
val stats = csHandler.onHandler { service.sampleConnectivityState() }
|
||||
assertEquals(3, stats.networks.networkDescriptionList.size)
|
||||
val foundCell = stats.networks.networkDescriptionList.find {
|
||||
it.transportTypes == (1 shl TRANSPORT_CELLULAR)
|
||||
} ?: fail("Can't find cell network (searching by transport)")
|
||||
val foundWifi1 = stats.networks.networkDescriptionList.find {
|
||||
it.transportTypes == (1 shl TRANSPORT_WIFI) &&
|
||||
0L != (it.capabilities and (1L shl NET_CAPABILITY_NOT_METERED))
|
||||
} ?: fail("Can't find wifi1 (searching by WIFI transport and the NOT_METERED capability)")
|
||||
val foundWifi2 = stats.networks.networkDescriptionList.find {
|
||||
it.transportTypes == (1 shl TRANSPORT_WIFI) &&
|
||||
0L != (it.capabilities and (1L shl NET_CAPABILITY_ENTERPRISE))
|
||||
} ?: fail("Can't find wifi2 (searching by WIFI transport and the ENTERPRISE capability)")
|
||||
|
||||
fun checkNetworkDescription(
|
||||
network: String,
|
||||
found: NetworkDescription,
|
||||
expected: NetworkCapabilities
|
||||
) {
|
||||
assertEquals(expected.transportTypesInternal, found.transportTypes.toLong(),
|
||||
"Transports differ for network $network, " +
|
||||
"expected ${expected.transportTypesInternal.toTransportsString()}, " +
|
||||
"found ${found.transportTypes.toTransportsString()}")
|
||||
val expectedCaps = expected.capabilitiesInternal.exceptCSManaged()
|
||||
val foundCaps = found.capabilities.exceptCSManaged()
|
||||
assertEquals(expectedCaps, foundCaps,
|
||||
"Capabilities differ for network $network, " +
|
||||
"expected ${expectedCaps.toCapsString()}, " +
|
||||
"found ${foundCaps.toCapsString()}")
|
||||
assertEquals(expected.enterpriseIdsInternal, found.enterpriseId,
|
||||
"Enterprise IDs differ for network $network, " +
|
||||
"expected ${expected.enterpriseIdsInternal}," +
|
||||
" found ${found.enterpriseId}")
|
||||
assertEquals(expected.meteredState, found.meteredState,
|
||||
"Metered states differ for network $network, " +
|
||||
"expected ${expected.meteredState}, " +
|
||||
"found ${found.meteredState}")
|
||||
assertEquals(expected.validatedState, found.validatedState,
|
||||
"Validated states differ for network $network, " +
|
||||
"expected ${expected.validatedState}, " +
|
||||
"found ${found.validatedState}")
|
||||
}
|
||||
|
||||
checkNetworkDescription("Cell network", foundCell, cellCaps)
|
||||
checkNetworkDescription("Wifi1", foundWifi1, wifi1Caps)
|
||||
checkNetworkDescription("Wifi2", foundWifi2, wifi2Caps)
|
||||
|
||||
assertEquals(0, foundCell.scorePolicies, "Cell score policies incorrect, expected 0, " +
|
||||
"found ${foundCell.scorePolicies.toPolicyString()}")
|
||||
val expectedWifi1Policies = flags(POLICY_EXITING, POLICY_IS_UNMETERED)
|
||||
assertEquals(expectedWifi1Policies, foundWifi1.scorePolicies,
|
||||
"Wifi1 score policies incorrect, " +
|
||||
"expected ${expectedWifi1Policies.toPolicyString()}, " +
|
||||
"found ${foundWifi1.scorePolicies.toPolicyString()}")
|
||||
val expectedWifi2Policies = flags(POLICY_TRANSPORT_PRIMARY)
|
||||
assertEquals(expectedWifi2Policies, foundWifi2.scorePolicies,
|
||||
"Wifi2 score policies incorrect, " +
|
||||
"expected ${expectedWifi2Policies.toPolicyString()}, " +
|
||||
"found ${foundWifi2.scorePolicies.toPolicyString()}")
|
||||
}
|
||||
}
|
||||
@@ -75,10 +75,7 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR
|
||||
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
|
||||
import static android.net.ConnectivityManager.TYPE_ETHERNET;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
|
||||
import static android.net.ConnectivityManager.TYPE_PROXY;
|
||||
import static android.net.ConnectivityManager.TYPE_VPN;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
|
||||
@@ -492,6 +489,8 @@ import java.util.stream.Collectors;
|
||||
* Build, install and run with:
|
||||
* runtest frameworks-net -c com.android.server.ConnectivityServiceTest
|
||||
*/
|
||||
// TODO : move methods from this test to smaller tests in the 'connectivityservice' directory
|
||||
// to enable faster testing of smaller groups of functionality.
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@SmallTest
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
@@ -756,6 +755,9 @@ public class ConnectivityServiceTest {
|
||||
if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
|
||||
if (Context.ACTIVITY_SERVICE.equals(name)) return mActivityManager;
|
||||
if (Context.TELEPHONY_SUBSCRIPTION_SERVICE.equals(name)) return mSubscriptionManager;
|
||||
// StatsManager is final and can't be mocked, and uses static methods for mostly
|
||||
// everything. The simplest fix is to return null and not have metrics in tests.
|
||||
if (Context.STATS_MANAGER.equals(name)) return null;
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
|
||||
@@ -1016,6 +1018,9 @@ public class ConnectivityServiceTest {
|
||||
}
|
||||
|
||||
private class TestNetworkAgentWrapper extends NetworkAgentWrapper {
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSAgentWrapper and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
private static final int VALIDATION_RESULT_INVALID = 0;
|
||||
|
||||
private static final long DATA_STALL_TIMESTAMP = 10L;
|
||||
@@ -1340,6 +1345,9 @@ public class ConnectivityServiceTest {
|
||||
* operations have been processed and test for them.
|
||||
*/
|
||||
private static class MockNetworkFactory extends NetworkFactory {
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSTest and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
|
||||
|
||||
static class RequestEntry {
|
||||
@@ -1476,6 +1484,10 @@ public class ConnectivityServiceTest {
|
||||
}
|
||||
|
||||
private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork {
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSTest and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
|
||||
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
|
||||
// not inherit from NetworkAgent.
|
||||
private TestNetworkAgentWrapper mMockNetworkAgent;
|
||||
@@ -1852,6 +1864,9 @@ public class ConnectivityServiceTest {
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSTest and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
|
||||
doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
|
||||
doReturn(PRIMARY_USER_INFO).when(mUserManager).getUserInfo(PRIMARY_USER);
|
||||
@@ -1938,6 +1953,9 @@ public class ConnectivityServiceTest {
|
||||
setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
|
||||
setAlwaysOnNetworks(false);
|
||||
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSTest and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
}
|
||||
|
||||
private void initMockedResources() {
|
||||
@@ -1974,6 +1992,9 @@ public class ConnectivityServiceTest {
|
||||
final ConnectivityResources mConnRes;
|
||||
final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>();
|
||||
|
||||
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
|
||||
// please add it in CSTest and use subclasses of CSTest instead of adding more
|
||||
// tools in ConnectivityServiceTest.
|
||||
ConnectivityServiceDependencies(final Context mockResContext) {
|
||||
mConnRes = new ConnectivityResources(mockResContext);
|
||||
}
|
||||
@@ -2582,23 +2603,6 @@ public class ConnectivityServiceTest {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkTypes() {
|
||||
// Ensure that our mocks for the networkAttributes config variable work as expected. If they
|
||||
// don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
|
||||
// will fail. Failing here is much easier to debug.
|
||||
assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
|
||||
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
|
||||
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
|
||||
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
|
||||
assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
|
||||
|
||||
// Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
|
||||
// mocks, this assert exercises the ConnectivityService code path that ensures that
|
||||
// TYPE_ETHERNET is supported if the ethernet service is running.
|
||||
assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkFeature() throws Exception {
|
||||
// Connect the cell agent and wait for the connected broadcast.
|
||||
@@ -18801,4 +18805,7 @@ public class ConnectivityServiceTest {
|
||||
|
||||
verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
|
||||
}
|
||||
|
||||
// Note : adding tests is ConnectivityServiceTest is deprecated, as it is too big for
|
||||
// maintenance. Please consider adding new tests in subclasses of CSTest instead.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
@file:Suppress("DEPRECATION") // This file tests a bunch of deprecated methods : don't warn about it
|
||||
|
||||
package com.android.server
|
||||
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@SmallTest
|
||||
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
class CSBasicMethodsTest : CSTest() {
|
||||
@Test
|
||||
fun testNetworkTypes() {
|
||||
// Ensure that mocks for the networkAttributes config variable work as expected. If they
|
||||
// don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
|
||||
// will fail. Failing here is much easier to debug.
|
||||
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_WIFI))
|
||||
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE))
|
||||
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_MMS))
|
||||
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE_FOTA))
|
||||
assertFalse(cm.isNetworkSupported(ConnectivityManager.TYPE_PROXY))
|
||||
|
||||
// Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
|
||||
// mocks, this assert exercises the ConnectivityService code path that ensures that
|
||||
// TYPE_ETHERNET is supported if the ethernet service is running.
|
||||
assertTrue(cm.isNetworkSupported(ConnectivityManager.TYPE_ETHERNET))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.android.server
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.INetworkMonitor
|
||||
import android.net.INetworkMonitorCallbacks
|
||||
import android.net.LinkProperties
|
||||
import android.net.Network
|
||||
import android.net.NetworkAgent
|
||||
import android.net.NetworkAgentConfig
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkProvider
|
||||
import android.net.NetworkRequest
|
||||
import android.net.NetworkScore
|
||||
import android.net.NetworkTestResultParcelable
|
||||
import android.net.networkstack.NetworkStackClientBase
|
||||
import android.os.HandlerThread
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.testutils.RecorderCallback.CallbackEntry.Available
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.doNothing
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.stubbing.Answer
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
private inline fun <reified T> ArgumentCaptor() = ArgumentCaptor.forClass(T::class.java)
|
||||
|
||||
private val agentCounter = AtomicInteger(1)
|
||||
private fun nextAgentId() = agentCounter.getAndIncrement()
|
||||
|
||||
/**
|
||||
* A wrapper for network agents, for use with CSTest.
|
||||
*
|
||||
* This class knows how to interact with CSTest and has helpful methods to make fake agents
|
||||
* that can be manipulated directly from a test.
|
||||
*/
|
||||
class CSAgentWrapper(
|
||||
val context: Context,
|
||||
csHandlerThread: HandlerThread,
|
||||
networkStack: NetworkStackClientBase,
|
||||
nac: NetworkAgentConfig,
|
||||
val nc: NetworkCapabilities,
|
||||
val lp: LinkProperties,
|
||||
val score: FromS<NetworkScore>,
|
||||
val provider: NetworkProvider?
|
||||
) : TestableNetworkCallback.HasNetwork {
|
||||
private val TAG = "CSAgent${nextAgentId()}"
|
||||
private val VALIDATION_RESULT_INVALID = 0
|
||||
private val VALIDATION_TIMESTAMP = 1234L
|
||||
private val agent: NetworkAgent
|
||||
private val nmCallbacks: INetworkMonitorCallbacks
|
||||
val networkMonitor = mock<INetworkMonitor>()
|
||||
|
||||
override val network: Network get() = agent.network!!
|
||||
|
||||
init {
|
||||
// Capture network monitor callbacks and simulate network monitor
|
||||
val validateAnswer = Answer {
|
||||
CSTest.CSTestExecutor.execute { onValidationRequested() }
|
||||
null
|
||||
}
|
||||
doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnected(any(), any())
|
||||
doAnswer(validateAnswer).`when`(networkMonitor).notifyNetworkConnectedParcel(any())
|
||||
doAnswer(validateAnswer).`when`(networkMonitor).forceReevaluation(anyInt())
|
||||
val nmNetworkCaptor = ArgumentCaptor<Network>()
|
||||
val nmCbCaptor = ArgumentCaptor<INetworkMonitorCallbacks>()
|
||||
doNothing().`when`(networkStack).makeNetworkMonitor(
|
||||
nmNetworkCaptor.capture(),
|
||||
any() /* name */,
|
||||
nmCbCaptor.capture())
|
||||
|
||||
// Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass.
|
||||
if (SdkLevel.isAtLeastS()) {
|
||||
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
|
||||
nc, lp, score.value, nac, provider) {}
|
||||
} else {
|
||||
agent = object : NetworkAgent(context, csHandlerThread.looper, TAG,
|
||||
nc, lp, 50 /* score */, nac, provider) {}
|
||||
}
|
||||
agent.register()
|
||||
assertEquals(agent.network!!.netId, nmNetworkCaptor.value.netId)
|
||||
nmCallbacks = nmCbCaptor.value
|
||||
nmCallbacks.onNetworkMonitorCreated(networkMonitor)
|
||||
}
|
||||
|
||||
private fun onValidationRequested() {
|
||||
if (SdkLevel.isAtLeastT()) {
|
||||
verify(networkMonitor).notifyNetworkConnectedParcel(any())
|
||||
} else {
|
||||
verify(networkMonitor).notifyNetworkConnected(any(), any())
|
||||
}
|
||||
nmCallbacks.notifyProbeStatusChanged(0 /* completed */, 0 /* succeeded */)
|
||||
val p = NetworkTestResultParcelable()
|
||||
p.result = VALIDATION_RESULT_INVALID
|
||||
p.probesAttempted = 0
|
||||
p.probesSucceeded = 0
|
||||
p.redirectUrl = null
|
||||
p.timestampMillis = VALIDATION_TIMESTAMP
|
||||
nmCallbacks.notifyNetworkTestedWithExtras(p)
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
val mgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val request = NetworkRequest.Builder().clearCapabilities()
|
||||
.addTransportType(nc.transportTypes[0])
|
||||
.build()
|
||||
val cb = TestableNetworkCallback()
|
||||
mgr.registerNetworkCallback(request, cb)
|
||||
agent.markConnected()
|
||||
if (null == cb.poll { it is Available && agent.network == it.network }) {
|
||||
if (!nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) &&
|
||||
nc.hasTransport(TRANSPORT_CELLULAR)) {
|
||||
// ConnectivityService adds NOT_SUSPENDED by default to all non-cell agents. An
|
||||
// agent without NOT_SUSPENDED will not connect, instead going into the SUSPENDED
|
||||
// state, so this call will not terminate.
|
||||
// Instead of forcefully adding NOT_SUSPENDED to all agents like older tools did,
|
||||
// it's better to let the developer manage it as they see fit but help them
|
||||
// debug if they forget.
|
||||
fail("Could not connect the agent. Did you forget to add " +
|
||||
"NET_CAPABILITY_NOT_SUSPENDED ?")
|
||||
}
|
||||
fail("Could not connect the agent. Instrumentation failure ?")
|
||||
}
|
||||
mgr.unregisterNetworkCallback(cb)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.android.server
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.content.pm.UserInfo
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.INetd
|
||||
import android.net.InetAddresses
|
||||
import android.net.IpPrefix
|
||||
import android.net.LinkAddress
|
||||
import android.net.LinkProperties
|
||||
import android.net.NetworkAgentConfig
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
|
||||
import android.net.NetworkPolicyManager
|
||||
import android.net.NetworkProvider
|
||||
import android.net.NetworkScore
|
||||
import android.net.PacProxyManager
|
||||
import android.net.RouteInfo
|
||||
import android.net.networkstack.NetworkStackClientBase
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.testing.TestableContext
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.internal.util.test.BroadcastInterceptingContext
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
|
||||
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
|
||||
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
|
||||
import com.android.server.connectivity.ClatCoordinator
|
||||
import com.android.server.connectivity.ConnectivityFlags
|
||||
import com.android.server.connectivity.MultinetworkPolicyTracker
|
||||
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
|
||||
import com.android.server.connectivity.ProxyTracker
|
||||
import com.android.testutils.waitForIdle
|
||||
import org.mockito.AdditionalAnswers.delegatesTo
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.test.fail
|
||||
|
||||
internal const val HANDLER_TIMEOUT_MS = 2_000
|
||||
internal const val TEST_PACKAGE_NAME = "com.android.test.package"
|
||||
internal const val WIFI_WOL_IFNAME = "test_wlan_wol"
|
||||
internal val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
|
||||
|
||||
open class FromS<Type>(val value: Type)
|
||||
|
||||
/**
|
||||
* Base class for tests testing ConnectivityService and its satellites.
|
||||
*
|
||||
* This class sets up a ConnectivityService running locally in the test.
|
||||
*/
|
||||
// TODO (b/272685721) : make ConnectivityServiceTest smaller and faster by moving the setup
|
||||
// parts into this class and moving the individual tests to multiple separate classes.
|
||||
open class CSTest {
|
||||
companion object {
|
||||
val CSTestExecutor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
init {
|
||||
if (!SdkLevel.isAtLeastS()) {
|
||||
throw UnsupportedApiLevelException("CSTest subclasses must be annotated to only " +
|
||||
"run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)");
|
||||
}
|
||||
}
|
||||
|
||||
val instrumentationContext =
|
||||
TestableContext(InstrumentationRegistry.getInstrumentation().context)
|
||||
val context = CSContext(instrumentationContext)
|
||||
|
||||
// See constructor for default-enabled features. All queried features must be either enabled
|
||||
// or disabled, because the test can't hold READ_DEVICE_CONFIG and device config utils query
|
||||
// permissions using static contexts.
|
||||
val enabledFeatures = HashMap<String, Boolean>().also {
|
||||
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
|
||||
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
|
||||
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
|
||||
}
|
||||
fun enableFeature(f: String) = enabledFeatures.set(f, true)
|
||||
fun disableFeature(f: String) = enabledFeatures.set(f, false)
|
||||
|
||||
// When adding new members, consider if it's not better to build the object in CSTestHelpers
|
||||
// to keep this file clean of implementation details. Generally, CSTestHelpers should only
|
||||
// need changes when new details of instrumentation are needed.
|
||||
val contentResolver = makeMockContentResolver(context)
|
||||
|
||||
val PRIMARY_USER = 0
|
||||
val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
|
||||
val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
|
||||
val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
|
||||
val activityManager = makeActivityManager()
|
||||
|
||||
val networkStack = mock<NetworkStackClientBase>()
|
||||
val csHandlerThread = HandlerThread("CSTestHandler")
|
||||
val sysResources = mock<Resources>().also { initMockedResources(it) }
|
||||
val packageManager = makeMockPackageManager()
|
||||
val connResources = makeMockConnResources(sysResources, packageManager)
|
||||
|
||||
val bpfNetMaps = mock<BpfNetMaps>()
|
||||
val clatCoordinator = mock<ClatCoordinator>()
|
||||
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
|
||||
val alarmManager = makeMockAlarmManager()
|
||||
val systemConfigManager = makeMockSystemConfigManager()
|
||||
val telephonyManager = mock<TelephonyManager>().also {
|
||||
doReturn(true).`when`(it).isDataCapable()
|
||||
}
|
||||
|
||||
val deps = CSDeps()
|
||||
val service = makeConnectivityService(context, deps).also { it.systemReadyInternal() }
|
||||
val cm = ConnectivityManager(context, service)
|
||||
val csHandler = Handler(csHandlerThread.looper)
|
||||
|
||||
inner class CSDeps : ConnectivityService.Dependencies() {
|
||||
override fun getResources(ctx: Context) = connResources
|
||||
override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
|
||||
override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
|
||||
override fun getNetworkStack() = this@CSTest.networkStack
|
||||
|
||||
override fun makeHandlerThread() = csHandlerThread
|
||||
override fun makeProxyTracker(context: Context, connServiceHandler: Handler) = proxyTracker
|
||||
|
||||
override fun makeCarrierPrivilegeAuthenticator(context: Context, tm: TelephonyManager) =
|
||||
if (SdkLevel.isAtLeastT()) mock<CarrierPrivilegeAuthenticator>() else null
|
||||
|
||||
private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
|
||||
override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
|
||||
return isFeatureEnabled(context, name)
|
||||
}
|
||||
}
|
||||
override fun makeAutomaticOnOffKeepaliveTracker(c: Context, h: Handler) =
|
||||
AutomaticOnOffKeepaliveTracker(c, h, AOOKTDeps(c))
|
||||
|
||||
override fun makeMultinetworkPolicyTracker(c: Context, h: Handler, r: Runnable) =
|
||||
MultinetworkPolicyTracker(c, h, r,
|
||||
MultinetworkPolicyTrackerTestDependencies(connResources.get()))
|
||||
|
||||
// All queried features must be mocked, because the test cannot hold the
|
||||
// READ_DEVICE_CONFIG permission and device config utils use static methods for
|
||||
// checking permissions.
|
||||
override fun isFeatureEnabled(context: Context?, name: String?) =
|
||||
enabledFeatures[name] ?: fail("Unmocked feature $name, see CSTest.enabledFeatures")
|
||||
}
|
||||
|
||||
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
|
||||
val pacProxyManager = mock<PacProxyManager>()
|
||||
val networkPolicyManager = mock<NetworkPolicyManager>()
|
||||
|
||||
override fun getPackageManager() = this@CSTest.packageManager
|
||||
override fun getContentResolver() = this@CSTest.contentResolver
|
||||
|
||||
// TODO : buff up the capabilities of this permission scheme to allow checking for
|
||||
// permission rejections
|
||||
override fun checkPermission(permission: String, pid: Int, uid: Int) = PERMISSION_GRANTED
|
||||
override fun checkCallingOrSelfPermission(permission: String) = PERMISSION_GRANTED
|
||||
|
||||
// Necessary for MultinetworkPolicyTracker, which tries to register a receiver for
|
||||
// all users. The test can't do that since it doesn't hold INTERACT_ACROSS_USERS.
|
||||
// TODO : ensure MultinetworkPolicyTracker's BroadcastReceiver is tested ; ideally,
|
||||
// just returning null should not have tests pass
|
||||
override fun registerReceiverForAllUsers(
|
||||
receiver: BroadcastReceiver?,
|
||||
filter: IntentFilter,
|
||||
broadcastPermission: String?,
|
||||
scheduler: Handler?
|
||||
): Intent? = null
|
||||
|
||||
// Create and cache user managers on the fly as necessary.
|
||||
val userManagers = HashMap<UserHandle, UserManager>()
|
||||
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
||||
val asUser = mock(Context::class.java, delegatesTo<Any>(this))
|
||||
doReturn(user).`when`(asUser).getUser()
|
||||
doAnswer { userManagers.computeIfAbsent(user) {
|
||||
mock(UserManager::class.java, delegatesTo<Any>(userManager)) }
|
||||
}.`when`(asUser).getSystemService(Context.USER_SERVICE)
|
||||
return asUser
|
||||
}
|
||||
|
||||
// List of mocked services. Add additional services here or in subclasses.
|
||||
override fun getSystemService(serviceName: String) = when (serviceName) {
|
||||
Context.CONNECTIVITY_SERVICE -> cm
|
||||
Context.PAC_PROXY_SERVICE -> pacProxyManager
|
||||
Context.NETWORK_POLICY_SERVICE -> networkPolicyManager
|
||||
Context.ALARM_SERVICE -> alarmManager
|
||||
Context.USER_SERVICE -> userManager
|
||||
Context.ACTIVITY_SERVICE -> activityManager
|
||||
Context.SYSTEM_CONFIG_SERVICE -> systemConfigManager
|
||||
Context.TELEPHONY_SERVICE -> telephonyManager
|
||||
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
|
||||
else -> super.getSystemService(serviceName)
|
||||
}
|
||||
}
|
||||
|
||||
// Utility methods for subclasses to use
|
||||
fun waitForIdle() = csHandlerThread.waitForIdle(HANDLER_TIMEOUT_MS)
|
||||
|
||||
private fun emptyAgentConfig() = NetworkAgentConfig.Builder().build()
|
||||
private fun defaultNc() = NetworkCapabilities.Builder()
|
||||
// Add sensible defaults for agents that don't want to care
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_ROAMING)
|
||||
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.build()
|
||||
private fun defaultScore() = FromS<NetworkScore>(NetworkScore.Builder().build())
|
||||
private fun defaultLp() = LinkProperties().apply {
|
||||
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
|
||||
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
|
||||
}
|
||||
|
||||
// Network agents. See CSAgentWrapper. This class contains utility methods to simplify
|
||||
// creation.
|
||||
fun Agent(
|
||||
nac: NetworkAgentConfig = emptyAgentConfig(),
|
||||
nc: NetworkCapabilities = defaultNc(),
|
||||
lp: LinkProperties = defaultLp(),
|
||||
score: FromS<NetworkScore> = defaultScore(),
|
||||
provider: NetworkProvider? = null
|
||||
) = CSAgentWrapper(context, csHandlerThread, networkStack, nac, nc, lp, score, provider)
|
||||
|
||||
fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
|
||||
val nc = NetworkCapabilities.Builder().apply {
|
||||
transports.forEach {
|
||||
addTransportType(it)
|
||||
}
|
||||
}.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.build()
|
||||
return Agent(nc = nc, lp = lp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
@file:JvmName("CsTestHelpers")
|
||||
|
||||
package com.android.server
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.AlarmManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.FEATURE_BLUETOOTH
|
||||
import android.content.pm.PackageManager.FEATURE_ETHERNET
|
||||
import android.content.pm.PackageManager.FEATURE_WIFI
|
||||
import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
|
||||
import android.content.pm.UserInfo
|
||||
import android.content.res.Resources
|
||||
import android.net.IDnsResolver
|
||||
import android.net.INetd
|
||||
import android.net.metrics.IpConnectivityLog
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.SystemClock
|
||||
import android.os.SystemConfigManager
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.provider.Settings
|
||||
import android.test.mock.MockContentResolver
|
||||
import com.android.connectivity.resources.R
|
||||
import com.android.internal.util.WakeupMessage
|
||||
import com.android.internal.util.test.FakeSettingsProvider
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.server.ConnectivityService.Dependencies
|
||||
import com.android.server.connectivity.ConnectivityResources
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyLong
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.ArgumentMatchers.argThat
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.doNothing
|
||||
import kotlin.test.fail
|
||||
|
||||
internal inline fun <reified T> mock() = Mockito.mock(T::class.java)
|
||||
internal inline fun <reified T> any() = any(T::class.java)
|
||||
|
||||
internal fun makeMockContentResolver(context: Context) = MockContentResolver(context).apply {
|
||||
addProvider(Settings.AUTHORITY, FakeSettingsProvider())
|
||||
}
|
||||
|
||||
internal fun makeMockUserManager(info: UserInfo, handle: UserHandle) = mock<UserManager>().also {
|
||||
doReturn(listOf(info)).`when`(it).getAliveUsers()
|
||||
doReturn(listOf(handle)).`when`(it).getUserHandles(ArgumentMatchers.anyBoolean())
|
||||
}
|
||||
|
||||
internal fun makeActivityManager() = mock<ActivityManager>().also {
|
||||
if (SdkLevel.isAtLeastU()) {
|
||||
doNothing().`when`(it).registerUidFrozenStateChangedCallback(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun makeMockPackageManager() = mock<PackageManager>().also { pm ->
|
||||
val supported = listOf(FEATURE_WIFI, FEATURE_WIFI_DIRECT, FEATURE_BLUETOOTH, FEATURE_ETHERNET)
|
||||
doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
|
||||
}
|
||||
|
||||
internal fun makeMockConnResources(resources: Resources, pm: PackageManager) = mock<Context>().let {
|
||||
doReturn(resources).`when`(it).resources
|
||||
doReturn(pm).`when`(it).packageManager
|
||||
ConnectivityResources.setResourcesContextForTest(it)
|
||||
ConnectivityResources(it)
|
||||
}
|
||||
|
||||
private val UNREASONABLY_LONG_ALARM_WAIT_MS = 1000
|
||||
internal fun makeMockAlarmManager() = mock<AlarmManager>().also { am ->
|
||||
val alrmHdlr = HandlerThread("TestAlarmManager").also { it.start() }.threadHandler
|
||||
doAnswer {
|
||||
val (_, date, _, wakeupMsg, handler) = it.arguments
|
||||
wakeupMsg as WakeupMessage
|
||||
handler as Handler
|
||||
val delayMs = ((date as Long) - SystemClock.elapsedRealtime()).coerceAtLeast(0)
|
||||
if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) {
|
||||
fail("Attempting to send msg more than $UNREASONABLY_LONG_ALARM_WAIT_MS" +
|
||||
"ms into the future : $delayMs")
|
||||
}
|
||||
alrmHdlr.postDelayed({ handler.post(wakeupMsg::onAlarm) }, wakeupMsg, delayMs)
|
||||
}.`when`(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(),
|
||||
any<WakeupMessage>(), any())
|
||||
doAnswer {
|
||||
alrmHdlr.removeCallbacksAndMessages(it.getArgument<WakeupMessage>(0))
|
||||
}.`when`(am).cancel(any<WakeupMessage>())
|
||||
}
|
||||
|
||||
internal fun makeMockSystemConfigManager() = mock<SystemConfigManager>().also {
|
||||
doReturn(intArrayOf(0)).`when`(it).getSystemPermissionUids(anyString())
|
||||
}
|
||||
|
||||
// Mocking resources used by ConnectivityService. Note these can't be defined to return the
|
||||
// value returned by the mocking, because a non-null method would mean the helper would also
|
||||
// return non-null and the compiler would check that, but mockito has no qualms returning null
|
||||
// from a @NonNull method when stubbing. Hence, mock() = doReturn().getString() would crash
|
||||
// at runtime, because getString() returns non-null String, therefore mock returns non-null String,
|
||||
// and kotlinc adds an intrinsics check for that, which crashes at runtime when mockito actually
|
||||
// returns null.
|
||||
private fun Resources.mock(r: Int, v: Boolean) { doReturn(v).`when`(this).getBoolean(r) }
|
||||
private fun Resources.mock(r: Int, v: Int) { doReturn(v).`when`(this).getInteger(r) }
|
||||
private fun Resources.mock(r: Int, v: String) { doReturn(v).`when`(this).getString(r) }
|
||||
private fun Resources.mock(r: Int, v: Array<String?>) { doReturn(v).`when`(this).getStringArray(r) }
|
||||
private fun Resources.mock(r: Int, v: IntArray) { doReturn(v).`when`(this).getIntArray(r) }
|
||||
|
||||
internal fun initMockedResources(res: Resources) {
|
||||
// Resources accessed through reflection need to return the id
|
||||
doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(res)
|
||||
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
|
||||
doReturn(R.array.network_switch_type_name).`when`(res)
|
||||
.getIdentifier(eq("network_switch_type_name"), eq("array"), any())
|
||||
// Mock the values themselves
|
||||
res.mock(R.integer.config_networkTransitionTimeout, 60_000)
|
||||
res.mock(R.string.config_networkCaptivePortalServerUrl, "")
|
||||
res.mock(R.array.config_wakeonlan_supported_interfaces, arrayOf(WIFI_WOL_IFNAME))
|
||||
res.mock(R.array.config_networkSupportedKeepaliveCount, arrayOf("0,1", "1,3"))
|
||||
res.mock(R.array.config_networkNotifySwitches, arrayOfNulls<String>(size = 0))
|
||||
res.mock(R.array.config_protectedNetworks, intArrayOf(10, 11, 12, 14, 15))
|
||||
res.mock(R.array.network_switch_type_name, arrayOfNulls<String>(size = 0))
|
||||
res.mock(R.integer.config_networkAvoidBadWifi, 1)
|
||||
res.mock(R.integer.config_activelyPreferBadWifi, 0)
|
||||
res.mock(R.bool.config_cellular_radio_timesharing_capable, true)
|
||||
}
|
||||
|
||||
private val TEST_LINGER_DELAY_MS = 400
|
||||
private val TEST_NASCENT_DELAY_MS = 300
|
||||
internal fun makeConnectivityService(context: Context, deps: Dependencies) = ConnectivityService(
|
||||
context,
|
||||
mock<IDnsResolver>(),
|
||||
mock<IpConnectivityLog>(),
|
||||
mock<INetd>(),
|
||||
deps).also {
|
||||
it.mLingerDelayMs = TEST_LINGER_DELAY_MS
|
||||
it.mNascentDelayMs = TEST_NASCENT_DELAY_MS
|
||||
}
|
||||
Reference in New Issue
Block a user