Track some event history and include it in netpolicy dump.

Bug: 66921847
Test: manual
Change-Id: I0c473790f83076def807308fe44db9cb9365769e
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index c22be2c..9a6e542 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@
         mBuffer[indexOf(mCursor++)] = t;
     }
 
+    /**
+     * Returns object of type <T> at the next writable slot, creating one if it is not already
+     * available. In case of any errors while creating the object, <code>null</code> will
+     * be returned.
+     */
+    public T getNextSlot() {
+        final int nextSlotIdx = indexOf(mCursor++);
+        T item = mBuffer[nextSlotIdx];
+        if (item == null) {
+            try {
+                item = (T) mBuffer.getClass().getComponentType().newInstance();
+            } catch (IllegalAccessException | InstantiationException e) {
+                return null;
+            }
+            mBuffer[nextSlotIdx] = item;
+        }
+        return item;
+    }
+
     public T[] toArray() {
         // Only generic way to create a T[] from another T[]
         T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1a19601..6fb3dbb 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@
 /**
  * Activity manager code dealing with processes.
  */
-final class ProcessList {
+public final class ProcessList {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
     // The minimum time we allow between crashes, for us to consider this
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 0000000..2bd9cab
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2017 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.net;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+    static final String TAG = "NetworkPolicy";
+
+    static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int MAX_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+    private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+    private static final int EVENT_TYPE_GENERIC = 0;
+    private static final int EVENT_NETWORK_BLOCKED = 1;
+    private static final int EVENT_UID_STATE_CHANGED = 2;
+    private static final int EVENT_POLICIES_CHANGED = 3;
+    private static final int EVENT_METEREDNESS_CHANGED = 4;
+    private static final int EVENT_USER_STATE_REMOVED = 5;
+    private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+    private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+    private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+    private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+    private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+    private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+    private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+    static final int NTWK_BLOCKED_POWER = 0;
+    static final int NTWK_ALLOWED_NON_METERED = 1;
+    static final int NTWK_BLOCKED_BLACKLIST = 2;
+    static final int NTWK_ALLOWED_WHITELIST = 3;
+    static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+    static final int NTWK_ALLOWED_DEFAULT = 6;
+
+    private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+    private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+    private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+    private final Object mLock = new Object();
+
+    void networkBlocked(int uid, int reason) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+            mNetworkBlockedBuffer.networkBlocked(uid, reason);
+        }
+    }
+
+    void uidStateChanged(int uid, int procState, long procStateSeq) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG,
+                    uid + " state changed to " + procState + " with seq=" + procStateSeq);
+            mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+        }
+    }
+
+    void event(String msg) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, msg);
+            mEventsBuffer.event(msg);
+        }
+    }
+
+    void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+            mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+        }
+    }
+
+    void meterednessChanged(int netId, boolean newMetered) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+            mEventsBuffer.meterednessChanged(netId, newMetered);
+        }
+    }
+
+    void removingUserState(int userId) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+            mEventsBuffer.userRemoved(userId);
+        }
+    }
+
+    void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG,
+                    getRestrictBackgroundChangedLog(oldValue, newValue));
+            mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+        }
+    }
+
+    void deviceIdleModeEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+            mEventsBuffer.deviceIdleModeEnabled(enabled);
+        }
+    }
+
+    void appIdleStateChanged(int uid, boolean idle) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+            mEventsBuffer.appIdleStateChanged(uid, idle);
+        }
+    }
+
+    void paroleStateChanged(boolean paroleOn) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+            mEventsBuffer.paroleStateChanged(paroleOn);
+        }
+    }
+
+    void tempPowerSaveWlChanged(int appId, boolean added) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+            mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+        }
+    }
+
+    void uidFirewallRuleChanged(int chain, int uid, int rule) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+            mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+        }
+    }
+
+    void firewallChainEnabled(int chain, boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+            mEventsBuffer.firewallChainEnabled(chain, enabled);
+        }
+    }
+
+    void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+        synchronized (mLock) {
+            final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+                    + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+            if (LOGD) Slog.d(TAG, log);
+            mEventsBuffer.event(log);
+        }
+    }
+
+    void dumpLogs(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("mEventLogs (most recent first):");
+            pw.increaseIndent();
+            mEventsBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mNetworkBlockedLogs (most recent first):");
+            pw.increaseIndent();
+            mNetworkBlockedBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mUidStateChangeLogs (most recent first):");
+            pw.increaseIndent();
+            mUidStateChangeBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+        }
+    }
+
+    private static String getBlockedReason(int reason) {
+        switch (reason) {
+            case NTWK_BLOCKED_POWER:
+                return "blocked by power restrictions";
+            case NTWK_ALLOWED_NON_METERED:
+                return "allowed on unmetered network";
+            case NTWK_BLOCKED_BLACKLIST:
+                return "blacklisted on metered network";
+            case NTWK_ALLOWED_WHITELIST:
+                return "whitelisted on metered network";
+            case NTWK_ALLOWED_TMP_WHITELIST:
+                return "temporary whitelisted on metered network";
+            case NTWK_BLOCKED_BG_RESTRICT:
+                return "blocked when background is restricted";
+            case NTWK_ALLOWED_DEFAULT:
+                return "allowed by default";
+            default:
+                return String.valueOf(reason);
+        }
+    }
+
+    private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+        return "Policy for " + uid + " changed from "
+                + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+                + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+    }
+
+    private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+        return "Meteredness of netId=" + netId + " changed to " + newMetered;
+    }
+
+    private static String getUserRemovedLog(int userId) {
+        return "Remove state for u" + userId;
+    }
+
+    private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+        return "Changed restrictBackground: " + oldValue + "->" + newValue;
+    }
+
+    private static String getDeviceIdleModeEnabled(boolean enabled) {
+        return "DeviceIdleMode enabled: " + enabled;
+    }
+
+    private static String getAppIdleChangedLog(int uid, boolean idle) {
+        return "App idle state of uid " + uid + ": " + idle;
+    }
+
+    private static String getParoleStateChanged(boolean paroleOn) {
+        return "Parole state: " + paroleOn;
+    }
+
+    private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+        return "temp-power-save whitelist for " + appId + " changed to: " + added;
+    }
+
+    private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+        return String.format("Firewall rule changed: %d-%s-%s",
+                uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+    }
+
+    private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+        return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+    }
+
+    private static String getFirewallChainName(int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return FIREWALL_CHAIN_NAME_DOZABLE;
+            case FIREWALL_CHAIN_STANDBY:
+                return FIREWALL_CHAIN_NAME_STANDBY;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return FIREWALL_CHAIN_NAME_POWERSAVE;
+            default:
+                return String.valueOf(chain);
+        }
+    }
+
+    private static String getFirewallRuleName(int rule) {
+        switch (rule) {
+            case FIREWALL_RULE_DEFAULT:
+                return "default";
+            case FIREWALL_RULE_ALLOW:
+                return "allow";
+            case FIREWALL_RULE_DENY:
+                return "deny";
+            default:
+                return String.valueOf(rule);
+        }
+    }
+
+    private final static class LogBuffer extends RingBuffer<Data> {
+        private static final SimpleDateFormat sFormatter
+                = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+        private static final Date sDate = new Date();
+
+        public LogBuffer(int capacity) {
+            super(Data.class, capacity);
+        }
+
+        public void uidStateChanged(int uid, int procState, long procStateSeq) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = procState;
+            data.lfield1 = procStateSeq;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void event(String msg) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TYPE_GENERIC;
+            data.sfield1 = msg;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void networkBlocked(int uid, int reason) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_NETWORK_BLOCKED;
+            data.ifield1 = uid;
+            data.ifield2 = reason;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_POLICIES_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = oldPolicy;
+            data.ifield3 = newPolicy;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void meterednessChanged(int netId, boolean newMetered) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_METEREDNESS_CHANGED;
+            data.ifield1 = netId;
+            data.bfield1 = newMetered;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void userRemoved(int userId) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_USER_STATE_REMOVED;
+            data.ifield1 = userId;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_RESTRICT_BG_CHANGED;
+            data.bfield1 = oldValue;
+            data.bfield2 = newValue;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void deviceIdleModeEnabled(boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void appIdleStateChanged(int uid, boolean idle) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_APP_IDLE_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.bfield1 = idle;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void paroleStateChanged(boolean paroleOn) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_PAROLE_STATE_CHANGED;
+            data.bfield1 = paroleOn;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void tempPowerSaveWlChanged(int appId, boolean added) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+            data.ifield1 = appId;
+            data.bfield1 = added;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+            data.ifield1 = chain;
+            data.ifield2 = uid;
+            data.ifield3 = rule;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void firewallChainEnabled(int chain, boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+            data.ifield1 = chain;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void reverseDump(IndentingPrintWriter pw) {
+            final Data[] allData = toArray();
+            for (int i = allData.length - 1; i >= 0; --i) {
+                if (allData[i] == null) {
+                    pw.println("NULL");
+                    continue;
+                }
+                pw.print(formatDate(allData[i].timeStamp));
+                pw.print(" - ");
+                pw.println(getContent(allData[i]));
+            }
+        }
+
+        public String getContent(Data data) {
+            switch (data.type) {
+                case EVENT_TYPE_GENERIC:
+                    return data.sfield1;
+                case EVENT_NETWORK_BLOCKED:
+                    return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+                case EVENT_UID_STATE_CHANGED:
+                    return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+                            + "-" + data.lfield1;
+                case EVENT_POLICIES_CHANGED:
+                    return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_METEREDNESS_CHANGED:
+                    return getMeterednessChangedLog(data.ifield1, data.bfield1);
+                case EVENT_USER_STATE_REMOVED:
+                    return getUserRemovedLog(data.ifield1);
+                case EVENT_RESTRICT_BG_CHANGED:
+                    return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+                case EVENT_DEVICE_IDLE_MODE_ENABLED:
+                    return getDeviceIdleModeEnabled(data.bfield1);
+                case EVENT_APP_IDLE_STATE_CHANGED:
+                    return getAppIdleChangedLog(data.ifield1, data.bfield1);
+                case EVENT_PAROLE_STATE_CHANGED:
+                    return getParoleStateChanged(data.bfield1);
+                case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+                    return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+                case EVENT_UID_FIREWALL_RULE_CHANGED:
+                    return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_FIREWALL_CHAIN_ENABLED:
+                    return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+                default:
+                    return String.valueOf(data.type);
+            }
+        }
+
+        private String formatDate(long millis) {
+            sDate.setTime(millis);
+            return sFormatter.format(sDate);
+        }
+    }
+
+    public final static class Data {
+        int type;
+        long timeStamp;
+
+        int ifield1;
+        int ifield2;
+        int ifield3;
+        long lfield1;
+        boolean bfield1;
+        boolean bfield2;
+        String sfield1;
+
+        public void reset(){
+            sfield1 = null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd4..fdfe241 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@
  * </ul>
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
-    static final String TAG = "NetworkPolicy";
-    private static final boolean LOGD = false;
-    private static final boolean LOGV = false;
+    static final String TAG = NetworkPolicyLogger.TAG;
+    private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+    private static final boolean LOGV = NetworkPolicyLogger.LOGV;
 
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@
     private static final int VERSION_ADDED_CYCLE = 11;
     private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
 
-    /**
-     * Max items written to {@link #ProcStateSeqHistory}.
-     */
-    @VisibleForTesting
-    public static final int MAX_PROC_STATE_SEQ_HISTORY =
-            ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
     @VisibleForTesting
     public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
     @VisibleForTesting
@@ -471,13 +471,7 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
-    /**
-     * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
-     * the uid and procStateSeq will be written to this and will be printed as part of dump.
-     */
-    @VisibleForTesting
-    public ProcStateSeqHistory mObservedHistory
-            = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+    private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
 
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@
                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
 
                 if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+                    mLogger.meterednessChanged(network.netId, newMetered);
                     mNetworkMetered.put(network.netId, newMetered);
                     updateNetworkRulesNL();
                 }
@@ -2148,6 +2143,7 @@
                 final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
                 if (oldPolicy != policy) {
                     setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                    mLogger.uidPolicyChanged(uid, oldPolicy, policy);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@
             policy |= oldPolicy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2185,6 +2182,7 @@
             policy = oldPolicy & ~policy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2264,7 +2262,7 @@
      */
     boolean removeUserStateUL(int userId, boolean writePolicy) {
 
-        if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+        mLogger.removingUserState(userId);
         boolean changed = false;
 
         // Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@
     @Override
     public void onTetheringChanged(String iface, boolean tethering) {
         // No need to enforce permission because setRestrictBackground() will do it.
-        if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
         synchronized (mUidRulesFirstLock) {
             if (mRestrictBackground && tethering) {
                 Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@
             }
 
             sendRestrictBackgroundChangedMsg();
+            mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
 
             if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
                 mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@
                     return;
                 }
                 mDeviceIdleMode = enabled;
+                mLogger.deviceIdleModeEnabled(enabled);
                 if (mSystemReady) {
                     // Device idle change means we need to rebuild rules for all
                     // known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@
                 }
                 fout.decreaseIndent();
 
-                fout.println("Observed uid state changes:");
-                fout.increaseIndent();
-                mObservedHistory.dumpUL(fout);
-                fout.decreaseIndent();
+                mLogger.dumpLogs(fout);
             }
         }
     }
@@ -3750,8 +3746,8 @@
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-                if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
                 synchronized (mUidRulesFirstLock) {
+                    mLogger.appIdleStateChanged(uid, idle);
                     updateRuleForAppIdleUL(uid);
                     updateRulesForPowerRestrictionsUL(uid);
                 }
@@ -3762,6 +3758,7 @@
         @Override
         public void onParoleStateChanged(boolean isParoleOn) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.paroleStateChanged(isParoleOn);
                 updateRulesForAppIdleParoleUL();
             }
         }
@@ -3947,7 +3944,7 @@
             synchronized (mUidRulesFirstLock) {
                 // We received a uid state change callback, add it to the history so that it
                 // will be useful for debugging.
-                mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                mLogger.uidStateChanged(uid, procState, procStateSeq);
                 // Now update the network policy rules as per the updated uid state.
                 updateUidStateUL(uid, procState);
                 // Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@
                 rules[index] = uidRules.valueAt(index);
             }
             mNetworkManager.setFirewallUidRules(chain, uids, rules);
+            mLogger.firewallRulesChanged(chain, uids, rules);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {
@@ -4107,6 +4105,7 @@
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
+                mLogger.uidFirewallRuleChanged(chain, uid, rule);
             } catch (IllegalStateException e) {
                 Log.wtf(TAG, "problem setting firewall uid rules", e);
             } catch (RemoteException e) {
@@ -4129,6 +4128,7 @@
         mFirewallChainStates.put(chain, enable);
         try {
             mNetworkManager.setFirewallChainEnabled(chain, enable);
+            mLogger.firewallChainEnabled(chain, enable);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem enable firewall chain", e);
         } catch (RemoteException e) {
@@ -4305,30 +4305,30 @@
             isBackgroundRestricted = mRestrictBackground;
         }
         if (hasRule(uidRules, RULE_REJECT_ALL)) {
-            if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
             return true;
         }
         if (!isNetworkMetered) {
-            if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
             return false;
         }
         if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
             return true;
         }
         if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
             return false;
         }
         if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
             return false;
         }
         if (isBackgroundRestricted) {
-            if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
             return true;
         }
-        if (LOGV) logUidStatus(uid, "allowed by default");
+        mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
         return false;
     }
 
@@ -4379,6 +4379,7 @@
         @Override
         public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.tempPowerSaveWlChanged(appId, added);
                 if (added) {
                     mPowerSaveTempWhitelistAppIds.put(appId, true);
                 } else {
@@ -4393,80 +4394,6 @@
         return (uidRules & rule) != 0;
     }
 
-    private static void logUidStatus(int uid, String descr) {
-        Slog.d(TAG, String.format("uid %d is %s", uid, descr));
-    }
-
-    /**
-     * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
-     * (uid, procStateSeq) pairs.
-     */
-    @VisibleForTesting
-    public static final class ProcStateSeqHistory {
-        private static final int INVALID_UID = -1;
-
-        /**
-         * Denotes maximum number of items this history can hold.
-         */
-        private final int mMaxCapacity;
-        /**
-         * Used for storing the uid information.
-         */
-        private final int[] mUids;
-        /**
-         * Used for storing the sequence numbers associated with {@link #mUids}.
-         */
-        private final long[] mProcStateSeqs;
-        /**
-         * Points to the next available slot for writing (uid, procStateSeq) pair.
-         */
-        private int mHistoryNext;
-
-        public ProcStateSeqHistory(int maxCapacity) {
-            mMaxCapacity = maxCapacity;
-            mUids = new int[mMaxCapacity];
-            Arrays.fill(mUids, INVALID_UID);
-            mProcStateSeqs = new long[mMaxCapacity];
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void addProcStateSeqUL(int uid, long procStateSeq) {
-            mUids[mHistoryNext] = uid;
-            mProcStateSeqs[mHistoryNext] = procStateSeq;
-            mHistoryNext = increaseNext(mHistoryNext, 1);
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void dumpUL(IndentingPrintWriter fout) {
-            if (mUids[0] == INVALID_UID) {
-                fout.println("NONE");
-                return;
-            }
-            int index = mHistoryNext;
-            do {
-                index = increaseNext(index, -1);
-                if (mUids[index] == INVALID_UID) {
-                    break;
-                }
-                fout.println(getString(mUids[index], mProcStateSeqs[index]));
-            } while (index != mHistoryNext);
-        }
-
-        public static String getString(int uid, long procStateSeq) {
-            return "UID=" + uid + " Seq=" + procStateSeq;
-        }
-
-        private int increaseNext(int next, int increment) {
-            next += increment;
-            if (next >= mMaxCapacity) {
-                next = 0;
-            } else if (next < 0) {
-                next = mMaxCapacity - 1;
-            }
-            return next;
-        }
-    }
-
     private class NotificationId {
         private final String mTag;
         private final int mId;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 4078829..f093d57 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -37,7 +37,6 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
 
-import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -61,7 +60,6 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -116,12 +114,10 @@
 import android.util.TrustedTime;
 
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -142,12 +138,10 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.PrintWriter;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -191,8 +185,6 @@
     private static final String TEST_IFACE = "test0";
     private static final String TEST_SSID = "AndroidAP";
 
-    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
     private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
 
     /**
@@ -1109,14 +1101,6 @@
         final long procStateSeq = 222;
         callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq);
         verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
-
-        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        final IndentingPrintWriter writer = new IndentingPrintWriter(
-                new PrintWriter(outputStream), " ");
-        mService.mObservedHistory.dumpUL(writer);
-        writer.flush();
-        assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
-                outputStream.toString().trim());
     }
 
     private void callOnUidStateChanged(int uid, int procState, long procStateSeq)
@@ -1129,59 +1113,6 @@
         latch.await(2, TimeUnit.SECONDS);
     }
 
-    @Test
-    public void testProcStateHistory() {
-        // Verify dump works correctly with no elements added.
-        verifyProcStateHistoryDump(0);
-
-        // Add items upto half of the max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
-
-        // Add items upto the max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
-
-        // Add more items than max capacity and verify that dump works correctly.
-        verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
-
-    }
-
-    private void verifyProcStateHistoryDump(int count) {
-        final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
-        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        final IndentingPrintWriter writer = new IndentingPrintWriter(
-                new PrintWriter(outputStream), " ");
-
-        if (count == 0) {
-            // Verify with no uid info written to history.
-            history.dumpUL(writer);
-            writer.flush();
-            assertEquals("When no uid info is there, dump should contain NONE",
-                    "NONE", outputStream.toString().trim());
-            return;
-        }
-
-        int uid = 111;
-        long procStateSeq = 222;
-        // Add count items and verify dump works correctly.
-        for (int i = 0; i < count; ++i) {
-            uid++;
-            procStateSeq++;
-            history.addProcStateSeqUL(uid, procStateSeq);
-        }
-        history.dumpUL(writer);
-        writer.flush();
-        final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
-        // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
-        final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
-                ? count : MAX_PROC_STATE_SEQ_HISTORY;
-        assertEquals(expectedCount, uidsDump.length);
-        for (int i = 0; i < expectedCount; ++i) {
-            assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
-            uid--;
-            procStateSeq--;
-        }
-    }
-
     private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay,
             boolean expectValid) {
         config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay);
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
index 7a2344317..90a373a 100644
--- a/tests/net/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import android.support.test.filters.SmallTest;
@@ -129,6 +130,55 @@
         assertArraysEqual(expected2, buffer.toArray());
     }
 
+    @Test
+    public void testGetNextSlot() {
+        int capacity = 100;
+        RingBuffer<DummyClass1> buffer = new RingBuffer<>(DummyClass1.class, capacity);
+
+        final DummyClass1[] actual = new DummyClass1[capacity];
+        final DummyClass1[] expected = new DummyClass1[capacity];
+        for (int i = 0; i < capacity; ++i) {
+            final DummyClass1 obj = buffer.getNextSlot();
+            obj.x = capacity * i;
+            actual[i] = obj;
+            expected[i] = new DummyClass1();
+            expected[i].x = capacity * i;
+        }
+        assertArraysEqual(expected, buffer.toArray());
+
+        for (int i = 0; i < capacity; ++i) {
+            if (actual[i] != buffer.getNextSlot()) {
+                fail("getNextSlot() should re-use objects if available");
+            }
+        }
+
+        RingBuffer<DummyClass2> buffer2 = new RingBuffer<>(DummyClass2.class, capacity);
+        assertNull("getNextSlot() should return null if the object can't be initiated "
+                + "(No nullary constructor)", buffer2.getNextSlot());
+
+        RingBuffer<DummyClass3> buffer3 = new RingBuffer<>(DummyClass3.class, capacity);
+        assertNull("getNextSlot() should return null if the object can't be initiated "
+                + "(Inaccessible class)", buffer3.getNextSlot());
+    }
+
+    public static final class DummyClass1 {
+        int x;
+
+        public boolean equals(Object o) {
+            if (o instanceof DummyClass1) {
+                final DummyClass1 other = (DummyClass1) o;
+                return other.x == this.x;
+            }
+            return false;
+        }
+    }
+
+    public static final class DummyClass2 {
+        public DummyClass2(int x) {}
+    }
+
+    private static final class DummyClass3 {}
+
     static <T> void assertArraysEqual(T[] expected, T[] got) {
         if (expected.length != got.length) {
             fail(Arrays.toString(expected) + " and " + Arrays.toString(got)