Report satellite coarse usage
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.metrics.SatelliteCoarseUsageMetricsCollectorTest \
--update-device
Test: atest ConnectivityCoverageTests --test-filter 'CSTest*' --update-device
Test: atest ConnectivityCoverageTests --test-filter 'ConnectivityService*' --update-device
Test: Manual:
1. Change code to use TRANSPORT_CELLULAR
2. m statsd_testdrive && statsd_testdrive -e 1102
3. Turn off mobile data.
Flag: EXEMPT controlled with kill switch
Bug: 419123367
Change-Id: I264efa6802612809fdbff22901f31447f370bbe5
This commit is contained in:
parent
f17adec833
commit
bb589cf33c
6 changed files with 515 additions and 0 deletions
|
|
@ -164,6 +164,7 @@ java_library {
|
|||
"framework-bluetooth.stubs.module_lib",
|
||||
"framework-configinfrastructure.stubs.module_lib",
|
||||
"framework-connectivity-pre-jarjar",
|
||||
"framework-connectivity-t-pre-jarjar",
|
||||
// The framework-connectivity-t library is only available on T+ platforms
|
||||
// so any calls to it must be protected with a check to ensure that it is
|
||||
// available. The linter will detect any unprotected calls through an API
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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 static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;
|
||||
import static android.net.NetworkTemplate.MATCH_MOBILE;
|
||||
|
||||
import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED;
|
||||
import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED__EVENT_TYPE__CRITICAL_BYTES_EVENT_TYPE_SATELLITE_COARSE_RX_USAGE;
|
||||
import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED__EVENT_TYPE__CRITICAL_BYTES_EVENT_TYPE_SATELLITE_COARSE_TX_USAGE;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.usage.NetworkStats;
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.modules.utils.BackgroundThread;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.FrameworkConnectivityStatsLog;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
|
||||
/**
|
||||
* Monitors satellite network connections to collect overall usage during active
|
||||
* satellite sessions. It uses NetworkStatsManager to query snapshots upon
|
||||
* connection and disconnection, then logs the difference.
|
||||
*
|
||||
* This class does not provide thread safety and should be only accessed on the same thread.
|
||||
*/
|
||||
public class SatelliteCoarseUsageMetricsCollector {
|
||||
private static final String TAG = SatelliteCoarseUsageMetricsCollector.class.getSimpleName();
|
||||
private static final NetworkTemplate SATELLITE_TEMPLATE;
|
||||
|
||||
static {
|
||||
// To prevent NetworkTemplate#Builder not found error when
|
||||
// initializing CSTest on S-based image.
|
||||
if (SdkLevel.isAtLeastU()) {
|
||||
SATELLITE_TEMPLATE = new NetworkTemplate.Builder(MATCH_MOBILE)
|
||||
.setTransportTypes(new int[]{TRANSPORT_SATELLITE}).build();
|
||||
} else {
|
||||
SATELLITE_TEMPLATE = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The max bucket duration used when storing usage in NetworkStatsService.
|
||||
* See NetworkStatsService#DefaultNetworkStatsSettings#getUidConfig.
|
||||
*/
|
||||
private static final long MAX_NETSTATS_BUCKET_DURATION_MS = HOURS.toMillis(2);
|
||||
|
||||
private ConnectivityManager mCm;
|
||||
private NetworkStatsManager mNsm;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final SatelliteNetworkCallback mNetworkCallback = new SatelliteNetworkCallback();
|
||||
|
||||
// Null when no satellite network is connected.
|
||||
@Nullable
|
||||
private MyStatsEntry mSatelliteBaseline = null;
|
||||
|
||||
private final MyStatsEntry mReportedUsage = new MyStatsEntry();
|
||||
private final Dependencies mDeps;
|
||||
|
||||
// The start timestamp used to query the snapshots of data usage.
|
||||
final long mStartTime;
|
||||
|
||||
/**
|
||||
* Constructs a helper to monitor satellite network usage.
|
||||
*
|
||||
* @param context The context.
|
||||
*/
|
||||
public SatelliteCoarseUsageMetricsCollector(@NonNull Context context) {
|
||||
this(context, new Dependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a helper to monitor satellite network usage.
|
||||
*
|
||||
* @param context The context.
|
||||
*/
|
||||
public SatelliteCoarseUsageMetricsCollector(@NonNull Context context,
|
||||
@NonNull Dependencies deps) {
|
||||
mContext = context;
|
||||
mDeps = deps;
|
||||
mHandler = mDeps.getBackgroundThreadHandler();
|
||||
mStartTime = getStartTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for test injection.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Dependencies {
|
||||
/**
|
||||
* Reports the given network usage entry to system metrics.
|
||||
*/
|
||||
public void reportUsage(MyStatsEntry entry) {
|
||||
FrameworkConnectivityStatsLog.write_non_chained(CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED,
|
||||
Process.SYSTEM_UID,
|
||||
TAG,
|
||||
CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED__EVENT_TYPE__CRITICAL_BYTES_EVENT_TYPE_SATELLITE_COARSE_RX_USAGE,
|
||||
entry.rxBytes);
|
||||
FrameworkConnectivityStatsLog.write_non_chained(CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED,
|
||||
Process.SYSTEM_UID,
|
||||
TAG,
|
||||
CORE_NETWORKING_CRITICAL_BYTES_EVENT_OCCURRED__EVENT_TYPE__CRITICAL_BYTES_EVENT_TYPE_SATELLITE_COARSE_TX_USAGE,
|
||||
entry.txBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the network stats summary for a given network template.
|
||||
*/
|
||||
@NonNull
|
||||
public MyStatsEntry getSummary(@NonNull NetworkStatsManager nsm, long startTime) {
|
||||
final MyStatsEntry ret = new MyStatsEntry();
|
||||
final NetworkStats stats = nsm.querySummary(SATELLITE_TEMPLATE,
|
||||
startTime, Long.MAX_VALUE);
|
||||
// This null check simplifies testing by avoiding the need to mock
|
||||
// the complex NetworkStats object. In production, this object
|
||||
// is not expected to be null. See ConnectivityServiceIntegrationTest
|
||||
// for context.
|
||||
if (stats == null) {
|
||||
return ret;
|
||||
}
|
||||
final NetworkStats.Bucket recycle = new NetworkStats.Bucket();
|
||||
while (stats.hasNextBucket()) {
|
||||
stats.getNextBucket(recycle);
|
||||
ret.add(new MyStatsEntry(recycle));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Get the background thread handler */
|
||||
@NonNull
|
||||
public Handler getBackgroundThreadHandler() {
|
||||
return BackgroundThread.getHandler();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static class MyStatsEntry {
|
||||
public long rxBytes;
|
||||
public long txBytes;
|
||||
|
||||
public MyStatsEntry() {
|
||||
this.rxBytes = 0;
|
||||
this.txBytes = 0;
|
||||
}
|
||||
|
||||
public MyStatsEntry(@NonNull NetworkStats.Bucket bucket) {
|
||||
this.rxBytes = bucket.getRxBytes();
|
||||
this.txBytes = bucket.getTxBytes();
|
||||
}
|
||||
|
||||
public MyStatsEntry(long rxBytes, long txBytes) {
|
||||
this.rxBytes = rxBytes;
|
||||
this.txBytes = txBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the stats from another {@link MyStatsEntry} to this entry.
|
||||
*
|
||||
* This method modifies the current object.
|
||||
*/
|
||||
public void add(@NonNull MyStatsEntry other) {
|
||||
this.rxBytes += other.rxBytes;
|
||||
this.txBytes += other.txBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts the stats from another {@link MyStatsEntry} to this entry.
|
||||
*
|
||||
* This method modifies the current object.
|
||||
*/
|
||||
public void subtract(@NonNull MyStatsEntry other) {
|
||||
this.rxBytes -= other.rxBytes;
|
||||
this.txBytes -= other.txBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the stats from another {@link MyStatsEntry} from this entry.
|
||||
*
|
||||
* This method does not modify the current object but returns a new one.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
@NonNull
|
||||
public MyStatsEntry plus(@NonNull MyStatsEntry other) {
|
||||
return new MyStatsEntry(rxBytes + other.rxBytes, txBytes + other.txBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts the stats from another {@link MyStatsEntry} from this entry.
|
||||
*
|
||||
* This method does not modify the current object but returns a new one.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
@NonNull
|
||||
public MyStatsEntry minus(@NonNull MyStatsEntry other) {
|
||||
return new MyStatsEntry(rxBytes - other.rxBytes, txBytes - other.txBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{rxBytes=" + rxBytes + ", txBytes=" + txBytes + "}";
|
||||
}
|
||||
}
|
||||
|
||||
private class SatelliteNetworkCallback extends ConnectivityManager.NetworkCallback {
|
||||
/**
|
||||
* Called when a satellite network is lost. Collects and reports the satellite coarse usage
|
||||
* difference.
|
||||
*
|
||||
* Note that multiple satellite networks could occur, for example, when
|
||||
* transitioning from one subscription to another. The amount of usage might
|
||||
* be slightly inaccurate in such cases.
|
||||
*
|
||||
* @param network The Network object that was just lost.
|
||||
*/
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
final MyStatsEntry snapshot = mDeps.getSummary(mNsm, mStartTime);
|
||||
// Report diff with metrics.
|
||||
final MyStatsEntry diff = snapshot.minus(mSatelliteBaseline);
|
||||
mDeps.reportUsage(diff);
|
||||
mReportedUsage.add(diff);
|
||||
mSatelliteBaseline = snapshot;
|
||||
Log.d(TAG, "onLost: Last satellite network " + network + ", Reported usage: " + diff);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the start timestamp used for querying data usage snapshots.
|
||||
// This timestamp needs to be early enough to ensure the query window
|
||||
// starts at or before the beginning of the usage bucket in which the
|
||||
// device boot event occurred. This is achieved by setting the start time
|
||||
// to one bucket duration prior to the calculated bootTimeMillis.
|
||||
//
|
||||
// It is acceptable if this timestamp is not perfectly aligned with exact
|
||||
// bucket boundaries or results in covering some redundant data from before
|
||||
// the boot event. Any such duplicated or irrelevant data will be effectively
|
||||
// subtracted out when calculating usage from the differences of two snapshots,
|
||||
// provided both snapshots are queried using this same consistent start timestamp.
|
||||
private long getStartTimestamp() {
|
||||
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
|
||||
final long currentTimeMillis = System.currentTimeMillis();
|
||||
final long bootTimeMillis = currentTimeMillis - elapsedMillisSinceBoot;
|
||||
return bootTimeMillis - MAX_NETSTATS_BUCKET_DURATION_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts monitoring for satellite network connections.
|
||||
*/
|
||||
public void startMonitoring() {
|
||||
if (!SdkLevel.isAtLeastU()) {
|
||||
throw new UnsupportedOperationException(TAG + " is not supported below U");
|
||||
}
|
||||
mHandler.post(() -> handleStartMonitoring());
|
||||
}
|
||||
|
||||
private void handleStartMonitoring() {
|
||||
// Note that in the constructor it is too early to get the managers.
|
||||
mCm = mContext.getSystemService(ConnectivityManager.class);
|
||||
mNsm = mContext.getSystemService(NetworkStatsManager.class);
|
||||
// Note: This might create heavy workload on NetworkStatsService if queried with high
|
||||
// frequency, as the calling UID is system. Each query made with this manager
|
||||
// updates usage stored in files before returning the newest result.
|
||||
// This behavior is not rate-limited because the calling uid is the system UID.
|
||||
mNsm.setPollOnOpen(true);
|
||||
// Get the first baseline.
|
||||
mSatelliteBaseline = mDeps.getSummary(mNsm, mStartTime);
|
||||
|
||||
final NetworkRequest satelliteRequest = new NetworkRequest.Builder()
|
||||
.clearCapabilities()
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(TRANSPORT_SATELLITE)
|
||||
.build();
|
||||
// The callback uses the shared background thread. Potential delays of
|
||||
// dozens of seconds are acceptable because this class measures coarse
|
||||
// usage after a connection is lost, so the metric's accuracy is not
|
||||
// that sensitive to the timing.
|
||||
// A dedicated thread would be needed if it turns out inaccurate.
|
||||
mCm.registerNetworkCallback(satelliteRequest, mNetworkCallback, mHandler);
|
||||
}
|
||||
|
||||
/** Dump info to dumpsys */
|
||||
public void dump(@NonNull IndentingPrintWriter pw) {
|
||||
pw.println(TAG + ":");
|
||||
pw.increaseIndent();
|
||||
pw.println("Reported usage " + mReportedUsage);
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
|
|
@ -344,6 +344,7 @@ import com.android.metrics.NetworkList;
|
|||
import com.android.metrics.NetworkRequestCount;
|
||||
import com.android.metrics.RequestCountForType;
|
||||
import com.android.metrics.SatelliteAccessInfo;
|
||||
import com.android.metrics.SatelliteCoarseUsageMetricsCollector;
|
||||
import com.android.modules.utils.BasicShellCommandHandler;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||
|
|
@ -1066,6 +1067,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
private final NetworkNotificationManager mNotifier;
|
||||
private final LingerMonitor mLingerMonitor;
|
||||
private final SatelliteAccessController mSatelliteAccessController;
|
||||
private final SatelliteCoarseUsageMetricsCollector mSatelliteCoarseUsageMetricsCollector;
|
||||
|
||||
private final L2capNetworkProvider mL2capNetworkProvider;
|
||||
|
||||
|
|
@ -1707,6 +1709,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
connectivityServiceInternalHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SatelliteCoarseUsageMetricsCollector
|
||||
*/
|
||||
@Nullable
|
||||
public SatelliteCoarseUsageMetricsCollector makeSatelliteCoarseUsageMetricsCollector(
|
||||
@NonNull final Context context) {
|
||||
return new SatelliteCoarseUsageMetricsCollector(context);
|
||||
}
|
||||
|
||||
/** Creates an L2capNetworkProvider */
|
||||
public L2capNetworkProvider makeL2capNetworkProvider(Context context) {
|
||||
return new L2capNetworkProvider(context);
|
||||
|
|
@ -2090,6 +2101,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
mConstrainedDataSatelliteMetrics = (mSatelliteAccessController != null)
|
||||
&& mDeps.isFeatureNotChickenedOut(mContext, CONSTRAINED_DATA_SATELLITE_METRICS);
|
||||
if (mConstrainedDataSatelliteMetrics) {
|
||||
mSatelliteCoarseUsageMetricsCollector =
|
||||
mDeps.makeSatelliteCoarseUsageMetricsCollector(mContext);
|
||||
} else {
|
||||
mSatelliteCoarseUsageMetricsCollector = null;
|
||||
}
|
||||
|
||||
if (mDeps.flagConnectivityServiceDestroySocket()) {
|
||||
mIpToNetworksMap = new HashMap<>();
|
||||
|
|
@ -4363,6 +4380,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
// forever, this replaces a synchronous call to PermissionMonitor#initialize, which
|
||||
// could have blocked forever too.
|
||||
permissionMonitorInitializeDone.block();
|
||||
|
||||
if (mConstrainedDataSatelliteMetrics) {
|
||||
mSatelliteCoarseUsageMetricsCollector.startMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4689,6 +4710,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
if (mSatelliteAccessController != null) {
|
||||
mSatelliteAccessController.dump(pw);
|
||||
}
|
||||
if (mConstrainedDataSatelliteMetrics) {
|
||||
mSatelliteCoarseUsageMetricsCollector.dump(pw);
|
||||
}
|
||||
|
||||
pw.println();
|
||||
pw.println("Legacy network activity:");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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.app.usage.NetworkStatsManager
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.android.metrics.SatelliteCoarseUsageMetricsCollector.MyStatsEntry
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.waitForIdle
|
||||
import kotlin.test.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyLong
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
/**
|
||||
* Unit tests for [SatelliteCoarseUsageMetricsCollector].
|
||||
* These tests mock Android system services and the custom Dependencies class
|
||||
* to verify the behavior of the collector during satellite network events,
|
||||
* using MockK for mocking and argument capturing.
|
||||
*/
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
|
||||
class SatelliteCoarseUsageMetricsCollectorTest {
|
||||
companion object {
|
||||
const val THREAD_BLOCK_TIMEOUT_MS = 1000L
|
||||
}
|
||||
|
||||
private val mockCm = mock(ConnectivityManager::class.java)
|
||||
private val mockNsm = mock(NetworkStatsManager::class.java)
|
||||
private val mockCtx = mock(Context::class.java).also {
|
||||
doReturn(mockCm).`when`(it).getSystemService(ConnectivityManager::class.java)
|
||||
doReturn(mockNsm).`when`(it).getSystemService(NetworkStatsManager::class.java)
|
||||
}
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val mockDeps = mock(SatelliteCoarseUsageMetricsCollector.Dependencies::class.java)
|
||||
.also {
|
||||
doReturn(handler).`when`(it).backgroundThreadHandler
|
||||
}
|
||||
private val collector = SatelliteCoarseUsageMetricsCollector(mockCtx, mockDeps)
|
||||
|
||||
// Return the network callback for listening network changes.
|
||||
private fun startMonitoring(): ConnectivityManager.NetworkCallback {
|
||||
val cbCaptor = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback::class.java)
|
||||
collector.startMonitoring()
|
||||
handler.waitForIdle(THREAD_BLOCK_TIMEOUT_MS)
|
||||
verify(mockCm).registerNetworkCallback(any(), cbCaptor.capture(), any())
|
||||
return cbCaptor.value.also {
|
||||
assertNotNull(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mockGetSummary(rxBytes: Long, txBytes: Long) {
|
||||
val statsEntry = MyStatsEntry(rxBytes, txBytes)
|
||||
doReturn(statsEntry).`when`(mockDeps).getSummary(any(), anyLong())
|
||||
}
|
||||
|
||||
private fun assertReportedUsage(rxBytes: Long, txBytes: Long) {
|
||||
val reportedUsageCaptor = ArgumentCaptor.forClass(MyStatsEntry::class.java)
|
||||
verify(mockDeps).reportUsage(reportedUsageCaptor.capture())
|
||||
|
||||
val reportedUsage = reportedUsageCaptor.value
|
||||
assertEquals(rxBytes, reportedUsage.rxBytes)
|
||||
assertEquals(txBytes, reportedUsage.txBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleNetwork() {
|
||||
val mockNet = mock(Network::class.java)
|
||||
mockGetSummary(100, 50)
|
||||
val cb = startMonitoring()
|
||||
mockGetSummary(300, 150)
|
||||
cb.onLost(mockNet)
|
||||
assertReportedUsage(200, 100)
|
||||
}
|
||||
|
||||
// Test multiple satellite networks. This could happen when telephony creates
|
||||
// satellite networks with different types of services (e.g., MMS, internet).
|
||||
@Test
|
||||
fun testMultipleNetworks() {
|
||||
// Mock multiple networks, ensuring they are identified as distinct.
|
||||
val mockNet1 = Network(1)
|
||||
val mockNet2 = Network(2)
|
||||
|
||||
// Simulate baseline.
|
||||
mockGetSummary(100, 50)
|
||||
val cb = startMonitoring()
|
||||
|
||||
// Simulate onAvailable events, verify nothing happens.
|
||||
cb.onAvailable(mockNet1)
|
||||
cb.onAvailable(mockNet2)
|
||||
verify(mockDeps, never()).reportUsage(any())
|
||||
|
||||
// Simulate first network lost (not the last), simulate no change at all,
|
||||
// verify reportUsage is invoked with zero bytes.
|
||||
cb.onLost(mockNet1)
|
||||
assertReportedUsage(0L, 0L)
|
||||
|
||||
// Simulate second network lost (the last one).
|
||||
clearInvocations(mockDeps)
|
||||
mockGetSummary(300, 150)
|
||||
cb.onLost(mockNet2)
|
||||
assertReportedUsage(200L, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMyStatsEntryPlusSubtract() {
|
||||
val entry1 = MyStatsEntry(10, 20)
|
||||
val entry2 = MyStatsEntry(5, 10)
|
||||
val result = entry1.plus(entry2)
|
||||
assertEquals(15L, result.rxBytes)
|
||||
assertEquals(30L, result.txBytes)
|
||||
entry1.subtract(entry2)
|
||||
assertEquals(5L, entry1.rxBytes)
|
||||
assertEquals(10L, entry1.txBytes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMyStatsEntryMinusAdd() {
|
||||
val entry1 = MyStatsEntry(100, 200)
|
||||
val entry2 = MyStatsEntry(10, 20)
|
||||
val result = entry1.minus(entry2)
|
||||
assertEquals(90L, result.rxBytes)
|
||||
assertEquals(180L, result.txBytes)
|
||||
entry1.add(entry2)
|
||||
assertEquals(110L, entry1.rxBytes)
|
||||
assertEquals(220L, entry1.txBytes)
|
||||
}
|
||||
}
|
||||
|
|
@ -391,6 +391,7 @@ import com.android.internal.net.VpnConfig;
|
|||
import com.android.internal.util.WakeupMessage;
|
||||
import com.android.internal.util.test.BroadcastInterceptingContext;
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.metrics.SatelliteCoarseUsageMetricsCollector;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.ArrayTrackRecord;
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||
|
|
@ -650,6 +651,7 @@ public class ConnectivityServiceTest {
|
|||
@Mock SubscriptionManager mSubscriptionManager;
|
||||
@Mock KeepaliveTracker.Dependencies mMockKeepaliveTrackerDependencies;
|
||||
@Mock SatelliteAccessController mSatelliteAccessController;
|
||||
@Mock SatelliteCoarseUsageMetricsCollector mSatelliteCoarseUsageMetricsCollector;
|
||||
|
||||
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
|
||||
// underlying binder calls.
|
||||
|
|
@ -2093,6 +2095,12 @@ public class ConnectivityServiceTest {
|
|||
return mSatelliteAccessController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SatelliteCoarseUsageMetricsCollector makeSatelliteCoarseUsageMetricsCollector(
|
||||
@NonNull final Context context) {
|
||||
return mSatelliteCoarseUsageMetricsCollector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
|
||||
return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import android.util.SparseArray
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.internal.app.IBatteryStats
|
||||
import com.android.internal.util.test.BroadcastInterceptingContext
|
||||
import com.android.metrics.SatelliteCoarseUsageMetricsCollector
|
||||
import com.android.modules.utils.build.SdkLevel
|
||||
import com.android.net.module.util.ArrayTrackRecord
|
||||
import com.android.net.module.util.SharedLog
|
||||
|
|
@ -233,6 +234,7 @@ open class CSTest {
|
|||
|
||||
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
|
||||
val satelliteAccessController = mock<SatelliteAccessController>()
|
||||
val satelliteCoarseUsageMetricsCollector = mock<SatelliteCoarseUsageMetricsCollector>()
|
||||
val quicConnectionCloser = mock<QuicConnectionCloser>()
|
||||
val destroySocketsWrapper = mock<DestroySocketsWrapper>()
|
||||
val dnsResolver = mock<IDnsResolver>()
|
||||
|
|
@ -345,6 +347,10 @@ open class CSTest {
|
|||
return satelliteAccessController
|
||||
}
|
||||
|
||||
override fun makeSatelliteCoarseUsageMetricsCollector(
|
||||
context: Context
|
||||
) = satelliteCoarseUsageMetricsCollector
|
||||
|
||||
private inner class AOOKTDeps(c: Context) : AutomaticOnOffKeepaliveTracker.Dependencies(c) {
|
||||
override fun isTetheringFeatureNotChickenedOut(name: String): Boolean {
|
||||
return isFeatureEnabled(context, name)
|
||||
|
|
|
|||
Loading…
Reference in a new issue