diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 8e219a687b2ff98e02ee503dd31fef2d05888caf..abda1fa71f1f180544534c3456b48df33544398a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -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. * diff --git a/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java b/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..93d1d5def1c7b773a3214474b17aff3f17c5108f --- /dev/null +++ b/service/src/com/android/metrics/ConnectivitySampleMetricsHelper.java @@ -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; + } +} diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 6770a8fe8c0464e459633e6739c7261e6fba166c..85507f6d16bf9244b6fa9d6764adbda2587c76c2 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -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 diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java index 87ae0c9e79e90769d1e4d5ead90ab0fbad9bf301..648f3bf250822ab48044d6f836d748daf1947975 100644 --- a/service/src/com/android/server/connectivity/FullScore.java +++ b/service/src/com/android/server/connectivity/FullScore.java @@ -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. */ diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index 845c04cec3eda0d4647427126ba7324a0ea7016b..bdd841f9ed6ba0fe36589c349ce04b141a2dcb42 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -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(); } diff --git a/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..3043d50cfadc2e5694839ce41f9b1815419eb764 --- /dev/null +++ b/tests/unit/java/com/android/metrics/ConnectivitySampleMetricsTest.kt @@ -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()}") + } +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 3243033d672345a871c68d3d1d4523652b0341ab..2fccdcb9a4254f5ea268ae1b5dd9ff2580d84924 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -755,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); }