Merge "Update the preferred network type APIs." into lmp-dev
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 2ef9828..0cb8eb8 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -453,11 +453,10 @@
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
if (action.equals(ALARM_WAKEUP)) {
- if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
startNavigating(false);
} else if (action.equals(ALARM_TIMEOUT)) {
- if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
hibernate();
} else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
checkSmsSuplInit(intent);
@@ -691,7 +690,6 @@
intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter = new IntentFilter();
intentFilter.addAction(SIM_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 6c80a65..5f639ab 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -23,13 +23,11 @@
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<String, UsageStats> packageStats = new ArrayMap<>();
public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
public Configuration activeConfiguration;
public TimeSparseArray<UsageEvents.Event> events;
@@ -44,13 +42,13 @@
* Gets the UsageStats object for the given package, or creates one and adds it internally.
*/
UsageStats getOrCreateUsageStats(String packageName) {
- UsageStats usageStats = stats.get(packageName);
+ UsageStats usageStats = packageStats.get(packageName);
if (usageStats == null) {
usageStats = new UsageStats();
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
- stats.put(usageStats.mPackageName, usageStats);
+ packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
}
diff --git a/services/usage/java/com/android/server/usage/UnixCalendar.java b/services/usage/java/com/android/server/usage/UnixCalendar.java
new file mode 100644
index 0000000..ce06a91
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UnixCalendar.java
@@ -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);
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 37340a4..62a7ec0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -21,24 +21,30 @@
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 = 2;
+
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 @@
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 @@
}
}
+ checkVersionLocked();
+
final FilenameFilter backupFileFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@@ -81,7 +90,45 @@
}
for (File f : files) {
- mSortedStatFiles[i].put(Long.parseLong(f.getName()), new AtomicFile(f));
+ final AtomicFile af = new AtomicFile(f);
+ mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af);
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+
+ 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) {
+ if (thisVersion < 2) {
+ // 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();
}
}
}
@@ -161,25 +208,48 @@
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 +300,22 @@
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());
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e77bf86..2dcdcc4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -38,6 +38,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -62,9 +63,7 @@
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;
@@ -78,6 +77,8 @@
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
private File mUsageStatsDir;
+ long mRealTimeSnapshot;
+ long mSystemTimeSnapshot;
public UsageStatsService(Context context) {
super(context);
@@ -104,6 +105,9 @@
cleanUpRemovedUsersLocked();
}
+ mRealTimeSnapshot = SystemClock.elapsedRealtime();
+ mSystemTimeSnapshot = System.currentTimeMillis();
+
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
@@ -178,7 +182,7 @@
}
/**
- * Called by the Bunder stub
+ * Called by the Binder stub
*/
void shutdown() {
synchronized (mLock) {
@@ -382,6 +386,14 @@
*/
private class LocalService extends UsageStatsManagerInternal {
+ /**
+ * The system may have its time change, so at least make sure the events
+ * are monotonic in order.
+ */
+ private long computeMonotonicSystemTime(long realTime) {
+ return (realTime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+ }
+
@Override
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
@@ -392,7 +404,7 @@
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
- event.mTimeStamp = System.currentTimeMillis();
+ event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@@ -406,7 +418,7 @@
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
- event.mTimeStamp = System.currentTimeMillis();
+ event.mTimeStamp = computeMonotonicSystemTime(SystemClock.elapsedRealtime());
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
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
deleted file mode 100644
index dd5f3b9..0000000
--- a/services/usage/java/com/android/server/usage/UsageStatsUtils.java
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java
index 48881d0..9ce6d63 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXml.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java
@@ -37,10 +37,15 @@
private static final String USAGESTATS_TAG = "usagestats";
private static final String VERSION_ATTR = "version";
+ public static long parseBeginTime(AtomicFile file) {
+ return Long.parseLong(file.getBaseFile().getName());
+ }
+
public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
try {
FileInputStream in = file.openRead();
try {
+ statsOut.beginTime = parseBeginTime(file);
read(in, statsOut);
statsOut.lastTimeSaved = file.getLastModifiedTime();
} finally {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 6529950..ef95a7b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -15,7 +15,6 @@
*/
package com.android.server.usage;
-import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -26,44 +25,52 @@
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 PACKAGES_TAG = "packages";
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 NAME_ATTR = "name";
+ private static final String CONFIGURATIONS_TAG = "configurations";
+ private static final String CONFIG_TAG = "config";
+
+ private static final String EVENT_LOG_TAG = "event-log";
+ private static final String EVENT_TAG = "event";
+
+ // Attributes
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 TOTAL_TIME_ACTIVE_ATTR = "timeActive";
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 TYPE_ATTR = "type";
+
+ // Time attributes stored as an offset of the beginTime.
+ private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+ private static final String END_TIME_ATTR = "endTime";
private static final String TIME_ATTR = "time";
private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- final String name = parser.getAttributeValue(null, NAME_ATTR);
- if (name == null) {
- throw new ProtocolException("no " + NAME_ATTR + " attribute present");
+ final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR);
+ if (pkg == null) {
+ throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
}
- UsageStats stats = statsOut.getOrCreateUsageStats(name);
+ final UsageStats stats = statsOut.getOrCreateUsageStats(pkg);
+
+ // Apply the offset to the beginTime to find the absolute time.
+ stats.mLastTimeUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
+ parser, LAST_TIME_ACTIVE_ATTR);
+
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);
}
@@ -72,8 +79,12 @@
final Configuration config = new Configuration();
Configuration.readXmlAttrs(parser, config);
- ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
- configStats.mLastTimeActive = XmlUtils.readLongAttribute(parser, LAST_TIME_ACTIVE_ATTR);
+ final ConfigurationStats configStats = statsOut.getOrCreateConfigurationStats(config);
+
+ // Apply the offset to the beginTime to find the absolute time.
+ configStats.mLastTimeActive = statsOut.beginTime + 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)) {
@@ -83,30 +94,19 @@
private static void loadEvent(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
- String className;
+ final String packageName = XmlUtils.readStringAttribute(parser, PACKAGE_ATTR);
if (packageName == null) {
- // Try getting the component name if it exists.
- final String componentName = XmlUtils.readStringAttribute(parser, NAME_ATTR);
- if (componentName == null) {
- throw new ProtocolException("no " + NAME_ATTR + " or " + PACKAGE_ATTR +
- " attribute present");
- }
- ComponentName component = ComponentName.unflattenFromString(componentName);
- if (component == null) {
- throw new ProtocolException("ComponentName " + componentName + " is invalid");
- }
-
- packageName = component.getPackageName();
- className = component.getClassName();
- } else {
- className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
+ throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present");
}
- UsageEvents.Event event = statsOut.buildEvent(packageName, className);
- event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
- event.mTimeStamp = XmlUtils.readLongAttribute(parser, TIME_ATTR);
+ final String className = XmlUtils.readStringAttribute(parser, CLASS_ATTR);
+ final UsageEvents.Event event = statsOut.buildEvent(packageName, className);
+
+ // Apply the offset to the beginTime to find the absolute time of this event.
+ event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
+
+ event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
event.mConfiguration = new Configuration();
Configuration.readXmlAttrs(parser, event.mConfiguration);
@@ -118,48 +118,60 @@
statsOut.events.put(event.mTimeStamp, event);
}
- private static void writeUsageStats(XmlSerializer xml, final UsageStats stats)
- throws IOException {
+ private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
+ final UsageStats usageStats) throws IOException {
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);
+
+ // Write the time offset.
+ XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+ usageStats.mLastTimeUsed - stats.beginTime);
+
+ XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
+ XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
+ XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.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);
+ private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats,
+ final ConfigurationStats configStats, boolean isActive) throws IOException {
+ xml.startTag(null, CONFIG_TAG);
+
+ // Write the time offset.
+ XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
+ configStats.mLastTimeActive - stats.beginTime);
+
+ XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, configStats.mTotalTimeActive);
+ XmlUtils.writeIntAttribute(xml, COUNT_ATTR, configStats.mActivationCount);
if (isActive) {
XmlUtils.writeBooleanAttribute(xml, ACTIVE_ATTR, true);
}
// Now write the attributes representing the configuration object.
- Configuration.writeXmlAttrs(xml, stats.mConfiguration);
+ Configuration.writeXmlAttrs(xml, configStats.mConfiguration);
- xml.endTag(null, CONFIGURATION_TAG);
+ xml.endTag(null, CONFIG_TAG);
}
- private static void writeEvent(XmlSerializer xml, final UsageEvents.Event event)
- throws IOException {
- xml.startTag(null, EVENT_LOG_TAG);
+ private static void writeEvent(XmlSerializer xml, final IntervalStats stats,
+ final UsageEvents.Event event) throws IOException {
+ xml.startTag(null, EVENT_TAG);
+
+ // Store the time offset.
+ XmlUtils.writeLongAttribute(xml, TIME_ATTR, event.mTimeStamp - stats.beginTime);
+
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, event.mPackage);
if (event.mClass != null) {
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);
}
- xml.endTag(null, EVENT_LOG_TAG);
+ xml.endTag(null, EVENT_TAG);
}
/**
@@ -171,7 +183,7 @@
*/
public static void read(XmlPullParser parser, IntervalStats statsOut)
throws XmlPullParserException, IOException {
- statsOut.stats.clear();
+ statsOut.packageStats.clear();
statsOut.configurations.clear();
statsOut.activeConfiguration = null;
@@ -179,7 +191,6 @@
statsOut.events.clear();
}
- statsOut.beginTime = XmlUtils.readLongAttribute(parser, BEGIN_TIME_ATTR);
statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
int eventCode;
@@ -196,11 +207,11 @@
loadUsageStats(parser, statsOut);
break;
- case CONFIGURATION_TAG:
+ case CONFIG_TAG:
loadConfigStats(parser, statsOut);
break;
- case EVENT_LOG_TAG:
+ case EVENT_TAG:
loadEvent(parser, statsOut);
break;
}
@@ -208,32 +219,38 @@
}
/**
- * Writes the stats object to an XML file. The {@link FastXmlSerializer}
+ * Writes the stats object to an XML file. The {@link XmlSerializer}
* 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 xml The serializer to which to write the packageStats 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));
+ public static void write(XmlSerializer xml, IntervalStats stats) throws IOException {
+ XmlUtils.writeLongAttribute(xml, END_TIME_ATTR, stats.endTime - stats.beginTime);
- final int statsCount = stats.stats.size();
+ xml.startTag(null, PACKAGES_TAG);
+ final int statsCount = stats.packageStats.size();
for (int i = 0; i < statsCount; i++) {
- writeUsageStats(serializer, stats.stats.valueAt(i));
+ writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
}
+ xml.endTag(null, PACKAGES_TAG);
+
+ xml.startTag(null, CONFIGURATIONS_TAG);
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);
+ writeConfigStats(xml, stats, stats.configurations.valueAt(i), active);
}
+ xml.endTag(null, CONFIGURATIONS_TAG);
+ xml.startTag(null, EVENT_LOG_TAG);
final int eventCount = stats.events != null ? stats.events.size() : 0;
for (int i = 0; i < eventCount; i++) {
- writeEvent(serializer, stats.events.valueAt(i));
+ writeEvent(xml, stats, stats.events.valueAt(i));
}
+ xml.endTag(null, EVENT_LOG_TAG);
}
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 7142a99..2769666 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -32,7 +32,6 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
/**
@@ -47,7 +46,7 @@
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 @@
}
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 @@
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 @@
// 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,17 +99,18 @@
// 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.
for (IntervalStats stat : mCurrentStats) {
- final int pkgCount = stat.stats.size();
+ final int pkgCount = stat.packageStats.size();
for (int i = 0; i < pkgCount; i++) {
- UsageStats pkgStats = stat.stats.valueAt(i);
+ UsageStats pkgStats = stat.packageStats.valueAt(i);
if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
@@ -162,13 +168,13 @@
public void combine(IntervalStats stats, boolean mutable,
List<UsageStats> accResult) {
if (!mutable) {
- accResult.addAll(stats.stats.values());
+ accResult.addAll(stats.packageStats.values());
return;
}
- final int statCount = stats.stats.size();
+ final int statCount = stats.packageStats.size();
for (int i = 0; i < statCount; i++) {
- accResult.add(new UsageStats(stats.stats.valueAt(i)));
+ accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
}
}
};
@@ -195,49 +201,68 @@
* 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 @@
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() {
@@ -314,9 +340,9 @@
mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
ArraySet<String> continuePreviousDay = new ArraySet<>();
for (IntervalStats stat : mCurrentStats) {
- final int pkgCount = stat.stats.size();
+ final int pkgCount = stat.packageStats.size();
for (int i = 0; i < pkgCount; i++) {
- UsageStats pkgStats = stat.stats.valueAt(i);
+ UsageStats pkgStats = stat.packageStats.valueAt(i);
if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
continuePreviousDay.add(pkgStats.mPackageName);
@@ -360,54 +386,53 @@
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);
+ Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
+ sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
+ ") for interval " + intervalType);
}
- 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");
- }
- }
+ 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() + ")");
}