diff options
5 files changed, 241 insertions, 15 deletions
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index acdd87e..14af584 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -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)); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index cfa4436..11da380 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -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(); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7ff246a..485b2a2 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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); + } } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java index 9ce6d63..26148ce 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXml.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java @@ -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 { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 4916ec2..6596781 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -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) { |