diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 1837b8426c..f904cd141e 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -1770,7 +1770,12 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + 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 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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 mActiveIdleTimers = new SparseArray<>(); + private final boolean mTrackMultiNetworkActivities; + // Store netIds of Wi-Fi networks whose idletimers report that they are active + private final Set mActiveWifiNetworks = new ArraySet<>(); + // Store netIds of cellular networks whose idletimers report that they are active + private final Set mActiveCellularNetworks = new ArraySet<>(); private static class IdleTimerParams { public final int timeout; @@ -11810,10 +11843,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 class ConnectivityService extends IConnectivityManager.Stub } } - 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 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); } } @@ -11891,6 +12003,30 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * 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. * @@ -11900,13 +12036,17 @@ public class ConnectivityService extends IConnectivityManager.Stub * @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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * 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 class ConnectivityService extends IConnectivityManager.Stub } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 97e134adab..b8cf08e609 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.VALID 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 @@ public class ConnectivityServiceTest { // 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 mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -10807,6 +10809,11 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { } 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { } @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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // Make network active netdUnsolicitedEventListener.onInterfaceClassActivityChanged(true /* isActive */, - transportType, TIMESTAMP, TEST_PACKAGE_UID); + idleTimerLabel, TIMESTAMP, TEST_PACKAGE_UID); waitForIdle(); if (expectDelay) { @@ -18783,8 +18888,8 @@ public class ConnectivityServiceTest { @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 class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 0000000000..526ec9d982 --- /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(dataNetworkAgent.network) + verify(netd).idletimerRemoveInterface(eq(DATA_CELL_IFNAME), anyInt(), eq(dataNetworkNetId)) + + imsNetworkAgent.disconnect() + imsNetworkCb.expect(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 1786edcec0..f21a42840c 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.NetworkScore 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 androidx.test.platform.app.InstrumentationRegistry 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.MultinetworkPolicyTrackerTestDependencies 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 @@ open class CSTest { val proxyTracker = ProxyTracker(context, mock(), 16 /* EVENT_PROXY_HAS_CHANGED */) val alarmManager = makeMockAlarmManager() val systemConfigManager = makeMockSystemConfigManager() - val batteryManager = BatteryStatsManager(mock()) + val batteryStats = mock() + val batteryManager = BatteryStatsManager(batteryStats) val telephonyManager = mock().also { doReturn(true).`when`(it).isDataCapable() } @@ -285,6 +290,26 @@ open class CSTest { Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked else -> super.getSystemService(serviceName) } + + internal val orderedBroadcastAsUserHistory = ArrayTrackRecord().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