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

Commit 49661141 authored by Adam Lesinski's avatar Adam Lesinski Committed by Android Git Automerger
Browse files

am 720a5c51: Merge "Add dumpsys output to UsageStatsService, along with...

am 720a5c51: Merge "Add dumpsys output to UsageStatsService, along with --checkin support" into lmp-mr1-dev

* commit '720a5c51':
  Add dumpsys output to UsageStatsService, along with --checkin support
parents 0319e704 720a5c51
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1371,7 +1371,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
            }
        }

        if (!config.locale.getLanguage().isEmpty()) {
        if (config.locale != null && !config.locale.getLanguage().isEmpty()) {
            parts.add(localeToResourceQualifier(config.locale));
        }

+65 −4
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ class UsageStatsDatabase {

    private static final String TAG = "UsageStatsDatabase";
    private static final boolean DEBUG = UsageStatsService.DEBUG;
    private static final String BAK_SUFFIX = ".bak";

    private final Object mLock = new Object();
    private final File[] mIntervalDirs;
@@ -95,11 +96,71 @@ class UsageStatsDatabase {
        }
    }

    public interface CheckinAction {
        boolean checkin(IntervalStats stats);
    }

    /**
     * Calls {@link CheckinAction#checkin(IntervalStats)} on the given {@link CheckinAction}
     * for all {@link IntervalStats} that haven't been checked-in.
     * If any of the calls to {@link CheckinAction#checkin(IntervalStats)} returns false or throws
     * an exception, the check-in will be aborted.
     *
     * @param checkinAction The callback to run when checking-in {@link IntervalStats}.
     * @return true if the check-in succeeded.
     */
    public boolean checkinDailyFiles(CheckinAction checkinAction) {
        synchronized (mLock) {
            final TimeSparseArray<AtomicFile> files =
                    mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY];
            final int fileCount = files.size();
            int start = 0;
            while (start < fileCount - 1) {
                if (!files.valueAt(start).getBaseFile().getName().endsWith("-c")) {
                    break;
                }
            }

            if (start == fileCount - 1) {
                return true;
            }

            try {
                IntervalStats stats = new IntervalStats();
                for (int i = start; i < fileCount - 1; i++) {
                    UsageStatsXml.read(files.valueAt(i), stats);
                    if (!checkinAction.checkin(stats)) {
                        return false;
                    }
                }
            } catch (IOException e) {
                Slog.e(TAG, "Failed to check-in", e);
                return false;
            }

            // We have successfully checked-in the stats, so rename the files so that they
            // are marked as checked-in.
            for (int i = start; i < fileCount - 1; i++) {
                final AtomicFile file = files.valueAt(i);
                final File checkedInFile = new File(file.getBaseFile().getParent(),
                        file.getBaseFile().getName() + "-c");
                if (!file.getBaseFile().renameTo(checkedInFile)) {
                    // We must return success, as we've already marked some files as checked-in.
                    // It's better to repeat ourselves than to lose data.
                    Slog.e(TAG, "Failed to mark file " + file.getBaseFile().getPath()
                            + " as checked-in");
                    return true;
                }
            }
        }
        return true;
    }

    private void indexFilesLocked() {
        final FilenameFilter backupFileFilter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return !name.endsWith(".bak");
                return !name.endsWith(BAK_SUFFIX);
            }
        };

@@ -383,10 +444,10 @@ class UsageStatsDatabase {
        if (files != null) {
            for (File f : files) {
                String path = f.getPath();
                if (path.endsWith(".bak")) {
                    f = new File(path.substring(0, path.length() - 4));
                if (path.endsWith(BAK_SUFFIX)) {
                    f = new File(path.substring(0, path.length() - BAK_SUFFIX.length()));
                }
                long beginTime = Long.parseLong(f.getName());
                long beginTime = UsageStatsXml.parseBeginTime(f);
                if (beginTime < expiryTime) {
                    new AtomicFile(f).delete();
                }
+40 −2
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Debug;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
@@ -48,9 +47,12 @@ import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;

import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;

@@ -177,7 +179,7 @@ public class UsageStatsService extends SystemService implements
            long currentTimeMillis) {
        UserUsageStatsService service = mUserState.get(userId);
        if (service == null) {
            service = new UserUsageStatsService(userId,
            service = new UserUsageStatsService(getContext(), userId,
                    new File(mUsageStatsDir, Integer.toString(userId)), this);
            service.init(currentTimeMillis);
            mUserState.put(userId, service);
@@ -320,6 +322,30 @@ public class UsageStatsService extends SystemService implements
        mHandler.removeMessages(MSG_FLUSH_TO_DISK);
    }

    /**
     * Called by the Binder stub.
     */
    void dump(String[] args, PrintWriter pw) {
        synchronized (mLock) {
            IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
            ArraySet<String> argSet = new ArraySet<>();
            argSet.addAll(Arrays.asList(args));

            final int userCount = mUserState.size();
            for (int i = 0; i < userCount; i++) {
                idpw.printPair("user", mUserState.keyAt(i));
                idpw.println();
                idpw.increaseIndent();
                if (argSet.contains("--checkin")) {
                    mUserState.valueAt(i).checkin(idpw);
                } else {
                    mUserState.valueAt(i).dump(idpw);
                }
                idpw.decreaseIndent();
            }
        }
    }

    class H extends Handler {
        public H(Looper looper) {
            super(looper);
@@ -422,6 +448,18 @@ public class UsageStatsService extends SystemService implements
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                    != PackageManager.PERMISSION_GRANTED) {
                pw.println("Permission Denial: can't dump UsageStats from pid="
                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
                        + " without permission " + android.Manifest.permission.DUMP);
                return;
            }
            UsageStatsService.this.dump(args, pw);
        }
    }

    /**
+12 −7
Original line number Diff line number Diff line
@@ -24,21 +24,26 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;

public class UsageStatsXml {
    private static final String TAG = "UsageStatsXml";
    private static final int CURRENT_VERSION = 1;
    private static final String USAGESTATS_TAG = "usagestats";
    private static final String VERSION_ATTR = "version";
    private static final String CHECKED_IN_SUFFIX = "-c";

    public static long parseBeginTime(AtomicFile file) {
        return Long.parseLong(file.getBaseFile().getName());
        return parseBeginTime(file.getBaseFile());
    }

    public static long parseBeginTime(File file) {
        final String name = file.getName();
        if (name.endsWith(CHECKED_IN_SUFFIX)) {
            return Long.parseLong(
                    name.substring(0, name.length() - CHECKED_IN_SUFFIX.length()));
        }
        return Long.parseLong(name);
    }

    public static void read(AtomicFile file, IntervalStats statsOut) throws IOException {
+123 −1
Original line number Diff line number Diff line
@@ -23,9 +23,13 @@ import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.res.Configuration;
import android.os.SystemClock;
import android.content.Context;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.util.IndentingPrintWriter;
import com.android.server.usage.UsageStatsDatabase.StatCombiner;

import java.io.File;
@@ -43,7 +47,13 @@ class UserUsageStatsService {
    private static final String TAG = "UsageStatsService";
    private static final boolean DEBUG = UsageStatsService.DEBUG;
    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final int sDateFormatFlags =
            DateUtils.FORMAT_SHOW_DATE
            | DateUtils.FORMAT_SHOW_TIME
            | DateUtils.FORMAT_SHOW_YEAR
            | DateUtils.FORMAT_NUMERIC_DATE;

    private final Context mContext;
    private final UsageStatsDatabase mDatabase;
    private final IntervalStats[] mCurrentStats;
    private boolean mStatsChanged = false;
@@ -55,7 +65,8 @@ class UserUsageStatsService {
        void onStatsUpdated();
    }

    UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) {
    UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) {
        mContext = context;
        mDailyExpiryDate = new UnixCalendar(0);
        mDatabase = new UsageStatsDatabase(usageStatsDir);
        mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
@@ -433,6 +444,117 @@ class UserUsageStatsService {
                tempCal.getTimeInMillis() + ")");
    }

    //
    // -- DUMP related methods --
    //

    void checkin(final IndentingPrintWriter pw) {
        mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
            @Override
            public boolean checkin(IntervalStats stats) {
                printIntervalStats(pw, stats, false);
                return true;
            }
        });
    }

    void dump(IndentingPrintWriter pw) {
        // This is not a check-in, only dump in-memory stats.
        for (int interval = 0; interval < mCurrentStats.length; interval++) {
            pw.print("In-memory ");
            pw.print(intervalToString(interval));
            pw.println(" stats");
            printIntervalStats(pw, mCurrentStats[interval], true);
        }
    }

    private String formatDateTime(long dateTime, boolean pretty) {
        if (pretty) {
            return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
        }
        return Long.toString(dateTime);
    }

    private String formatElapsedTime(long elapsedTime, boolean pretty) {
        if (pretty) {
            return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
        }
        return Long.toString(elapsedTime);
    }

    void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates) {
        if (prettyDates) {
            pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
                    stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
        } else {
            pw.printPair("beginTime", stats.beginTime);
            pw.printPair("endTime", stats.endTime);
        }
        pw.println();
        pw.increaseIndent();
        pw.println("packages");
        pw.increaseIndent();
        final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
        final int pkgCount = pkgStats.size();
        for (int i = 0; i < pkgCount; i++) {
            final UsageStats usageStats = pkgStats.valueAt(i);
            pw.printPair("package", usageStats.mPackageName);
            pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
            pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
            pw.println();
        }
        pw.decreaseIndent();

        pw.println("configurations");
        pw.increaseIndent();
        final ArrayMap<Configuration, ConfigurationStats> configStats =
                stats.configurations;
        final int configCount = configStats.size();
        for (int i = 0; i < configCount; i++) {
            final ConfigurationStats config = configStats.valueAt(i);
            pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
            pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
            pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
            pw.printPair("count", config.mActivationCount);
            pw.println();
        }
        pw.decreaseIndent();

        pw.println("events");
        pw.increaseIndent();
        final TimeSparseArray<UsageEvents.Event> events = stats.events;
        final int eventCount = events != null ? events.size() : 0;
        for (int i = 0; i < eventCount; i++) {
            final UsageEvents.Event event = events.valueAt(i);
            pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
            pw.printPair("type", eventToString(event.mEventType));
            pw.printPair("package", event.mPackage);
            if (event.mClass != null) {
                pw.printPair("class", event.mClass);
            }
            if (event.mConfiguration != null) {
                pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
            }
            pw.println();
        }
        pw.decreaseIndent();
        pw.decreaseIndent();
    }

    private static String intervalToString(int interval) {
        switch (interval) {
            case UsageStatsManager.INTERVAL_DAILY:
                return "daily";
            case UsageStatsManager.INTERVAL_WEEKLY:
                return "weekly";
            case UsageStatsManager.INTERVAL_MONTHLY:
                return "monthly";
            case UsageStatsManager.INTERVAL_YEARLY:
                return "yearly";
            default:
                return "?";
        }
    }

    private static String eventToString(int eventType) {
        switch (eventType) {