Merge "Using a list to store usage events" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-04-26 22:58:40 +00:00
committed by Android (Google) Code Review
7 changed files with 265 additions and 97 deletions

View File

@@ -0,0 +1,106 @@
/*
* 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;
}
}

View File

@@ -27,14 +27,12 @@ import android.util.Slog;
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.
@@ -75,22 +73,16 @@ public class TimeSparseArray<E> extends LongSparseArray<E> {
/**
* {@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);

View File

@@ -0,0 +1,130 @@
/*
* 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());
}
}

View File

@@ -1,47 +0,0 @@
/*
* 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);
}
}
}

View File

@@ -16,6 +16,7 @@
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;
@@ -37,7 +38,7 @@ class IntervalStats {
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

View File

@@ -22,12 +22,11 @@ import org.xmlpull.v1.XmlPullParserException;
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;
@@ -193,9 +192,9 @@ final class UsageStatsXmlV1 {
}
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,
@@ -411,7 +410,7 @@ final class UsageStatsXmlV1 {
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);
}

View File

@@ -17,15 +17,14 @@
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;
@@ -174,10 +173,10 @@ class UserUsageStatsService {
// 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;
@@ -367,18 +366,14 @@ class UserUsageStatsService {
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();
}
@@ -410,18 +405,14 @@ class UserUsageStatsService {
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;
}
@@ -633,18 +624,14 @@ class UserUsageStatsService {
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;
}
@@ -779,10 +766,10 @@ class UserUsageStatsService {
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;
}