Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 930740d1 authored by Mark Brophy's avatar Mark Brophy Committed by Android (Google) Code Review
Browse files

Merge "Store last-resume times in a separate XML file."

parents 75e02547 c6350272
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -5847,6 +5847,8 @@ public final class ActivityManagerService extends ActivityManagerNative
        }
        mSelf.mCoreSettingsObserver = new CoreSettingsObserver(mSelf);
        mSelf.mUsageStatsService.monitorPackages();
    }
    /**
+205 −43
Original line number Diff line number Diff line
@@ -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<String, PkgUsageStatsExtended> mStats;

    // Maintains the last time any component was resumed, for all time.
    final private Map<String, Map<String, Long>> 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<String, TimeStats> mLaunchTimes
                = new HashMap<String, TimeStats>();
        int mLaunchCount;
        final HashMap<String, Long> mLastResumeTimes
                = new HashMap<String, Long>();
        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<numResumeTimes; i++) {
                String comp = in.readString();
                if (localLOGV) Slog.v(TAG, "Component: " + comp);
                mLastResumeTimes.put(comp, in.readLong());
            }
        }

        void updateResume(String comp, boolean launched) {
            if (launched) {
                mLaunchCount ++;
            }
            mLastResumeTimes.put(comp, System.currentTimeMillis());
            mResumedTime = SystemClock.elapsedRealtime();
        }
        
@@ -222,26 +231,18 @@ public final class UsageStatsService extends IUsageStats.Stub {
                    times.writeToParcel(out);
                }
            }
            final int numResumeTimes = mLastResumeTimes.size();
            out.writeInt(numResumeTimes);
            if (numResumeTimes > 0) {
                for (Map.Entry<String, Long> 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<String, PkgUsageStatsExtended>();
        mLastResumeTimes = new HashMap<String, Map<String, Long>>();
        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<String, Long> lrt = mLastResumeTimes.get(pkg);
                                        if (lrt == null) {
                                            lrt = new HashMap<String, Long>();
                                            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<String> 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,14 +587,84 @@ 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<String, Map<String, Long>> tmpLastResumeTimes =
                new HashMap<String, Map<String, Long>>(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<String, Map<String, Long>> pkgEntry : mLastResumeTimes.entrySet()) {
                    out.startTag(null, "pkg");
                    out.attribute(null, "name", pkgEntry.getKey());
                    for (Map.Entry<String, Long> 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());
    }

    /**
     * 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);
        writeStatsToFile(true, true);
    }

    public static IUsageStats getService() {
@@ -569,6 +716,13 @@ public final class UsageStatsService extends IUsageStats.Stub {
            if (!sameComp) {
                pus.addLaunchCount(mLastResumedComp);
            }

            Map<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
            if (componentResumeTimes == null) {
                componentResumeTimes = new HashMap<String, Long>();
                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) {
            Map<String, Long> 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<String> 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<String, Map<String, Long>> 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<String> packages = null;