Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 352dc571 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Track some event history and include it in netpolicy dump.

Bug: 66921847
Test: manual
Change-Id: I0c473790f83076def807308fe44db9cb9365769e
parent 3e585ecb
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -60,6 +60,25 @@ public class RingBuffer<T> {
        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());
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ import android.view.Display;
/**
 * 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
+514 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+33 −106

File changed.

Preview size limit exceeded, changes collapsed.

+0 −69
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_
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.ArgumentMatchers.isNull;
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.RecurrenceRule;
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.MockitoAnnotations;
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 @@ public class NetworkPolicyManagerServiceTest {
    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 @@ public class NetworkPolicyManagerServiceTest {
        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 @@ public class NetworkPolicyManagerServiceTest {
        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);
Loading