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

Commit 9fdbc957 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Android (Google) Code Review
Browse files

Merge "Track some event history and include it in netpolicy dump."

parents 241d9878 352dc571
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