diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-07-21 15:25:30 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-08-08 12:08:15 -0700 |
commit | 3516800b611a79339a3c188332d13a26e9086b09 (patch) | |
tree | 4c9f8791534cc81cd9562223d2929ee8ff554bfc /services/usage | |
parent | 5c09e8ad5ee8e67976066366527ee58792551953 (diff) | |
download | frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.zip frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.tar.gz frameworks_base-3516800b611a79339a3c188332d13a26e9086b09.tar.bz2 |
Second iteration of the UsageStats API
Based on feedback from API council, updated the API.
Also added support for querying the event log.
Change-Id: Ibaa008b9e5bd145acdfe8e20c25c2ed2d96be123
Diffstat (limited to 'services/usage')
7 files changed, 605 insertions, 222 deletions
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java new file mode 100644 index 0000000..43027ad --- /dev/null +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -0,0 +1,81 @@ +/** + * 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.TimeSparseArray; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; +import android.content.ComponentName; +import android.util.ArrayMap; + +class IntervalStats { + public long beginTime; + public long endTime; + public long lastTimeSaved; + public final ArrayMap<String, UsageStats> stats = new ArrayMap<>(); + public TimeSparseArray<UsageEvents.Event> events; + + // Maps flattened string representations of component names to ComponentName. + // This helps save memory from using many duplicate ComponentNames and + // parse time when reading XML. + private final ArrayMap<String, ComponentName> mComponentNames = new ArrayMap<>(); + + UsageStats getOrCreateUsageStats(String packageName) { + UsageStats usageStats = stats.get(packageName); + if (usageStats == null) { + usageStats = new UsageStats(); + usageStats.mPackageName = packageName; + usageStats.mBeginTimeStamp = beginTime; + usageStats.mEndTimeStamp = endTime; + stats.put(packageName, usageStats); + } + return usageStats; + } + + void update(String packageName, long timeStamp, int eventType) { + UsageStats usageStats = getOrCreateUsageStats(packageName); + + // TODO(adamlesinski): Ensure that we recover from incorrect event sequences + // like double MOVE_TO_BACKGROUND, etc. + if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND || + eventType == UsageEvents.Event.END_OF_DAY) { + if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || + usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { + usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed; + } + } + usageStats.mLastEvent = eventType; + usageStats.mLastTimeUsed = timeStamp; + usageStats.mEndTimeStamp = timeStamp; + endTime = timeStamp; + } + + /** + * Return a ComponentName for the given string representation. This will use a cached + * copy of the ComponentName if possible, otherwise it will parse and add it to the + * internal cache. + */ + ComponentName getCachedComponentName(String str) { + ComponentName name = mComponentNames.get(str); + if (name == null) { + name = ComponentName.unflattenFromString(str); + if (name != null) { + mComponentNames.put(str, name); + } + } + return name; + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 4e75f61..e6ce0fe 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -27,30 +27,37 @@ 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 String TAG = "UsageStatsDatabase"; private static final boolean DEBUG = UsageStatsService.DEBUG; private final Object mLock = new Object(); - private final File[] mBucketDirs; + private final File[] mIntervalDirs; private final TimeSparseArray<AtomicFile>[] mSortedStatFiles; private final Calendar mCal; public UsageStatsDatabase(File dir) { - mBucketDirs = new File[] { + mIntervalDirs = new File[] { new File(dir, "daily"), new File(dir, "weekly"), new File(dir, "monthly"), new File(dir, "yearly"), }; - mSortedStatFiles = new TimeSparseArray[mBucketDirs.length]; + mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length]; mCal = Calendar.getInstance(); } + /** + * Initialize any directories required and index what stats are available. + */ void init() { synchronized (mLock) { - for (File f : mBucketDirs) { + for (File f : mIntervalDirs) { f.mkdirs(); if (!f.exists()) { throw new IllegalStateException("Failed to create directory " @@ -68,10 +75,10 @@ class UsageStatsDatabase { // Index the available usage stat files on disk. for (int i = 0; i < mSortedStatFiles.length; i++) { mSortedStatFiles[i] = new TimeSparseArray<>(); - File[] files = mBucketDirs[i].listFiles(backupFileFilter); + File[] files = mIntervalDirs[i].listFiles(backupFileFilter); if (files != null) { if (DEBUG) { - Slog.d(TAG, "Found " + files.length + " stat files for bucket " + i); + Slog.d(TAG, "Found " + files.length + " stat files for interval " + i); } for (File f : files) { @@ -82,21 +89,24 @@ class UsageStatsDatabase { } } - public UsageStats getLatestUsageStats(int bucketType) { + /** + * Get the latest stats that exist for this interval type. + */ + public IntervalStats getLatestUsageStats(int intervalType) { synchronized (mLock) { - if (bucketType < 0 || bucketType >= mBucketDirs.length) { - throw new IllegalArgumentException("Bad bucket type " + bucketType); + if (intervalType < 0 || intervalType >= mIntervalDirs.length) { + throw new IllegalArgumentException("Bad interval type " + intervalType); } - final int fileCount = mSortedStatFiles[bucketType].size(); + final int fileCount = mSortedStatFiles[intervalType].size(); if (fileCount == 0) { return null; } try { - final AtomicFile f = mSortedStatFiles[bucketType].valueAt(fileCount - 1); - UsageStats stats = UsageStatsXml.read(f); - stats.mLastTimeSaved = f.getLastModifiedTime(); + final AtomicFile f = mSortedStatFiles[intervalType].valueAt(fileCount - 1); + IntervalStats stats = new IntervalStats(); + UsageStatsXml.read(f, stats); return stats; } catch (IOException e) { Slog.e(TAG, "Failed to read usage stats file", e); @@ -105,62 +115,114 @@ class UsageStatsDatabase { return null; } - public UsageStats[] getUsageStats(int bucketType, long beginTime, int limit) { + /** + * Get the time at which the latest stats begin for this interval type. + */ + public long getLatestUsageStatsBeginTime(int intervalType) { synchronized (mLock) { - if (bucketType < 0 || bucketType >= mBucketDirs.length) { - throw new IllegalArgumentException("Bad bucket type " + bucketType); + if (intervalType < 0 || intervalType >= mIntervalDirs.length) { + throw new IllegalArgumentException("Bad interval type " + intervalType); } - if (limit <= 0) { - return UsageStats.EMPTY_STATS; + final int statsFileCount = mSortedStatFiles[intervalType].size(); + if (statsFileCount > 0) { + return mSortedStatFiles[intervalType].keyAt(statsFileCount - 1); } + return -1; + } + } - int startIndex = mSortedStatFiles[bucketType].closestIndexAfter(beginTime); + /** + * Find all {@link UsageStats} for the given range and interval type. + */ + public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) { + synchronized (mLock) { + if (intervalType < 0 || intervalType >= mIntervalDirs.length) { + throw new IllegalArgumentException("Bad interval type " + intervalType); + } + + if (endTime < beginTime) { + return null; + } + + final int startIndex = mSortedStatFiles[intervalType].closestIndexOnOrBefore(beginTime); if (startIndex < 0) { - return UsageStats.EMPTY_STATS; + return null; + } + + int endIndex = mSortedStatFiles[intervalType].closestIndexOnOrAfter(endTime); + if (endIndex < 0) { + endIndex = mSortedStatFiles[intervalType].size() - 1; } - final int realLimit = Math.min(limit, mSortedStatFiles[bucketType].size() - startIndex); try { - ArrayList<UsageStats> stats = new ArrayList<>(realLimit); - for (int i = 0; i < realLimit; i++) { - final AtomicFile f = mSortedStatFiles[bucketType].valueAt(startIndex + i); + IntervalStats stats = new IntervalStats(); + ArrayList<UsageStats> results = new ArrayList<>(); + for (int i = startIndex; i <= endIndex; i++) { + final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i); if (DEBUG) { Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath()); } - UsageStats stat = UsageStatsXml.read(f); - if (beginTime < stat.mEndTimeStamp) { - stat.mLastTimeSaved = f.getLastModifiedTime(); - stats.add(stat); + UsageStatsXml.read(f, stats); + if (beginTime < stats.endTime) { + results.addAll(stats.stats.values()); } } - return stats.toArray(new UsageStats[stats.size()]); + return results; } catch (IOException e) { Slog.e(TAG, "Failed to read usage stats file", e); - return UsageStats.EMPTY_STATS; + return null; } } } + /** + * Find the interval that best matches this range. + * + * TODO(adamlesinski): Use endTimeStamp in best fit calculation. + */ + public int findBestFitBucket(long beginTimeStamp, long endTimeStamp) { + synchronized (mLock) { + int bestBucket = -1; + long smallestDiff = Long.MAX_VALUE; + for (int i = mSortedStatFiles.length - 1; i >= 0; i--) { + final int index = mSortedStatFiles[i].closestIndexOnOrBefore(beginTimeStamp); + int size = mSortedStatFiles[i].size(); + if (index >= 0 && index < size) { + // We have some results here, check if they are better than our current match. + long diff = Math.abs(mSortedStatFiles[i].keyAt(index) - beginTimeStamp); + if (diff < smallestDiff) { + smallestDiff = diff; + bestBucket = i; + } + } + } + return bestBucket; + } + } + + /** + * Remove any usage stat files that are too old. + */ public void prune() { synchronized (mLock) { long timeNow = System.currentTimeMillis(); mCal.setTimeInMillis(timeNow); mCal.add(Calendar.MONTH, -6); - pruneFilesOlderThan(mBucketDirs[UsageStatsManager.MONTHLY_BUCKET], + pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY], mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); mCal.add(Calendar.WEEK_OF_YEAR, -4); - pruneFilesOlderThan(mBucketDirs[UsageStatsManager.WEEKLY_BUCKET], + pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY], mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); mCal.add(Calendar.DAY_OF_YEAR, -7); - pruneFilesOlderThan(mBucketDirs[UsageStatsManager.DAILY_BUCKET], + pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY], mCal.getTimeInMillis()); } } @@ -177,23 +239,24 @@ class UsageStatsDatabase { } } - public void putUsageStats(int bucketType, UsageStats stats) - throws IOException { + /** + * Update the stats in the database. They may not be written to disk immediately. + */ + public void putUsageStats(int intervalType, IntervalStats stats) throws IOException { synchronized (mLock) { - if (bucketType < 0 || bucketType >= mBucketDirs.length) { - throw new IllegalArgumentException("Bad bucket type " + bucketType); + if (intervalType < 0 || intervalType >= mIntervalDirs.length) { + throw new IllegalArgumentException("Bad interval type " + intervalType); } - AtomicFile f = mSortedStatFiles[bucketType].get(stats.mBeginTimeStamp); + AtomicFile f = mSortedStatFiles[intervalType].get(stats.beginTime); if (f == null) { - f = new AtomicFile(new File(mBucketDirs[bucketType], - Long.toString(stats.mBeginTimeStamp))); - mSortedStatFiles[bucketType].append(stats.mBeginTimeStamp, f); + f = new AtomicFile(new File(mIntervalDirs[intervalType], + Long.toString(stats.beginTime))); + mSortedStatFiles[intervalType].put(stats.beginTime, f); } - UsageStatsXml.write(stats, f); - stats.mLastTimeSaved = f.getLastModifiedTime(); + UsageStatsXml.write(f, stats); + stats.lastTimeSaved = f.getLastModifiedTime(); } } - } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 1c20d5d..c38391a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -19,8 +19,8 @@ package com.android.server.usage; import android.Manifest; import android.app.AppOpsManager; import android.app.usage.IUsageStatsManager; +import android.app.usage.UsageEvents; import android.app.usage.UsageStats; -import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.os.Binder; import android.os.Environment; @@ -47,6 +48,10 @@ import java.io.File; import java.util.Arrays; import java.util.List; +/** + * A service that collects, aggregates, and persists application usage data. + * This data can be queried by apps that have been granted permission by AppOps. + */ public class UsageStatsService extends SystemService implements UserUsageStatsService.StatsUpdatedListener { static final String TAG = "UsageStatsService"; @@ -54,8 +59,9 @@ 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; - static final int USAGE_STAT_RESULT_LIMIT = 10; + private static final long END_TIME_DELAY = DEBUG ? 0 : TWO_MINUTES; // Handler message types. static final int MSG_REPORT_EVENT = 0; @@ -181,7 +187,7 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - void reportEvent(UsageStats.Event event, int userId) { + void reportEvent(UsageEvents.Event event, int userId) { synchronized (mLock) { final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); service.reportEvent(event); @@ -211,27 +217,37 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageStats[] getUsageStats(int userId, int bucketType, long beginTime) { - if (bucketType < 0 || bucketType >= UsageStatsManager.BUCKET_COUNT) { - return UsageStats.EMPTY_STATS; - } - + List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) { final long timeNow = System.currentTimeMillis(); if (beginTime > timeNow) { - return UsageStats.EMPTY_STATS; + return null; } synchronized (mLock) { UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); - return service.getUsageStats(bucketType, beginTime); + return service.queryUsageStats(bucketType, beginTime, endTime); } } /** * Called by the Binder stub. */ - UsageStats.Event[] getEvents(int userId, long time) { - return UsageStats.Event.EMPTY_EVENTS; + UsageEvents queryEvents(int userId, long beginTime, long endTime) { + final long timeNow = System.currentTimeMillis(); + + // Adjust the endTime so that we don't query for the latest events. + // This is to prevent apps from making decision based on what app launched them, + // etc. + endTime = Math.min(endTime, timeNow - END_TIME_DELAY); + + if (beginTime > endTime) { + return null; + } + + synchronized (mLock) { + UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + return service.queryEvents(beginTime, endTime); + } } private void flushToDiskLocked() { @@ -253,7 +269,7 @@ public class UsageStatsService extends SystemService implements public void handleMessage(Message msg) { switch (msg.what) { case MSG_REPORT_EVENT: - reportEvent((UsageStats.Event) msg.obj, msg.arg1); + reportEvent((UsageEvents.Event) msg.obj, msg.arg1); break; case MSG_FLUSH_TO_DISK: @@ -286,30 +302,32 @@ public class UsageStatsService extends SystemService implements } @Override - public UsageStats[] getStatsSince(int bucketType, long time, String callingPackage) { + public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime, + long endTime, String callingPackage) { if (!hasPermission(callingPackage)) { - return UsageStats.EMPTY_STATS; + return null; } final int userId = UserHandle.getCallingUserId(); final long token = Binder.clearCallingIdentity(); try { - return getUsageStats(userId, bucketType, time); + return new ParceledListSlice<>(UsageStatsService.this.queryUsageStats( + userId, bucketType, beginTime, endTime)); } finally { Binder.restoreCallingIdentity(token); } } @Override - public UsageStats.Event[] getEventsSince(long time, String callingPackage) { + public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) { if (!hasPermission(callingPackage)) { - return UsageStats.Event.EMPTY_EVENTS; + return null; } final int userId = UserHandle.getCallingUserId(); final long token = Binder.clearCallingIdentity(); try { - return getEvents(userId, time); + return UsageStatsService.this.queryEvents(userId, beginTime, endTime); } finally { Binder.restoreCallingIdentity(token); } @@ -326,8 +344,15 @@ public class UsageStatsService extends SystemService implements @Override public void reportEvent(ComponentName component, int userId, long timeStamp, int eventType) { - UsageStats.Event event = new UsageStats.Event(component.getPackageName(), timeStamp, - eventType); + if (component == null) { + Slog.w(TAG, "Event reported without a component name"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mComponent = component; + event.mTimeStamp = timeStamp; + event.mEventType = eventType; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsUtils.java b/services/usage/java/com/android/server/usage/UsageStatsUtils.java index 887e016..dd5f3b9 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsUtils.java +++ b/services/usage/java/com/android/server/usage/UsageStatsUtils.java @@ -28,7 +28,7 @@ final class UsageStatsUtils { /** * Truncates the date to the given UsageStats bucket. For example, if the bucket is - * {@link UsageStatsManager#YEARLY_BUCKET}, the date is truncated to the 1st day of the year, + * {@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. @@ -41,19 +41,19 @@ final class UsageStatsUtils { cal.set(Calendar.MILLISECOND, 0); switch (bucket) { - case UsageStatsManager.YEARLY_BUCKET: + case UsageStatsManager.INTERVAL_YEARLY: cal.set(Calendar.DAY_OF_YEAR, 0); break; - case UsageStatsManager.MONTHLY_BUCKET: + case UsageStatsManager.INTERVAL_MONTHLY: cal.set(Calendar.DAY_OF_MONTH, 0); break; - case UsageStatsManager.WEEKLY_BUCKET: + case UsageStatsManager.INTERVAL_WEEKLY: cal.set(Calendar.DAY_OF_WEEK, 0); break; - case UsageStatsManager.DAILY_BUCKET: + case UsageStatsManager.INTERVAL_DAILY: break; default: diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java index 78f89d0..48881d0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXml.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java @@ -16,8 +16,6 @@ package com.android.server.usage; -import android.app.usage.PackageUsageStats; -import android.app.usage.UsageStats; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -32,17 +30,19 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.ProtocolException; public class UsageStatsXml { private static final String TAG = "UsageStatsXml"; private static final int CURRENT_VERSION = 1; + private static final String USAGESTATS_TAG = "usagestats"; + private static final String VERSION_ATTR = "version"; - public static UsageStats read(AtomicFile file) throws IOException { + public static void read(AtomicFile file, IntervalStats statsOut) throws IOException { try { FileInputStream in = file.openRead(); try { - return read(in); + read(in, statsOut); + statsOut.lastTimeSaved = file.getLastModifiedTime(); } finally { try { in.close(); @@ -56,17 +56,19 @@ public class UsageStatsXml { } } - private static final String USAGESTATS_TAG = "usagestats"; - private static final String VERSION_ATTR = "version"; - private static final String BEGIN_TIME_ATTR = "beginTime"; - private static final String END_TIME_ATTR = "endTime"; - private static final String PACKAGE_TAG = "package"; - private static final String NAME_ATTR = "name"; - private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive"; - private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; - private static final String LAST_EVENT_ATTR = "lastEvent"; + public static void write(AtomicFile file, IntervalStats stats) throws IOException { + FileOutputStream fos = file.startWrite(); + try { + write(fos, stats); + file.finishWrite(fos); + fos = null; + } finally { + // When fos is null (successful write), this will no-op + file.failWrite(fos); + } + } - public static UsageStats read(InputStream in) throws IOException { + private static void read(InputStream in, IntervalStats statsOut) throws IOException { XmlPullParser parser = Xml.newPullParser(); try { parser.setInput(in, "utf-8"); @@ -75,7 +77,9 @@ public class UsageStatsXml { try { switch (Integer.parseInt(versionStr)) { case 1: - return loadVersion1(parser); + UsageStatsXmlV1.read(parser, statsOut); + break; + default: Slog.e(TAG, "Unrecognized version " + versionStr); throw new IOException("Unrecognized version " + versionStr); @@ -90,70 +94,15 @@ public class UsageStatsXml { } } - private static UsageStats loadVersion1(XmlPullParser parser) - throws IOException, XmlPullParserException { - long beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR); - long endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR); - UsageStats stats = UsageStats.create(beginTime, endTime); - - XmlUtils.nextElement(parser); - - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - if (parser.getName().equals(PACKAGE_TAG)) { - String name = parser.getAttributeValue(null, NAME_ATTR); - if (name == null) { - throw new ProtocolException("no " + NAME_ATTR + " attribute present"); - } - - PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(name); - pkgStats.mTotalTimeSpent = XmlUtils.readLongAttribute(parser, - TOTAL_TIME_ACTIVE_ATTR); - pkgStats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR); - pkgStats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); - } - - // TODO(adamlesinski): Read in events here if there are any. - - XmlUtils.skipCurrentTag(parser); - } - return stats; - } - - public static void write(UsageStats stats, AtomicFile file) throws IOException { - FileOutputStream fos = file.startWrite(); - try { - write(stats, fos); - file.finishWrite(fos); - fos = null; - } finally { - // When fos is null (successful write), this will no-op - file.failWrite(fos); - } - } - - public static void write(UsageStats stats, OutputStream out) throws IOException { + private static void write(OutputStream out, IntervalStats stats) throws IOException { FastXmlSerializer xml = new FastXmlSerializer(); xml.setOutput(out, "utf-8"); xml.startDocument("utf-8", true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, USAGESTATS_TAG); xml.attribute(null, VERSION_ATTR, Integer.toString(CURRENT_VERSION)); - xml.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.mBeginTimeStamp)); - xml.attribute(null, END_TIME_ATTR, Long.toString(stats.mEndTimeStamp)); - - // Body of the stats - final int pkgCount = stats.getPackageCount(); - for (int i = 0; i < pkgCount; i++) { - final PackageUsageStats pkgStats = stats.getPackage(i); - xml.startTag(null, PACKAGE_TAG); - xml.attribute(null, NAME_ATTR, pkgStats.mPackageName); - xml.attribute(null, TOTAL_TIME_ACTIVE_ATTR, Long.toString(pkgStats.mTotalTimeSpent)); - xml.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(pkgStats.mLastTimeUsed)); - xml.attribute(null, LAST_EVENT_ATTR, Integer.toString(pkgStats.mLastEvent)); - xml.endTag(null, PACKAGE_TAG); - } - // TODO(adamlesinski): Write out events here if there are any. + UsageStatsXmlV1.write(xml, stats); xml.endTag(null, USAGESTATS_TAG); xml.endDocument(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java new file mode 100644 index 0000000..916601b --- /dev/null +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -0,0 +1,183 @@ +/** + * 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 com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.usage.TimeSparseArray; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; +import android.content.ComponentName; + +import java.io.IOException; +import java.net.ProtocolException; + +/** + * UsageStats reader/writer for version 1 of the XML format. + */ +final class UsageStatsXmlV1 { + private static final String BEGIN_TIME_ATTR = "beginTime"; + private static final String END_TIME_ATTR = "endTime"; + private static final String PACKAGE_TAG = "package"; + private static final String NAME_ATTR = "name"; + private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive"; + private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String LAST_EVENT_ATTR = "lastEvent"; + private static final String EVENT_LOG_TAG = "event-log"; + private static final String TYPE_ATTR = "type"; + private static final String TIME_ATTR = "time"; + + private static UsageStats readNextUsageStats(XmlPullParser parser) + throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + XmlUtils.nextElement(parser); + } + + if (parser.getEventType() != XmlPullParser.START_TAG || + !parser.getName().equals(PACKAGE_TAG)) { + return null; + } + + final String name = parser.getAttributeValue(null, NAME_ATTR); + if (name == null) { + throw new ProtocolException("no " + NAME_ATTR + " attribute present"); + } + + UsageStats stats = new UsageStats(); + stats.mPackageName = name; + stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); + stats.mLastTimeUsed = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR); + stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); + XmlUtils.skipCurrentTag(parser); + return stats; + } + + private static UsageEvents.Event readNextEvent(XmlPullParser parser, IntervalStats statsOut) + throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + XmlUtils.nextElement(parser); + } + + if (parser.getEventType() != XmlPullParser.START_TAG || + !parser.getName().equals(EVENT_LOG_TAG)) { + return null; + } + + final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR); + if (componentName == null) { + throw new ProtocolException("no " + NAME_ATTR + " attribute present"); + } + + ComponentName component = statsOut.getCachedComponentName(componentName); + if (component == null) { + throw new ProtocolException("ComponentName " + componentName + " is invalid"); + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mComponent = component; + event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR); + event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR); + XmlUtils.skipCurrentTag(parser); + return event; + } + + private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats) + throws IOException { + serializer.startTag(null, PACKAGE_TAG); + serializer.attribute(null, NAME_ATTR, stats.mPackageName); + serializer.attribute(null, TOTAL_TIME_ACTIVE_ATTR, + Long.toString(stats.mTotalTimeInForeground)); + serializer.attribute(null, LAST_TIME_ACTIVE_ATTR, Long.toString(stats.mLastTimeUsed)); + serializer.attribute(null, LAST_EVENT_ATTR, Integer.toString(stats.mLastEvent)); + serializer.endTag(null, PACKAGE_TAG); + } + + private static void writeEvent(FastXmlSerializer serializer, UsageEvents.Event event) + throws IOException { + serializer.startTag(null, EVENT_LOG_TAG); + serializer.attribute(null, NAME_ATTR, event.getComponent().flattenToString()); + serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType())); + serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp())); + serializer.endTag(null, EVENT_LOG_TAG); + } + + /** + * Reads from the {@link XmlPullParser}, assuming that it is already on the + * <code><usagestats></code> tag. + * + * @param parser The parser from which to read events. + * @param statsOut The stats object to populate with the data from the XML file. + */ + public static void read(XmlPullParser parser, IntervalStats statsOut) + throws XmlPullParserException, IOException { + statsOut.stats.clear(); + + if (statsOut.events != null) { + statsOut.events.clear(); + } + + statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR); + statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR); + XmlUtils.nextElement(parser); + + UsageStats pkgStats; + while ((pkgStats = readNextUsageStats(parser)) != null) { + pkgStats.mBeginTimeStamp = statsOut.beginTime; + pkgStats.mEndTimeStamp = statsOut.endTime; + statsOut.stats.put(pkgStats.mPackageName, pkgStats); + } + + UsageEvents.Event event; + while ((event = readNextEvent(parser, statsOut)) != null) { + if (statsOut.events == null) { + statsOut.events = new TimeSparseArray<>(); + } + statsOut.events.put(event.getTimeStamp(), event); + } + } + + /** + * Writes the stats object to an XML file. The {@link FastXmlSerializer} + * has already written the <code><usagestats></code> tag, but attributes may still + * be added. + * + * @param serializer The serializer to which to write the stats data. + * @param stats The stats object to write to the XML file. + */ + public static void write(FastXmlSerializer serializer, IntervalStats stats) throws IOException { + serializer.attribute(null, BEGIN_TIME_ATTR, Long.toString(stats.beginTime)); + serializer.attribute(null, END_TIME_ATTR, Long.toString(stats.endTime)); + + final int statsCount = stats.stats.size(); + for (int i = 0; i < statsCount; i++) { + writeUsageStats(serializer, stats.stats.valueAt(i)); + } + + if (stats.events != null) { + final int eventCount = stats.events.size(); + for (int i = 0; i < eventCount; i++) { + writeEvent(serializer, stats.events.valueAt(i)); + } + } + } + + private UsageStatsXmlV1() { + } +} diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index d124188..2dfd0f6 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -1,15 +1,36 @@ +/** + * 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.PackageUsageStats; +import android.app.usage.TimeSparseArray; +import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; +import android.content.ComponentName; import android.util.ArraySet; import android.util.Slog; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.List; /** * A per-user UsageStatsService. All methods are meant to be called with the main lock held @@ -21,7 +42,7 @@ class UserUsageStatsService { private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private final UsageStatsDatabase mDatabase; - private final UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT]; + private final IntervalStats[] mCurrentStats; private boolean mStatsChanged = false; private final Calendar mDailyExpiryDate; private final StatsUpdatedListener mListener; @@ -34,6 +55,7 @@ class UserUsageStatsService { UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) { mDailyExpiryDate = Calendar.getInstance(); mDatabase = new UsageStatsDatabase(usageStatsDir); + mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; mListener = listener; mLogPrefix = "User[" + Integer.toString(userId) + "] "; } @@ -45,6 +67,8 @@ class UserUsageStatsService { for (int i = 0; i < mCurrentStats.length; i++) { mCurrentStats[i] = mDatabase.getLatestUsageStats(i); if (mCurrentStats[i] == null) { + // Find out how many intervals we don't have data for. + // Ideally it should be all or none. nullCount++; } } @@ -66,85 +90,138 @@ class UserUsageStatsService { // This may actually be today and we will rollover on the first event // that is reported. mDailyExpiryDate.setTimeInMillis( - mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp); + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); + UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate); Slog.i(TAG, mLogPrefix + "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime())); } // Now close off any events that were open at the time this was saved. - for (UsageStats stat : mCurrentStats) { - final int pkgCount = stat.getPackageCount(); + for (IntervalStats stat : mCurrentStats) { + final int pkgCount = stat.stats.size(); for (int i = 0; i < pkgCount; i++) { - PackageUsageStats pkgStats = stat.getPackage(i); - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { - updateStats(stat, pkgStats.mPackageName, stat.mLastTimeSaved, - UsageStats.Event.END_OF_DAY); + UsageStats pkgStats = stat.stats.valueAt(i); + if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || + pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { + stat.update(pkgStats.mPackageName, stat.lastTimeSaved, + UsageEvents.Event.END_OF_DAY); notifyStatsChanged(); } } } } - void reportEvent(UsageStats.Event event) { + void reportEvent(UsageEvents.Event event) { if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Got usage event for " + event.packageName - + "[" + event.timeStamp + "]: " - + eventToString(event.eventType)); + Slog.d(TAG, mLogPrefix + "Got usage event for " + event.getComponent().getPackageName() + + "[" + event.getTimeStamp() + "]: " + + eventToString(event.getEventType())); } - if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) { + if (event.getTimeStamp() >= mDailyExpiryDate.getTimeInMillis()) { // Need to rollover rolloverStats(); } - for (UsageStats stats : mCurrentStats) { - updateStats(stats, event.packageName, event.timeStamp, event.eventType); + if (mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events == null) { + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events = new TimeSparseArray<>(); + } + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event); + + for (IntervalStats stats : mCurrentStats) { + stats.update(event.getComponent().getPackageName(), event.getTimeStamp(), + event.getEventType()); } notifyStatsChanged(); } - UsageStats[] getUsageStats(int bucketType, long beginTime) { - if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) { + List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { + if (bucketType == UsageStatsManager.INTERVAL_BEST) { + bucketType = mDatabase.findBestFitBucket(beginTime, endTime); + } + + if (bucketType < 0 || bucketType >= mCurrentStats.length) { + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Bad bucketType used " + bucketType); + } + return null; + } + + if (beginTime >= mCurrentStats[bucketType].endTime) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " - + mCurrentStats[bucketType].mEndTimeStamp); + + mCurrentStats[bucketType].endTime); } // Nothing newer available. - return UsageStats.EMPTY_STATS; + return null; - } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) { + } else if (beginTime >= mCurrentStats[bucketType].beginTime) { if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); + Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType); } // Fast path for retrieving in-memory state. - // TODO(adamlesinski): This copy just to parcel the object is wasteful. - // It would be nice to parcel it here and send that back, but the Binder API - // would need to change. - return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) }; - - } else { - // Flush any changes that were made to disk before we do a disk query. - persistActiveStats(); + ArrayList<UsageStats> results = new ArrayList<>(); + final int packageCount = mCurrentStats[bucketType].stats.size(); + for (int i = 0; i < packageCount; i++) { + results.add(new UsageStats(mCurrentStats[bucketType].stats.valueAt(i))); + } + 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(); + if (DEBUG) { Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= " - + beginTime + " LIMIT " + UsageStatsService.USAGE_STAT_RESULT_LIMIT); + + beginTime + " AND endTime < " + endTime); } - final UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime, - UsageStatsService.USAGE_STAT_RESULT_LIMIT); - + final List<UsageStats> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime); if (DEBUG) { - Slog.d(TAG, mLogPrefix + "Results: " + results.length); + Slog.d(TAG, mLogPrefix + "Results: " + results.size()); } return results; } + UsageEvents queryEvents(long beginTime, long endTime) { + if (endTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime) { + if (beginTime > mCurrentStats[UsageStatsManager.INTERVAL_DAILY].endTime) { + return null; + } + + TimeSparseArray<UsageEvents.Event> events = + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events; + if (events == null) { + return null; + } + + final int startIndex = events.closestIndexOnOrAfter(beginTime); + if (startIndex < 0) { + return null; + } + + ArraySet<ComponentName> 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; + } + names.add(events.valueAt(i).getComponent()); + results.add(events.valueAt(i)); + } + ComponentName[] table = names.toArray(new ComponentName[names.size()]); + Arrays.sort(table); + return new UsageEvents(results, table); + } + + // TODO(adamlesinski): Query the previous days. + return null; + } + void persistActiveStats() { if (mStatsChanged) { Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); @@ -166,15 +243,15 @@ class UserUsageStatsService { // Finish any ongoing events with an END_OF_DAY event. Make a note of which components // need a new CONTINUE_PREVIOUS_DAY entry. ArraySet<String> continuePreviousDay = new ArraySet<>(); - for (UsageStats stat : mCurrentStats) { - final int pkgCount = stat.getPackageCount(); + for (IntervalStats stat : mCurrentStats) { + final int pkgCount = stat.stats.size(); for (int i = 0; i < pkgCount; i++) { - PackageUsageStats pkgStats = stat.getPackage(i); - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { + UsageStats pkgStats = stat.stats.valueAt(i); + if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || + pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { continuePreviousDay.add(pkgStats.mPackageName); - updateStats(stat, pkgStats.mPackageName, - mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY); + stat.update(pkgStats.mPackageName, + mDailyExpiryDate.getTimeInMillis() - 1, UsageEvents.Event.END_OF_DAY); mStatsChanged = true; } } @@ -187,10 +264,9 @@ class UserUsageStatsService { final int continueCount = continuePreviousDay.size(); for (int i = 0; i < continueCount; i++) { String name = continuePreviousDay.valueAt(i); - for (UsageStats stat : mCurrentStats) { - updateStats(stat, name, - mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp, - UsageStats.Event.CONTINUE_PREVIOUS_DAY); + for (IntervalStats stat : mCurrentStats) { + stat.update(name, mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime, + UsageEvents.Event.CONTINUE_PREVIOUS_DAY); mStatsChanged = true; } } @@ -212,61 +288,67 @@ class UserUsageStatsService { final long timeNow = System.currentTimeMillis(); Calendar tempCal = mDailyExpiryDate; - for (int i = 0; i < mCurrentStats.length; i++) { + for (int bucketType = 0; bucketType < mCurrentStats.length; bucketType++) { tempCal.setTimeInMillis(timeNow); - UsageStatsUtils.truncateDateTo(i, tempCal); + UsageStatsUtils.truncateDateTo(bucketType, tempCal); - if (mCurrentStats[i] != null && - mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) { + if (mCurrentStats[bucketType] != null && + mCurrentStats[bucketType].beginTime == tempCal.getTimeInMillis()) { // These are the same, no need to load them (in memory stats are always newer // than persisted stats). continue; } - UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1); - if (stats != null && stats.length > 0) { - mCurrentStats[i] = stats[stats.length - 1]; + final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(bucketType); + 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"); + } + } } else { - mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow); + mCurrentStats[bucketType] = null; + } + + if (mCurrentStats[bucketType] == null) { + if (DEBUG) { + Slog.d(TAG, "Creating new stats (" + tempCal.getTimeInMillis() + + ") for bucket " + bucketType); + + } + mCurrentStats[bucketType] = new IntervalStats(); + mCurrentStats[bucketType].beginTime = tempCal.getTimeInMillis(); + mCurrentStats[bucketType].endTime = timeNow; } } mStatsChanged = false; mDailyExpiryDate.setTimeInMillis(timeNow); mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); + UsageStatsUtils.truncateDateTo(UsageStatsManager.INTERVAL_DAILY, mDailyExpiryDate); Slog.i(TAG, mLogPrefix + "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime())); } - private void updateStats(UsageStats stats, String packageName, long timeStamp, - int eventType) { - PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName); - - // TODO(adamlesinski): Ensure that we recover from incorrect event sequences - // like double MOVE_TO_BACKGROUND, etc. - if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND || - eventType == UsageStats.Event.END_OF_DAY) { - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { - pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed; - } - } - pkgStats.mLastEvent = eventType; - pkgStats.mLastTimeUsed = timeStamp; - stats.mEndTimeStamp = timeStamp; - } private static String eventToString(int eventType) { switch (eventType) { - case UsageStats.Event.NONE: + case UsageEvents.Event.NONE: return "NONE"; - case UsageStats.Event.MOVE_TO_BACKGROUND: + case UsageEvents.Event.MOVE_TO_BACKGROUND: return "MOVE_TO_BACKGROUND"; - case UsageStats.Event.MOVE_TO_FOREGROUND: + case UsageEvents.Event.MOVE_TO_FOREGROUND: return "MOVE_TO_FOREGROUND"; - case UsageStats.Event.END_OF_DAY: + case UsageEvents.Event.END_OF_DAY: return "END_OF_DAY"; - case UsageStats.Event.CONTINUE_PREVIOUS_DAY: + case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; default: return "UNKNOWN"; |