Reduce persist threshold for lower warning/limit.

Default is 2MB persist threshold, but even that can be substantial
for devices on 100MB/month plans. This change gradually reduces the
persist threshold up to 8x lower (256kb outstanding) based on lowest
active policy.

Bug: 5382676
Change-Id: Ief4e8cdb169bfb151a3d1b45722a8eaa01926508
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 08d4c6c..b7b8731 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -42,5 +42,7 @@
     void setUidForeground(int uid, boolean uidForeground);
     /** Force update of statistics. */
     void forceUpdate();
+    /** Advise persistance threshold; may be overridden internally. */
+    void advisePersistThreshold(long thresholdBytes);
 
 }
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
index 2892a74..c2e475a 100644
--- a/services/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -186,12 +186,12 @@
         if (history.size() == 0) return;
         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
 
-        final NetworkStatsHistory existing = mStats.get(key);
-        if (existing != null) {
-            existing.recordEntireHistory(history);
-        } else {
-            mStats.put(key, history);
+        NetworkStatsHistory target = mStats.get(key);
+        if (target == null) {
+            target = new NetworkStatsHistory(history.getBucketDuration());
+            mStats.put(key, target);
         }
+        target.recordEntireHistory(history);
     }
 
     /**
diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java
index 743a746..2ce7771 100644
--- a/services/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/java/com/android/server/net/NetworkStatsRecorder.java
@@ -17,6 +17,8 @@
 package com.android.server.net;
 
 import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.KB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.net.NetworkStats;
@@ -25,6 +27,7 @@
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Slog;
 
 import com.android.internal.util.FileRotator;
@@ -58,9 +61,9 @@
     private final String mCookie;
 
     private final long mBucketDuration;
-    private final long mPersistThresholdBytes;
     private final boolean mOnlyTags;
 
+    private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
     private NetworkStats mLastSnapshot;
 
     private final NetworkStatsCollection mPending;
@@ -71,13 +74,12 @@
     private WeakReference<NetworkStatsCollection> mComplete;
 
     public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
-            String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) {
+            String cookie, long bucketDuration, boolean onlyTags) {
         mRotator = checkNotNull(rotator, "missing FileRotator");
         mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
         mCookie = cookie;
 
         mBucketDuration = bucketDuration;
-        mPersistThresholdBytes = persistThresholdBytes;
         mOnlyTags = onlyTags;
 
         mPending = new NetworkStatsCollection(bucketDuration);
@@ -86,6 +88,12 @@
         mPendingRewriter = new CombiningRewriter(mPending);
     }
 
+    public void setPersistThreshold(long thresholdBytes) {
+        if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
+        mPersistThresholdBytes = MathUtils.constrain(
+                thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
+    }
+
     public void resetLocked() {
         mLastSnapshot = null;
         mPending.reset();
@@ -153,7 +161,7 @@
                 continue;
             }
 
-            // skip when no delta occured
+            // skip when no delta occurred
             if (entry.isEmpty()) continue;
 
             // only record tag data when requested
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 6d6f59c..a9d4b59 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -36,6 +36,7 @@
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE;
@@ -49,6 +50,10 @@
 import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE;
 import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES;
 import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_UID_TAG_BUCKET_DURATION;
+import static android.provider.Settings.Secure.NETSTATS_UID_TAG_DELETE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_UID_TAG_PERSIST_BYTES;
+import static android.provider.Settings.Secure.NETSTATS_UID_TAG_ROTATE_AGE;
 import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
 import static android.telephony.PhoneStateListener.LISTEN_NONE;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -94,10 +99,12 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -169,19 +176,15 @@
     public interface NetworkStatsSettings {
         public long getPollInterval();
         public long getTimeCacheMaxAge();
-        public long getGlobalAlertBytes();
         public boolean getSampleEnabled();
 
         public static class Config {
             public final long bucketDuration;
-            public final long persistBytes;
             public final long rotateAgeMillis;
             public final long deleteAgeMillis;
 
-            public Config(long bucketDuration, long persistBytes, long rotateAgeMillis,
-                    long deleteAgeMillis) {
+            public Config(long bucketDuration, long rotateAgeMillis, long deleteAgeMillis) {
                 this.bucketDuration = bucketDuration;
-                this.persistBytes = persistBytes;
                 this.rotateAgeMillis = rotateAgeMillis;
                 this.deleteAgeMillis = deleteAgeMillis;
             }
@@ -191,6 +194,12 @@
         public Config getXtConfig();
         public Config getUidConfig();
         public Config getUidTagConfig();
+
+        public long getGlobalAlertBytes(long def);
+        public long getDevPersistBytes(long def);
+        public long getXtPersistBytes(long def);
+        public long getUidPersistBytes(long def);
+        public long getUidTagPersistBytes(long def);
     }
 
     private final Object mStatsLock = new Object();
@@ -223,6 +232,8 @@
     private final Handler mHandler;
 
     private boolean mSystemReady;
+    private long mPersistThreshold = 2 * MB_IN_BYTES;
+    private long mGlobalAlertBytes;
 
     public NetworkStatsService(
             Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
@@ -275,6 +286,8 @@
         mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
         mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
 
+        updatePersistThresholds();
+
         synchronized (mStatsLock) {
             // upgrade any legacy stats, migrating them to rotated files
             maybeUpgradeLegacyStatsLocked();
@@ -325,10 +338,9 @@
 
     private NetworkStatsRecorder buildRecorder(
             String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
-        return new NetworkStatsRecorder(
-                new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
-                mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes,
-                includeTags);
+        return new NetworkStatsRecorder(new FileRotator(
+                mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+                mNonMonotonicObserver, prefix, config.bucketDuration, includeTags);
     }
 
     private void shutdownLocked() {
@@ -414,8 +426,7 @@
      */
     private void registerGlobalAlert() {
         try {
-            final long alertBytes = mSettings.getGlobalAlertBytes();
-            mNetworkManager.setGlobalAlert(alertBytes);
+            mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
         } catch (IllegalStateException e) {
             Slog.w(TAG, "problem registering for global alert: " + e);
         } catch (RemoteException e) {
@@ -437,14 +448,18 @@
 
             private NetworkStatsCollection getUidComplete() {
                 if (mUidComplete == null) {
-                    mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
+                    synchronized (mStatsLock) {
+                        mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
+                    }
                 }
                 return mUidComplete;
             }
 
             private NetworkStatsCollection getUidTagComplete() {
                 if (mUidTagComplete == null) {
-                    mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
+                    synchronized (mStatsLock) {
+                        mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
+                    }
                 }
                 return mUidTagComplete;
             }
@@ -584,6 +599,45 @@
         }
     }
 
+    @Override
+    public void advisePersistThreshold(long thresholdBytes) {
+        mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
+        assertBandwidthControlEnabled();
+
+        // clamp threshold into safe range
+        mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
+        updatePersistThresholds();
+
+        if (LOGV) {
+            Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+                    + mPersistThreshold);
+        }
+
+        // persist if beyond new thresholds
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+        mDevRecorder.maybePersistLocked(currentTime);
+        mXtRecorder.maybePersistLocked(currentTime);
+        mUidRecorder.maybePersistLocked(currentTime);
+        mUidTagRecorder.maybePersistLocked(currentTime);
+
+        // re-arm global alert
+        registerGlobalAlert();
+    }
+
+    /**
+     * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
+     * reflect current {@link #mPersistThreshold} value. Always defers to
+     * {@link Secure} values when defined.
+     */
+    private void updatePersistThresholds() {
+        mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
+        mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
+        mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
+        mUidTagRecorder.setPersistThreshold(mSettings.getUidTagPersistBytes(mPersistThreshold));
+        mGlobalAlertBytes = mSettings.getGlobalAlertBytes(mPersistThreshold);
+    }
+
     /**
      * Receiver that watches for {@link IConnectivityManager} to claim network
      * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
@@ -1122,41 +1176,50 @@
             return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
         }
         @Override
-        public long getGlobalAlertBytes() {
-            return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES);
+        public long getGlobalAlertBytes(long def) {
+            return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
         }
         @Override
         public boolean getSampleEnabled() {
             return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true);
         }
-
         @Override
         public Config getDevConfig() {
             return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES),
                     getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
                     getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
         }
-
         @Override
         public Config getXtConfig() {
             return getDevConfig();
         }
-
         @Override
         public Config getUidConfig() {
             return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
                     getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
                     getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
         }
-
         @Override
         public Config getUidTagConfig() {
-            return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
-                    getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS));
+            return new Config(getSecureLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+                    getSecureLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
+                    getSecureLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
+        }
+        @Override
+        public long getDevPersistBytes(long def) {
+            return getSecureLong(NETSTATS_DEV_PERSIST_BYTES, def);
+        }
+        @Override
+        public long getXtPersistBytes(long def) {
+            return getDevPersistBytes(def);
+        }
+        @Override
+        public long getUidPersistBytes(long def) {
+            return getSecureLong(NETSTATS_UID_PERSIST_BYTES, def);
+        }
+        @Override
+        public long getUidTagPersistBytes(long def) {
+            return getSecureLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
         }
     }
 }