diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java index 47146c1a6d70b..d08e5dc94206f 100644 --- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java @@ -21,26 +21,23 @@ import static java.lang.Integer.bitCount; import android.annotation.Nullable; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Arrays; +import java.util.ConcurrentModificationException; import java.util.NoSuchElementException; import java.util.Objects; /** * 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 log event type */ public class LocalEventLog { - /** - * Consumer of log events for iterating over the log. - * - * @param log event type - */ + /** Consumer of log events for iterating over the log. */ public interface LogConsumer { /** Invoked with a time and a logEvent. */ void acceptLog(long time, T logEvent); @@ -48,12 +45,13 @@ public class LocalEventLog { // 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 IS_FILLER_MASK = 0b10000000000000000000000000000000; private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111; private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK); private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK); + @VisibleForTesting static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1; private static int countTrailingZeros(int i) { @@ -79,7 +77,7 @@ public class LocalEventLog { return (entry & IS_FILLER_MASK) != 0; } - // circular buffer of log entries and events. each entry corrosponds to the log event at the + // circular buffer of log entries and events. each entry corresponds to the log event at the // 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. @@ -103,6 +101,9 @@ public class LocalEventLog { @GuardedBy("this") long mLastLogTime; + @GuardedBy("this") + long mModificationCount; + @SuppressWarnings("unchecked") public LocalEventLog(int size, Class clazz) { Preconditions.checkArgument(size > 0); @@ -143,6 +144,7 @@ public class LocalEventLog { if (isEmpty()) { mStartTime = time; mLastLogTime = mStartTime; + mModificationCount++; } addLogEventInternal(false, (int) delta, logEvent); @@ -156,6 +158,7 @@ public class LocalEventLog { if (mLogSize == mEntries.length) { // if log is full, size will remain the same, but update the start time mStartTime += getTimeDelta(mEntries[startIndex()]); + mModificationCount++; } else { // otherwise add an item mLogSize++; @@ -170,11 +173,12 @@ public class LocalEventLog { /** Clears the log of all entries. */ public synchronized void clear() { - // clear entries to allow gc + // clear entries to aid gc Arrays.fill(mLogEvents, null); mLogEndIndex = 0; mLogSize = 0; + mModificationCount++; mStartTime = -1; mLastLogTime = -1; @@ -186,7 +190,10 @@ public class LocalEventLog { return mLogSize == 0; } - /** Iterates over the event log, passing each log string to the given consumer. */ + /** + * Iterates over the event log, passing each log event to the given consumer. Locks the log + * while executing so that {@link ConcurrentModificationException}s cannot occur. + */ public synchronized void iterate(LogConsumer consumer) { LogIterator it = new LogIterator(); while (it.hasNext()) { @@ -195,15 +202,53 @@ public class LocalEventLog { } } + /** + * Iterates over all the given event logs in time order, passing each log event to the given + * consumer. It is the caller's responsibility to ensure that {@link + * ConcurrentModificationException}s cannot occur, whether through locking or other means. + */ + @SafeVarargs + public static void iterate(LogConsumer consumer, LocalEventLog... logs) { + ArrayList.LogIterator> its = new ArrayList<>(logs.length); + for (LocalEventLog log : logs) { + LocalEventLog.LogIterator it = log.new LogIterator(); + if (it.hasNext()) { + its.add(it); + it.next(); + } + } + + while (true) { + LocalEventLog.LogIterator next = null; + for (LocalEventLog.LogIterator it : its) { + if (it != null && (next == null || it.getTime() < next.getTime())) { + next = it; + } + } + + if (next == null) { + return; + } + + consumer.acceptLog(next.getTime(), next.getLog()); + + if (next.hasNext()) { + next.next(); + } else { + its.remove(next); + } + } + } + // returns the index of the first element @GuardedBy("this") - private int startIndex() { + int startIndex() { return wrapIndex(mLogEndIndex - mLogSize); } // returns the index after this one @GuardedBy("this") - private int incrementIndex(int index) { + int incrementIndex(int index) { if (index == -1) { return startIndex(); } else if (index >= 0) { @@ -215,12 +260,15 @@ public class LocalEventLog { // rolls over the given index if necessary @GuardedBy("this") - private int wrapIndex(int index) { + int wrapIndex(int index) { // java modulo will keep negative sign, we need to rollover return (index % mEntries.length + mEntries.length) % mEntries.length; } - private class LogIterator { + /** Iterator over log times and events. */ + protected final class LogIterator { + + private final long mModificationCount; private long mLogTime; private int mIndex; @@ -229,8 +277,10 @@ public class LocalEventLog { private long mCurrentTime; private T mCurrentLogEvent; - LogIterator() { + public LogIterator() { synchronized (LocalEventLog.this) { + mModificationCount = LocalEventLog.this.mModificationCount; + mLogTime = mStartTime; mIndex = -1; mCount = -1; @@ -241,6 +291,7 @@ public class LocalEventLog { public boolean hasNext() { synchronized (LocalEventLog.this) { + checkModifications(); return mCount < mLogSize; } } @@ -277,5 +328,12 @@ public class LocalEventLog { } } while (mCount < mLogSize && isFiller(mEntries[mIndex])); } + + @GuardedBy("LocalEventLog.this") + private void checkModifications() { + if (mModificationCount != LocalEventLog.this.mModificationCount) { + throw new ConcurrentModificationException(); + } + } } } diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 94953e0b72f1f..45436e753191f 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -52,16 +52,28 @@ public class LocationEventLog extends LocalEventLog { if (D) { return 600; } else { + return 300; + } + } + + private static int getLocationsLogSize() { + if (D) { return 200; + } else { + return 100; } } @GuardedBy("mAggregateStats") private final ArrayMap> mAggregateStats; - public LocationEventLog() { + @GuardedBy("this") + private final LocationsEventLog mLocationsLog; + + private LocationEventLog() { super(getLogSize(), Object.class); mAggregateStats = new ArrayMap<>(4); + mLocationsLog = new LocationsEventLog(getLocationsLogSize()); } /** Copies out all aggregated stats. */ @@ -95,39 +107,39 @@ public class LocationEventLog extends LocalEventLog { /** Logs a user switched event. */ public void logUserSwitched(int userIdFrom, int userIdTo) { - addLogEvent(new UserSwitchedEvent(userIdFrom, userIdTo)); + addLog(new UserSwitchedEvent(userIdFrom, userIdTo)); } /** Logs a location enabled/disabled event. */ public void logLocationEnabled(int userId, boolean enabled) { - addLogEvent(new LocationEnabledEvent(userId, enabled)); + addLog(new LocationEnabledEvent(userId, enabled)); } /** Logs a location enabled/disabled event. */ public void logAdasLocationEnabled(int userId, boolean enabled) { - addLogEvent(new LocationAdasEnabledEvent(userId, enabled)); + addLog(new LocationAdasEnabledEvent(userId, enabled)); } /** Logs a location provider enabled/disabled event. */ public void logProviderEnabled(String provider, int userId, boolean enabled) { - addLogEvent(new ProviderEnabledEvent(provider, userId, enabled)); + addLog(new ProviderEnabledEvent(provider, userId, enabled)); } /** Logs a location provider being replaced/unreplaced by a mock provider. */ public void logProviderMocked(String provider, boolean mocked) { - addLogEvent(new ProviderMockedEvent(provider, mocked)); + addLog(new ProviderMockedEvent(provider, mocked)); } /** Logs a new client registration for a location provider. */ public void logProviderClientRegistered(String provider, CallerIdentity identity, LocationRequest request) { - addLogEvent(new ProviderClientRegisterEvent(provider, true, identity, request)); + addLog(new ProviderClientRegisterEvent(provider, true, identity, request)); getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis()); } /** Logs a client unregistration for a location provider. */ public void logProviderClientUnregistered(String provider, CallerIdentity identity) { - addLogEvent(new ProviderClientRegisterEvent(provider, false, identity, null)); + addLog(new ProviderClientRegisterEvent(provider, false, identity, null)); getAggregateStats(provider, identity).markRequestRemoved(); } @@ -144,7 +156,7 @@ public class LocationEventLog extends LocalEventLog { /** Logs a client for a location provider entering the foreground state. */ public void logProviderClientForeground(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientForegroundEvent(provider, true, identity)); + addLog(new ProviderClientForegroundEvent(provider, true, identity)); } getAggregateStats(provider, identity).markRequestForeground(); } @@ -152,7 +164,7 @@ public class LocationEventLog extends LocalEventLog { /** Logs a client for a location provider leaving the foreground state. */ public void logProviderClientBackground(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientForegroundEvent(provider, false, identity)); + addLog(new ProviderClientForegroundEvent(provider, false, identity)); } getAggregateStats(provider, identity).markRequestBackground(); } @@ -160,32 +172,34 @@ public class LocationEventLog extends LocalEventLog { /** Logs a client for a location provider entering the permitted state. */ public void logProviderClientPermitted(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientPermittedEvent(provider, true, identity)); + addLog(new ProviderClientPermittedEvent(provider, true, identity)); } } /** Logs a client for a location provider leaving the permitted state. */ public void logProviderClientUnpermitted(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientPermittedEvent(provider, false, identity)); + addLog(new ProviderClientPermittedEvent(provider, false, identity)); } } /** Logs a change to the provider request for a location provider. */ public void logProviderUpdateRequest(String provider, ProviderRequest request) { - addLogEvent(new ProviderUpdateEvent(provider, request)); + addLog(new ProviderUpdateEvent(provider, request)); } /** Logs a new incoming location for a location provider. */ public void logProviderReceivedLocations(String provider, int numLocations) { - addLogEvent(new ProviderReceiveLocationEvent(provider, numLocations)); + synchronized (this) { + mLocationsLog.logProviderReceivedLocations(provider, numLocations); + } } /** Logs a location deliver for a client of a location provider. */ public void logProviderDeliveredLocations(String provider, int numLocations, CallerIdentity identity) { - if (D) { - addLogEvent(new ProviderDeliverLocationEvent(provider, numLocations, identity)); + synchronized (this) { + mLocationsLog.logProviderDeliveredLocations(provider, numLocations, identity); } getAggregateStats(provider, identity).markLocationDelivered(); } @@ -193,19 +207,24 @@ public class LocationEventLog extends LocalEventLog { /** Logs that a provider has entered or exited stationary throttling. */ public void logProviderStationaryThrottled(String provider, boolean throttled, ProviderRequest request) { - addLogEvent(new ProviderStationaryThrottledEvent(provider, throttled, request)); + addLog(new ProviderStationaryThrottledEvent(provider, throttled, request)); } /** Logs that the location power save mode has changed. */ public void logLocationPowerSaveMode( @LocationPowerSaveMode int locationPowerSaveMode) { - addLogEvent(new LocationPowerSaveModeEvent(locationPowerSaveMode)); + addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode)); } - private void addLogEvent(Object logEvent) { + private void addLog(Object logEvent) { addLog(SystemClock.elapsedRealtime(), logEvent); } + @Override + public synchronized void iterate(LogConsumer consumer) { + iterate(consumer, this, mLocationsLog); + } + public void iterate(Consumer consumer) { iterate(consumer, null); } @@ -488,6 +507,26 @@ public class LocationEventLog extends LocalEventLog { } } + private static final class LocationsEventLog extends LocalEventLog { + + LocationsEventLog(int size) { + super(size, Object.class); + } + + public void logProviderReceivedLocations(String provider, int numLocations) { + addLog(new ProviderReceiveLocationEvent(provider, numLocations)); + } + + public void logProviderDeliveredLocations(String provider, int numLocations, + CallerIdentity identity) { + addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity)); + } + + private void addLog(Object logEvent) { + this.addLog(SystemClock.elapsedRealtime(), logEvent); + } + } + /** * Aggregate statistics for a single package under a single provider. */