diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-09-02 16:43:52 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-09-04 12:15:32 -0700 |
commit | 7f61e96db7c90c1f4418359672aa4656aebee500 (patch) | |
tree | 6748f94db635e24c26a6a7236c444e2c0d906181 /services/usage/java/com | |
parent | d400ca2f8db42e57e41f2a999833703619348fef (diff) | |
download | frameworks_base-7f61e96db7c90c1f4418359672aa4656aebee500.zip frameworks_base-7f61e96db7c90c1f4418359672aa4656aebee500.tar.gz frameworks_base-7f61e96db7c90c1f4418359672aa4656aebee500.tar.bz2 |
Add Configuration changes to UsageStats
Bug:17354208
Change-Id: I9b2f595e51b656607e30e798926cfb7e25134944
Diffstat (limited to 'services/usage/java/com')
5 files changed, 346 insertions, 105 deletions
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index dc036e2..6c80a65 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -15,17 +15,23 @@ */ package com.android.server.usage; +import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; +import android.content.res.Configuration; import android.util.ArrayMap; import android.util.ArraySet; +import java.util.ArrayList; + class IntervalStats { public long beginTime; public long endTime; public long lastTimeSaved; public final ArrayMap<String, UsageStats> stats = new ArrayMap<>(); + public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>(); + public Configuration activeConfiguration; public TimeSparseArray<UsageEvents.Event> events; // A string cache. This is important as when we're parsing XML files, we don't want to @@ -34,18 +40,49 @@ class IntervalStats { // strings that had identical copies in the cache. private final ArraySet<String> mStringCache = new ArraySet<>(); + /** + * Gets the UsageStats object for the given package, or creates one and adds it internally. + */ UsageStats getOrCreateUsageStats(String packageName) { UsageStats usageStats = stats.get(packageName); if (usageStats == null) { usageStats = new UsageStats(); - usageStats.mPackageName = packageName; + usageStats.mPackageName = getCachedStringRef(packageName); usageStats.mBeginTimeStamp = beginTime; usageStats.mEndTimeStamp = endTime; - stats.put(packageName, usageStats); + stats.put(usageStats.mPackageName, usageStats); } return usageStats; } + /** + * Gets the ConfigurationStats object for the given configuration, or creates one and adds it + * internally. + */ + ConfigurationStats getOrCreateConfigurationStats(Configuration config) { + ConfigurationStats configStats = configurations.get(config); + if (configStats == null) { + configStats = new ConfigurationStats(); + configStats.mBeginTimeStamp = beginTime; + configStats.mEndTimeStamp = endTime; + configStats.mConfiguration = config; + configurations.put(config, configStats); + } + return configStats; + } + + /** + * Builds a UsageEvents.Event, but does not add it internally. + */ + UsageEvents.Event buildEvent(String packageName, String className) { + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = getCachedStringRef(packageName); + if (className != null) { + event.mClass = getCachedStringRef(className); + } + return event; + } + void update(String packageName, long timeStamp, int eventType) { UsageStats usageStats = getOrCreateUsageStats(packageName); @@ -61,6 +98,28 @@ class IntervalStats { usageStats.mLastEvent = eventType; usageStats.mLastTimeUsed = timeStamp; usageStats.mEndTimeStamp = timeStamp; + + if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { + usageStats.mLaunchCount += 1; + } + + endTime = timeStamp; + } + + void updateConfigurationStats(Configuration config, long timeStamp) { + if (activeConfiguration != null) { + ConfigurationStats activeStats = configurations.get(activeConfiguration); + activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive; + activeStats.mLastTimeActive = timeStamp - 1; + } + + if (config != null) { + ConfigurationStats configStats = getOrCreateConfigurationStats(config); + configStats.mLastTimeActive = timeStamp; + configStats.mActivationCount += 1; + activeConfiguration = configStats.mConfiguration; + } + endTime = timeStamp; } @@ -72,13 +131,4 @@ class IntervalStats { } return mStringCache.valueAt(index); } - - UsageEvents.Event buildEvent(String packageName, String className) { - UsageEvents.Event event = new UsageEvents.Event(); - event.mPackage = getCachedStringRef(packageName); - if (className != null) { - event.mClass = getCachedStringRef(className); - } - return event; - } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index e6ce0fe..37340a4 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -17,7 +17,6 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; -import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.util.AtomicFile; import android.util.Slog; @@ -133,9 +132,30 @@ class UsageStatsDatabase { } /** - * Find all {@link UsageStats} for the given range and interval type. + * Figures out what to extract from the given IntervalStats object. */ - public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) { + interface StatCombiner<T> { + + /** + * Implementations should extract interesting from <code>stats</code> and add it + * to the <code>accumulatedResult</code> list. + * + * If the <code>stats</code> object is mutable, <code>mutable</code> will be true, + * which means you should make a copy of the data before adding it to the + * <code>accumulatedResult</code> list. + * + * @param stats The {@link IntervalStats} object selected. + * @param mutable Whether or not the data inside the stats object is mutable. + * @param accumulatedResult The list to which to add extracted data. + */ + void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult); + } + + /** + * Find all {@link IntervalStats} for the given range and interval type. + */ + public <T> List<T> queryUsageStats(int intervalType, long beginTime, long endTime, + StatCombiner<T> combiner) { synchronized (mLock) { if (intervalType < 0 || intervalType >= mIntervalDirs.length) { throw new IllegalArgumentException("Bad interval type " + intervalType); @@ -157,7 +177,7 @@ class UsageStatsDatabase { try { IntervalStats stats = new IntervalStats(); - ArrayList<UsageStats> results = new ArrayList<>(); + ArrayList<T> results = new ArrayList<>(); for (int i = startIndex; i <= endIndex; i++) { final AtomicFile f = mSortedStatFiles[intervalType].valueAt(i); @@ -167,7 +187,7 @@ class UsageStatsDatabase { UsageStatsXml.read(f, stats); if (beginTime < stats.endTime) { - results.addAll(stats.stats.values()); + combiner.combine(stats, false, results); } } return results; @@ -209,6 +229,10 @@ class UsageStatsDatabase { public void prune() { synchronized (mLock) { long timeNow = System.currentTimeMillis(); + mCal.setTimeInMillis(timeNow); + mCal.add(Calendar.YEAR, -3); + pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY], + mCal.getTimeInMillis()); mCal.setTimeInMillis(timeNow); mCal.add(Calendar.MONTH, -6); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0e8b427..e77bf86 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -18,6 +18,7 @@ package com.android.server.usage; import android.Manifest; import android.app.AppOpsManager; +import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; @@ -30,11 +31,13 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; @@ -218,8 +221,7 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime) { - final long timeNow = System.currentTimeMillis(); - if (beginTime > timeNow) { + if (!validRange(beginTime, endTime)) { return null; } @@ -232,15 +234,23 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageEvents queryEvents(int userId, long beginTime, long endTime) { - final long timeNow = System.currentTimeMillis(); + List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime, + long endTime) { + if (!validRange(beginTime, endTime)) { + return null; + } - // 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); + synchronized (mLock) { + UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + return service.queryConfigurationStats(bucketType, beginTime, endTime); + } + } - if (beginTime > endTime) { + /** + * Called by the Binder stub. + */ + UsageEvents queryEvents(int userId, long beginTime, long endTime) { + if (!validRange(beginTime, endTime)) { return null; } @@ -250,6 +260,11 @@ public class UsageStatsService extends SystemService implements } } + private static boolean validRange(long beginTime, long endTime) { + final long timeNow = System.currentTimeMillis(); + return beginTime <= timeNow && beginTime < endTime; + } + private void flushToDiskLocked() { final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { @@ -323,6 +338,28 @@ public class UsageStatsService extends SystemService implements } @Override + public ParceledListSlice<ConfigurationStats> queryConfigurationStats(int bucketType, + long beginTime, long endTime, String callingPackage) throws RemoteException { + if (!hasPermission(callingPackage)) { + return null; + } + + final int userId = UserHandle.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); + try { + final List<ConfigurationStats> results = + UsageStatsService.this.queryConfigurationStats(userId, bucketType, + beginTime, endTime); + if (results != null) { + return new ParceledListSlice<>(results); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return null; + } + + @Override public UsageEvents queryEvents(long beginTime, long endTime, String callingPackage) { if (!hasPermission(callingPackage)) { return null; @@ -346,8 +383,7 @@ public class UsageStatsService extends SystemService implements private class LocalService extends UsageStatsManagerInternal { @Override - public void reportEvent(ComponentName component, int userId, - long timeStamp, int eventType) { + public void reportEvent(ComponentName component, int userId, int eventType) { if (component == null) { Slog.w(TAG, "Event reported without a component name"); return; @@ -356,12 +392,27 @@ public class UsageStatsService extends SystemService implements UsageEvents.Event event = new UsageEvents.Event(); event.mPackage = component.getPackageName(); event.mClass = component.getClassName(); - event.mTimeStamp = timeStamp; + event.mTimeStamp = System.currentTimeMillis(); event.mEventType = eventType; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @Override + public void reportConfigurationChange(Configuration config, int userId) { + if (config == null) { + Slog.w(TAG, "Configuration event reported with a null config"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = "android"; + event.mTimeStamp = System.currentTimeMillis(); + event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; + event.mConfiguration = new Configuration(config); + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } + + @Override public void prepareShutdown() { // This method *WILL* do IO work, but we must block until it is finished or else // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 374429a..6529950 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -20,68 +20,69 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; +import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.content.ComponentName; +import android.content.res.Configuration; import java.io.IOException; import java.net.ProtocolException; +import java.util.Locale; /** * UsageStats reader/writer for version 1 of the XML format. */ final class UsageStatsXmlV1 { + private static final String PACKAGE_TAG = "package"; + private static final String CONFIGURATION_TAG = "config"; + private static final String EVENT_LOG_TAG = "event-log"; + 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 PACKAGE_ATTR = "package"; private static final String CLASS_ATTR = "class"; private static final String TOTAL_TIME_ACTIVE_ATTR = "totalTimeActive"; private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String COUNT_ATTR = "count"; + private static final String ACTIVE_ATTR = "active"; 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) + private static void loadUsageStats(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(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; + UsageStats stats = statsOut.getOrCreateUsageStats(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) + private static void loadConfigStats(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 Configuration config = new Configuration(); + Configuration.readXmlAttrs(parser, config); + + ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config); + configStats.mLastTimeActive = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR); + configStats.mTotalTimeActive = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); + configStats.mActivationCount = XmlUtils.readIntAttribute(parser, COUNT_ATTR); + if (XmlUtils.readBooleanAttribute(parser, ACTIVE_ATTR)) { + statsOut.activeConfiguration = configStats.mConfiguration; } + } + private static void loadEvent(XmlPullParser parser, IntervalStats statsOut) + throws XmlPullParserException, IOException { String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR); String className; if (packageName == null) { @@ -105,31 +106,60 @@ final class UsageStatsXmlV1 { UsageEvents.Event event = statsOut.buildEvent(packageName, className); event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR); event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR); - XmlUtils.skipCurrentTag(parser); - return event; + + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { + event.mConfiguration = new Configuration(); + Configuration.readXmlAttrs(parser, event.mConfiguration); + } + + if (statsOut.events == null) { + statsOut.events = new TimeSparseArray<>(); + } + statsOut.events.put(event.mTimeStamp, event); } - private static void writeUsageStats(FastXmlSerializer serializer, UsageStats stats) + private static void writeUsageStats(XmlSerializer xml, final 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); + xml.startTag(null, PACKAGE_TAG); + XmlUtils.writeStringAttribute(xml, NAME_ATTR, stats.mPackageName); + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeInForeground); + XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeUsed); + XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, stats.mLastEvent); + xml.endTag(null, PACKAGE_TAG); + } + + private static void writeConfigStats(XmlSerializer xml, final ConfigurationStats stats, + boolean isActive) throws IOException { + xml.startTag(null, CONFIGURATION_TAG); + XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, stats.mLastTimeActive); + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, stats.mTotalTimeActive); + XmlUtils.writeIntAttribute(xml, COUNT_ATTR, stats.mActivationCount); + if (isActive) { + XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true); + } + + // Now write the attributes representing the configuration object. + Configuration.writeXmlAttrs(xml, stats.mConfiguration); + + xml.endTag(null, CONFIGURATION_TAG); } - private static void writeEvent(FastXmlSerializer serializer, UsageEvents.Event event) + private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event) throws IOException { - serializer.startTag(null, EVENT_LOG_TAG); - serializer.attribute(null, PACKAGE_ATTR, event.mPackage); + xml.startTag(null, EVENT_LOG_TAG); + XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage); if (event.mClass != null) { - serializer.attribute(null, CLASS_ATTR, event.mClass); + XmlUtils.writeStringAttribute(xml, CLASS_ATTR, event.mClass); + } + XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType); + XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp); + + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE + && event.mConfiguration != null) { + Configuration.writeXmlAttrs(xml, event.mConfiguration); } - serializer.attribute(null, TYPE_ATTR, Integer.toString(event.getEventType())); - serializer.attribute(null, TIME_ATTR, Long.toString(event.getTimeStamp())); - serializer.endTag(null, EVENT_LOG_TAG); + + xml.endTag(null, EVENT_LOG_TAG); } /** @@ -142,6 +172,8 @@ final class UsageStatsXmlV1 { public static void read(XmlPullParser parser, IntervalStats statsOut) throws XmlPullParserException, IOException { statsOut.stats.clear(); + statsOut.configurations.clear(); + statsOut.activeConfiguration = null; if (statsOut.events != null) { statsOut.events.clear(); @@ -149,21 +181,29 @@ final class UsageStatsXmlV1 { 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); - } + int eventCode; + int outerDepth = parser.getDepth(); + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT + && (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (eventCode != XmlPullParser.START_TAG) { + continue; + } + + final String tag = parser.getName(); + switch (tag) { + case PACKAGE_TAG: + loadUsageStats(parser, statsOut); + break; - UsageEvents.Event event; - while ((event = readNextEvent(parser, statsOut)) != null) { - if (statsOut.events == null) { - statsOut.events = new TimeSparseArray<>(); + case CONFIGURATION_TAG: + loadConfigStats(parser, statsOut); + break; + + case EVENT_LOG_TAG: + loadEvent(parser, statsOut); + break; } - statsOut.events.put(event.getTimeStamp(), event); } } @@ -184,11 +224,15 @@ final class UsageStatsXmlV1 { 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)); - } + final int configCount = stats.configurations.size(); + for (int i = 0; i < configCount; i++) { + boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i)); + writeConfigStats(serializer, stats.configurations.valueAt(i), active); + } + + final int eventCount = stats.events != null ? stats.events.size() : 0; + for (int i = 0; i < eventCount; i++) { + writeEvent(serializer, stats.events.valueAt(i)); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 6951590..7142a99 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -16,13 +16,17 @@ package com.android.server.usage; +import android.app.usage.ConfigurationStats; 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.util.ArraySet; import android.util.Slog; +import com.android.server.usage.UsageStatsDatabase.StatCombiner; + import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; @@ -108,35 +112,91 @@ class UserUsageStatsService { notifyStatsChanged(); } } + + stat.updateConfigurationStats(null, stat.lastTimeSaved); } } void reportEvent(UsageEvents.Event event) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage - + "[" + event.getTimeStamp() + "]: " - + eventToString(event.getEventType())); + + "[" + event.mTimeStamp + "]: " + + eventToString(event.mEventType)); } - if (event.getTimeStamp() >= mDailyExpiryDate.getTimeInMillis()) { + if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { // Need to rollover rolloverStats(); } - if (mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events == null) { - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events = new TimeSparseArray<>(); + final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; + + final Configuration newFullConfig = event.mConfiguration; + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && + currentDailyStats.activeConfiguration != null) { + // Make the event configuration a delta. + event.mConfiguration = Configuration.generateDelta( + currentDailyStats.activeConfiguration, newFullConfig); + } + + // Add the event to the daily list. + if (currentDailyStats.events == null) { + currentDailyStats.events = new TimeSparseArray<>(); } - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].events.put(event.getTimeStamp(), event); + currentDailyStats.events.put(event.mTimeStamp, event); for (IntervalStats stats : mCurrentStats) { - stats.update(event.mPackage, event.getTimeStamp(), - event.getEventType()); + if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { + stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); + } else { + stats.update(event.mPackage, event.mTimeStamp, event.mEventType); + } } notifyStatsChanged(); } - List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { + private static final StatCombiner<UsageStats> sUsageStatsCombiner = + new StatCombiner<UsageStats>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<UsageStats> accResult) { + if (!mutable) { + accResult.addAll(stats.stats.values()); + return; + } + + final int statCount = stats.stats.size(); + for (int i = 0; i < statCount; i++) { + accResult.add(new UsageStats(stats.stats.valueAt(i))); + } + } + }; + + private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = + new StatCombiner<ConfigurationStats>() { + @Override + public void combine(IntervalStats stats, boolean mutable, + List<ConfigurationStats> accResult) { + if (!mutable) { + accResult.addAll(stats.configurations.values()); + return; + } + + final int configCount = stats.configurations.size(); + for (int i = 0; i < configCount; i++) { + accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); + } + } + }; + + /** + * Generic query method that selects the appropriate IntervalStats for the specified time range + * 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, + StatCombiner<T> combiner) { if (bucketType == UsageStatsManager.INTERVAL_BEST) { bucketType = mDatabase.findBestFitBucket(beginTime, endTime); } @@ -161,11 +221,8 @@ class UserUsageStatsService { Slog.d(TAG, mLogPrefix + "Returning in-memory stats for bucket " + bucketType); } // Fast path for retrieving in-memory state. - 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))); - } + ArrayList<T> results = new ArrayList<>(); + combiner.combine(mCurrentStats[bucketType], true, results); return results; } @@ -178,13 +235,21 @@ class UserUsageStatsService { + beginTime + " AND endTime < " + endTime); } - final List<UsageStats> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime); + final List<T> results = mDatabase.queryUsageStats(bucketType, beginTime, endTime, combiner); if (DEBUG) { Slog.d(TAG, mLogPrefix + "Results: " + (results == null ? 0 : results.size())); } return results; } + List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { + return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner); + } + + List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { + 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) { @@ -245,6 +310,8 @@ 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. + final Configuration previousConfig = + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; ArraySet<String> continuePreviousDay = new ArraySet<>(); for (IntervalStats stat : mCurrentStats) { final int pkgCount = stat.stats.size(); @@ -253,11 +320,13 @@ class UserUsageStatsService { if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { continuePreviousDay.add(pkgStats.mPackageName); - stat.update(pkgStats.mPackageName, - mDailyExpiryDate.getTimeInMillis() - 1, UsageEvents.Event.END_OF_DAY); + stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1, + UsageEvents.Event.END_OF_DAY); mStatsChanged = true; } } + + stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); } persistActiveStats(); @@ -267,9 +336,10 @@ class UserUsageStatsService { final int continueCount = continuePreviousDay.size(); for (int i = 0; i < continueCount; i++) { String name = continuePreviousDay.valueAt(i); + final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; for (IntervalStats stat : mCurrentStats) { - stat.update(name, mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime, - UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.updateConfigurationStats(previousConfig, beginTime); mStatsChanged = true; } } @@ -353,6 +423,8 @@ class UserUsageStatsService { return "END_OF_DAY"; case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; + case UsageEvents.Event.CONFIGURATION_CHANGE: + return "CONFIGURATION_CHANGE"; default: return "UNKNOWN"; } |