diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d3e12f7740840..d5a1b8fe434f8 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -5847,6 +5847,8 @@ public final class ActivityManagerService extends ActivityManagerNative } mSelf.mCoreSettingsObserver = new CoreSettingsObserver(mSelf); + + mSelf.mUsageStatsService.monitorPackages(); } /** diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 12c8ccf2c3afd..0d36d69cfc334 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -16,20 +16,29 @@ package com.android.server.am; -import com.android.internal.app.IUsageStats; - import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageInfo; import android.os.Binder; import android.os.IBinder; -import com.android.internal.os.PkgUsageStats; - import android.os.FileUtils; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Slog; +import android.util.Xml; + +import com.android.internal.app.IUsageStats; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.AtomicFile; +import com.android.internal.os.PkgUsageStats; +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -63,12 +72,14 @@ public final class UsageStatsService extends IUsageStats.Stub { private static final String TAG = "UsageStats"; // Current on-disk Parcel version - private static final int VERSION = 1006; + private static final int VERSION = 1007; private static final int CHECKIN_VERSION = 4; private static final String FILE_PREFIX = "usage-"; - + + private static final String FILE_HISTORY = FILE_PREFIX + "history.xml"; + private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms private static final int MAX_NUM_FILES = 5; @@ -82,6 +93,13 @@ public final class UsageStatsService extends IUsageStats.Stub { private Context mContext; // structure used to maintain statistics since the last checkin. final private Map mStats; + + // Maintains the last time any component was resumed, for all time. + final private Map> mLastResumeTimes; + + // To remove last-resume time stats when a pacakge is removed. + private PackageMonitor mPackageMonitor; + // Lock to update package stats. Methods suffixed by SLOCK should invoked with // this lock held final Object mStatsLock; @@ -93,6 +111,7 @@ public final class UsageStatsService extends IUsageStats.Stub { private String mLastResumedComp; private boolean mIsResumed; private File mFile; + private AtomicFile mHistoryFile; private String mFileLeaf; private File mDir; @@ -145,8 +164,6 @@ public final class UsageStatsService extends IUsageStats.Stub { final HashMap mLaunchTimes = new HashMap(); int mLaunchCount; - final HashMap mLastResumeTimes - = new HashMap(); long mUsageTime; long mPausedTime; long mResumedTime; @@ -170,20 +187,12 @@ public final class UsageStatsService extends IUsageStats.Stub { TimeStats times = new TimeStats(in); mLaunchTimes.put(comp, times); } - final int numResumeTimes = in.readInt(); - if (localLOGV) Slog.v(TAG, "Reading last resume times: " + numResumeTimes); - for (int i=0; i 0) { - for (Map.Entry ent : mLastResumeTimes.entrySet()) { - out.writeString(ent.getKey()); - out.writeLong(ent.getValue()); - } - } } void clear() { mLaunchTimes.clear(); mLaunchCount = 0; - mLastResumeTimes.clear(); mUsageTime = 0; } } UsageStatsService(String dir) { mStats = new HashMap(); + mLastResumeTimes = new HashMap>(); mStatsLock = new Object(); mFileLock = new Object(); mDir = new File(dir); @@ -267,7 +268,9 @@ public final class UsageStatsService extends IUsageStats.Stub { // Update current stats which are binned by date mFileLeaf = getCurrentDateStr(FILE_PREFIX); mFile = new File(mDir, mFileLeaf); + mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY)); readStatsFromFile(); + readHistoryStatsFromFile(); mLastWriteElapsedTime.set(SystemClock.elapsedRealtime()); // mCal was set by getCurrentDateStr(), want to use that same time. mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR)); @@ -347,6 +350,73 @@ public final class UsageStatsService extends IUsageStats.Stub { } } + private void readHistoryStatsFromFile() { + synchronized (mFileLock) { + if (mHistoryFile.getBaseFile().exists()) { + readHistoryStatsFLOCK(mHistoryFile); + } + } + } + + private void readHistoryStatsFLOCK(AtomicFile file) { + FileInputStream fis = null; + try { + fis = mHistoryFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG) { + eventType = parser.next(); + } + String tagName = parser.getName(); + if ("usage-history".equals(tagName)) { + String pkg = null; + do { + eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + int depth = parser.getDepth(); + if ("pkg".equals(tagName) && depth == 2) { + pkg = parser.getAttributeValue(null, "name"); + } else if ("comp".equals(tagName) && depth == 3 && pkg != null) { + String comp = parser.getAttributeValue(null, "name"); + String lastResumeTimeStr = parser.getAttributeValue(null, "lrt"); + if (comp != null && lastResumeTimeStr != null) { + try { + long lastResumeTime = Long.parseLong(lastResumeTimeStr); + synchronized (mStatsLock) { + Map lrt = mLastResumeTimes.get(pkg); + if (lrt == null) { + lrt = new HashMap(); + mLastResumeTimes.put(pkg, lrt); + } + lrt.put(comp, lastResumeTime); + } + } catch (NumberFormatException e) { + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if ("pkg".equals(parser.getName())) { + pkg = null; + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Slog.w(TAG,"Error reading history stats: " + e); + } catch (IOException e) { + Slog.w(TAG,"Error reading history stats: " + e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + } + private ArrayList getUsageStatsFileListFLOCK() { // Check if there are too many files in the system and delete older files String fList[] = mDir.list(); @@ -400,8 +470,9 @@ public final class UsageStatsService extends IUsageStats.Stub { * * @params force do an unconditional, synchronous stats flush * to disk on the current thread. + * @params forceWriteHistoryStats Force writing of historical stats. */ - private void writeStatsToFile(final boolean force) { + private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) { int curDay; synchronized (mCal) { mCal.setTimeInMillis(System.currentTimeMillis()); @@ -427,7 +498,7 @@ public final class UsageStatsService extends IUsageStats.Stub { public void run() { try { if (localLOGV) Slog.d(TAG, "Disk writer thread starting."); - writeStatsToFile(true); + writeStatsToFile(true, false); } finally { mUnforcedDiskWriteRunning.set(false); if (localLOGV) Slog.d(TAG, "Disk writer thread ending."); @@ -468,6 +539,12 @@ public final class UsageStatsService extends IUsageStats.Stub { mFile = new File(mDir, mFileLeaf); checkFileLimitFLOCK(); } + + if (dayChanged || forceWriteHistoryStats) { + // Write history stats daily, or when forced (due to shutdown). + writeHistoryStatsFLOCK(mHistoryFile); + } + // Delete the backup file if (backupFile != null) { backupFile.delete(); @@ -510,16 +587,86 @@ public final class UsageStatsService extends IUsageStats.Stub { } } + /** Filter out stats for any packages which aren't present anymore. */ + private void filterHistoryStats() { + synchronized (mStatsLock) { + // Copy and clear the last resume times map, then copy back stats + // for all installed packages. + Map> tmpLastResumeTimes = + new HashMap>(mLastResumeTimes); + mLastResumeTimes.clear(); + for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) { + if (tmpLastResumeTimes.containsKey(info.packageName)) { + mLastResumeTimes.put(info.packageName, tmpLastResumeTimes.get(info.packageName)); + } + } + } + } + + private void writeHistoryStatsFLOCK(AtomicFile historyFile) { + FileOutputStream fos = null; + try { + fos = historyFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + out.startTag(null, "usage-history"); + synchronized (mStatsLock) { + for (Map.Entry> pkgEntry : mLastResumeTimes.entrySet()) { + out.startTag(null, "pkg"); + out.attribute(null, "name", pkgEntry.getKey()); + for (Map.Entry compEntry : pkgEntry.getValue().entrySet()) { + out.startTag(null, "comp"); + out.attribute(null, "name", compEntry.getKey()); + out.attribute(null, "lrt", compEntry.getValue().toString()); + out.endTag(null, "comp"); + } + out.endTag(null, "pkg"); + } + } + out.endTag(null, "usage-history"); + out.endDocument(); + + historyFile.finishWrite(fos); + } catch (IOException e) { + Slog.w(TAG,"Error writing history stats" + e); + if (fos != null) { + historyFile.failWrite(fos); + } + } + } + public void publish(Context context) { mContext = context; ServiceManager.addService(SERVICE_NAME, asBinder()); } - - public void shutdown() { - Slog.i(TAG, "Writing usage stats before shutdown..."); - writeStatsToFile(true); + + /** + * Start watching packages to remove stats when a package is uninstalled. + * May only be called when the package manager is ready. + */ + public void monitorPackages() { + mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mStatsLock) { + mLastResumeTimes.remove(packageName); + } + } + }; + mPackageMonitor.register(mContext, true); + filterHistoryStats(); } - + + public void shutdown() { + if (mPackageMonitor != null) { + mPackageMonitor.unregister(); + } + Slog.i(TAG, "Writing usage stats before shutdown..."); + writeStatsToFile(true, true); + } + public static IUsageStats getService() { if (sService != null) { return sService; @@ -569,6 +716,13 @@ public final class UsageStatsService extends IUsageStats.Stub { if (!sameComp) { pus.addLaunchCount(mLastResumedComp); } + + Map componentResumeTimes = mLastResumeTimes.get(pkgName); + if (componentResumeTimes == null) { + componentResumeTimes = new HashMap(); + mLastResumeTimes.put(pkgName, componentResumeTimes); + } + componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis()); } } @@ -600,7 +754,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } // Persist current data to file if needed. - writeStatsToFile(false); + writeStatsToFile(false, false); } public void noteLaunchTime(ComponentName componentName, int millis) { @@ -612,7 +766,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } // Persist current data to file if needed. - writeStatsToFile(false); + writeStatsToFile(false, false); synchronized (mStatsLock) { PkgUsageStatsExtended pus = mStats.get(pkgName); @@ -640,11 +794,13 @@ public final class UsageStatsService extends IUsageStats.Stub { } synchronized (mStatsLock) { PkgUsageStatsExtended pus = mStats.get(pkgName); - if (pus == null) { - return null; + Map lastResumeTimes = mLastResumeTimes.get(pkgName); + if (pus == null && lastResumeTimes == null) { + return null; } - return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime, - pus.mLastResumeTimes); + int launchCount = pus != null ? pus.mLaunchCount : 0; + long usageTime = pus != null ? pus.mUsageTime : 0; + return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes); } } @@ -652,17 +808,23 @@ public final class UsageStatsService extends IUsageStats.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); synchronized (mStatsLock) { - Set keys = mStats.keySet(); - int size = keys.size(); + int size = mLastResumeTimes.size(); if (size <= 0) { return null; } PkgUsageStats retArr[] = new PkgUsageStats[size]; int i = 0; - for (String key: keys) { - PkgUsageStatsExtended pus = mStats.get(key); - retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime, - pus.mLastResumeTimes); + for (Map.Entry> entry : mLastResumeTimes.entrySet()) { + String pkg = entry.getKey(); + long usageTime = 0; + int launchCount = 0; + + PkgUsageStatsExtended pus = mStats.get(pkg); + if (pus != null) { + usageTime = pus.mUsageTime; + launchCount = pus.mLaunchCount; + } + retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime, entry.getValue()); i++; } return retArr; @@ -881,7 +1043,7 @@ public final class UsageStatsService extends IUsageStats.Stub { // doesn't need to be done if we are deleting files after printing, // since it that case we won't print the current stats. if (!deleteAfterPrint) { - writeStatsToFile(true); + writeStatsToFile(true, false); } HashSet packages = null;