From e3245081b128398eb4802855717b05749a742b9b Mon Sep 17 00:00:00 2001 From: Danuta Brotikovskaya Date: Tue, 12 Dec 2023 09:59:24 +0000 Subject: [PATCH] Implement NetworkRequestsState atom Bug: 278833398 Test: atest FrameworksNetTests Change-Id: Ie83bf49693411e4c3f629c07d98071505f48c08f --- .../metrics/NetworkRequestStateInfo.java | 99 +++++++++ .../NetworkRequestStateStatsMetrics.java | 192 ++++++++++++++++++ .../android/server/ConnectivityService.java | 21 ++ .../metrics/NetworkRequestStateInfoTest.java | 84 ++++++++ .../NetworkRequestStateStatsMetricsTest.java | 145 +++++++++++++ .../CSNetworkRequestStateStatsMetricsTests.kt | 83 ++++++++ .../server/connectivityservice/base/CSTest.kt | 5 + 7 files changed, 629 insertions(+) create mode 100644 service/src/com/android/metrics/NetworkRequestStateInfo.java create mode 100644 service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java create mode 100644 tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java create mode 100644 tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java create mode 100644 tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt diff --git a/service/src/com/android/metrics/NetworkRequestStateInfo.java b/service/src/com/android/metrics/NetworkRequestStateInfo.java new file mode 100644 index 0000000000..e3e172abd2 --- /dev/null +++ b/service/src/com/android/metrics/NetworkRequestStateInfo.java @@ -0,0 +1,99 @@ +/* + * 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 static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED; +import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED; +import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN; + +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.SystemClock; + +import com.android.net.module.util.BitUtils; + + +class NetworkRequestStateInfo { + private final NetworkRequest mNetworkRequest; + private final long mNetworkRequestReceivedTime; + + private enum NetworkRequestState { + RECEIVED, + REMOVED + } + private NetworkRequestState mNetworkRequestState; + private int mNetworkRequestDurationMillis; + private final Dependencies mDependencies; + + NetworkRequestStateInfo(NetworkRequest networkRequest, + Dependencies deps) { + mDependencies = deps; + mNetworkRequest = networkRequest; + mNetworkRequestReceivedTime = mDependencies.getElapsedRealtime(); + mNetworkRequestDurationMillis = 0; + mNetworkRequestState = NetworkRequestState.RECEIVED; + } + + public void setNetworkRequestRemoved() { + mNetworkRequestState = NetworkRequestState.REMOVED; + mNetworkRequestDurationMillis = (int) ( + mDependencies.getElapsedRealtime() - mNetworkRequestReceivedTime); + } + + public int getNetworkRequestStateStatsType() { + if (mNetworkRequestState == NetworkRequestState.RECEIVED) { + return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED; + } else if (mNetworkRequestState == NetworkRequestState.REMOVED) { + return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED; + } else { + return NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_UNKNOWN; + } + } + + public int getRequestId() { + return mNetworkRequest.requestId; + } + + public int getPackageUid() { + return mNetworkRequest.networkCapabilities.getRequestorUid(); + } + + public int getTransportTypes() { + return (int) BitUtils.packBits(mNetworkRequest.networkCapabilities.getTransportTypes()); + } + + public boolean getNetCapabilityNotMetered() { + return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + public boolean getNetCapabilityInternet() { + return mNetworkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + + public int getNetworkRequestDurationMillis() { + return mNetworkRequestDurationMillis; + } + + /** Dependency class */ + public static class Dependencies { + // Returns a timestamp with the time base of SystemClock.elapsedRealtime to keep durations + // relative to start time and avoid timezone change, including time spent in deep sleep. + public long getElapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + } +} diff --git a/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java b/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java new file mode 100644 index 0000000000..361ad22a2b --- /dev/null +++ b/service/src/com/android/metrics/NetworkRequestStateStatsMetrics.java @@ -0,0 +1,192 @@ +/* + * 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 static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED; + +import android.annotation.NonNull; +import android.net.NetworkRequest; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ConnectivityStatsLog; + +/** + * A Connectivity Service helper class to push atoms capturing network requests have been received + * and removed and its metadata. + * + * Atom events are logged in the ConnectivityStatsLog. Network request id: network request metadata + * hashmap is stored to calculate network request duration when it is removed. + * + * Note that this class is not thread-safe. The instance of the class needs to be + * synchronized in the callers when being used in multiple threads. + */ +public class NetworkRequestStateStatsMetrics { + + private static final String TAG = "NetworkRequestStateStatsMetrics"; + private static final int MSG_NETWORK_REQUEST_STATE_CHANGED = 0; + + // 1 second internal is suggested by experiment team + private static final int ATOM_INTERVAL_MS = 1000; + private final SparseArray mNetworkRequestsActive; + + private final Handler mStatsLoggingHandler; + + private final Dependencies mDependencies; + + private final NetworkRequestStateInfo.Dependencies mNRStateInfoDeps; + + public NetworkRequestStateStatsMetrics() { + this(new Dependencies(), new NetworkRequestStateInfo.Dependencies()); + } + + @VisibleForTesting + NetworkRequestStateStatsMetrics(Dependencies deps, + NetworkRequestStateInfo.Dependencies nrStateInfoDeps) { + mNetworkRequestsActive = new SparseArray<>(); + mDependencies = deps; + mNRStateInfoDeps = nrStateInfoDeps; + HandlerThread handlerThread = mDependencies.makeHandlerThread(TAG); + handlerThread.start(); + mStatsLoggingHandler = new StatsLoggingHandler(handlerThread.getLooper()); + } + + /** + * Register network request receive event, push RECEIVE atom + * + * @param networkRequest network request received + */ + public void onNetworkRequestReceived(NetworkRequest networkRequest) { + if (mNetworkRequestsActive.contains(networkRequest.requestId)) { + Log.w(TAG, "Received already registered network request, id = " + + networkRequest.requestId); + } else { + Log.d(TAG, "Registered nr with ID = " + networkRequest.requestId + + ", package_uid = " + networkRequest.networkCapabilities.getRequestorUid()); + NetworkRequestStateInfo networkRequestStateInfo = new NetworkRequestStateInfo( + networkRequest, mNRStateInfoDeps); + mNetworkRequestsActive.put(networkRequest.requestId, networkRequestStateInfo); + mStatsLoggingHandler.sendMessage( + Message.obtain( + mStatsLoggingHandler, + MSG_NETWORK_REQUEST_STATE_CHANGED, + networkRequestStateInfo)); + } + } + + /** + * Register network request remove event, push REMOVE atom + * + * @param networkRequest network request removed + */ + public void onNetworkRequestRemoved(NetworkRequest networkRequest) { + NetworkRequestStateInfo networkRequestStateInfo = mNetworkRequestsActive.get( + networkRequest.requestId); + if (networkRequestStateInfo == null) { + Log.w(TAG, "This NR hasn't been registered. NR id = " + networkRequest.requestId); + } else { + Log.d(TAG, "Removed nr with ID = " + networkRequest.requestId); + + mNetworkRequestsActive.remove(networkRequest.requestId); + networkRequestStateInfo.setNetworkRequestRemoved(); + mStatsLoggingHandler.sendMessage( + Message.obtain( + mStatsLoggingHandler, + MSG_NETWORK_REQUEST_STATE_CHANGED, + networkRequestStateInfo)); + + } + } + + /** Dependency class */ + public static class Dependencies { + /** + * Creates a thread with provided tag. + * + * @param tag for the thread. + */ + public HandlerThread makeHandlerThread(@NonNull final String tag) { + return new HandlerThread(tag); + } + + /** + * Sleeps the thread for provided intervalMs millis. + * + * @param intervalMs number of millis for the thread sleep. + */ + public void threadSleep(int intervalMs) { + try { + Thread.sleep(intervalMs); + } catch (InterruptedException e) { + Log.w(TAG, "Cool down interrupted!", e); + } + } + + /** + * Writes a NETWORK_REQUEST_STATE_CHANGED event to ConnectivityStatsLog. + * + * @param networkRequestStateInfo NetworkRequestStateInfo containing network request info. + */ + public void writeStats(NetworkRequestStateInfo networkRequestStateInfo) { + ConnectivityStatsLog.write( + NETWORK_REQUEST_STATE_CHANGED, + networkRequestStateInfo.getPackageUid(), + networkRequestStateInfo.getTransportTypes(), + networkRequestStateInfo.getNetCapabilityNotMetered(), + networkRequestStateInfo.getNetCapabilityInternet(), + networkRequestStateInfo.getNetworkRequestStateStatsType(), + networkRequestStateInfo.getNetworkRequestDurationMillis()); + } + } + + private class StatsLoggingHandler extends Handler { + private static final String TAG = "NetworkRequestsStateStatsLoggingHandler"; + private long mLastLogTime = 0; + + StatsLoggingHandler(Looper looper) { + super(looper); + } + + private void checkStatsLoggingTimeout() { + // Cool down before next execution. Required by atom logging frequency. + long now = SystemClock.elapsedRealtime(); + if (now - mLastLogTime < ATOM_INTERVAL_MS) { + mDependencies.threadSleep(ATOM_INTERVAL_MS); + } + mLastLogTime = now; + } + + @Override + public void handleMessage(Message msg) { + NetworkRequestStateInfo loggingInfo; + switch (msg.what) { + case MSG_NETWORK_REQUEST_STATE_CHANGED: + checkStatsLoggingTimeout(); + loggingInfo = (NetworkRequestStateInfo) msg.obj; + mDependencies.writeStats(loggingInfo); + break; + default: // fall out + } + } + } +} diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 0ec0f13154..6b47654775 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -281,6 +281,7 @@ import com.android.metrics.NetworkCountPerTransports; import com.android.metrics.NetworkDescription; import com.android.metrics.NetworkList; import com.android.metrics.NetworkRequestCount; +import com.android.metrics.NetworkRequestStateStatsMetrics; import com.android.metrics.RequestCountForType; import com.android.modules.utils.BasicShellCommandHandler; import com.android.modules.utils.build.SdkLevel; @@ -941,6 +942,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private final IpConnectivityLog mMetricsLog; + private final NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics; + @GuardedBy("mBandwidthRequests") private final SparseArray mBandwidthRequests = new SparseArray<>(10); @@ -1421,6 +1424,19 @@ public class ConnectivityService extends IConnectivityManager.Stub return new AutomaticOnOffKeepaliveTracker(c, h); } + /** + * @see NetworkRequestStateStatsMetrics + */ + public NetworkRequestStateStatsMetrics makeNetworkRequestStateStatsMetrics( + Context context) { + // We currently have network requests metric for Watch devices only + if (context.getPackageManager().hasSystemFeature(FEATURE_WATCH)) { + return new NetworkRequestStateStatsMetrics(); + } else { + return null; + } + } + /** * @see BatteryStatsManager */ @@ -1654,6 +1670,7 @@ public class ConnectivityService extends IConnectivityManager.Stub new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1); mMetricsLog = logger; + mNetworkRequestStateStatsMetrics = mDeps.makeNetworkRequestStateStatsMetrics(mContext); final NetworkRequest defaultInternetRequest = createDefaultRequest(); mDefaultRequest = new NetworkRequestInfo( Process.myUid(), defaultInternetRequest, null, @@ -5324,6 +5341,8 @@ public class ConnectivityService extends IConnectivityManager.Stub updateSignalStrengthThresholds(network, "REGISTER", req); } } + } else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) { + mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(req); } } @@ -5541,6 +5560,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (req.isListen()) { removeListenRequestFromNetworks(req); + } else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) { + mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(req); } } nri.unlinkDeathRecipient(); diff --git a/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java b/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java new file mode 100644 index 0000000000..5709ed1918 --- /dev/null +++ b/tests/unit/java/com/android/metrics/NetworkRequestStateInfoTest.java @@ -0,0 +1,84 @@ +/* + * 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 static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED; +import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED; + +import static org.junit.Assert.assertEquals; + +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Build; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@RunWith(DevSdkIgnoreRunner.class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +public class NetworkRequestStateInfoTest { + + @Mock + private NetworkRequestStateInfo.Dependencies mDependencies; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + @Test + public void testSetNetworkRequestRemoved() { + final long nrStartTime = 1L; + final long nrEndTime = 101L; + + NetworkRequest notMeteredWifiNetworkRequest = new NetworkRequest( + new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true), + 0, 1, NetworkRequest.Type.REQUEST + ); + + // This call will be used to calculate NR received time + Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrStartTime); + NetworkRequestStateInfo mNetworkRequestStateInfo = new NetworkRequestStateInfo( + notMeteredWifiNetworkRequest, mDependencies); + + // This call will be used to calculate NR removed time + Mockito.when(mDependencies.getElapsedRealtime()).thenReturn(nrEndTime); + mNetworkRequestStateInfo.setNetworkRequestRemoved(); + assertEquals( + nrEndTime - nrStartTime, + mNetworkRequestStateInfo.getNetworkRequestDurationMillis()); + assertEquals(mNetworkRequestStateInfo.getNetworkRequestStateStatsType(), + NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED); + } + + @Test + public void testCheckInitialState() { + NetworkRequestStateInfo mNetworkRequestStateInfo = new NetworkRequestStateInfo( + new NetworkRequest(new NetworkCapabilities(), 0, 1, NetworkRequest.Type.REQUEST), + mDependencies); + assertEquals(mNetworkRequestStateInfo.getNetworkRequestStateStatsType(), + NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED); + } +} diff --git a/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java b/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java new file mode 100644 index 0000000000..17a0719238 --- /dev/null +++ b/tests/unit/java/com/android/metrics/NetworkRequestStateStatsMetricsTest.java @@ -0,0 +1,145 @@ +/* + * 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 static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED; +import static com.android.server.ConnectivityStatsLog.NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.HandlerThread; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.testutils.HandlerUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class NetworkRequestStateStatsMetricsTest { + @Mock + private NetworkRequestStateStatsMetrics.Dependencies mNRStateStatsDeps; + @Mock + private NetworkRequestStateInfo.Dependencies mNRStateInfoDeps; + @Captor + private ArgumentCaptor mNetworkRequestStateInfoCaptor; + private NetworkRequestStateStatsMetrics mNetworkRequestStateStatsMetrics; + private HandlerThread mHandlerThread; + private static final int TEST_REQUEST_ID = 10; + private static final int TEST_PACKAGE_UID = 20; + private static final int TIMEOUT_MS = 30_000; + private static final NetworkRequest NOT_METERED_WIFI_NETWORK_REQUEST = new NetworkRequest( + new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true) + .setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, false) + .setRequestorUid(TEST_PACKAGE_UID), + 0, TEST_REQUEST_ID, NetworkRequest.Type.REQUEST + ); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mHandlerThread = new HandlerThread("NetworkRequestStateStatsMetrics"); + Mockito.when(mNRStateStatsDeps.makeHandlerThread("NetworkRequestStateStatsMetrics")) + .thenReturn(mHandlerThread); + mNetworkRequestStateStatsMetrics = new NetworkRequestStateStatsMetrics( + mNRStateStatsDeps, mNRStateInfoDeps); + } + + @Test + public void testNetworkRequestReceivedRemoved() { + final long nrStartTime = 1L; + final long nrEndTime = 101L; + // This call will be used to calculate NR received time + Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrStartTime); + mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST); + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + + verify(mNRStateStatsDeps, times(1)) + .writeStats(mNetworkRequestStateInfoCaptor.capture()); + + NetworkRequestStateInfo nrStateInfoSent = mNetworkRequestStateInfoCaptor.getValue(); + assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_RECEIVED, + nrStateInfoSent.getNetworkRequestStateStatsType()); + assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId()); + assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid()); + assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes()); + assertTrue(nrStateInfoSent.getNetCapabilityNotMetered()); + assertFalse(nrStateInfoSent.getNetCapabilityInternet()); + assertEquals(0, nrStateInfoSent.getNetworkRequestDurationMillis()); + + clearInvocations(mNRStateStatsDeps); + // This call will be used to calculate NR removed time + Mockito.when(mNRStateInfoDeps.getElapsedRealtime()).thenReturn(nrEndTime); + mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST); + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + + verify(mNRStateStatsDeps, times(1)) + .writeStats(mNetworkRequestStateInfoCaptor.capture()); + + nrStateInfoSent = mNetworkRequestStateInfoCaptor.getValue(); + assertEquals(NETWORK_REQUEST_STATE_CHANGED__STATE__NETWORK_REQUEST_STATE_REMOVED, + nrStateInfoSent.getNetworkRequestStateStatsType()); + assertEquals(NOT_METERED_WIFI_NETWORK_REQUEST.requestId, nrStateInfoSent.getRequestId()); + assertEquals(TEST_PACKAGE_UID, nrStateInfoSent.getPackageUid()); + assertEquals(1 << NetworkCapabilities.TRANSPORT_WIFI, nrStateInfoSent.getTransportTypes()); + assertTrue(nrStateInfoSent.getNetCapabilityNotMetered()); + assertFalse(nrStateInfoSent.getNetCapabilityInternet()); + assertEquals(nrEndTime - nrStartTime, nrStateInfoSent.getNetworkRequestDurationMillis()); + } + + @Test + public void testUnreceivedNetworkRequestRemoved() { + mNetworkRequestStateStatsMetrics.onNetworkRequestRemoved(NOT_METERED_WIFI_NETWORK_REQUEST); + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + verify(mNRStateStatsDeps, never()) + .writeStats(any(NetworkRequestStateInfo.class)); + } + + @Test + public void testExistingNetworkRequestReceived() { + mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST); + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + verify(mNRStateStatsDeps, times(1)) + .writeStats(any(NetworkRequestStateInfo.class)); + + clearInvocations(mNRStateStatsDeps); + mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(NOT_METERED_WIFI_NETWORK_REQUEST); + HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); + verify(mNRStateStatsDeps, never()) + .writeStats(any(NetworkRequestStateInfo.class)); + + } +} diff --git a/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt new file mode 100644 index 0000000000..35f8ae515c --- /dev/null +++ b/tests/unit/java/com/android/server/connectivityservice/CSNetworkRequestStateStatsMetricsTests.kt @@ -0,0 +1,83 @@ +/* + * 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.server + +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Build +import android.os.Process +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.TestableNetworkCallback +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.argThat +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +class CSNetworkRequestStateStatsMetricsTests : CSTest() { + private val CELL_INTERNET_NOT_METERED_NC = NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .build().setRequestorUidAndPackageName(Process.myUid(), context.getPackageName()) + + private val CELL_INTERNET_NOT_METERED_NR = NetworkRequest.Builder() + .setCapabilities(CELL_INTERNET_NOT_METERED_NC).build() + + @Before + fun setup() { + waitForIdle() + clearInvocations(networkRequestStateStatsMetrics) + } + + @Test + fun testRequestTypeNRProduceMetrics() { + cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback()) + waitForIdle() + + verify(networkRequestStateStatsMetrics).onNetworkRequestReceived( + argThat{req -> req.networkCapabilities.equals( + CELL_INTERNET_NOT_METERED_NR.networkCapabilities)}) + } + + @Test + fun testListenTypeNRProduceNoMetrics() { + cm.registerNetworkCallback(CELL_INTERNET_NOT_METERED_NR, TestableNetworkCallback()) + waitForIdle() + verify(networkRequestStateStatsMetrics, never()).onNetworkRequestReceived(any()) + } + + @Test + fun testRemoveRequestTypeNRProduceMetrics() { + val cb = TestableNetworkCallback() + cm.requestNetwork(CELL_INTERNET_NOT_METERED_NR, cb) + + waitForIdle() + clearInvocations(networkRequestStateStatsMetrics) + + cm.unregisterNetworkCallback(cb) + waitForIdle() + verify(networkRequestStateStatsMetrics).onNetworkRequestRemoved( + argThat{req -> req.networkCapabilities.equals( + CELL_INTERNET_NOT_METERED_NR.networkCapabilities)}) + } +} diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index 958c4f2961..5c9a762dbd 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -54,6 +54,7 @@ import android.util.ArraySet import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.app.IBatteryStats import com.android.internal.util.test.BroadcastInterceptingContext +import com.android.metrics.NetworkRequestStateStatsMetrics import com.android.modules.utils.build.SdkLevel import com.android.net.module.util.ArrayTrackRecord import com.android.networkstack.apishim.common.UnsupportedApiLevelException @@ -157,6 +158,7 @@ open class CSTest { val netd = mock() val bpfNetMaps = mock() val clatCoordinator = mock() + val networkRequestStateStatsMetrics = mock() val proxyTracker = ProxyTracker(context, mock(), 16 /* EVENT_PROXY_HAS_CHANGED */) val alarmManager = makeMockAlarmManager() val systemConfigManager = makeMockSystemConfigManager() @@ -197,6 +199,9 @@ open class CSTest { MultinetworkPolicyTracker(c, h, r, MultinetworkPolicyTrackerTestDependencies(connResources.get())) + override fun makeNetworkRequestStateStatsMetrics(c: Context) = + this@CSTest.networkRequestStateStatsMetrics + // 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.