diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl index 4078b249218c3..74c3ba44b69e8 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl @@ -23,6 +23,6 @@ package android.net.netstats.provider; */ oneway interface INetworkStatsProvider { void onRequestStatsUpdate(int token); - void onSetLimit(String iface, long quotaBytes); void onSetAlert(long quotaBytes); + void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes); } diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl index bd336dd348fe0..7eaa01e262fec 100644 --- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl +++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl @@ -26,6 +26,6 @@ import android.net.NetworkStats; oneway interface INetworkStatsProviderCallback { void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats); void notifyAlertReached(); - void notifyLimitReached(); + void notifyWarningOrLimitReached(); void unregister(); } diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java index 7639d2244cfe4..65b336ad6fce3 100644 --- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java +++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java @@ -29,7 +29,8 @@ import android.os.RemoteException; @SystemApi public abstract class NetworkStatsProvider { /** - * A value used by {@link #onSetLimit} and {@link #onSetAlert} indicates there is no limit. + * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit} + * indicates there is no limit. */ public static final int QUOTA_UNLIMITED = -1; @@ -42,13 +43,13 @@ public abstract class NetworkStatsProvider { } @Override - public void onSetLimit(String iface, long quotaBytes) { - NetworkStatsProvider.this.onSetLimit(iface, quotaBytes); + public void onSetAlert(long quotaBytes) { + NetworkStatsProvider.this.onSetAlert(quotaBytes); } @Override - public void onSetAlert(long quotaBytes) { - NetworkStatsProvider.this.onSetAlert(quotaBytes); + public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) { + NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes); } }; @@ -145,11 +146,28 @@ public abstract class NetworkStatsProvider { } /** - * Notify system that the quota set by {@code onSetLimit} has been reached. + * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached. + * + * @hide + */ + // TODO: Expose as system API. + public void notifyWarningReached() { + try { + // Reuse the code path to notify warning reached with limit reached + // since framework handles them in the same way. + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@link #onSetLimit} or limit set by + * {@link #onSetWarningAndLimit} has been reached. */ public void notifyLimitReached() { try { - getProviderCallbackBinderOrThrow().notifyLimitReached(); + getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -180,8 +198,34 @@ public abstract class NetworkStatsProvider { * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. */ + // TODO: deprecate this once onSetWarningAndLimit is ready. public abstract void onSetLimit(@NonNull String iface, long quotaBytes); + /** + * Called by {@code NetworkStatsService} when setting the interface quotas for the specified + * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system + * will not call {@link #onSetLimit}. When this method is called, the implementation + * should behave as follows: + * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}. + * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on + * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}. + * + * @param iface the interface requiring the operation. + * @param warningBytes the warning defined as the number of bytes, starting from zero and + * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates + * there is no warning. + * @param limitBytes the limit defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. + * + * @hide + */ + // TODO: Expose as system API. + public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) { + // Backward compatibility for those who didn't override this function. + onSetLimit(iface, limitBytes); + } + /** * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations * MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 48f1ddb023fd4..7bd9a9ff7e5c2 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -131,9 +131,10 @@ public abstract class NetworkPolicyManagerInternal { /** * Notifies that the specified {@link NetworkStatsProvider} has reached its quota - * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)}. + * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or + * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}. * * @param tag the human readable identifier of the custom network stats provider. */ - public abstract void onStatsProviderLimitReached(@NonNull String tag); + public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d6557f6410ecc..e9530def317ec 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -383,15 +383,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_LIMIT_REACHED = 5; private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7; - private static final int MSG_UPDATE_INTERFACE_QUOTA = 10; - private static final int MSG_REMOVE_INTERFACE_QUOTA = 11; + private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10; + private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11; private static final int MSG_POLICIES_CHANGED = 13; private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15; private static final int MSG_SUBSCRIPTION_OVERRIDE = 16; private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17; private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18; private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19; - private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20; + private static final int MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED = 20; private static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; @@ -518,8 +518,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final SparseBooleanArray mRestrictBackgroundWhitelistRevokedUids = new SparseBooleanArray(); + final Object mMeteredIfacesLock = new Object(); /** Set of ifaces that are metered. */ - @GuardedBy("mNetworkPoliciesSecondLock") + @GuardedBy("mMeteredIfacesLock") private ArraySet mMeteredIfaces = new ArraySet<>(); /** Set of over-limit templates that have been notified. */ @GuardedBy("mNetworkPoliciesSecondLock") @@ -1908,39 +1909,45 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED; final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED; - if (hasLimit || policy.metered) { - final long quotaBytes; - if (hasLimit && policy.hasCycle()) { - final Pair cycle = NetworkPolicyManager - .cycleIterator(policy).next(); - final long start = cycle.first.toInstant().toEpochMilli(); - final long end = cycle.second.toInstant().toEpochMilli(); - final long totalBytes = getTotalBytes(policy.template, start, end); + long limitBytes = Long.MAX_VALUE; + long warningBytes = Long.MAX_VALUE; + if ((hasLimit || hasWarning) && policy.hasCycle()) { + final Pair cycle = NetworkPolicyManager + .cycleIterator(policy).next(); + final long start = cycle.first.toInstant().toEpochMilli(); + final long end = cycle.second.toInstant().toEpochMilli(); + final long totalBytes = getTotalBytes(policy.template, start, end); - if (policy.lastLimitSnooze >= start) { - // snoozing past quota, but we still need to restrict apps, - // so push really high quota. - quotaBytes = Long.MAX_VALUE; - } else { - // remaining "quota" bytes are based on total usage in - // current cycle. kernel doesn't like 0-byte rules, so we - // set 1-byte quota and disable the radio later. - quotaBytes = Math.max(1, policy.limitBytes - totalBytes); - } - } else { - // metered network, but no policy limit; we still need to - // restrict apps, so push really high quota. - quotaBytes = Long.MAX_VALUE; + // If the limit notification is not snoozed, the limit quota needs to be calculated. + if (hasLimit && policy.lastLimitSnooze < start) { + // remaining "quota" bytes are based on total usage in + // current cycle. kernel doesn't like 0-byte rules, so we + // set 1-byte quota and disable the radio later. + limitBytes = Math.max(1, policy.limitBytes - totalBytes); } + // If the warning notification was snoozed by user, or the service already knows + // it is over warning bytes, doesn't need to calculate warning bytes. + if (hasWarning && policy.lastWarningSnooze < start + && !policy.isOverWarning(totalBytes)) { + warningBytes = Math.max(1, policy.warningBytes - totalBytes); + } + } + + if (hasWarning || hasLimit || policy.metered) { if (matchingIfaces.size() > 1) { // TODO: switch to shared quota once NMS supports Slog.w(TAG, "shared quota unsupported; generating rule for each iface"); } + // Set the interface warning and limit. For interfaces which has no cycle, + // or metered with no policy quotas, or snoozed notification; we still need to put + // iptables rule hooks to restrict apps for data saver, so push really high quota. + // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to + // providers. for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); - setInterfaceQuotaAsync(iface, quotaBytes); + setInterfaceQuotasAsync(iface, warningBytes, limitBytes); newMeteredIfaces.add(iface); } } @@ -1964,7 +1971,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); if (!newMeteredIfaces.contains(iface)) { - setInterfaceQuotaAsync(iface, Long.MAX_VALUE); + setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE); newMeteredIfaces.add(iface); } } @@ -1972,13 +1979,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // Remove quota from any interfaces that are no longer metered. - for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) { - final String iface = mMeteredIfaces.valueAt(i); - if (!newMeteredIfaces.contains(iface)) { - removeInterfaceQuotaAsync(iface); + synchronized (mMeteredIfacesLock) { + for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) { + final String iface = mMeteredIfaces.valueAt(i); + if (!newMeteredIfaces.contains(iface)) { + removeInterfaceQuotasAsync(iface); + } } + mMeteredIfaces = newMeteredIfaces; } - mMeteredIfaces = newMeteredIfaces; final ContentResolver cr = mContext.getContentResolver(); final boolean quotaEnabled = Settings.Global.getInt(cr, @@ -2030,7 +2039,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mSubscriptionOpportunisticQuota.put(subId, quotaBytes); } - final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]); + final String[] meteredIfaces; + synchronized (mMeteredIfacesLock) { + meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]); + } mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget(); mHandler.obtainMessage(MSG_ADVISE_PERSIST_THRESHOLD, lowestRule).sendToTarget(); @@ -3436,7 +3448,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.print("Restrict background: "); fout.println(mRestrictBackground); fout.print("Restrict power: "); fout.println(mRestrictPower); fout.print("Device idle: "); fout.println(mDeviceIdleMode); - fout.print("Metered ifaces: "); fout.println(mMeteredIfaces); + synchronized (mMeteredIfacesLock) { + fout.print("Metered ifaces: "); + fout.println(mMeteredIfaces); + } fout.println(); fout.print("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -4618,7 +4633,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mListeners.finishBroadcast(); return true; } - case MSG_STATS_PROVIDER_LIMIT_REACHED: { + case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: { mNetworkStats.forceUpdate(); synchronized (mNetworkPoliciesSecondLock) { @@ -4632,7 +4647,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } case MSG_LIMIT_REACHED: { final String iface = (String) msg.obj; - synchronized (mNetworkPoliciesSecondLock) { + synchronized (mMeteredIfacesLock) { // fast return if not needed. if (!mMeteredIfaces.contains(iface)) { return true; @@ -4689,19 +4704,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkStats.advisePersistThreshold(persistThreshold); return true; } - case MSG_UPDATE_INTERFACE_QUOTA: { - final String iface = (String) msg.obj; - // int params need to be stitched back into a long - final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); - removeInterfaceQuota(iface); - setInterfaceQuota(iface, quota); - mNetworkStats.setStatsProviderLimitAsync(iface, quota); + case MSG_UPDATE_INTERFACE_QUOTAS: { + final IfaceQuotas val = (IfaceQuotas) msg.obj; + // TODO: Consider set a new limit before removing the original one. + removeInterfaceLimit(val.iface); + setInterfaceLimit(val.iface, val.limit); + mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning, + val.limit); return true; } - case MSG_REMOVE_INTERFACE_QUOTA: { + case MSG_REMOVE_INTERFACE_QUOTAS: { final String iface = (String) msg.obj; - removeInterfaceQuota(iface); - mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED); + removeInterfaceLimit(iface); + mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED, + QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { @@ -4829,15 +4845,32 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void setInterfaceQuotaAsync(String iface, long quotaBytes) { - // long quotaBytes split up into two ints to fit in message - mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32), - (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget(); + private static final class IfaceQuotas { + @NonNull public final String iface; + // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE + // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules. + public final long warning; + public final long limit; + + private IfaceQuotas(@NonNull String iface, long warning, long limit) { + this.iface = iface; + this.warning = warning; + this.limit = limit; + } } - private void setInterfaceQuota(String iface, long quotaBytes) { + private void setInterfaceQuotasAsync(@NonNull String iface, + long warningBytes, long limitBytes) { + mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS, + new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget(); + } + + private void setInterfaceLimit(String iface, long limitBytes) { try { - mNetworkManager.setInterfaceQuota(iface, quotaBytes); + // For legacy design the data warning is covered by global alert, where the + // kernel will notify upper layer for a small amount of change of traffic + // statistics. Thus, passing warning is not needed. + mNetworkManager.setInterfaceQuota(iface, limitBytes); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting interface quota", e); } catch (RemoteException e) { @@ -4845,11 +4878,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void removeInterfaceQuotaAsync(String iface) { - mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget(); + private void removeInterfaceQuotasAsync(String iface) { + mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget(); } - private void removeInterfaceQuota(String iface) { + private void removeInterfaceLimit(String iface) { try { mNetworkManager.removeInterfaceQuota(iface); } catch (IllegalStateException e) { @@ -5274,7 +5307,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { isBackgroundRestricted = mRestrictBackground; } final boolean isNetworkMetered; - synchronized (mNetworkPoliciesSecondLock) { + synchronized (mMeteredIfacesLock) { isNetworkMetered = mMeteredIfaces.contains(ifname); } final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, @@ -5361,9 +5394,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void onStatsProviderLimitReached(@NonNull String tag) { - Log.v(TAG, "onStatsProviderLimitReached: " + tag); - mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget(); + public void onStatsProviderWarningOrLimitReached(@NonNull String tag) { + Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag); + mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget(); } } diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 0cb0bc2c08960..0e9a9da6804b4 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -37,8 +37,9 @@ public abstract class NetworkStatsManagerInternal { public abstract void forceUpdate(); /** - * Set the quota limit to all registered custom network stats providers. + * Set the warning and limit to all registered custom network stats providers. * Note that invocation of any interface will be sent to all providers. */ - public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota); + public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, + long limit); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index ba9f486092f7f..0bc20540a7595 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1658,9 +1658,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public void setStatsProviderLimitAsync(@NonNull String iface, long quota) { - if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")"); - invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetLimit(iface, quota)); + public void setStatsProviderWarningAndLimitAsync( + @NonNull String iface, long warning, long limit) { + if (LOGV) { + Slog.v(TAG, "setStatsProviderWarningAndLimitAsync(" + + iface + "," + warning + "," + limit + ")"); + } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, + warning, limit)); } } @@ -2051,10 +2056,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public void notifyLimitReached() { - Log.d(TAG, mTag + ": onLimitReached"); + public void notifyWarningOrLimitReached() { + Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); LocalServices.getService(NetworkPolicyManagerInternal.class) - .onStatsProviderLimitReached(mTag); + .onStatsProviderWarningOrLimitReached(mTag); } @Override diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 128177b073b08..3ccc8868f6fb2 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1760,57 +1760,75 @@ public class NetworkPolicyManagerServiceTest { true); } - /** - * Test that when StatsProvider triggers limit reached, new limit will be calculated and - * re-armed. - */ - @Test - public void testStatsProviderLimitReached() throws Exception { - final int CYCLE_DAY = 15; - - final NetworkStats stats = new NetworkStats(0L, 1); + private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 2999, 1, 2000, 1, 0); + rxBytes, 1, txBytes, 1, 0); when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) .thenReturn(stats.getTotalBytes()); when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) .thenReturn(stats); + } + + private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + npmi.onStatsProviderWarningOrLimitReached("TEST"); + // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. + postMsgAndWaitForCompletion(); + verify(mStatsService).forceUpdate(); + // Wait for processing of MSG_*_INTERFACE_QUOTAS. + postMsgAndWaitForCompletion(); + } + + /** + * Test that when StatsProvider triggers warning and limit reached, new quotas will be + * calculated and re-armed. + */ + @Test + public void testStatsProviderWarningAndLimitReached() throws Exception { + final int CYCLE_DAY = 15; + + final NetworkStats stats = new NetworkStats(0L, 1); + increaseMockedTotalBytes(stats, 2999, 2000); // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, + Long.MAX_VALUE); - // Set limit to 10KB. + // Set warning to 7KB and limit to 10KB. setNetworkPolicies(new NetworkPolicy( - sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L, - true)); + sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true)); postMsgAndWaitForCompletion(); - // Verifies that remaining quota is set to providers. - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L); - + // Verifies that remaining quotas are set to providers. + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); reset(mStatsService); - // Increase the usage. - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 1000, 1, 999, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + // Increase the usage and simulates that limit reached fires earlier by provider, + // but actually the quota is not yet reached. Verifies that the limit reached leads to + // a force update and new quotas should be set. + increaseMockedTotalBytes(stats, 1000, 999); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); + reset(mStatsService); - // Simulates that limit reached fires earlier by provider, but actually the quota is not - // yet reached. - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); - npmi.onStatsProviderLimitReached("TEST"); + // Increase the usage and simulate warning reached, the new warning should be unlimited + // since service will disable warning quota to stop lower layer from keep triggering + // warning reached event. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync( + TEST_IFACE, Long.MAX_VALUE, 1002L); + reset(mStatsService); - // Verifies that the limit reached leads to a force update and new limit should be set. - postMsgAndWaitForCompletion(); - verify(mStatsService).forceUpdate(); - postMsgAndWaitForCompletion(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L); + // Increase the usage that over the warning and limit, the new limit should set to 1 to + // block the network traffic. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); + reset(mStatsService); } /**