Returns UsageEvents from previous days
- Avoid writing to disk when querying UsageStats. - Use new UnixCalendar to avoid issues with Locale and TimeZone. Bug: 16951313 Change-Id: I2473b8ef8dc1e2f6be22d4c689b96e346bdcafd5
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (C) 2014 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 com.android.server.usage;
|
||||
|
||||
import android.app.usage.UsageStatsManager;
|
||||
|
||||
/**
|
||||
* A handy calendar object that knows nothing of Locale's or TimeZones. This simplifies
|
||||
* interval book-keeping. It is *NOT* meant to be used as a user-facing calendar, as it has
|
||||
* no concept of Locale or TimeZone.
|
||||
*/
|
||||
public class UnixCalendar {
|
||||
private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000;
|
||||
private static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
|
||||
private static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
|
||||
private static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
|
||||
private long mTime;
|
||||
|
||||
public UnixCalendar(long time) {
|
||||
mTime = time;
|
||||
}
|
||||
|
||||
public void truncateToDay() {
|
||||
mTime -= mTime % DAY_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void truncateToWeek() {
|
||||
mTime -= mTime % WEEK_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void truncateToMonth() {
|
||||
mTime -= mTime % MONTH_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void truncateToYear() {
|
||||
mTime -= mTime % YEAR_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void addDays(int val) {
|
||||
mTime += val * DAY_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void addWeeks(int val) {
|
||||
mTime += val * WEEK_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void addMonths(int val) {
|
||||
mTime += val * MONTH_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void addYears(int val) {
|
||||
mTime += val * YEAR_IN_MILLIS;
|
||||
}
|
||||
|
||||
public void setTimeInMillis(long time) {
|
||||
mTime = time;
|
||||
}
|
||||
|
||||
public long getTimeInMillis() {
|
||||
return mTime;
|
||||
}
|
||||
|
||||
public static void truncateTo(UnixCalendar calendar, int intervalType) {
|
||||
switch (intervalType) {
|
||||
case UsageStatsManager.INTERVAL_YEARLY:
|
||||
calendar.truncateToYear();
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_MONTHLY:
|
||||
calendar.truncateToMonth();
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_WEEKLY:
|
||||
calendar.truncateToWeek();
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_DAILY:
|
||||
calendar.truncateToDay();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Can't truncate date to interval " +
|
||||
intervalType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,24 +21,30 @@ import android.app.usage.UsageStatsManager;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides an interface to query for UsageStat data from an XML database.
|
||||
*/
|
||||
class UsageStatsDatabase {
|
||||
private static final int CURRENT_VERSION = 1;
|
||||
|
||||
private static final String TAG = "UsageStatsDatabase";
|
||||
private static final boolean DEBUG = UsageStatsService.DEBUG;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final File[] mIntervalDirs;
|
||||
private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
|
||||
private final Calendar mCal;
|
||||
private final UnixCalendar mCal;
|
||||
private final File mVersionFile;
|
||||
|
||||
public UsageStatsDatabase(File dir) {
|
||||
mIntervalDirs = new File[] {
|
||||
@@ -47,8 +53,9 @@ class UsageStatsDatabase {
|
||||
new File(dir, "monthly"),
|
||||
new File(dir, "yearly"),
|
||||
};
|
||||
mVersionFile = new File(dir, "version");
|
||||
mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
|
||||
mCal = Calendar.getInstance();
|
||||
mCal = new UnixCalendar(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +71,8 @@ class UsageStatsDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
checkVersionLocked();
|
||||
|
||||
final FilenameFilter backupFileFilter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
@@ -88,6 +97,43 @@ class UsageStatsDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVersionLocked() {
|
||||
int version;
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) {
|
||||
version = Integer.parseInt(reader.readLine());
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
version = 0;
|
||||
}
|
||||
|
||||
if (version != CURRENT_VERSION) {
|
||||
Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION);
|
||||
doUpgradeLocked(version, CURRENT_VERSION);
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
|
||||
writer.write(Integer.toString(CURRENT_VERSION));
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to write new version");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doUpgradeLocked(int thisVersion, int nextVersion) {
|
||||
if (thisVersion == 0) {
|
||||
// Delete all files if we are version 0. This is a pre-release version,
|
||||
// so this is fine.
|
||||
Slog.i(TAG, "Deleting all usage stats files");
|
||||
for (int i = 0; i < mIntervalDirs.length; i++) {
|
||||
File[] files = mIntervalDirs[i].listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest stats that exist for this interval type.
|
||||
*/
|
||||
@@ -161,25 +207,48 @@ class UsageStatsDatabase {
|
||||
throw new IllegalArgumentException("Bad interval type " + intervalType);
|
||||
}
|
||||
|
||||
if (endTime < beginTime) {
|
||||
final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType];
|
||||
|
||||
if (endTime <= beginTime) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "endTime(" + endTime + ") <= beginTime(" + beginTime + ")");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final int startIndex = mSortedStatFiles[intervalType].closestIndexOnOrBefore(beginTime);
|
||||
int startIndex = intervalStats.closestIndexOnOrBefore(beginTime);
|
||||
if (startIndex < 0) {
|
||||
// All the stats available have timestamps after beginTime, which means they all
|
||||
// match.
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
int endIndex = intervalStats.closestIndexOnOrBefore(endTime);
|
||||
if (endIndex < 0) {
|
||||
// All the stats start after this range ends, so nothing matches.
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "No results for this range. All stats start after.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int endIndex = mSortedStatFiles[intervalType].closestIndexOnOrAfter(endTime);
|
||||
if (endIndex < 0) {
|
||||
endIndex = mSortedStatFiles[intervalType].size() - 1;
|
||||
if (intervalStats.keyAt(endIndex) == endTime) {
|
||||
// The endTime is exclusive, so if we matched exactly take the one before.
|
||||
endIndex--;
|
||||
if (endIndex < 0) {
|
||||
// All the stats start after this range ends, so nothing matches.
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "No results for this range. All stats start after.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
IntervalStats stats = new IntervalStats();
|
||||
ArrayList<T> results = new ArrayList<>();
|
||||
for (int i = startIndex; i <= endIndex; i++) {
|
||||
final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i);
|
||||
final AtomicFile f = intervalStats.valueAt(i);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
|
||||
@@ -230,22 +299,22 @@ class UsageStatsDatabase {
|
||||
synchronized (mLock) {
|
||||
long timeNow = System.currentTimeMillis();
|
||||
mCal.setTimeInMillis(timeNow);
|
||||
mCal.add(Calendar.YEAR, -3);
|
||||
mCal.addYears(-3);
|
||||
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY],
|
||||
mCal.getTimeInMillis());
|
||||
|
||||
mCal.setTimeInMillis(timeNow);
|
||||
mCal.add(Calendar.MONTH, -6);
|
||||
mCal.addMonths(-6);
|
||||
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY],
|
||||
mCal.getTimeInMillis());
|
||||
|
||||
mCal.setTimeInMillis(timeNow);
|
||||
mCal.add(Calendar.WEEK_OF_YEAR, -4);
|
||||
mCal.addWeeks(-4);
|
||||
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY],
|
||||
mCal.getTimeInMillis());
|
||||
|
||||
mCal.setTimeInMillis(timeNow);
|
||||
mCal.add(Calendar.DAY_OF_YEAR, -7);
|
||||
mCal.addDays(-7);
|
||||
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
|
||||
mCal.getTimeInMillis());
|
||||
}
|
||||
|
||||
@@ -62,9 +62,7 @@ public class UsageStatsService extends SystemService implements
|
||||
static final boolean DEBUG = false;
|
||||
private static final long TEN_SECONDS = 10 * 1000;
|
||||
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
|
||||
private static final long TWO_MINUTES = 2 * 60 * 1000;
|
||||
private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES;
|
||||
private static final long END_TIME_DELAY = DEBUG ? 0 : TWO_MINUTES;
|
||||
|
||||
// Handler message types.
|
||||
static final int MSG_REPORT_EVENT = 0;
|
||||
@@ -178,7 +176,7 @@ public class UsageStatsService extends SystemService implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the Bunder stub
|
||||
* Called by the Binder stub
|
||||
*/
|
||||
void shutdown() {
|
||||
synchronized (mLock) {
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 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 com.android.server.usage;
|
||||
|
||||
import android.app.usage.UsageStatsManager;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* A collection of utility methods used by the UsageStatsService and accompanying classes.
|
||||
*/
|
||||
final class UsageStatsUtils {
|
||||
private UsageStatsUtils() {}
|
||||
|
||||
/**
|
||||
* Truncates the date to the given UsageStats bucket. For example, if the bucket is
|
||||
* {@link UsageStatsManager#INTERVAL_YEARLY}, the date is truncated to the 1st day of the year,
|
||||
* with the time set to 00:00:00.
|
||||
*
|
||||
* @param bucket The UsageStats bucket to truncate to.
|
||||
* @param cal The date to truncate.
|
||||
*/
|
||||
public static void truncateDateTo(int bucket, Calendar cal) {
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
switch (bucket) {
|
||||
case UsageStatsManager.INTERVAL_YEARLY:
|
||||
cal.set(Calendar.DAY_OF_YEAR, 0);
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_MONTHLY:
|
||||
cal.set(Calendar.DAY_OF_MONTH, 0);
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_WEEKLY:
|
||||
cal.set(Calendar.DAY_OF_WEEK, 0);
|
||||
break;
|
||||
|
||||
case UsageStatsManager.INTERVAL_DAILY:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Can't truncate date to bucket " + bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -47,7 +46,7 @@ class UserUsageStatsService {
|
||||
private final UsageStatsDatabase mDatabase;
|
||||
private final IntervalStats[] mCurrentStats;
|
||||
private boolean mStatsChanged = false;
|
||||
private final Calendar mDailyExpiryDate;
|
||||
private final UnixCalendar mDailyExpiryDate;
|
||||
private final StatsUpdatedListener mListener;
|
||||
private final String mLogPrefix;
|
||||
|
||||
@@ -56,7 +55,7 @@ class UserUsageStatsService {
|
||||
}
|
||||
|
||||
UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
|
||||
mDailyExpiryDate = Calendar.getInstance();
|
||||
mDailyExpiryDate = new UnixCalendar(0);
|
||||
mDatabase = new UsageStatsDatabase(usageStatsDir);
|
||||
mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
|
||||
mListener = listener;
|
||||
@@ -66,6 +65,7 @@ class UserUsageStatsService {
|
||||
void init() {
|
||||
mDatabase.init();
|
||||
|
||||
final long timeNow = System.currentTimeMillis();
|
||||
int nullCount = 0;
|
||||
for (int i = 0; i < mCurrentStats.length; i++) {
|
||||
mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
|
||||
@@ -73,6 +73,11 @@ class UserUsageStatsService {
|
||||
// Find out how many intervals we don't have data for.
|
||||
// Ideally it should be all or none.
|
||||
nullCount++;
|
||||
} else if (mCurrentStats[i].beginTime > timeNow) {
|
||||
Slog.e(TAG, mLogPrefix + "Interval " + i + " has stat in the future " +
|
||||
mCurrentStats[i].beginTime);
|
||||
mCurrentStats[i] = null;
|
||||
nullCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,10 +99,11 @@ class UserUsageStatsService {
|
||||
// that is reported.
|
||||
mDailyExpiryDate.setTimeInMillis(
|
||||
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
|
||||
mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
|
||||
UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
|
||||
Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
|
||||
+ sDateFormat.format(mDailyExpiryDate.getTime()));
|
||||
mDailyExpiryDate.addDays(1);
|
||||
mDailyExpiryDate.truncateToDay();
|
||||
Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
|
||||
sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
|
||||
"(" + mDailyExpiryDate.getTimeInMillis() + ")");
|
||||
}
|
||||
|
||||
// Now close off any events that were open at the time this was saved.
|
||||
@@ -195,49 +201,68 @@ class UserUsageStatsService {
|
||||
* and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
|
||||
* provided to select the stats to use from the IntervalStats object.
|
||||
*/
|
||||
private <T> List<T> queryStats(int bucketType, long beginTime, long endTime,
|
||||
private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
|
||||
StatCombiner<T> combiner) {
|
||||
if (bucketType == UsageStatsManager.INTERVAL_BEST) {
|
||||
bucketType = mDatabase.findBestFitBucket(beginTime, endTime);
|
||||
if (intervalType == UsageStatsManager.INTERVAL_BEST) {
|
||||
intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
|
||||
if (intervalType < 0) {
|
||||
// Nothing saved to disk yet, so every stat is just as equal (no rollover has
|
||||
// occurred.
|
||||
intervalType = UsageStatsManager.INTERVAL_DAILY;
|
||||
}
|
||||
}
|
||||
|
||||
if (bucketType < 0 || bucketType >= mCurrentStats.length) {
|
||||
if (intervalType < 0 || intervalType >= mCurrentStats.length) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Bad bucketType used " + bucketType);
|
||||
Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (beginTime >= mCurrentStats[bucketType].endTime) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
|
||||
+ mCurrentStats[bucketType].endTime);
|
||||
}
|
||||
// Nothing newer available.
|
||||
return null;
|
||||
|
||||
} else if (beginTime >= mCurrentStats[bucketType].beginTime) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType);
|
||||
}
|
||||
// Fast path for retrieving in-memory state.
|
||||
ArrayList<T> results = new ArrayList<>();
|
||||
combiner.combine(mCurrentStats[bucketType], true, results);
|
||||
return results;
|
||||
}
|
||||
|
||||
// Flush any changes that were made to disk before we do a disk query.
|
||||
// If we're not grabbing the ongoing stats, no need to persist.
|
||||
persistActiveStats();
|
||||
final IntervalStats currentStats = mCurrentStats[intervalType];
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= "
|
||||
Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
|
||||
+ beginTime + " AND endTime < " + endTime);
|
||||
}
|
||||
|
||||
final List<T> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime, combiner);
|
||||
if (beginTime >= currentStats.endTime) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
|
||||
+ currentStats.endTime);
|
||||
}
|
||||
// Nothing newer available.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Truncate the endTime to just before the in-memory stats. Then, we'll append the
|
||||
// in-memory stats to the results (if necessary) so as to avoid writing to disk too
|
||||
// often.
|
||||
final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
|
||||
|
||||
// Get the stats from disk.
|
||||
List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
|
||||
truncatedEndTime, combiner);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Results: " + (results == null ? 0 : results.size()));
|
||||
Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
|
||||
Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
|
||||
" endTime=" + currentStats.endTime);
|
||||
}
|
||||
|
||||
// Now check if the in-memory stats match the range and add them if they do.
|
||||
if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
|
||||
}
|
||||
|
||||
if (results == null) {
|
||||
results = new ArrayList<>();
|
||||
}
|
||||
combiner.combine(currentStats, true, results);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -250,44 +275,45 @@ class UserUsageStatsService {
|
||||
return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
|
||||
}
|
||||
|
||||
UsageEvents queryEvents(long beginTime, long endTime) {
|
||||
if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) {
|
||||
if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) {
|
||||
return null;
|
||||
}
|
||||
UsageEvents queryEvents(final long beginTime, final long endTime) {
|
||||
final ArraySet<String> names = new ArraySet<>();
|
||||
List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
|
||||
beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
|
||||
@Override
|
||||
public void combine(IntervalStats stats, boolean mutable,
|
||||
List<UsageEvents.Event> accumulatedResult) {
|
||||
if (stats.events == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSparseArray<UsageEvents.Event> events =
|
||||
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events;
|
||||
if (events == null) {
|
||||
return null;
|
||||
}
|
||||
final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
|
||||
if (startIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int startIndex = events.closestIndexOnOrAfter(beginTime);
|
||||
if (startIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
final int size = stats.events.size();
|
||||
for (int i = startIndex; i < size; i++) {
|
||||
if (stats.events.keyAt(i) >= endTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArraySet<String> names = new ArraySet<>();
|
||||
ArrayList<UsageEvents.Event> results = new ArrayList<>();
|
||||
final int size = events.size();
|
||||
for (int i = startIndex; i < size; i++) {
|
||||
if (events.keyAt(i) >= endTime) {
|
||||
break;
|
||||
}
|
||||
final UsageEvents.Event event = events.valueAt(i);
|
||||
names.add(event.mPackage);
|
||||
if (event.mClass != null) {
|
||||
names.add(event.mClass);
|
||||
}
|
||||
results.add(event);
|
||||
}
|
||||
String[] table = names.toArray(new String[names.size()]);
|
||||
Arrays.sort(table);
|
||||
return new UsageEvents(results, table);
|
||||
final UsageEvents.Event event = stats.events.valueAt(i);
|
||||
names.add(event.mPackage);
|
||||
if (event.mClass != null) {
|
||||
names.add(event.mClass);
|
||||
}
|
||||
accumulatedResult.add(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (results == null || results.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(adamlesinski): Query the previous days.
|
||||
return null;
|
||||
String[] table = names.toArray(new String[names.size()]);
|
||||
Arrays.sort(table);
|
||||
return new UsageEvents(results, table);
|
||||
}
|
||||
|
||||
void persistActiveStats() {
|
||||
@@ -360,54 +386,53 @@ class UserUsageStatsService {
|
||||
private void loadActiveStats() {
|
||||
final long timeNow = System.currentTimeMillis();
|
||||
|
||||
Calendar tempCal = mDailyExpiryDate;
|
||||
for (int bucketType = 0; bucketType < mCurrentStats.length; bucketType++) {
|
||||
final UnixCalendar tempCal = mDailyExpiryDate;
|
||||
for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
|
||||
tempCal.setTimeInMillis(timeNow);
|
||||
UsageStatsUtils.truncateDateTo(bucketType, tempCal);
|
||||
UnixCalendar.truncateTo(tempCal, intervalType);
|
||||
|
||||
if (mCurrentStats[bucketType] != null &&
|
||||
mCurrentStats[bucketType].beginTime == tempCal.getTimeInMillis()) {
|
||||
if (mCurrentStats[intervalType] != null &&
|
||||
mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
|
||||
// These are the same, no need to load them (in memory stats are always newer
|
||||
// than persisted stats).
|
||||
continue;
|
||||
}
|
||||
|
||||
final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(bucketType);
|
||||
if (lastBeginTime >= tempCal.getTimeInMillis()) {
|
||||
final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
|
||||
if (lastBeginTime > timeNow) {
|
||||
Slog.e(TAG, mLogPrefix + "Latest usage stats for interval " +
|
||||
intervalType + " begins in the future");
|
||||
mCurrentStats[intervalType] = null;
|
||||
} else if (lastBeginTime >= tempCal.getTimeInMillis()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, mLogPrefix + "Loading existing stats (" + lastBeginTime +
|
||||
") for bucket " + bucketType);
|
||||
}
|
||||
mCurrentStats[bucketType] = mDatabase.getLatestUsageStats(bucketType);
|
||||
if (DEBUG) {
|
||||
if (mCurrentStats[bucketType] != null) {
|
||||
Slog.d(TAG, mLogPrefix + "Found " +
|
||||
(mCurrentStats[bucketType].events == null ?
|
||||
0 : mCurrentStats[bucketType].events.size()) +
|
||||
" events");
|
||||
}
|
||||
Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
|
||||
sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
|
||||
") for interval " + intervalType);
|
||||
}
|
||||
mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
|
||||
} else {
|
||||
mCurrentStats[bucketType] = null;
|
||||
mCurrentStats[intervalType] = null;
|
||||
}
|
||||
|
||||
if (mCurrentStats[bucketType] == null) {
|
||||
if (mCurrentStats[intervalType] == null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating new stats (" + tempCal.getTimeInMillis() +
|
||||
") for bucket " + bucketType);
|
||||
Slog.d(TAG, "Creating new stats @ " +
|
||||
sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
|
||||
tempCal.getTimeInMillis() + ") for interval " + intervalType);
|
||||
|
||||
}
|
||||
mCurrentStats[bucketType] = new IntervalStats();
|
||||
mCurrentStats[bucketType].beginTime = tempCal.getTimeInMillis();
|
||||
mCurrentStats[bucketType].endTime = timeNow;
|
||||
mCurrentStats[intervalType] = new IntervalStats();
|
||||
mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
|
||||
mCurrentStats[intervalType].endTime = timeNow;
|
||||
}
|
||||
}
|
||||
mStatsChanged = false;
|
||||
mDailyExpiryDate.setTimeInMillis(timeNow);
|
||||
mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1);
|
||||
UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate);
|
||||
Slog.i(TAG, mLogPrefix + "Rollover scheduled for "
|
||||
+ sDateFormat.format(mDailyExpiryDate.getTime()));
|
||||
mDailyExpiryDate.addDays(1);
|
||||
mDailyExpiryDate.truncateToDay();
|
||||
Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
|
||||
sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
|
||||
tempCal.getTimeInMillis() + ")");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user