Merge "Using a list to store usage events" into pi-dev
am: 10b77a39a2
Change-Id: I92f83289bd5a10b0a17012c2fd993ade0033375e
This commit is contained in:
106
core/java/android/app/usage/EventList.java
Normal file
106
core/java/android/app/usage/EventList.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
130
core/tests/coretests/src/android/app/usage/EventListTest.java
Normal file
130
core/tests/coretests/src/android/app/usage/EventListTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user