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

Commit 65a88ea4 authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Do not cache battery stats iterator and make it safer

Avoid heap explosion in case the history parcel is corrupted.

Bug: 261622968
Test: atest FrameworksServicesTests:BatteryStatsTests
Change-Id: Ic593062b3ac40c559ec8fcdb1b7e5b9935ee147b
parent d5698b2c
Loading
Loading
Loading
Loading
+117 −125
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.util.LongSparseArray;
import android.util.MutableBoolean;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
@@ -52,6 +53,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.Display;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistoryIterator;

import com.google.android.collect.Lists;

@@ -2398,9 +2400,6 @@ public abstract class BatteryStats {

    public abstract int getHistoryUsedSize();

    @UnsupportedAppUsage
    public abstract boolean startIteratingHistoryLocked();

    public abstract int getHistoryStringPoolSize();

    public abstract int getHistoryStringPoolBytes();
@@ -2409,10 +2408,11 @@ public abstract class BatteryStats {

    public abstract int getHistoryTagPoolUid(int index);

    @UnsupportedAppUsage
    public abstract boolean getNextHistoryLocked(HistoryItem out);

    public abstract void finishIteratingHistoryLocked();
    /**
     * Returns a BatteryStatsHistoryIterator. Battery history will remain immutable until the
     * {@link BatteryStatsHistoryIterator#close()} method is invoked.
     */
    public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();

    /**
     * Returns the number of times the device has been started.
@@ -7451,12 +7451,14 @@ public abstract class BatteryStats {

    private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
        final HistoryPrinter hprinter = new HistoryPrinter();
        final HistoryItem rec = new HistoryItem();
        long lastTime = -1;
        long baseTime = -1;
        boolean printed = false;
        HistoryEventTracker tracker = null;
        while (getNextHistoryLocked(rec)) {
        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
            HistoryItem rec;
            while ((rec = iterator.next()) != null) {
                try {
                    lastTime = rec.time;
                    if (baseTime < 0) {
                        baseTime = lastTime;
@@ -7489,13 +7491,13 @@ public abstract class BatteryStats {
                                HistoryTag oldEventTag = rec.eventTag;
                                rec.eventTag = new HistoryTag();
                                for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) {
                            HashMap<String, SparseIntArray> active
                                    = tracker.getStateForEvent(i);
                                    Map<String, SparseIntArray> active =
                                            tracker.getStateForEvent(i);
                                    if (active == null) {
                                        continue;
                                    }
                            for (HashMap.Entry<String, SparseIntArray> ent
                                    : active.entrySet()) {
                                    for (Map.Entry<String, SparseIntArray> ent :
                                            active.entrySet()) {
                                        SparseIntArray uids = ent.getValue();
                                        for (int j = 0; j < uids.size(); j++) {
                                            rec.eventCode = i;
@@ -7516,7 +7518,7 @@ public abstract class BatteryStats {
                        }
                        hprinter.printNextItem(pw, rec, baseTime, checkin,
                                (flags & DUMP_VERBOSE) != 0);
            } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
                    } else if (false/* && rec.eventCode != HistoryItem.EVENT_NONE */) {
                        // This is an attempt to aggregate the previous state and generate
                        // fake events to reflect that state at the point where we start
                        // printing real events.  It doesn't really work right, so is turned off.
@@ -7526,6 +7528,12 @@ public abstract class BatteryStats {
                        tracker.updateState(rec.eventCode, rec.eventTag.string,
                                rec.eventTag.uid, rec.eventTag.poolIdx);
                    }
                } catch (Throwable t) {
                    t.printStackTrace(pw);
                    Slog.wtf(TAG, "Corrupted battery history", t);
                    break;
                }
            }
        }
        if (histStart >= 0) {
            commitCurrentHistoryBatchLocked();
@@ -7595,8 +7603,6 @@ public abstract class BatteryStats {
        if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
            final long historyTotalSize = getHistoryTotalSize();
            final long historyUsedSize = getHistoryUsedSize();
            if (startIteratingHistoryLocked()) {
                try {
            pw.print("Battery History (");
            pw.print((100 * historyUsedSize) / historyTotalSize);
            pw.print("% used, ");
@@ -7610,10 +7616,6 @@ public abstract class BatteryStats {
            pw.println("):");
            dumpHistoryLocked(pw, flags, histStart, false);
            pw.println();
                } finally {
                    finishIteratingHistoryLocked();
                }
            }
        }

        if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
@@ -7770,11 +7772,11 @@ public abstract class BatteryStats {
                getEndPlatformVersion());

        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
            if (startIteratingHistoryLocked()) {
                try {
            for (int i = 0; i < getHistoryStringPoolSize(); i++) {
                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
                        pw.print(HISTORY_STRING_POOL); pw.print(',');
                pw.print(BATTERY_STATS_CHECKIN_VERSION);
                pw.print(',');
                pw.print(HISTORY_STRING_POOL);
                pw.print(',');
                pw.print(i);
                pw.print(",");
                pw.print(getHistoryTagPoolUid(i));
@@ -7787,11 +7789,7 @@ public abstract class BatteryStats {
                }
                pw.print("\"");
                pw.println();
                    }
                dumpHistoryLocked(pw, flags, histStart, true);
                } finally {
                    finishIteratingHistoryLocked();
                }
            }
        }

@@ -8331,17 +8329,12 @@ public abstract class BatteryStats {
    }

    private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) {
        if (!startIteratingHistoryLocked()) {
            return;
        }

        proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION);
        proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion());
        proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION,
                getStartPlatformVersion());
        proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION,
                getEndPlatformVersion());
        try {
            long token;
            // History string pool (HISTORY_STRING_POOL)
            for (int i = 0; i < getHistoryStringPoolSize(); ++i) {
@@ -8355,12 +8348,13 @@ public abstract class BatteryStats {

        // History data (HISTORY_DATA)
        final HistoryPrinter hprinter = new HistoryPrinter();
            final HistoryItem rec = new HistoryItem();
        long lastTime = -1;
        long baseTime = -1;
        boolean printed = false;
        HistoryEventTracker tracker = null;
            while (getNextHistoryLocked(rec)) {
        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
            HistoryItem rec;
            while ((rec = iterator.next()) != null) {
                lastTime = rec.time;
                if (baseTime < 0) {
                    baseTime = lastTime;
@@ -8427,8 +8421,6 @@ public abstract class BatteryStats {
                proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES,
                        "NEXT: " + (lastTime + 1));
            }
        } finally {
            finishIteratingHistoryLocked();
        }
    }

+18 −26
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -162,10 +163,6 @@ public class BatteryStatsHistory {
     * When iterating history file, the current parcel's Parcel.dataSize().
     */
    private int mCurrentParcelEnd;
    /**
     * When iterating history files, the current record count.
     */
    private int mRecordCount = 0;
    /**
     * Used when BatteryStatsImpl object is created from deserialization of a parcel,
     * such as Settings app or checkin file, to iterate over history parcels.
@@ -199,10 +196,8 @@ public class BatteryStatsHistory {
    private boolean mMeasuredEnergyHeaderWritten = false;
    private boolean mCpuUsageHeaderWritten = false;
    private final VarintParceler mVarintParceler = new VarintParceler();

    private byte mLastHistoryStepLevel = 0;

    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
    private boolean mMutable = true;

    /**
     * A delegate responsible for computing additional details for a step in battery history.
@@ -493,25 +488,21 @@ public class BatteryStatsHistory {
     * @return always return true.
     */
    public BatteryStatsHistoryIterator iterate() {
        mRecordCount = 0;
        mCurrentFileIndex = 0;
        mCurrentParcel = null;
        mCurrentParcelEnd = 0;
        mParcelIndex = 0;
        mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
        return mBatteryStatsHistoryIterator;
        mMutable = false;
        return new BatteryStatsHistoryIterator(this);
    }

    /**
     * Finish iterating history files and history buffer.
     */
    void finishIteratingHistory() {
    void iteratorFinished() {
        // setDataPosition so mHistoryBuffer Parcel can be written.
        mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
        mBatteryStatsHistoryIterator = null;
        if (DEBUG) {
            Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
        }
        mMutable = true;
    }

    /**
@@ -519,17 +510,11 @@ public class BatteryStatsHistory {
     * history file, when reached the mActiveFile (highest numbered history file), do not read from
     * mActiveFile, read from history buffer instead because the buffer has more updated data.
     *
     * @param out a history item.
     * @return The parcel that has next record. null if finished all history files and history
     * buffer
     */
    public Parcel getNextParcel(HistoryItem out) {
        if (mRecordCount == 0) {
            // reset out if it is the first record.
            out.clear();
        }
        ++mRecordCount;

    @Nullable
    public Parcel getNextParcel() {
        // First iterate through all records in current parcel.
        if (mCurrentParcel != null) {
            if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -1270,6 +1255,10 @@ public class BatteryStatsHistory {
            return;
        }

        if (!mMutable) {
            throw new ConcurrentModificationException("Battery history is not writable");
        }

        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
        final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
        final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
@@ -1390,8 +1379,8 @@ public class BatteryStatsHistory {

    private void writeHistoryItem(long elapsedRealtimeMs,
            @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
        if (mBatteryStatsHistoryIterator != null) {
            throw new IllegalStateException("Can't do this while iterating history!");
        if (!mMutable) {
            throw new ConcurrentModificationException("Battery history is not writable");
        }
        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
@@ -1687,7 +1676,10 @@ public class BatteryStatsHistory {
                    Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
                }
                if (!mCpuUsageHeaderWritten) {
                    dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
                    dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
                    for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
                        dest.writeString(desc);
                    }
                    mCpuUsageHeaderWritten = true;
                }
                dest.writeInt(cur.cpuUsageDetails.uid);
+56 −15
Original line number Diff line number Diff line
@@ -23,10 +23,13 @@ import android.os.Parcel;
import android.util.Slog;
import android.util.SparseArray;

import java.util.Iterator;

/**
 * An iterator for {@link BatteryStats.HistoryItem}'s.
 */
public class BatteryStatsHistoryIterator {
public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
        AutoCloseable {
    private static final boolean DEBUG = false;
    private static final String TAG = "BatteryStatsHistoryItr";
    private final BatteryStatsHistory mBatteryStatsHistory;
@@ -38,29 +41,51 @@ public class BatteryStatsHistoryIterator {
    private final BatteryStatsHistory.VarintParceler mVarintParceler =
            new BatteryStatsHistory.VarintParceler();

    private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();

    private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
    private static final int MAX_CPU_BRACKET_COUNT = 100;

    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
        mBatteryStatsHistory = history;
        mHistoryItem.clear();
    }

    @Override
    public boolean hasNext() {
        Parcel p = mBatteryStatsHistory.getNextParcel();
        if (p == null) {
            close();
            return false;
        }
        return true;
    }

    /**
     * Retrieves the next HistoryItem from battery history, if available. Returns false if there
     * Retrieves the next HistoryItem from battery history, if available. Returns null if there
     * are no more items.
     */
    public boolean next(BatteryStats.HistoryItem out) {
        Parcel p = mBatteryStatsHistory.getNextParcel(out);
    @Override
    public BatteryStats.HistoryItem next() {
        Parcel p = mBatteryStatsHistory.getNextParcel();
        if (p == null) {
            mBatteryStatsHistory.finishIteratingHistory();
            return false;
            close();
            return null;
        }

        final long lastRealtimeMs = out.time;
        final long lastWalltimeMs = out.currentTime;
        readHistoryDelta(p, out);
        if (out.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                && out.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
            out.currentTime = lastWalltimeMs + (out.time - lastRealtimeMs);
        final long lastRealtimeMs = mHistoryItem.time;
        final long lastWalltimeMs = mHistoryItem.currentTime;
        try {
            readHistoryDelta(p, mHistoryItem);
        } catch (Throwable t) {
            Slog.wtf(TAG, "Corrupted battery history", t);
            return null;
        }
        return true;
        if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
                && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
            mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
        }
        return mHistoryItem;
    }

    private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -210,6 +235,12 @@ public class BatteryStatsHistoryIterator {
                }

                final int consumerCount = src.readInt();
                if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
                    // Check to avoid a heap explosion in case the parcel is corrupted
                    throw new IllegalStateException(
                            "EnergyConsumer count too high: " + consumerCount
                                    + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
                }
                mMeasuredEnergyDetails.consumers =
                        new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
                mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
@@ -236,7 +267,16 @@ public class BatteryStatsHistoryIterator {

            if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
                mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
                mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
                final int cpuBracketCount = src.readInt();
                if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
                    // Check to avoid a heap explosion in case the parcel is corrupted
                    throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
                            + ". Max = " + MAX_CPU_BRACKET_COUNT);
                }
                mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
                for (int i = 0; i < cpuBracketCount; i++) {
                    mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
                }
                mCpuUsageDetails.cpuUsageMs =
                        new long[mCpuUsageDetails.cpuBracketDescriptions.length];
            } else if (mCpuUsageDetails != null) {
@@ -294,7 +334,8 @@ public class BatteryStatsHistoryIterator {
    /**
     * Should be called when iteration is complete.
     */
    @Override
    public void close() {
        mBatteryStatsHistory.finishIteratingHistory();
        mBatteryStatsHistory.iteratorFinished();
    }
}
+2 −21
Original line number Diff line number Diff line
@@ -763,8 +763,6 @@ public class BatteryStatsImpl extends BatteryStats {
    @NonNull
    private final BatteryStatsHistory mHistory;
    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
    int mStartCount;
    /**
@@ -11242,17 +11240,11 @@ public class BatteryStatsImpl extends BatteryStats {
        return mHistory.getHistoryUsedSize();
    }
    @Override
    public boolean startIteratingHistoryLocked() {
        mBatteryStatsHistoryIterator = createBatteryStatsHistoryIterator();
        return true;
    }
    /**
     * Creates an iterator for battery stats history.
     */
    @VisibleForTesting
    public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
    @Override
    public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
        return mHistory.iterate();
    }
@@ -11276,17 +11268,6 @@ public class BatteryStatsImpl extends BatteryStats {
        return mHistory.getHistoryTagPoolUid(index);
    }
    @Override
    public boolean getNextHistoryLocked(HistoryItem out) {
        return mBatteryStatsHistoryIterator.next(out);
    }
    @Override
    public void finishIteratingHistoryLocked() {
        mBatteryStatsHistoryIterator.close();
        mBatteryStatsHistoryIterator = null;
    }
    @Override
    public int getStartCount() {
        return mStartCount;
+19 −17
Original line number Diff line number Diff line
@@ -93,43 +93,44 @@ public class BatteryStatsHistoryIteratorTest {
        }

        final BatteryStatsHistoryIterator iterator =
                mBatteryStats.createBatteryStatsHistoryIterator();
                mBatteryStats.iterateBatteryStatsHistory();

        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
        BatteryStats.HistoryItem item;

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                null, 0, 3_600_000, 90, 1_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                null, 0, 3_600_000, 90, 1_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                null, 0, 2_400_000, 80, 2_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                null, 0, 2_400_000, 80, 2_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_UPDATE,
                BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                "foo", APP_UID, 2_400_000, 80, 3_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertHistoryItem(item,
                BatteryStats.HistoryItem.CMD_UPDATE,
                BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                "foo", APP_UID, 2_400_000, 80, 3_001_000);

        assertThat(iterator.next(item)).isFalse();
        assertThat(iterator.hasNext()).isFalse();
        assertThat(iterator.next()).isNull();
    }

    // Test history that spans multiple buffers and uses more than 32k different strings.
@@ -163,21 +164,21 @@ public class BatteryStatsHistoryIteratorTest {
        }

        final BatteryStatsHistoryIterator iterator =
                mBatteryStats.createBatteryStatsHistoryIterator();
                mBatteryStats.iterateBatteryStatsHistory();

        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
        assertThat(iterator.next(item)).isTrue();
        BatteryStats.HistoryItem item;
        assertThat(item = iterator.next()).isNotNull();
        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
        assertThat(item.eventTag).isNull();

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
        assertThat(item.eventTag).isNull();
        assertThat(item.time).isEqualTo(1_000_000);

        assertThat(iterator.next(item)).isTrue();
        assertThat(item = iterator.next()).isNotNull();
        assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
        assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
        assertThat(item.eventTag).isNull();
@@ -186,7 +187,7 @@ public class BatteryStatsHistoryIteratorTest {
        for (int i = 0; i < eventCount; i++) {
            String name = "a" + (i % 10);
            do {
                assertThat(iterator.next(item)).isTrue();
                assertThat(item = iterator.next()).isNotNull();
                // Skip a blank event inserted at the start of every buffer
            } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                    || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
@@ -196,7 +197,7 @@ public class BatteryStatsHistoryIteratorTest {
            assertThat(item.eventTag.string).isEqualTo(name);

            do {
                assertThat(iterator.next(item)).isTrue();
                assertThat(item = iterator.next()).isNotNull();
            } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                    || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);

@@ -205,7 +206,8 @@ public class BatteryStatsHistoryIteratorTest {
            assertThat(item.eventTag.string).isEqualTo(name);
        }

        assertThat(iterator.next(item)).isFalse();
        assertThat(iterator.hasNext()).isFalse();
        assertThat(iterator.next()).isNull();
    }

    private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
Loading