--- /dev/null
+/*
+ * Copyright (C) 2018 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 android.app.usage;
+
+import java.util.ArrayList;
+
+/**
+ * A container to keep {@link UsageEvents.Event usage events} in non-descending order of their
+ * {@link UsageEvents.Event#mTimeStamp timestamps}.
+ *
+ * @hide
+ */
+public class EventList {
+
+ private final ArrayList<UsageEvents.Event> mEvents;
+
+ /**
+ * Create a new event list with default capacity
+ */
+ public EventList() {
+ mEvents = new ArrayList<>();
+ }
+
+ /**
+ * Returns the size of the list
+ * @return the number of events in the list
+ */
+ public int size() {
+ return mEvents.size();
+ }
+
+ /**
+ * Removes all events from the list
+ */
+ public void clear() {
+ mEvents.clear();
+ }
+
+ /**
+ * Returns the {@link UsageEvents.Event event} at the specified position in this list.
+ * @param index the index of the event to return, such that {@code 0 <= index < size()}
+ * @return The {@link UsageEvents.Event event} at position {@code index}
+ */
+ public UsageEvents.Event get(int index) {
+ return mEvents.get(index);
+ }
+
+ /**
+ * Inserts the given {@link UsageEvents.Event event} into the list while keeping the list sorted
+ * based on the event {@link UsageEvents.Event#mTimeStamp timestamps}.
+ *
+ * @param event The event to insert
+ */
+ public void insert(UsageEvents.Event event) {
+ final int size = mEvents.size();
+ // fast case: just append if this is the latest event
+ if (size == 0 || event.mTimeStamp >= mEvents.get(size - 1).mTimeStamp) {
+ mEvents.add(event);
+ return;
+ }
+ // To minimize number of elements being shifted, insert at the first occurrence of the next
+ // greatest timestamp in the list.
+ final int insertIndex = firstIndexOnOrAfter(event.mTimeStamp + 1);
+ mEvents.add(insertIndex, event);
+ }
+
+ /**
+ * Finds the index of the first event whose timestamp is greater than or equal to the given
+ * timestamp.
+ *
+ * @param timeStamp The timestamp for which to search the list.
+ * @return The smallest {@code index} for which {@code (get(index).mTimeStamp >= timeStamp)} is
+ * {@code true}, or {@link #size() size} if no such {@code index} exists.
+ */
+ public int firstIndexOnOrAfter(long timeStamp) {
+ final int size = mEvents.size();
+ int result = size;
+ int lo = 0;
+ int hi = size - 1;
+ while (lo <= hi) {
+ final int mid = (lo + hi) >>> 1;
+ final long midTimeStamp = mEvents.get(mid).mTimeStamp;
+ if (midTimeStamp >= timeStamp) {
+ hi = mid - 1;
+ result = mid;
+ } else {
+ lo = mid + 1;
+ }
+ }
+ return result;
+ }
+}
public class TimeSparseArray<E> extends LongSparseArray<E> {
private static final String TAG = TimeSparseArray.class.getSimpleName();
+ private boolean mWtfReported;
+
public TimeSparseArray() {
super();
}
- public TimeSparseArray(int initialCapacity) {
- super(initialCapacity);
- }
-
/**
* Finds the index of the first element whose timestamp is greater or equal to
* the given time.
/**
* {@inheritDoc}
*
- * Overridden to ensure no collisions. The key (time in milliseconds) is incremented till an
- * empty place is found.
+ * <p> As this container is being used only to keep {@link android.util.AtomicFile files},
+ * there should not be any collisions. Reporting a {@link Slog#wtf(String, String)} in case that
+ * happens, as that will lead to one whole file being dropped.
*/
@Override
public void put(long key, E value) {
- final long origKey = key;
- int keyIndex = indexOfKey(key);
- if (keyIndex >= 0) {
- final long sz = size();
- while (keyIndex < sz && keyAt(keyIndex) == key) {
- key++;
- keyIndex++;
- }
- if (key >= origKey + 100) {
- Slog.w(TAG, "Value " + value + " supposed to be inserted at " + origKey
- + " displaced to " + key);
+ if (indexOfKey(key) >= 0) {
+ if (!mWtfReported) {
+ Slog.wtf(TAG, "Overwriting value " + get(key) + " by " + value);
+ mWtfReported = true;
}
}
super.put(key, value);
--- /dev/null
+/*
+ * Copyright (C) 2018 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 android.app.usage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class EventListTest {
+ private static final String TAG = EventListTest.class.getSimpleName();
+
+ private UsageEvents.Event getUsageEvent(long timeStamp) {
+ final UsageEvents.Event event = new UsageEvents.Event();
+ event.mTimeStamp = timeStamp;
+ return event;
+ }
+
+ private static String getListTimeStamps(EventList list) {
+ final StringBuilder builder = new StringBuilder("[");
+ for (int i = 0; i < list.size() - 1; i++) {
+ builder.append(list.get(i).mTimeStamp);
+ builder.append(", ");
+ }
+ builder.append(list.get(list.size() - 1).mTimeStamp);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ private static void assertSorted(EventList eventList) {
+ for (int i = 1; i < eventList.size(); i++) {
+ final long lastTimeStamp = eventList.get(i - 1).mTimeStamp;
+ if (eventList.get(i).mTimeStamp < lastTimeStamp) {
+ Log.e(TAG, "Unsorted timestamps in list: " + getListTimeStamps(eventList));
+ fail("Timestamp " + eventList.get(i).mTimeStamp + " at " + i
+ + " follows larger timestamp " + lastTimeStamp);
+ }
+ }
+ }
+
+ @Test
+ public void testInsertsSortedRandom() {
+ final Random random = new Random(128);
+ final EventList listUnderTest = new EventList();
+ for (int i = 0; i < 100; i++) {
+ listUnderTest.insert(getUsageEvent(random.nextLong()));
+ }
+ assertSorted(listUnderTest);
+ }
+
+ @Test
+ public void testInsertsSortedWithDuplicates() {
+ final Random random = new Random(256);
+ final EventList listUnderTest = new EventList();
+ for (int i = 0; i < 10; i++) {
+ final long randomTimeStamp = random.nextLong();
+ for (int j = 0; j < 10; j++) {
+ listUnderTest.insert(getUsageEvent(randomTimeStamp));
+ }
+ }
+ assertSorted(listUnderTest);
+ }
+
+ @Test
+ public void testFirstIndexOnOrAfter() {
+ final EventList listUnderTest = new EventList();
+ listUnderTest.insert(getUsageEvent(2));
+ listUnderTest.insert(getUsageEvent(5));
+ listUnderTest.insert(getUsageEvent(5));
+ listUnderTest.insert(getUsageEvent(5));
+ listUnderTest.insert(getUsageEvent(8));
+ assertTrue(listUnderTest.firstIndexOnOrAfter(1) == 0);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(2) == 0);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(3) == 1);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(4) == 1);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(5) == 1);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(6) == 4);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(7) == 4);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(8) == 4);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(9) == listUnderTest.size());
+ assertTrue(listUnderTest.firstIndexOnOrAfter(100) == listUnderTest.size());
+
+ listUnderTest.clear();
+ assertTrue(listUnderTest.firstIndexOnOrAfter(5) == 0);
+ assertTrue(listUnderTest.firstIndexOnOrAfter(100) == 0);
+ }
+
+ @Test
+ public void testClear() {
+ final EventList listUnderTest = new EventList();
+ for (int i = 1; i <= 100; i++) {
+ listUnderTest.insert(getUsageEvent(i));
+ }
+ listUnderTest.clear();
+ assertEquals(0, listUnderTest.size());
+ }
+
+ @Test
+ public void testSize() {
+ final EventList listUnderTest = new EventList();
+ for (int i = 1; i <= 100; i++) {
+ listUnderTest.insert(getUsageEvent(i));
+ }
+ assertEquals(100, listUnderTest.size());
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2018 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 android.app.usage;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.SystemClock;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class TimeSparseArrayTest {
- @Test
- public void testDuplicateKeysNotDropped() {
- final TimeSparseArray<Integer> testTimeSparseArray = new TimeSparseArray<>();
- final long key = SystemClock.elapsedRealtime();
- for (int i = 0; i < 5; i++) {
- testTimeSparseArray.put(key, i);
- }
- for (int i = 0; i < 5; i++) {
- final int valueIndex = testTimeSparseArray.indexOfValue(i);
- assertTrue("Value " + i + " not found; intended key: " + key , valueIndex >= 0);
- final long keyForValue = testTimeSparseArray.keyAt(valueIndex);
- assertTrue("Value " + i + " stored too far (at " + keyForValue + ") from intended key "
- + key, Math.abs(keyForValue - key) < 100);
- }
- }
-}
package com.android.server.usage;
import android.app.usage.ConfigurationStats;
+import android.app.usage.EventList;
import android.app.usage.EventStats;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
public Configuration activeConfiguration;
- public TimeSparseArray<UsageEvents.Event> events;
+ public EventList events;
// A string cache. This is important as when we're parsing XML files, we don't want to
// keep hundreds of strings that have the same contents. We will read the string
import org.xmlpull.v1.XmlSerializer;
import android.app.usage.ConfigurationStats;
-import android.app.usage.TimeSparseArray;
+import android.app.usage.EventList;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.ArrayMap;
-import android.util.Pair;
import java.io.IOException;
import java.net.ProtocolException;
}
if (statsOut.events == null) {
- statsOut.events = new TimeSparseArray<>();
+ statsOut.events = new EventList();
}
- statsOut.events.put(event.mTimeStamp, event);
+ statsOut.events.insert(event);
}
private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
xml.startTag(null, EVENT_LOG_TAG);
final int eventCount = stats.events != null ? stats.events.size() : 0;
for (int i = 0; i < eventCount; i++) {
- writeEvent(xml, stats, stats.events.valueAt(i));
+ writeEvent(xml, stats, stats.events.get(i));
}
xml.endTag(null, EVENT_LOG_TAG);
}
package com.android.server.usage;
import android.app.usage.ConfigurationStats;
+import android.app.usage.EventList;
import android.app.usage.EventStats;
-import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.res.Configuration;
import android.os.SystemClock;
import android.content.Context;
-import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
// Add the event to the daily list.
if (currentDailyStats.events == null) {
- currentDailyStats.events = new TimeSparseArray<>();
+ currentDailyStats.events = new EventList();
}
if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
- currentDailyStats.events.put(event.mTimeStamp, event);
+ currentDailyStats.events.insert(event);
}
boolean incrementAppLaunch = false;
return;
}
- final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
- if (startIndex < 0) {
- return;
- }
-
+ final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
final int size = stats.events.size();
for (int i = startIndex; i < size; i++) {
- if (stats.events.keyAt(i) >= endTime) {
+ if (stats.events.get(i).mTimeStamp >= endTime) {
return;
}
- UsageEvents.Event event = stats.events.valueAt(i);
+ UsageEvents.Event event = stats.events.get(i);
if (obfuscateInstantApps) {
event = event.getObfuscatedIfInstantApp();
}
return;
}
- final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
- if (startIndex < 0) {
- return;
- }
-
+ final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
final int size = stats.events.size();
for (int i = startIndex; i < size; i++) {
- if (stats.events.keyAt(i) >= endTime) {
+ if (stats.events.get(i).mTimeStamp >= endTime) {
return;
}
- final UsageEvents.Event event = stats.events.valueAt(i);
+ final UsageEvents.Event event = stats.events.get(i);
if (!packageName.equals(event.mPackage)) {
continue;
}
return;
}
- final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
- if (startIndex < 0) {
- return;
- }
-
+ final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
final int size = stats.events.size();
for (int i = startIndex; i < size; i++) {
- if (stats.events.keyAt(i) >= endTime) {
+ if (stats.events.get(i).mTimeStamp >= endTime) {
return;
}
- UsageEvents.Event event = stats.events.valueAt(i);
+ UsageEvents.Event event = stats.events.get(i);
if (pkg != null && !pkg.equals(event.mPackage)) {
continue;
}
if (!skipEvents) {
pw.println("events");
pw.increaseIndent();
- final TimeSparseArray<UsageEvents.Event> events = stats.events;
+ final EventList events = stats.events;
final int eventCount = events != null ? events.size() : 0;
for (int i = 0; i < eventCount; i++) {
- final UsageEvents.Event event = events.valueAt(i);
+ final UsageEvents.Event event = events.get(i);
if (pkg != null && !pkg.equals(event.mPackage)) {
continue;
}