Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 38465c19 authored by Suprabh Shukla's avatar Suprabh Shukla Committed by android-build-merger
Browse files

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

am: 10b77a39

Change-Id: I92f83289bd5a10b0a17012c2fd993ade0033375e
parents 2b864f62 10b77a39
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+9 −17
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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());
    }
}
+0 −47
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -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
Loading