Loading services/core/java/com/android/server/location/LocationManagerService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -1385,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub implements ipw.println("Event Log:"); ipw.println("Event Log:"); ipw.increaseIndent(); ipw.increaseIndent(); EVENT_LOG.iterate(manager.getName(), ipw::println); EVENT_LOG.iterate(ipw::println, manager.getName()); ipw.decreaseIndent(); ipw.decreaseIndent(); return; return; } } Loading services/core/java/com/android/server/location/eventlog/LocalEventLog.java +136 −161 Original line number Original line Diff line number Diff line Loading @@ -16,217 +16,193 @@ package com.android.server.location.eventlog; package com.android.server.location.eventlog; import static java.lang.Integer.bitCount; import android.annotation.Nullable; import android.annotation.Nullable; import android.os.SystemClock; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions; import java.util.Iterator; import java.lang.reflect.Array; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.Objects; /** /** * An in-memory event log to support historical event information. * An in-memory event log to support historical event information. The log is of a constant size, * and new events will overwrite old events as the log fills up. * * @param <T> log event type */ */ public abstract class LocalEventLog { public class LocalEventLog<T> { private interface Log { /** // true if this is a filler element that should not be queried * Consumer of log events for iterating over the log. boolean isFiller(); * long getTimeDeltaMs(); * @param <T> log event type String getLogString(); */ boolean filter(@Nullable String filter); public interface LogConsumer<T> { /** Invoked with a time and a logEvent. */ void acceptLog(long time, T logEvent); } } private static final class FillerEvent implements Log { // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not, // and 31 bits to store the time delta. private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000; private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111; static final long MAX_TIME_DELTA = (1L << 32) - 1; private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK); private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK); private final int mTimeDelta; static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1; FillerEvent(long timeDelta) { private static int countTrailingZeros(int i) { Preconditions.checkArgument(timeDelta >= 0); int c = 0; mTimeDelta = (int) timeDelta; while (i != 0 && (i & 1) == 0) { c++; i = i >>> 1; } } return c; @Override public boolean isFiller() { return true; } } @Override private static int createEntry(boolean isFiller, int timeDelta) { public long getTimeDeltaMs() { Preconditions.checkArgument(timeDelta >= 0 && timeDelta <= MAX_TIME_DELTA); return Integer.toUnsignedLong(mTimeDelta); return (((isFiller ? 1 : 0) << IS_FILLER_OFFSET) & IS_FILLER_MASK) | ((timeDelta << TIME_DELTA_OFFSET) & TIME_DELTA_MASK); } } @Override static int getTimeDelta(int entry) { public String getLogString() { return (entry & TIME_DELTA_MASK) >>> TIME_DELTA_OFFSET; throw new AssertionError(); } } @Override static boolean isFiller(int entry) { public boolean filter(String filter) { return (entry & IS_FILLER_MASK) != 0; return false; } } } /** // circular buffer of log entries and events. each entry corrosponds to the log event at the * An abstraction of a log event to be implemented by subclasses. // same index. the log entry holds the filler status and time delta according to the bit masks */ // above, and the log event is the log event. public abstract static class LogEvent implements Log { static final long MAX_TIME_DELTA = (1L << 32) - 1; @GuardedBy("this") final int[] mEntries; private final int mTimeDelta; @GuardedBy("this") final @Nullable T[] mLogEvents; protected LogEvent(long timeDelta) { Preconditions.checkArgument(timeDelta >= 0); mTimeDelta = (int) timeDelta; } @Override @GuardedBy("this") public final boolean isFiller() { int mLogSize; return false; } @Override @GuardedBy("this") public final long getTimeDeltaMs() { int mLogEndIndex; return Integer.toUnsignedLong(mTimeDelta); } @Override // invalid if log is empty public boolean filter(String filter) { return false; } } // circular buffer of log entries @GuardedBy("this") private final Log[] mLog; long mStartTime; private int mLogSize; private int mLogEndIndex; // invalid if log is empty @GuardedBy("this") private long mStartRealtimeMs; long mLastLogTime; private long mLastLogRealtimeMs; public LocalEventLog(int size) { @SuppressWarnings("unchecked") public LocalEventLog(int size, Class<T> clazz) { Preconditions.checkArgument(size > 0); Preconditions.checkArgument(size > 0); mLog = new Log[size]; mEntries = new int[size]; mLogEvents = (T[]) Array.newInstance(clazz, size); mLogSize = 0; mLogSize = 0; mLogEndIndex = 0; mLogEndIndex = 0; mStartRealtimeMs = -1; mStartTime = -1; mLastLogRealtimeMs = -1; mLastLogTime = -1; } /** * Should be overridden by subclasses to return a new immutable log event for the given * arguments (as passed into {@link #addLogEvent(int, Object...)}. */ protected abstract LogEvent createLogEvent(long timeDelta, int event, Object... args); /** * May be optionally overridden by subclasses if they wish to change how log event time is * formatted. */ protected String getTimePrefix(long timeMs) { return TimeUtils.logTimeOfDay(timeMs) + ": "; } } /** /** Call to add a new log event at the given time. */ * Call to add a new log event at the current time. The arguments provided here will be passed protected synchronized void addLog(long time, T logEvent) { * into {@link #createLogEvent(long, int, Object...)} in addition to a time delta, and should be Preconditions.checkArgument(logEvent != null); * used to construct an appropriate {@link LogEvent} object. */ public synchronized void addLogEvent(int event, Object... args) { long timeMs = SystemClock.elapsedRealtime(); // calculate delta // calculate delta long delta = 0; long delta = 0; if (!isEmpty()) { if (!isEmpty()) { delta = timeMs - mLastLogRealtimeMs; delta = time - mLastLogTime; // if the delta is invalid, or if the delta is great enough using filler elements would // if the delta is negative, or if the delta is great enough using filler elements would // result in an empty log anyways, just clear the log and continue, otherwise insert // result in an empty log anyways, just clear the log and continue, otherwise insert // filler elements until we have a reasonable delta // filler elements until we have a reasonable delta if (delta < 0 || (delta / FillerEvent.MAX_TIME_DELTA) >= mLog.length - 1) { if (delta < 0 || (delta / MAX_TIME_DELTA) >= mEntries.length - 1) { clear(); clear(); delta = 0; delta = 0; } else { } else { while (delta >= LogEvent.MAX_TIME_DELTA) { while (delta >= MAX_TIME_DELTA) { long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta); addLogEventInternal(true, MAX_TIME_DELTA, null); addLogEventInternal(new FillerEvent(timeDelta)); delta -= MAX_TIME_DELTA; delta -= timeDelta; } } } } } } // for first log entry, set initial times // for first log entry, set initial times if (isEmpty()) { if (isEmpty()) { mStartRealtimeMs = timeMs; mStartTime = time; mLastLogRealtimeMs = mStartRealtimeMs; mLastLogTime = mStartTime; } } addLogEventInternal(createLogEvent(delta, event, args)); addLogEventInternal(false, (int) delta, logEvent); } } private void addLogEventInternal(Log event) { @GuardedBy("this") Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1); private void addLogEventInternal(boolean isFiller, int timeDelta, @Nullable T logEvent) { Preconditions.checkArgument(isFiller || logEvent != null); Preconditions.checkState(mStartTime != -1 && mLastLogTime != -1); if (mLogSize == mLog.length) { if (mLogSize == mEntries.length) { // if log is full, size will remain the same, but update the start time // if log is full, size will remain the same, but update the start time mStartRealtimeMs += mLog[startIndex()].getTimeDeltaMs(); mStartTime += getTimeDelta(mEntries[startIndex()]); } else { } else { // otherwise add an item // otherwise add an item mLogSize++; mLogSize++; } } // set log and increment end index // set log and increment end index mLog[mLogEndIndex] = event; mEntries[mLogEndIndex] = createEntry(isFiller, timeDelta); mLogEvents[mLogEndIndex] = logEvent; mLogEndIndex = incrementIndex(mLogEndIndex); mLogEndIndex = incrementIndex(mLogEndIndex); mLastLogRealtimeMs = mLastLogRealtimeMs + event.getTimeDeltaMs(); mLastLogTime = mLastLogTime + timeDelta; } } /** Clears the log of all entries. */ /** Clears the log of all entries. */ public synchronized void clear() { public synchronized void clear() { // clear entries to allow gc Arrays.fill(mLogEvents, null); mLogEndIndex = 0; mLogEndIndex = 0; mLogSize = 0; mLogSize = 0; mStartRealtimeMs = -1; mStartTime = -1; mLastLogRealtimeMs = -1; mLastLogTime = -1; } } // checks if the log is empty (if empty, times are invalid) // checks if the log is empty (if empty, times are invalid) private synchronized boolean isEmpty() { @GuardedBy("this") private boolean isEmpty() { return mLogSize == 0; return mLogSize == 0; } } /** Iterates over the event log, passing each log string to the given consumer. */ /** Iterates over the event log, passing each log string to the given consumer. */ public synchronized void iterate(Consumer<String> consumer) { public synchronized void iterate(LogConsumer<? super T> consumer) { LogIterator it = new LogIterator(); LogIterator it = new LogIterator(); while (it.hasNext()) { while (it.hasNext()) { consumer.accept(it.next()); it.next(); } consumer.acceptLog(it.getTime(), it.getLog()); } /** * Iterates over the event log, passing each filter-matching log string to the given * consumer. */ public synchronized void iterate(String filter, Consumer<String> consumer) { LogIterator it = new LogIterator(filter); while (it.hasNext()) { consumer.accept(it.next()); } } } } // returns the index of the first element // returns the index of the first element @GuardedBy("this") private int startIndex() { private int startIndex() { return wrapIndex(mLogEndIndex - mLogSize); return wrapIndex(mLogEndIndex - mLogSize); } } // returns the index after this one // returns the index after this one @GuardedBy("this") private int incrementIndex(int index) { private int incrementIndex(int index) { if (index == -1) { if (index == -1) { return startIndex(); return startIndex(); Loading @@ -238,69 +214,68 @@ public abstract class LocalEventLog { } } // rolls over the given index if necessary // rolls over the given index if necessary @GuardedBy("this") private int wrapIndex(int index) { private int wrapIndex(int index) { // java modulo will keep negative sign, we need to rollover // java modulo will keep negative sign, we need to rollover return (index % mLog.length + mLog.length) % mLog.length; return (index % mEntries.length + mEntries.length) % mEntries.length; } } private class LogIterator implements Iterator<String> { private class LogIterator { private final @Nullable String mFilter; private final long mSystemTimeDeltaMs; private long mCurrentRealtimeMs; private long mLogTime; private int mIndex; private int mIndex; private int mCount; private int mCount; LogIterator() { private long mCurrentTime; this(null); private T mCurrentLogEvent; } LogIterator(@Nullable String filter) { LogIterator() { mFilter = filter; synchronized (LocalEventLog.this) { mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime(); mLogTime = mStartTime; mCurrentRealtimeMs = mStartRealtimeMs; mIndex = -1; mIndex = -1; mCount = -1; mCount = -1; increment(); increment(); } } } @Override public boolean hasNext() { public boolean hasNext() { synchronized (LocalEventLog.this) { return mCount < mLogSize; return mCount < mLogSize; } } } public String next() { public void next() { synchronized (LocalEventLog.this) { if (!hasNext()) { if (!hasNext()) { throw new NoSuchElementException(); throw new NoSuchElementException(); } } Log log = mLog[mIndex]; mCurrentTime = mLogTime + getTimeDelta(mEntries[mIndex]); long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs; mCurrentLogEvent = Objects.requireNonNull(mLogEvents[mIndex]); increment(); increment(); } } return getTimePrefix(timeMs) + log.getLogString(); public long getTime() { return mCurrentTime; } } @Override public T getLog() { public void remove() { return mCurrentLogEvent; throw new UnsupportedOperationException(); } } private void increment() { @GuardedBy("LocalEventLog.this") long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs(); private void increment(LogIterator this) { long nextDeltaMs = mIndex == -1 ? 0 : getTimeDelta(mEntries[mIndex]); do { do { mCurrentRealtimeMs += nextDeltaMs; mLogTime += nextDeltaMs; mIndex = incrementIndex(mIndex); mIndex = incrementIndex(mIndex); if (++mCount < mLogSize) { if (++mCount < mLogSize) { nextDeltaMs = mLog[mIndex].getTimeDeltaMs(); nextDeltaMs = getTimeDelta(mEntries[mIndex]); } } } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null } while (mCount < mLogSize && isFiller(mEntries[mIndex])); && !mLog[mIndex].filter(mFilter)))); } } } } } } Loading
services/core/java/com/android/server/location/LocationManagerService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -1385,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub implements ipw.println("Event Log:"); ipw.println("Event Log:"); ipw.increaseIndent(); ipw.increaseIndent(); EVENT_LOG.iterate(manager.getName(), ipw::println); EVENT_LOG.iterate(ipw::println, manager.getName()); ipw.decreaseIndent(); ipw.decreaseIndent(); return; return; } } Loading
services/core/java/com/android/server/location/eventlog/LocalEventLog.java +136 −161 Original line number Original line Diff line number Diff line Loading @@ -16,217 +16,193 @@ package com.android.server.location.eventlog; package com.android.server.location.eventlog; import static java.lang.Integer.bitCount; import android.annotation.Nullable; import android.annotation.Nullable; import android.os.SystemClock; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions; import java.util.Iterator; import java.lang.reflect.Array; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.Objects; /** /** * An in-memory event log to support historical event information. * An in-memory event log to support historical event information. The log is of a constant size, * and new events will overwrite old events as the log fills up. * * @param <T> log event type */ */ public abstract class LocalEventLog { public class LocalEventLog<T> { private interface Log { /** // true if this is a filler element that should not be queried * Consumer of log events for iterating over the log. boolean isFiller(); * long getTimeDeltaMs(); * @param <T> log event type String getLogString(); */ boolean filter(@Nullable String filter); public interface LogConsumer<T> { /** Invoked with a time and a logEvent. */ void acceptLog(long time, T logEvent); } } private static final class FillerEvent implements Log { // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not, // and 31 bits to store the time delta. private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000; private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111; static final long MAX_TIME_DELTA = (1L << 32) - 1; private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK); private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK); private final int mTimeDelta; static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1; FillerEvent(long timeDelta) { private static int countTrailingZeros(int i) { Preconditions.checkArgument(timeDelta >= 0); int c = 0; mTimeDelta = (int) timeDelta; while (i != 0 && (i & 1) == 0) { c++; i = i >>> 1; } } return c; @Override public boolean isFiller() { return true; } } @Override private static int createEntry(boolean isFiller, int timeDelta) { public long getTimeDeltaMs() { Preconditions.checkArgument(timeDelta >= 0 && timeDelta <= MAX_TIME_DELTA); return Integer.toUnsignedLong(mTimeDelta); return (((isFiller ? 1 : 0) << IS_FILLER_OFFSET) & IS_FILLER_MASK) | ((timeDelta << TIME_DELTA_OFFSET) & TIME_DELTA_MASK); } } @Override static int getTimeDelta(int entry) { public String getLogString() { return (entry & TIME_DELTA_MASK) >>> TIME_DELTA_OFFSET; throw new AssertionError(); } } @Override static boolean isFiller(int entry) { public boolean filter(String filter) { return (entry & IS_FILLER_MASK) != 0; return false; } } } /** // circular buffer of log entries and events. each entry corrosponds to the log event at the * An abstraction of a log event to be implemented by subclasses. // same index. the log entry holds the filler status and time delta according to the bit masks */ // above, and the log event is the log event. public abstract static class LogEvent implements Log { static final long MAX_TIME_DELTA = (1L << 32) - 1; @GuardedBy("this") final int[] mEntries; private final int mTimeDelta; @GuardedBy("this") final @Nullable T[] mLogEvents; protected LogEvent(long timeDelta) { Preconditions.checkArgument(timeDelta >= 0); mTimeDelta = (int) timeDelta; } @Override @GuardedBy("this") public final boolean isFiller() { int mLogSize; return false; } @Override @GuardedBy("this") public final long getTimeDeltaMs() { int mLogEndIndex; return Integer.toUnsignedLong(mTimeDelta); } @Override // invalid if log is empty public boolean filter(String filter) { return false; } } // circular buffer of log entries @GuardedBy("this") private final Log[] mLog; long mStartTime; private int mLogSize; private int mLogEndIndex; // invalid if log is empty @GuardedBy("this") private long mStartRealtimeMs; long mLastLogTime; private long mLastLogRealtimeMs; public LocalEventLog(int size) { @SuppressWarnings("unchecked") public LocalEventLog(int size, Class<T> clazz) { Preconditions.checkArgument(size > 0); Preconditions.checkArgument(size > 0); mLog = new Log[size]; mEntries = new int[size]; mLogEvents = (T[]) Array.newInstance(clazz, size); mLogSize = 0; mLogSize = 0; mLogEndIndex = 0; mLogEndIndex = 0; mStartRealtimeMs = -1; mStartTime = -1; mLastLogRealtimeMs = -1; mLastLogTime = -1; } /** * Should be overridden by subclasses to return a new immutable log event for the given * arguments (as passed into {@link #addLogEvent(int, Object...)}. */ protected abstract LogEvent createLogEvent(long timeDelta, int event, Object... args); /** * May be optionally overridden by subclasses if they wish to change how log event time is * formatted. */ protected String getTimePrefix(long timeMs) { return TimeUtils.logTimeOfDay(timeMs) + ": "; } } /** /** Call to add a new log event at the given time. */ * Call to add a new log event at the current time. The arguments provided here will be passed protected synchronized void addLog(long time, T logEvent) { * into {@link #createLogEvent(long, int, Object...)} in addition to a time delta, and should be Preconditions.checkArgument(logEvent != null); * used to construct an appropriate {@link LogEvent} object. */ public synchronized void addLogEvent(int event, Object... args) { long timeMs = SystemClock.elapsedRealtime(); // calculate delta // calculate delta long delta = 0; long delta = 0; if (!isEmpty()) { if (!isEmpty()) { delta = timeMs - mLastLogRealtimeMs; delta = time - mLastLogTime; // if the delta is invalid, or if the delta is great enough using filler elements would // if the delta is negative, or if the delta is great enough using filler elements would // result in an empty log anyways, just clear the log and continue, otherwise insert // result in an empty log anyways, just clear the log and continue, otherwise insert // filler elements until we have a reasonable delta // filler elements until we have a reasonable delta if (delta < 0 || (delta / FillerEvent.MAX_TIME_DELTA) >= mLog.length - 1) { if (delta < 0 || (delta / MAX_TIME_DELTA) >= mEntries.length - 1) { clear(); clear(); delta = 0; delta = 0; } else { } else { while (delta >= LogEvent.MAX_TIME_DELTA) { while (delta >= MAX_TIME_DELTA) { long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta); addLogEventInternal(true, MAX_TIME_DELTA, null); addLogEventInternal(new FillerEvent(timeDelta)); delta -= MAX_TIME_DELTA; delta -= timeDelta; } } } } } } // for first log entry, set initial times // for first log entry, set initial times if (isEmpty()) { if (isEmpty()) { mStartRealtimeMs = timeMs; mStartTime = time; mLastLogRealtimeMs = mStartRealtimeMs; mLastLogTime = mStartTime; } } addLogEventInternal(createLogEvent(delta, event, args)); addLogEventInternal(false, (int) delta, logEvent); } } private void addLogEventInternal(Log event) { @GuardedBy("this") Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1); private void addLogEventInternal(boolean isFiller, int timeDelta, @Nullable T logEvent) { Preconditions.checkArgument(isFiller || logEvent != null); Preconditions.checkState(mStartTime != -1 && mLastLogTime != -1); if (mLogSize == mLog.length) { if (mLogSize == mEntries.length) { // if log is full, size will remain the same, but update the start time // if log is full, size will remain the same, but update the start time mStartRealtimeMs += mLog[startIndex()].getTimeDeltaMs(); mStartTime += getTimeDelta(mEntries[startIndex()]); } else { } else { // otherwise add an item // otherwise add an item mLogSize++; mLogSize++; } } // set log and increment end index // set log and increment end index mLog[mLogEndIndex] = event; mEntries[mLogEndIndex] = createEntry(isFiller, timeDelta); mLogEvents[mLogEndIndex] = logEvent; mLogEndIndex = incrementIndex(mLogEndIndex); mLogEndIndex = incrementIndex(mLogEndIndex); mLastLogRealtimeMs = mLastLogRealtimeMs + event.getTimeDeltaMs(); mLastLogTime = mLastLogTime + timeDelta; } } /** Clears the log of all entries. */ /** Clears the log of all entries. */ public synchronized void clear() { public synchronized void clear() { // clear entries to allow gc Arrays.fill(mLogEvents, null); mLogEndIndex = 0; mLogEndIndex = 0; mLogSize = 0; mLogSize = 0; mStartRealtimeMs = -1; mStartTime = -1; mLastLogRealtimeMs = -1; mLastLogTime = -1; } } // checks if the log is empty (if empty, times are invalid) // checks if the log is empty (if empty, times are invalid) private synchronized boolean isEmpty() { @GuardedBy("this") private boolean isEmpty() { return mLogSize == 0; return mLogSize == 0; } } /** Iterates over the event log, passing each log string to the given consumer. */ /** Iterates over the event log, passing each log string to the given consumer. */ public synchronized void iterate(Consumer<String> consumer) { public synchronized void iterate(LogConsumer<? super T> consumer) { LogIterator it = new LogIterator(); LogIterator it = new LogIterator(); while (it.hasNext()) { while (it.hasNext()) { consumer.accept(it.next()); it.next(); } consumer.acceptLog(it.getTime(), it.getLog()); } /** * Iterates over the event log, passing each filter-matching log string to the given * consumer. */ public synchronized void iterate(String filter, Consumer<String> consumer) { LogIterator it = new LogIterator(filter); while (it.hasNext()) { consumer.accept(it.next()); } } } } // returns the index of the first element // returns the index of the first element @GuardedBy("this") private int startIndex() { private int startIndex() { return wrapIndex(mLogEndIndex - mLogSize); return wrapIndex(mLogEndIndex - mLogSize); } } // returns the index after this one // returns the index after this one @GuardedBy("this") private int incrementIndex(int index) { private int incrementIndex(int index) { if (index == -1) { if (index == -1) { return startIndex(); return startIndex(); Loading @@ -238,69 +214,68 @@ public abstract class LocalEventLog { } } // rolls over the given index if necessary // rolls over the given index if necessary @GuardedBy("this") private int wrapIndex(int index) { private int wrapIndex(int index) { // java modulo will keep negative sign, we need to rollover // java modulo will keep negative sign, we need to rollover return (index % mLog.length + mLog.length) % mLog.length; return (index % mEntries.length + mEntries.length) % mEntries.length; } } private class LogIterator implements Iterator<String> { private class LogIterator { private final @Nullable String mFilter; private final long mSystemTimeDeltaMs; private long mCurrentRealtimeMs; private long mLogTime; private int mIndex; private int mIndex; private int mCount; private int mCount; LogIterator() { private long mCurrentTime; this(null); private T mCurrentLogEvent; } LogIterator(@Nullable String filter) { LogIterator() { mFilter = filter; synchronized (LocalEventLog.this) { mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime(); mLogTime = mStartTime; mCurrentRealtimeMs = mStartRealtimeMs; mIndex = -1; mIndex = -1; mCount = -1; mCount = -1; increment(); increment(); } } } @Override public boolean hasNext() { public boolean hasNext() { synchronized (LocalEventLog.this) { return mCount < mLogSize; return mCount < mLogSize; } } } public String next() { public void next() { synchronized (LocalEventLog.this) { if (!hasNext()) { if (!hasNext()) { throw new NoSuchElementException(); throw new NoSuchElementException(); } } Log log = mLog[mIndex]; mCurrentTime = mLogTime + getTimeDelta(mEntries[mIndex]); long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs; mCurrentLogEvent = Objects.requireNonNull(mLogEvents[mIndex]); increment(); increment(); } } return getTimePrefix(timeMs) + log.getLogString(); public long getTime() { return mCurrentTime; } } @Override public T getLog() { public void remove() { return mCurrentLogEvent; throw new UnsupportedOperationException(); } } private void increment() { @GuardedBy("LocalEventLog.this") long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs(); private void increment(LogIterator this) { long nextDeltaMs = mIndex == -1 ? 0 : getTimeDelta(mEntries[mIndex]); do { do { mCurrentRealtimeMs += nextDeltaMs; mLogTime += nextDeltaMs; mIndex = incrementIndex(mIndex); mIndex = incrementIndex(mIndex); if (++mCount < mLogSize) { if (++mCount < mLogSize) { nextDeltaMs = mLog[mIndex].getTimeDeltaMs(); nextDeltaMs = getTimeDelta(mEntries[mIndex]); } } } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null } while (mCount < mLogSize && isFiller(mEntries[mIndex])); && !mLog[mIndex].filter(mFilter)))); } } } } } }