Merge changes I0afdda02,I1c47f616 into main

* changes:
  Implement ConnectivityStateMetrics sample
  Add base classes for common ConnectivityService tests.
This commit is contained in:
Jean Chalard
2023-09-11 11:32:06 +00:00
committed by Gerrit Code Review
12 changed files with 1038 additions and 22 deletions

View File

@@ -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.
*

View File

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

View File

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

View File

@@ -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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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