Merge changes from topic "multi_network_activity_tracking" into main
* changes:
Make ConnectivityService update BatteryStats radio power state
Track multiple network activities on V+
Use netId as idleTimer label on V+
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 1837b84..f904cd1 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1770,7 +1770,12 @@
mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
null /* broadcastPermission */, mHandler);
- mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNetd, mHandler);
+ // TrackMultiNetworkActivities feature should be enabled by trunk stable flag.
+ // But reading the trunk stable flags from mainline modules is not supported yet.
+ // So enabling this feature on V+ release.
+ mTrackMultiNetworkActivities = mDeps.isAtLeastV();
+ mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNetd, mHandler,
+ mTrackMultiNetworkActivities);
final NetdCallback netdCallback = new NetdCallback();
try {
@@ -3246,9 +3251,20 @@
private void handleReportNetworkActivity(final NetworkActivityParams params) {
mNetworkActivityTracker.handleReportNetworkActivity(params);
+ final boolean isCellNetworkActivity;
+ if (mTrackMultiNetworkActivities) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(params.label);
+ // nai could be null if netd receives a netlink message and calls the network
+ // activity change callback after the network is unregistered from ConnectivityService.
+ isCellNetworkActivity = nai != null
+ && nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+ } else {
+ isCellNetworkActivity = params.label == TRANSPORT_CELLULAR;
+ }
+
if (mDelayDestroyFrozenSockets
&& params.isActive
- && params.label == TRANSPORT_CELLULAR
+ && isCellNetworkActivity
&& !mPendingFrozenUids.isEmpty()) {
closePendingFrozenSockets();
}
@@ -4965,6 +4981,11 @@
if (wasDefault) {
mDefaultInetConditionPublished = 0;
}
+ if (mTrackMultiNetworkActivities) {
+ // If trackMultiNetworkActivities is disabled, ActivityTracker removes idleTimer when
+ // the network becomes no longer the default network.
+ mNetworkActivityTracker.removeDataActivityTracking(nai);
+ }
notifyIfacesChangedForNetworkStats();
// If this was a local network forwarded to some upstream, or if some local network was
// forwarded to this nai, then disable forwarding rules now.
@@ -5018,12 +5039,7 @@
}
if (mDefaultRequest == nri) {
- // TODO : make battery stats aware that since 2013 multiple interfaces may be
- // active at the same time. For now keep calling this with the default
- // network, because while incorrect this is the closest to the old (also
- // incorrect) behavior.
- mNetworkActivityTracker.updateDataActivityTracking(
- null /* newNetwork */, nai);
+ mNetworkActivityTracker.updateDefaultNetwork(null /* newNetwork */, nai);
maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
@@ -9644,7 +9660,7 @@
if (oldDefaultNetwork != null) {
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
- mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
+ mNetworkActivityTracker.updateDefaultNetwork(newDefaultNetwork, oldDefaultNetwork);
maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
@@ -10544,6 +10560,15 @@
SystemClock.elapsedRealtime(), mNascentDelayMs);
networkAgent.setInactive();
+ if (mTrackMultiNetworkActivities) {
+ // Start tracking activity of this network.
+ // This must be called before rematchAllNetworksAndRequests since the network
+ // should be tracked when the network becomes the default network.
+ // This method does not trigger any callbacks or broadcasts. Callbacks or broadcasts
+ // can be triggered later if this network becomes the default network.
+ mNetworkActivityTracker.setupDataActivityTracking(networkAgent);
+ }
+
// Consider network even though it is not yet validated.
rematchAllNetworksAndRequests();
@@ -11735,8 +11760,8 @@
*/
private static final class NetworkActivityParams {
public final boolean isActive;
- // Label used for idle timer. Transport type is used as label.
- // label is int since NMS was using the identifier as int, and it has not been changed
+ // If TrackMultiNetworkActivities is enabled, idleTimer label is netid.
+ // If TrackMultiNetworkActivities is disabled, idleTimer label is transport type.
public final int label;
public final long timestampNs;
// Uid represents the uid that was responsible for waking the radio.
@@ -11778,13 +11803,15 @@
}
}
+ private final boolean mTrackMultiNetworkActivities;
private final LegacyNetworkActivityTracker mNetworkActivityTracker;
/**
* Class used for updating network activity tracking with netd and notify network activity
* changes.
*/
- private static final class LegacyNetworkActivityTracker {
+ @VisibleForTesting
+ public static final class LegacyNetworkActivityTracker {
private static final int NO_UID = -1;
private final Context mContext;
private final INetd mNetd;
@@ -11796,8 +11823,14 @@
// If there is no default network, default network is considered active to keep the existing
// behavior. Initial value is used until first connect to the default network.
private volatile boolean mIsDefaultNetworkActive = true;
+ private Network mDefaultNetwork;
// Key is netId. Value is configured idle timer information.
private final SparseArray<IdleTimerParams> mActiveIdleTimers = new SparseArray<>();
+ private final boolean mTrackMultiNetworkActivities;
+ // Store netIds of Wi-Fi networks whose idletimers report that they are active
+ private final Set<Integer> mActiveWifiNetworks = new ArraySet<>();
+ // Store netIds of cellular networks whose idletimers report that they are active
+ private final Set<Integer> mActiveCellularNetworks = new ArraySet<>();
private static class IdleTimerParams {
public final int timeout;
@@ -11810,10 +11843,11 @@
}
LegacyNetworkActivityTracker(@NonNull Context context, @NonNull INetd netd,
- @NonNull Handler handler) {
+ @NonNull Handler handler, boolean trackMultiNetworkActivities) {
mContext = context;
mNetd = netd;
mHandler = handler;
+ mTrackMultiNetworkActivities = trackMultiNetworkActivities;
}
private void ensureRunningOnConnectivityServiceThread() {
@@ -11823,19 +11857,97 @@
}
}
- public void handleReportNetworkActivity(NetworkActivityParams activityParams) {
- ensureRunningOnConnectivityServiceThread();
+ /**
+ * Update network activity and call BatteryStats to update radio power state if the
+ * mobile or Wi-Fi activity is changed.
+ * LegacyNetworkActivityTracker considers the mobile network is active if at least one
+ * mobile network is active since BatteryStatsService only maintains a single power state
+ * for the mobile network.
+ * The Wi-Fi network is also the same.
+ *
+ * {@link #setupDataActivityTracking} and {@link #removeDataActivityTracking} use
+ * TRANSPORT_CELLULAR as the transportType argument if the network has both cell and Wi-Fi
+ * transports.
+ */
+ private void maybeUpdateRadioPowerState(final int netId, final int transportType,
+ final boolean isActive, final int uid) {
+ if (transportType != TRANSPORT_WIFI && transportType != TRANSPORT_CELLULAR) {
+ Log.e(TAG, "Unexpected transportType in maybeUpdateRadioPowerState: "
+ + transportType);
+ return;
+ }
+ final Set<Integer> activeNetworks = transportType == TRANSPORT_WIFI
+ ? mActiveWifiNetworks : mActiveCellularNetworks;
+
+ final boolean wasEmpty = activeNetworks.isEmpty();
+ if (isActive) {
+ activeNetworks.add(netId);
+ } else {
+ activeNetworks.remove(netId);
+ }
+
+ if (wasEmpty != activeNetworks.isEmpty()) {
+ updateRadioPowerState(isActive, transportType, uid);
+ }
+ }
+
+ private void handleDefaultNetworkActivity(final int transportType,
+ final boolean isActive, final long timestampNs) {
+ mIsDefaultNetworkActive = isActive;
+ sendDataActivityBroadcast(transportTypeToLegacyType(transportType),
+ isActive, timestampNs);
+ if (isActive) {
+ reportNetworkActive();
+ }
+ }
+
+ private void handleReportNetworkActivityWithNetIdLabel(
+ NetworkActivityParams activityParams) {
+ final int netId = activityParams.label;
+ final IdleTimerParams idleTimerParams = mActiveIdleTimers.get(netId);
+ if (idleTimerParams == null) {
+ // This network activity change is not tracked anymore
+ // This can happen if netd callback post activity change event message but idle
+ // timer is removed before processing this message.
+ return;
+ }
+ // TODO: if a network changes transports, storing the transport type in the
+ // IdleTimerParams is not correct. Consider getting it from the network's
+ // NetworkCapabilities instead.
+ final int transportType = idleTimerParams.transportType;
+ maybeUpdateRadioPowerState(netId, transportType,
+ activityParams.isActive, activityParams.uid);
+
+ if (mDefaultNetwork == null || mDefaultNetwork.netId != netId) {
+ // This activity change is not for the default network.
+ return;
+ }
+
+ handleDefaultNetworkActivity(transportType, activityParams.isActive,
+ activityParams.timestampNs);
+ }
+
+ private void handleReportNetworkActivityWithTransportTypeLabel(
+ NetworkActivityParams activityParams) {
if (mActiveIdleTimers.size() == 0) {
// This activity change is not for the current default network.
// This can happen if netd callback post activity change event message but
// the default network is lost before processing this message.
return;
}
- sendDataActivityBroadcast(transportTypeToLegacyType(activityParams.label),
- activityParams.isActive, activityParams.timestampNs);
- mIsDefaultNetworkActive = activityParams.isActive;
- if (mIsDefaultNetworkActive) {
- reportNetworkActive();
+ handleDefaultNetworkActivity(activityParams.label, activityParams.isActive,
+ activityParams.timestampNs);
+ }
+
+ /**
+ * Handle network activity change
+ */
+ public void handleReportNetworkActivity(NetworkActivityParams activityParams) {
+ ensureRunningOnConnectivityServiceThread();
+ if (mTrackMultiNetworkActivities) {
+ handleReportNetworkActivityWithNetIdLabel(activityParams);
+ } else {
+ handleReportNetworkActivityWithTransportTypeLabel(activityParams);
}
}
@@ -11892,6 +12004,30 @@
}
/**
+ * Get idle timer label
+ */
+ @VisibleForTesting
+ public static int getIdleTimerLabel(final boolean trackMultiNetworkActivities,
+ final int netId, final int transportType) {
+ return trackMultiNetworkActivities ? netId : transportType;
+ }
+
+ private boolean maybeCreateIdleTimer(
+ String iface, int netId, int timeout, int transportType) {
+ if (timeout <= 0 || iface == null) return false;
+ try {
+ final String label = Integer.toString(getIdleTimerLabel(
+ mTrackMultiNetworkActivities, netId, transportType));
+ mNetd.idletimerAddInterface(iface, timeout, label);
+ mActiveIdleTimers.put(netId, new IdleTimerParams(timeout, transportType));
+ return true;
+ } catch (Exception e) {
+ loge("Exception in createIdleTimer", e);
+ return false;
+ }
+ }
+
+ /**
* Setup data activity tracking for the given network.
*
* Every {@code setupDataActivityTracking} should be paired with a
@@ -11900,13 +12036,17 @@
* @return true if the idleTimer is added to the network, false otherwise
*/
private boolean setupDataActivityTracking(NetworkAgentInfo networkAgent) {
+ ensureRunningOnConnectivityServiceThread();
final String iface = networkAgent.linkProperties.getInterfaceName();
final int netId = networkAgent.network().netId;
final int timeout;
final int type;
- if (networkAgent.networkCapabilities.hasTransport(
+ if (!networkAgent.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_VPN)) {
+ // Do not track VPN network.
+ return false;
+ } else if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_CELLULAR)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE,
@@ -11922,25 +12062,21 @@
return false; // do not track any other networks
}
- updateRadioPowerState(true /* isActive */, type);
-
- if (timeout > 0 && iface != null) {
- try {
- mActiveIdleTimers.put(netId, new IdleTimerParams(timeout, type));
- mNetd.idletimerAddInterface(iface, timeout, Integer.toString(type));
- return true;
- } catch (Exception e) {
- // You shall not crash!
- loge("Exception in setupDataActivityTracking " + e);
- }
+ final boolean hasIdleTimer = maybeCreateIdleTimer(iface, netId, timeout, type);
+ if (hasIdleTimer || !mTrackMultiNetworkActivities) {
+ // If trackMultiNetwork is disabled, NetworkActivityTracker updates radio power
+ // state in all cases. If trackMultiNetwork is enabled, it updates radio power
+ // state only about a network that has an idletimer.
+ maybeUpdateRadioPowerState(netId, type, true /* isActive */, NO_UID);
}
- return false;
+ return hasIdleTimer;
}
/**
* Remove data activity tracking when network disconnects.
*/
- private void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
+ public void removeDataActivityTracking(NetworkAgentInfo networkAgent) {
+ ensureRunningOnConnectivityServiceThread();
final String iface = networkAgent.linkProperties.getInterfaceName();
final int netId = networkAgent.network().netId;
final NetworkCapabilities caps = networkAgent.networkCapabilities;
@@ -11948,7 +12084,10 @@
if (iface == null) return;
final int type;
- if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ if (!networkAgent.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_VPN)) {
+ // Do not track VPN network.
+ return;
+ } else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
type = NetworkCapabilities.TRANSPORT_CELLULAR;
} else if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
type = NetworkCapabilities.TRANSPORT_WIFI;
@@ -11957,16 +12096,17 @@
}
try {
- updateRadioPowerState(false /* isActive */, type);
+ maybeUpdateRadioPowerState(netId, type, false /* isActive */, NO_UID);
final IdleTimerParams params = mActiveIdleTimers.get(netId);
if (params == null) {
// IdleTimer is not added if the configured timeout is 0 or negative value
return;
}
mActiveIdleTimers.remove(netId);
- // The call fails silently if no idle timer setup for this interface
- mNetd.idletimerRemoveInterface(iface, params.timeout,
- Integer.toString(params.transportType));
+ final String label = Integer.toString(getIdleTimerLabel(
+ mTrackMultiNetworkActivities, netId, params.transportType));
+ // The call fails silently if no idle timer setup for this interface
+ mNetd.idletimerRemoveInterface(iface, params.timeout, label);
} catch (Exception e) {
// You shall not crash!
loge("Exception in removeDataActivityTracking " + e);
@@ -11976,12 +12116,15 @@
private void updateDefaultNetworkActivity(NetworkAgentInfo defaultNetwork,
boolean hasIdleTimer) {
if (defaultNetwork != null) {
+ mDefaultNetwork = defaultNetwork.network();
mIsDefaultNetworkActive = true;
- // Callbacks are called only when the network has the idle timer.
- if (hasIdleTimer) {
+ // If only the default network is tracked, callbacks are called only when the
+ // network has the idle timer.
+ if (mTrackMultiNetworkActivities || hasIdleTimer) {
reportNetworkActive();
}
} else {
+ mDefaultNetwork = null;
// If there is no default network, default network is considered active to keep the
// existing behavior.
mIsDefaultNetworkActive = true;
@@ -11989,29 +12132,34 @@
}
/**
- * Update data activity tracking when network state is updated.
+ * Update the default network this class tracks the activity of.
*/
- public void updateDataActivityTracking(NetworkAgentInfo newNetwork,
+ public void updateDefaultNetwork(NetworkAgentInfo newNetwork,
NetworkAgentInfo oldNetwork) {
ensureRunningOnConnectivityServiceThread();
+ // If TrackMultiNetworkActivities is enabled, devices add idleTimer when the network is
+ // first connected and remove when the network is disconnected.
+ // If TrackMultiNetworkActivities is disabled, devices add idleTimer when the network
+ // becomes the default network and remove when the network becomes no longer the default
+ // network.
boolean hasIdleTimer = false;
- if (newNetwork != null) {
+ if (!mTrackMultiNetworkActivities && newNetwork != null) {
hasIdleTimer = setupDataActivityTracking(newNetwork);
}
updateDefaultNetworkActivity(newNetwork, hasIdleTimer);
- if (oldNetwork != null) {
+ if (!mTrackMultiNetworkActivities && oldNetwork != null) {
removeDataActivityTracking(oldNetwork);
}
}
- private void updateRadioPowerState(boolean isActive, int transportType) {
+ private void updateRadioPowerState(boolean isActive, int transportType, int uid) {
final BatteryStatsManager bs = mContext.getSystemService(BatteryStatsManager.class);
switch (transportType) {
case NetworkCapabilities.TRANSPORT_CELLULAR:
- bs.reportMobileRadioPowerState(isActive, NO_UID);
+ bs.reportMobileRadioPowerState(isActive, uid);
break;
case NetworkCapabilities.TRANSPORT_WIFI:
- bs.reportWifiRadioPowerState(isActive, NO_UID);
+ bs.reportWifiRadioPowerState(isActive, uid);
break;
default:
logw("Untracked transport type:" + transportType);
@@ -12031,7 +12179,9 @@
}
public void dump(IndentingPrintWriter pw) {
+ pw.print("mTrackMultiNetworkActivities="); pw.println(mTrackMultiNetworkActivities);
pw.print("mIsDefaultNetworkActive="); pw.println(mIsDefaultNetworkActive);
+ pw.print("mDefaultNetwork="); pw.println(mDefaultNetwork);
pw.println("Idle timers:");
try {
for (int i = 0; i < mActiveIdleTimers.size(); i++) {
@@ -12040,11 +12190,13 @@
pw.print(" timeout="); pw.print(params.timeout);
pw.print(" type="); pw.println(params.transportType);
}
+ pw.println("WiFi active networks: " + mActiveWifiNetworks);
+ pw.println("Cellular active networks: " + mActiveCellularNetworks);
} catch (Exception e) {
- // mActiveIdleTimers should only be accessed from handler thread, except dump().
- // As dump() is never called in normal usage, it would be needlessly expensive
- // to lock the collection only for its benefit.
- // Also, mActiveIdleTimers is not expected to be updated frequently.
+ // mActiveIdleTimers, mActiveWifiNetworks, and mActiveCellularNetworks should only
+ // be accessed from handler thread, except dump(). As dump() is never called in
+ // normal usage, it would be needlessly expensive to lock the collection only for
+ // its benefit. Also, they are not expected to be updated frequently.
// So catching the exception and logging.
pw.println("Failed to dump NetworkActivityTracker: " + e);
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 97e134a..b8cf08e 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -155,6 +155,8 @@
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;
+import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
+import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
@@ -640,8 +642,8 @@
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
- final BatteryStatsManager mBatteryStatsManager =
- new BatteryStatsManager(mock(IBatteryStats.class));
+ final IBatteryStats mIBatteryStats = mock(IBatteryStats.class);
+ final BatteryStatsManager mBatteryStatsManager = new BatteryStatsManager(mIBatteryStats);
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -10807,6 +10809,11 @@
expectNativeNetworkCreated(netId, permission, iface, null /* inOrder */);
}
+ private int getIdleTimerLabel(int netId, int transportType) {
+ return ConnectivityService.LegacyNetworkActivityTracker.getIdleTimerLabel(
+ mDeps.isAtLeastV(), netId, transportType);
+ }
+
@Test
public void testStackedLinkProperties() throws Exception {
final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
@@ -11048,7 +11055,7 @@
networkCallback.expect(LOST, mCellAgent);
networkCallback.assertNoCallback();
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(Integer.toString(getIdleTimerLabel(cellNetId, TRANSPORT_CELLULAR))));
verify(mMockNetd).networkDestroy(cellNetId);
if (mDeps.isAtLeastU()) {
verify(mMockNetd).setNetworkAllowlist(any());
@@ -11107,7 +11114,7 @@
}
verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(Integer.toString(getIdleTimerLabel(cellNetId, TRANSPORT_CELLULAR))));
verify(mMockNetd).networkDestroy(cellNetId);
if (mDeps.isAtLeastU()) {
verify(mMockNetd).setNetworkAllowlist(any());
@@ -11362,8 +11369,21 @@
final ConditionVariable onNetworkActiveCv = new ConditionVariable();
final ConnectivityManager.OnNetworkActiveListener listener = onNetworkActiveCv::open;
+ TestNetworkCallback defaultCallback = new TestNetworkCallback();
+
testAndCleanup(() -> {
+ mCm.registerDefaultNetworkCallback(defaultCallback);
agent.connect(true);
+ defaultCallback.expectAvailableThenValidatedCallbacks(agent);
+ if (transportType == TRANSPORT_CELLULAR) {
+ verify(mIBatteryStats).noteMobileRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID));
+ } else if (transportType == TRANSPORT_WIFI) {
+ verify(mIBatteryStats).noteWifiRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID));
+ }
+ clearInvocations(mIBatteryStats);
+ final int idleTimerLabel = getIdleTimerLabel(agent.getNetwork().netId, transportType);
// Network is considered active when the network becomes the default network.
assertTrue(mCm.isDefaultNetworkActive());
@@ -11372,19 +11392,57 @@
// Interface goes to inactive state
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
- transportType, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
+ idleTimerLabel, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
mServiceContext.expectDataActivityBroadcast(legacyType, false /* isActive */,
TIMESTAMP);
assertFalse(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
assertFalse(mCm.isDefaultNetworkActive());
+ if (mDeps.isAtLeastV()) {
+ if (transportType == TRANSPORT_CELLULAR) {
+ verify(mIBatteryStats).noteMobileRadioPowerState(eq(DC_POWER_STATE_LOW),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID));
+ } else if (transportType == TRANSPORT_WIFI) {
+ verify(mIBatteryStats).noteWifiRadioPowerState(eq(DC_POWER_STATE_LOW),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID));
+ }
+ } else {
+ // If TrackMultiNetworks is disabled, LegacyNetworkActivityTracker does not call
+ // BatteryStats API by the netd activity change callback since BatteryStatsService
+ // listen to netd callback via NetworkManagementService and update battery stats by
+ // itself.
+ verify(mIBatteryStats, never())
+ .noteMobileRadioPowerState(anyInt(), anyLong(), anyInt());
+ verify(mIBatteryStats, never())
+ .noteWifiRadioPowerState(anyInt(), anyLong(), anyInt());
+ }
// Interface goes to active state
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
- transportType, TIMESTAMP, TEST_PACKAGE_UID);
+ idleTimerLabel, TIMESTAMP, TEST_PACKAGE_UID);
mServiceContext.expectDataActivityBroadcast(legacyType, true /* isActive */, TIMESTAMP);
assertTrue(onNetworkActiveCv.block(TEST_CALLBACK_TIMEOUT_MS));
assertTrue(mCm.isDefaultNetworkActive());
+ if (mDeps.isAtLeastV()) {
+ if (transportType == TRANSPORT_CELLULAR) {
+ verify(mIBatteryStats).noteMobileRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(TEST_PACKAGE_UID));
+ } else if (transportType == TRANSPORT_WIFI) {
+ verify(mIBatteryStats).noteWifiRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(TEST_PACKAGE_UID));
+ }
+ } else {
+ // If TrackMultiNetworks is disabled, LegacyNetworkActivityTracker does not call
+ // BatteryStats API by the netd activity change callback since BatteryStatsService
+ // listen to netd callback via NetworkManagementService and update battery stats by
+ // itself.
+ verify(mIBatteryStats, never())
+ .noteMobileRadioPowerState(anyInt(), anyLong(), anyInt());
+ verify(mIBatteryStats, never())
+ .noteWifiRadioPowerState(anyInt(), anyLong(), anyInt());
+ }
}, () -> { // Cleanup
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }, () -> { // Cleanup
mCm.removeDefaultNetworkActiveListener(listener);
}, () -> { // Cleanup
agent.disconnect();
@@ -11432,12 +11490,13 @@
}
@Test
- public void testOnNetworkActive_NewEthernetConnects_CallbackNotCalled() throws Exception {
- // LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
- // tracker adds the idle timer to. And the tracker does not set the idle timer for the
- // ethernet network.
+ public void testOnNetworkActive_NewEthernetConnects_Callback() throws Exception {
+ // On pre-V devices, LegacyNetworkActivityTracker calls onNetworkActive callback only for
+ // networks that tracker adds the idle timer to. And the tracker does not set the idle timer
+ // for the ethernet network.
// So onNetworkActive is not called when the ethernet becomes the default network
- doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, false /* expectCallback */);
+ final boolean expectCallback = mDeps.isAtLeastV();
+ doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, expectCallback);
}
@Test
@@ -11467,15 +11526,19 @@
mCm.registerNetworkCallback(networkRequest, networkCallback);
mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ final String cellIdleTimerLabel = Integer.toString(getIdleTimerLabel(
+ mCellAgent.getNetwork().netId, TRANSPORT_CELLULAR));
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
mCellAgent.sendLinkProperties(cellLp);
mCellAgent.connect(true);
networkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(cellIdleTimerLabel));
mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ String wifiIdleTimerLabel = Integer.toString(getIdleTimerLabel(
+ mWiFiAgent.getNetwork().netId, TRANSPORT_WIFI));
final LinkProperties wifiLp = new LinkProperties();
wifiLp.setInterfaceName(WIFI_IFNAME);
mWiFiAgent.sendLinkProperties(wifiLp);
@@ -11486,9 +11549,18 @@
networkCallback.expectLosing(mCellAgent);
networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_WIFI)));
- verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(wifiIdleTimerLabel));
+ if (mDeps.isAtLeastV()) {
+ // V+ devices add idleTimer when the network is first connected and remove when the
+ // network is disconnected.
+ verify(mMockNetd, never()).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(mCellAgent.getNetwork().netId)));
+ } else {
+ // pre V devices add idleTimer when the network becomes the default network and remove
+ // when the network becomes no longer the default network.
+ verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(TRANSPORT_CELLULAR)));
+ }
// Disconnect wifi and switch back to cell
reset(mMockNetd);
@@ -11496,13 +11568,20 @@
networkCallback.expect(LOST, mWiFiAgent);
assertNoCallbacks(networkCallback);
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_WIFI)));
- verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(wifiIdleTimerLabel));
+ if (mDeps.isAtLeastV()) {
+ verify(mMockNetd, never()).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(mCellAgent.getNetwork().netId)));
+ } else {
+ verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(TRANSPORT_CELLULAR)));
+ }
// reconnect wifi
reset(mMockNetd);
mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ wifiIdleTimerLabel = Integer.toString(getIdleTimerLabel(
+ mWiFiAgent.getNetwork().netId, TRANSPORT_WIFI));
wifiLp.setInterfaceName(WIFI_IFNAME);
mWiFiAgent.sendLinkProperties(wifiLp);
mWiFiAgent.connect(true);
@@ -11510,20 +11589,30 @@
networkCallback.expectLosing(mCellAgent);
networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_WIFI)));
- verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ eq(wifiIdleTimerLabel));
+ if (mDeps.isAtLeastV()) {
+ verify(mMockNetd, never()).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(mCellAgent.getNetwork().netId)));
+ } else {
+ verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(TRANSPORT_CELLULAR)));
+ }
// Disconnect cell
reset(mMockNetd);
mCellAgent.disconnect();
networkCallback.expect(LOST, mCellAgent);
- // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
- // sent as network being switched. Ensure rule removal for cell will not be triggered
- // unexpectedly before network being removed.
waitForIdle();
- verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_CELLULAR)));
+ if (mDeps.isAtLeastV()) {
+ verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(mCellAgent.getNetwork().netId)));
+ } else {
+ // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
+ // sent as network being switched. Ensure rule removal for cell will not be triggered
+ // unexpectedly before network being removed.
+ verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
+ eq(Integer.toString(TRANSPORT_CELLULAR)));
+ }
verify(mMockNetd, times(1)).networkDestroy(eq(mCellAgent.getNetwork().netId));
verify(mMockDnsResolver, times(1)).destroyNetworkCache(eq(mCellAgent.getNetwork().netId));
@@ -11532,12 +11621,27 @@
mWiFiAgent.disconnect();
b.expectBroadcast();
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
- eq(Integer.toString(TRANSPORT_WIFI)));
+ eq(wifiIdleTimerLabel));
// Clean up
mCm.unregisterNetworkCallback(networkCallback);
}
+ @Test
+ public void testDataActivityTracking_VpnNetwork() throws Exception {
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiAgent.connect(true /* validated */);
+ mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
+
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(VPN_IFNAME);
+ mMockVpn.establishForMyUid(lp);
+
+ // NetworkActivityTracker should not track the VPN network since VPN can change the
+ // underlying network without disconnect.
+ verify(mMockNetd, never()).idletimerAddInterface(eq(VPN_IFNAME), anyInt(), any());
+ }
+
private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception {
String[] values = tcpBufferSizes.split(",");
String rmemValues = String.join(" ", values[0], values[1], values[2]);
@@ -18728,6 +18832,7 @@
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(transportToTestIfaceName(transportType));
final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(transportType, lp);
+ final int idleTimerLabel = getIdleTimerLabel(agent.getNetwork().netId, transportType);
testAndCleanup(() -> {
final UidFrozenStateChangedCallback uidFrozenStateChangedCallback =
getUidFrozenStateChangedCallback().get();
@@ -18740,7 +18845,7 @@
if (freezeWithNetworkInactive) {
// Make network inactive
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
- transportType, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
+ idleTimerLabel, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
}
// Freeze TEST_FROZEN_UID and TEST_UNFROZEN_UID
@@ -18764,7 +18869,7 @@
// Make network active
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
- transportType, TIMESTAMP, TEST_PACKAGE_UID);
+ idleTimerLabel, TIMESTAMP, TEST_PACKAGE_UID);
waitForIdle();
if (expectDelay) {
@@ -18783,8 +18888,8 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testDelayFrozenUidSocketDestroy_ActiveCellular() throws Exception {
- doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR,
- false /* freezeWithNetworkInactive */, false /* expectDelay */);
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR, false /* freezeWithNetworkInactive */,
+ false /* expectDelay */);
}
@Test
@@ -18792,22 +18897,22 @@
public void testDelayFrozenUidSocketDestroy_InactiveCellular() throws Exception {
// When the default network is cellular and cellular network is inactive, closing socket
// is delayed.
- doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR,
- true /* freezeWithNetworkInactive */, true /* expectDelay */);
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_CELLULAR, true /* freezeWithNetworkInactive */,
+ true /* expectDelay */);
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testDelayFrozenUidSocketDestroy_ActiveWifi() throws Exception {
- doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI,
- false /* freezeWithNetworkInactive */, false /* expectDelay */);
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI, false /* freezeWithNetworkInactive */,
+ false /* expectDelay */);
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testDelayFrozenUidSocketDestroy_InactiveWifi() throws Exception {
- doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI,
- true /* freezeWithNetworkInactive */, false /* expectDelay */);
+ doTestDelayFrozenUidSocketDestroy(TRANSPORT_WIFI, true /* freezeWithNetworkInactive */,
+ false /* expectDelay */);
}
/**
@@ -18828,6 +18933,8 @@
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+ final int idleTimerLabel =
+ getIdleTimerLabel(mCellAgent.getNetwork().netId, TRANSPORT_CELLULAR);
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultCallback);
@@ -18837,7 +18944,7 @@
// Make cell network inactive
netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
- TRANSPORT_CELLULAR, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
+ idleTimerLabel, TIMESTAMP, NETWORK_ACTIVITY_NO_UID);
// Freeze TEST_FROZEN_UID
final int[] uids = {TEST_FROZEN_UID};
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSNetworkActivityTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSNetworkActivityTest.kt
new file mode 100644
index 0000000..526ec9d
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSNetworkActivityTest.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.ConnectivityManager
+import android.net.ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE
+import android.net.ConnectivityManager.EXTRA_DEVICE_TYPE
+import android.net.ConnectivityManager.EXTRA_IS_ACTIVE
+import android.net.ConnectivityManager.EXTRA_REALTIME_NS
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_IMS
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.os.Build
+import android.os.ConditionVariable
+import android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+import android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener
+import com.android.server.CSTest.CSContext
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import kotlin.test.assertNotNull
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+
+private const val DATA_CELL_IFNAME = "rmnet_data"
+private const val IMS_CELL_IFNAME = "rmnet_ims"
+private const val WIFI_IFNAME = "wlan0"
+private const val TIMESTAMP = 1234L
+private const val NETWORK_ACTIVITY_NO_UID = -1
+private const val PACKAGE_UID = 123
+private const val TIMEOUT_MS = 250L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSNetworkActivityTest : CSTest() {
+
+ private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener {
+ val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java)
+ verify(netd).registerUnsolicitedEventListener(captor.capture())
+ return captor.value
+ }
+
+ @Test
+ fun testInterfaceClassActivityChanged_NonDefaultNetwork() {
+ val netdUnsolicitedEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val batteryStatsInorder = inOrder(batteryStats)
+
+ val cellNr = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ val cellCb = TestableNetworkCallback()
+ // Request cell network to keep cell network up
+ cm.requestNetwork(cellNr, cellCb)
+
+ val defaultCb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(defaultCb)
+
+ val cellNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+ val cellLp = LinkProperties().apply {
+ interfaceName = DATA_CELL_IFNAME
+ }
+ // Connect Cellular network
+ val cellAgent = Agent(nc = cellNc, lp = cellLp)
+ cellAgent.connect()
+ defaultCb.expectAvailableCallbacks(cellAgent.network, validated = false)
+
+ val wifiNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+ val wifiLp = LinkProperties().apply {
+ interfaceName = WIFI_IFNAME
+ }
+ // Connect Wi-Fi network, Wi-Fi network should be the default network.
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ defaultCb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ batteryStatsInorder.verify(batteryStats).noteWifiRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID))
+
+ val onNetworkActiveCv = ConditionVariable()
+ val listener = ConnectivityManager.OnNetworkActiveListener { onNetworkActiveCv::open }
+ cm.addDefaultNetworkActiveListener(listener)
+
+ // Cellular network (non default network) goes to inactive state.
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ cellAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
+ // Non-default network activity change does not change default network activity
+ // But cellular radio power state is updated
+ assertFalse(onNetworkActiveCv.block(TIMEOUT_MS))
+ context.expectNoDataActivityBroadcast(0 /* timeoutMs */)
+ assertTrue(cm.isDefaultNetworkActive)
+ batteryStatsInorder.verify(batteryStats).noteMobileRadioPowerState(eq(DC_POWER_STATE_LOW),
+ anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID))
+
+ // Cellular network (non default network) goes to active state.
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
+ cellAgent.network.netId, TIMESTAMP, PACKAGE_UID)
+ // Non-default network activity change does not change default network activity
+ // But cellular radio power state is updated
+ assertFalse(onNetworkActiveCv.block(TIMEOUT_MS))
+ context.expectNoDataActivityBroadcast(0 /* timeoutMs */)
+ assertTrue(cm.isDefaultNetworkActive)
+ batteryStatsInorder.verify(batteryStats).noteMobileRadioPowerState(eq(DC_POWER_STATE_HIGH),
+ anyLong() /* timestampNs */, eq(PACKAGE_UID))
+
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(defaultCb)
+ cm.removeDefaultNetworkActiveListener(listener)
+ }
+
+ @Test
+ fun testDataActivityTracking_MultiCellNetwork() {
+ val netdUnsolicitedEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val batteryStatsInorder = inOrder(batteryStats)
+
+ val dataNetworkNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .build()
+ val dataNetworkNr = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ val dataNetworkLp = LinkProperties().apply {
+ interfaceName = DATA_CELL_IFNAME
+ }
+ val dataNetworkCb = TestableNetworkCallback()
+ cm.requestNetwork(dataNetworkNr, dataNetworkCb)
+ val dataNetworkAgent = Agent(nc = dataNetworkNc, lp = dataNetworkLp)
+ val dataNetworkNetId = dataNetworkAgent.network.netId.toString()
+
+ val imsNetworkNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_IMS)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .build()
+ val imsNetworkNr = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_IMS)
+ .build()
+ val imsNetworkLp = LinkProperties().apply {
+ interfaceName = IMS_CELL_IFNAME
+ }
+ val imsNetworkCb = TestableNetworkCallback()
+ cm.requestNetwork(imsNetworkNr, imsNetworkCb)
+ val imsNetworkAgent = Agent(nc = imsNetworkNc, lp = imsNetworkLp)
+ val imsNetworkNetId = imsNetworkAgent.network.netId.toString()
+
+ dataNetworkAgent.connect()
+ dataNetworkCb.expectAvailableCallbacks(dataNetworkAgent.network, validated = false)
+
+ imsNetworkAgent.connect()
+ imsNetworkCb.expectAvailableCallbacks(imsNetworkAgent.network, validated = false)
+
+ // Both cell networks have idleTimers
+ verify(netd).idletimerAddInterface(eq(DATA_CELL_IFNAME), anyInt(), eq(dataNetworkNetId))
+ verify(netd).idletimerAddInterface(eq(IMS_CELL_IFNAME), anyInt(), eq(imsNetworkNetId))
+ verify(netd, never()).idletimerRemoveInterface(eq(DATA_CELL_IFNAME), anyInt(),
+ eq(dataNetworkNetId))
+ verify(netd, never()).idletimerRemoveInterface(eq(IMS_CELL_IFNAME), anyInt(),
+ eq(imsNetworkNetId))
+
+ // Both cell networks go to inactive state
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ imsNetworkAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ dataNetworkAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
+
+ // Data cell network goes to active state. This should update the cellular radio power state
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
+ dataNetworkAgent.network.netId, TIMESTAMP, PACKAGE_UID)
+ batteryStatsInorder.verify(batteryStats, timeout(TIMEOUT_MS)).noteMobileRadioPowerState(
+ eq(DC_POWER_STATE_HIGH), anyLong() /* timestampNs */, eq(PACKAGE_UID))
+ // Ims cell network goes to active state. But this should not update the cellular radio
+ // power state since cellular radio power state is already high
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */,
+ imsNetworkAgent.network.netId, TIMESTAMP, PACKAGE_UID)
+ waitForIdle()
+ batteryStatsInorder.verify(batteryStats, never()).noteMobileRadioPowerState(anyInt(),
+ anyLong() /* timestampNs */, anyInt())
+
+ // Data cell network goes to inactive state. But this should not update the cellular radio
+ // power state ims cell network is still active state
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ dataNetworkAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
+ waitForIdle()
+ batteryStatsInorder.verify(batteryStats, never()).noteMobileRadioPowerState(anyInt(),
+ anyLong() /* timestampNs */, anyInt())
+
+ // Ims cell network goes to inactive state.
+ // This should update the cellular radio power state
+ netdUnsolicitedEventListener.onInterfaceClassActivityChanged(false /* isActive */,
+ imsNetworkAgent.network.netId, TIMESTAMP, NETWORK_ACTIVITY_NO_UID)
+ batteryStatsInorder.verify(batteryStats, timeout(TIMEOUT_MS)).noteMobileRadioPowerState(
+ eq(DC_POWER_STATE_LOW), anyLong() /* timestampNs */, eq(NETWORK_ACTIVITY_NO_UID))
+
+ dataNetworkAgent.disconnect()
+ dataNetworkCb.expect<Lost>(dataNetworkAgent.network)
+ verify(netd).idletimerRemoveInterface(eq(DATA_CELL_IFNAME), anyInt(), eq(dataNetworkNetId))
+
+ imsNetworkAgent.disconnect()
+ imsNetworkCb.expect<Lost>(imsNetworkAgent.network)
+ verify(netd).idletimerRemoveInterface(eq(IMS_CELL_IFNAME), anyInt(), eq(imsNetworkNetId))
+
+ cm.unregisterNetworkCallback(dataNetworkCb)
+ cm.unregisterNetworkCallback(imsNetworkCb)
+ }
+}
+
+internal fun CSContext.expectDataActivityBroadcast(
+ deviceType: Int,
+ isActive: Boolean,
+ tsNanos: Long
+) {
+ assertNotNull(orderedBroadcastAsUserHistory.poll(BROADCAST_TIMEOUT_MS) {
+ intent -> intent.action.equals(ACTION_DATA_ACTIVITY_CHANGE) &&
+ intent.getIntExtra(EXTRA_DEVICE_TYPE, -1) == deviceType &&
+ intent.getBooleanExtra(EXTRA_IS_ACTIVE, !isActive) == isActive &&
+ intent.getLongExtra(EXTRA_REALTIME_NS, -1) == tsNanos
+ })
+}
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 1786edc..f21a428 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -43,6 +43,7 @@
import android.net.PacProxyManager
import android.net.networkstack.NetworkStackClientBase
import android.os.BatteryStatsManager
+import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.os.UserHandle
@@ -54,6 +55,7 @@
import com.android.internal.app.IBatteryStats
import com.android.internal.util.test.BroadcastInterceptingContext
import com.android.modules.utils.build.SdkLevel
+import com.android.net.module.util.ArrayTrackRecord
import com.android.networkstack.apishim.common.UnsupportedApiLevelException
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
@@ -64,14 +66,16 @@
import com.android.server.connectivity.ProxyTracker
import com.android.testutils.visibleOnHandlerThread
import com.android.testutils.waitForIdle
+import java.util.concurrent.Executors
+import kotlin.test.assertNull
+import kotlin.test.fail
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 BROADCAST_TIMEOUT_MS = 3_000L
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")
@@ -155,7 +159,8 @@
val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
val alarmManager = makeMockAlarmManager()
val systemConfigManager = makeMockSystemConfigManager()
- val batteryManager = BatteryStatsManager(mock<IBatteryStats>())
+ val batteryStats = mock<IBatteryStats>()
+ val batteryManager = BatteryStatsManager(batteryStats)
val telephonyManager = mock<TelephonyManager>().also {
doReturn(true).`when`(it).isDataCapable()
}
@@ -285,6 +290,26 @@
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
else -> super.getSystemService(serviceName)
}
+
+ internal val orderedBroadcastAsUserHistory = ArrayTrackRecord<Intent>().newReadHead()
+
+ fun expectNoDataActivityBroadcast(timeoutMs: Int) {
+ assertNull(orderedBroadcastAsUserHistory.poll(
+ timeoutMs.toLong()) { intent -> true })
+ }
+
+ override fun sendOrderedBroadcastAsUser(
+ intent: Intent,
+ user: UserHandle,
+ receiverPermission: String?,
+ resultReceiver: BroadcastReceiver?,
+ scheduler: Handler?,
+ initialCode: Int,
+ initialData: String?,
+ initialExtras: Bundle?
+ ) {
+ orderedBroadcastAsUserHistory.add(intent)
+ }
}
// Utility methods for subclasses to use