OSDN Git Service

am 7911c8b9: (-s ours) am 15e549a8: am 4f5e6c63: Skip warnings for some unresolved...
[android-x86/frameworks-base.git] / services / usage / java / com / android / server / usage / UserUsageStatsService.java
1 /**
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16
17 package com.android.server.usage;
18
19 import android.app.usage.ConfigurationStats;
20 import android.app.usage.TimeSparseArray;
21 import android.app.usage.UsageEvents;
22 import android.app.usage.UsageEvents.Event;
23 import android.app.usage.UsageStats;
24 import android.app.usage.UsageStatsManager;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.res.Configuration;
28 import android.os.SystemClock;
29 import android.content.Context;
30 import android.text.format.DateUtils;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.Slog;
34
35 import com.android.internal.util.IndentingPrintWriter;
36 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.text.SimpleDateFormat;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44
45 /**
46  * A per-user UsageStatsService. All methods are meant to be called with the main lock held
47  * in UsageStatsService.
48  */
49 class UserUsageStatsService {
50     private static final String TAG = "UsageStatsService";
51     private static final boolean DEBUG = UsageStatsService.DEBUG;
52     private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
53     private static final int sDateFormatFlags =
54             DateUtils.FORMAT_SHOW_DATE
55             | DateUtils.FORMAT_SHOW_TIME
56             | DateUtils.FORMAT_SHOW_YEAR
57             | DateUtils.FORMAT_NUMERIC_DATE;
58
59     private final Context mContext;
60     private final UsageStatsDatabase mDatabase;
61     private final IntervalStats[] mCurrentStats;
62     private boolean mStatsChanged = false;
63     private final UnixCalendar mDailyExpiryDate;
64     private final StatsUpdatedListener mListener;
65     private final String mLogPrefix;
66     private final int mUserId;
67
68     interface StatsUpdatedListener {
69         void onStatsUpdated();
70     }
71
72     UserUsageStatsService(Context context, int userId, File usageStatsDir,
73             StatsUpdatedListener listener) {
74         mContext = context;
75         mDailyExpiryDate = new UnixCalendar(0);
76         mDatabase = new UsageStatsDatabase(usageStatsDir);
77         mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
78         mListener = listener;
79         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
80         mUserId = userId;
81     }
82
83     void init(final long currentTimeMillis, final long deviceUsageTime) {
84         mDatabase.init(currentTimeMillis);
85
86         int nullCount = 0;
87         for (int i = 0; i < mCurrentStats.length; i++) {
88             mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
89             if (mCurrentStats[i] == null) {
90                 // Find out how many intervals we don't have data for.
91                 // Ideally it should be all or none.
92                 nullCount++;
93             }
94         }
95
96         if (nullCount > 0) {
97             if (nullCount != mCurrentStats.length) {
98                 // This is weird, but we shouldn't fail if something like this
99                 // happens.
100                 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
101             } else {
102                 // This must be first boot.
103             }
104
105             // By calling loadActiveStats, we will
106             // generate new stats for each bucket.
107             loadActiveStats(currentTimeMillis,/*force=*/ false, /*resetBeginIdleTime=*/ false);
108         } else {
109             // Set up the expiry date to be one day from the latest daily stat.
110             // This may actually be today and we will rollover on the first event
111             // that is reported.
112             mDailyExpiryDate.setTimeInMillis(
113                     mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
114             mDailyExpiryDate.addDays(1);
115             mDailyExpiryDate.truncateToDay();
116             Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
117                     sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
118                     "(" + mDailyExpiryDate.getTimeInMillis() + ")");
119         }
120
121         // Now close off any events that were open at the time this was saved.
122         for (IntervalStats stat : mCurrentStats) {
123             final int pkgCount = stat.packageStats.size();
124             for (int i = 0; i < pkgCount; i++) {
125                 UsageStats pkgStats = stat.packageStats.valueAt(i);
126                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
127                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
128                     stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
129                             UsageEvents.Event.END_OF_DAY);
130                     notifyStatsChanged();
131                 }
132             }
133
134             stat.updateConfigurationStats(null, stat.lastTimeSaved);
135         }
136
137         if (mDatabase.isNewUpdate()) {
138             initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
139                     mDatabase.isFirstUpdate());
140         }
141     }
142
143     /**
144      * If any of the apps don't have a last-used entry, add one now.
145      * @param currentTimeMillis the current time
146      * @param firstUpdate if it is the first update, touch all installed apps, otherwise only
147      *        touch the system apps
148      */
149     private void initializeDefaultsForApps(long currentTimeMillis, long deviceUsageTime,
150             boolean firstUpdate) {
151         PackageManager pm = mContext.getPackageManager();
152         List<PackageInfo> packages = pm.getInstalledPackages(0, mUserId);
153         final int packageCount = packages.size();
154         for (int i = 0; i < packageCount; i++) {
155             final PackageInfo pi = packages.get(i);
156             String packageName = pi.packageName;
157             if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
158                     && getBeginIdleTime(packageName) == -1) {
159                 for (IntervalStats stats : mCurrentStats) {
160                     stats.update(packageName, currentTimeMillis, Event.SYSTEM_INTERACTION);
161                     stats.updateBeginIdleTime(packageName, deviceUsageTime);
162                     mStatsChanged = true;
163                 }
164             }
165         }
166         // Persist the new OTA-related access stats.
167         persistActiveStats();
168     }
169
170     void onTimeChanged(long oldTime, long newTime, boolean resetBeginIdleTime) {
171         persistActiveStats();
172         mDatabase.onTimeChanged(newTime - oldTime);
173         loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime);
174     }
175
176     void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
177         if (DEBUG) {
178             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
179                     + "[" + event.mTimeStamp + "]: "
180                     + eventToString(event.mEventType));
181         }
182
183         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
184             // Need to rollover
185             rolloverStats(event.mTimeStamp);
186         }
187
188         final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
189
190         final Configuration newFullConfig = event.mConfiguration;
191         if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
192                 currentDailyStats.activeConfiguration != null) {
193             // Make the event configuration a delta.
194             event.mConfiguration = Configuration.generateDelta(
195                     currentDailyStats.activeConfiguration, newFullConfig);
196         }
197
198         // Add the event to the daily list.
199         if (currentDailyStats.events == null) {
200             currentDailyStats.events = new TimeSparseArray<>();
201         }
202         if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
203             currentDailyStats.events.put(event.mTimeStamp, event);
204         }
205
206         for (IntervalStats stats : mCurrentStats) {
207             if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
208                 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
209             } else {
210                 stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
211                 stats.updateBeginIdleTime(event.mPackage, deviceUsageTime);
212             }
213         }
214
215         notifyStatsChanged();
216     }
217
218     /**
219      * Sets the beginIdleTime for each of the intervals.
220      * @param beginIdleTime
221      */
222     void setBeginIdleTime(String packageName, long beginIdleTime) {
223         for (IntervalStats stats : mCurrentStats) {
224             stats.updateBeginIdleTime(packageName, beginIdleTime);
225         }
226         notifyStatsChanged();
227     }
228
229     void setSystemLastUsedTime(String packageName, long lastUsedTime) {
230         for (IntervalStats stats : mCurrentStats) {
231             stats.updateSystemLastUsedTime(packageName, lastUsedTime);
232         }
233         notifyStatsChanged();
234     }
235
236     private static final StatCombiner<UsageStats> sUsageStatsCombiner =
237             new StatCombiner<UsageStats>() {
238                 @Override
239                 public void combine(IntervalStats stats, boolean mutable,
240                         List<UsageStats> accResult) {
241                     if (!mutable) {
242                         accResult.addAll(stats.packageStats.values());
243                         return;
244                     }
245
246                     final int statCount = stats.packageStats.size();
247                     for (int i = 0; i < statCount; i++) {
248                         accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
249                     }
250                 }
251             };
252
253     private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
254             new StatCombiner<ConfigurationStats>() {
255                 @Override
256                 public void combine(IntervalStats stats, boolean mutable,
257                         List<ConfigurationStats> accResult) {
258                     if (!mutable) {
259                         accResult.addAll(stats.configurations.values());
260                         return;
261                     }
262
263                     final int configCount = stats.configurations.size();
264                     for (int i = 0; i < configCount; i++) {
265                         accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
266                     }
267                 }
268             };
269
270     /**
271      * Generic query method that selects the appropriate IntervalStats for the specified time range
272      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
273      * provided to select the stats to use from the IntervalStats object.
274      */
275     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
276             StatCombiner<T> combiner) {
277         if (intervalType == UsageStatsManager.INTERVAL_BEST) {
278             intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
279             if (intervalType < 0) {
280                 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
281                 // occurred.
282                 intervalType = UsageStatsManager.INTERVAL_DAILY;
283             }
284         }
285
286         if (intervalType < 0 || intervalType >= mCurrentStats.length) {
287             if (DEBUG) {
288                 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
289             }
290             return null;
291         }
292
293         final IntervalStats currentStats = mCurrentStats[intervalType];
294
295         if (DEBUG) {
296             Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
297                     + beginTime + " AND endTime < " + endTime);
298         }
299
300         if (beginTime >= currentStats.endTime) {
301             if (DEBUG) {
302                 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
303                         + currentStats.endTime);
304             }
305             // Nothing newer available.
306             return null;
307         }
308
309         // Truncate the endTime to just before the in-memory stats. Then, we'll append the
310         // in-memory stats to the results (if necessary) so as to avoid writing to disk too
311         // often.
312         final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
313
314         // Get the stats from disk.
315         List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
316                 truncatedEndTime, combiner);
317         if (DEBUG) {
318             Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
319             Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
320                     " endTime=" + currentStats.endTime);
321         }
322
323         // Now check if the in-memory stats match the range and add them if they do.
324         if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
325             if (DEBUG) {
326                 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
327             }
328
329             if (results == null) {
330                 results = new ArrayList<>();
331             }
332             combiner.combine(currentStats, true, results);
333         }
334
335         if (DEBUG) {
336             Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
337         }
338         return results;
339     }
340
341     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
342         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
343     }
344
345     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
346         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
347     }
348
349     UsageEvents queryEvents(final long beginTime, final long endTime) {
350         final ArraySet<String> names = new ArraySet<>();
351         List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
352                 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
353                     @Override
354                     public void combine(IntervalStats stats, boolean mutable,
355                             List<UsageEvents.Event> accumulatedResult) {
356                         if (stats.events == null) {
357                             return;
358                         }
359
360                         final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
361                         if (startIndex < 0) {
362                             return;
363                         }
364
365                         final int size = stats.events.size();
366                         for (int i = startIndex; i < size; i++) {
367                             if (stats.events.keyAt(i) >= endTime) {
368                                 return;
369                             }
370
371                             final UsageEvents.Event event = stats.events.valueAt(i);
372                             names.add(event.mPackage);
373                             if (event.mClass != null) {
374                                 names.add(event.mClass);
375                             }
376                             accumulatedResult.add(event);
377                         }
378                     }
379                 });
380
381         if (results == null || results.isEmpty()) {
382             return null;
383         }
384
385         String[] table = names.toArray(new String[names.size()]);
386         Arrays.sort(table);
387         return new UsageEvents(results, table);
388     }
389
390     long getBeginIdleTime(String packageName) {
391         final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
392         UsageStats packageUsage;
393         if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
394             return -1;
395         } else {
396             return packageUsage.getBeginIdleTime();
397         }
398     }
399
400     long getSystemLastUsedTime(String packageName) {
401         final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
402         UsageStats packageUsage;
403         if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
404             return -1;
405         } else {
406             return packageUsage.getLastTimeSystemUsed();
407         }
408     }
409
410     void persistActiveStats() {
411         if (mStatsChanged) {
412             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
413             try {
414                 for (int i = 0; i < mCurrentStats.length; i++) {
415                     mDatabase.putUsageStats(i, mCurrentStats[i]);
416                 }
417                 mStatsChanged = false;
418             } catch (IOException e) {
419                 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
420             }
421         }
422     }
423
424     private void rolloverStats(final long currentTimeMillis) {
425         final long startTime = SystemClock.elapsedRealtime();
426         Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
427
428         // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
429         // need a new CONTINUE_PREVIOUS_DAY entry.
430         final Configuration previousConfig =
431                 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
432         ArraySet<String> continuePreviousDay = new ArraySet<>();
433         for (IntervalStats stat : mCurrentStats) {
434             final int pkgCount = stat.packageStats.size();
435             for (int i = 0; i < pkgCount; i++) {
436                 UsageStats pkgStats = stat.packageStats.valueAt(i);
437                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
438                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
439                     continuePreviousDay.add(pkgStats.mPackageName);
440                     stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
441                             UsageEvents.Event.END_OF_DAY);
442                     notifyStatsChanged();
443                 }
444             }
445
446             stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
447         }
448
449         persistActiveStats();
450         mDatabase.prune(currentTimeMillis);
451         loadActiveStats(currentTimeMillis, /*force=*/ false, /*resetBeginIdleTime=*/ false);
452
453         final int continueCount = continuePreviousDay.size();
454         for (int i = 0; i < continueCount; i++) {
455             String name = continuePreviousDay.valueAt(i);
456             final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
457             for (IntervalStats stat : mCurrentStats) {
458                 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
459                 stat.updateConfigurationStats(previousConfig, beginTime);
460                 notifyStatsChanged();
461             }
462         }
463         persistActiveStats();
464
465         final long totalTime = SystemClock.elapsedRealtime() - startTime;
466         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
467                 + " milliseconds");
468     }
469
470     private void notifyStatsChanged() {
471         if (!mStatsChanged) {
472             mStatsChanged = true;
473             mListener.onStatsUpdated();
474         }
475     }
476
477     /**
478      * @param force To force all in-memory stats to be reloaded.
479      */
480     private void loadActiveStats(final long currentTimeMillis, boolean force,
481             boolean resetBeginIdleTime) {
482         final UnixCalendar tempCal = mDailyExpiryDate;
483         for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
484             tempCal.setTimeInMillis(currentTimeMillis);
485             UnixCalendar.truncateTo(tempCal, intervalType);
486
487             if (!force && mCurrentStats[intervalType] != null &&
488                     mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
489                 // These are the same, no need to load them (in memory stats are always newer
490                 // than persisted stats).
491                 continue;
492             }
493
494             final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
495             if (lastBeginTime >= tempCal.getTimeInMillis()) {
496                 if (DEBUG) {
497                     Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
498                             sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
499                             ") for interval " + intervalType);
500                 }
501                 mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
502             } else {
503                 mCurrentStats[intervalType] = null;
504             }
505
506             if (mCurrentStats[intervalType] == null) {
507                 if (DEBUG) {
508                     Slog.d(TAG, "Creating new stats @ " +
509                             sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
510                             tempCal.getTimeInMillis() + ") for interval " + intervalType);
511
512                 }
513                 mCurrentStats[intervalType] = new IntervalStats();
514                 mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
515                 mCurrentStats[intervalType].endTime = currentTimeMillis;
516             }
517
518             if (resetBeginIdleTime) {
519                 for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) {
520                     usageStats.mBeginIdleTime = 0;
521                 }
522             }
523         }
524         mStatsChanged = false;
525         mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
526         mDailyExpiryDate.addDays(1);
527         mDailyExpiryDate.truncateToDay();
528         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
529                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
530                 tempCal.getTimeInMillis() + ")");
531     }
532
533     //
534     // -- DUMP related methods --
535     //
536
537     void checkin(final IndentingPrintWriter pw, final long screenOnTime) {
538         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
539             @Override
540             public boolean checkin(IntervalStats stats) {
541                 printIntervalStats(pw, stats, screenOnTime, false);
542                 return true;
543             }
544         });
545     }
546
547     void dump(IndentingPrintWriter pw, final long screenOnTime) {
548         // This is not a check-in, only dump in-memory stats.
549         for (int interval = 0; interval < mCurrentStats.length; interval++) {
550             pw.print("In-memory ");
551             pw.print(intervalToString(interval));
552             pw.println(" stats");
553             printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
554         }
555     }
556
557     private String formatDateTime(long dateTime, boolean pretty) {
558         if (pretty) {
559             return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
560         }
561         return Long.toString(dateTime);
562     }
563
564     private String formatElapsedTime(long elapsedTime, boolean pretty) {
565         if (pretty) {
566             return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
567         }
568         return Long.toString(elapsedTime);
569     }
570
571     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, long screenOnTime,
572             boolean prettyDates) {
573         if (prettyDates) {
574             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
575                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
576         } else {
577             pw.printPair("beginTime", stats.beginTime);
578             pw.printPair("endTime", stats.endTime);
579         }
580         pw.println();
581         pw.increaseIndent();
582         pw.println("packages");
583         pw.increaseIndent();
584         final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
585         final int pkgCount = pkgStats.size();
586         for (int i = 0; i < pkgCount; i++) {
587             final UsageStats usageStats = pkgStats.valueAt(i);
588             pw.printPair("package", usageStats.mPackageName);
589             pw.printPair("totalTime",
590                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
591             pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
592             pw.printPair("lastTimeSystem",
593                     formatDateTime(usageStats.mLastTimeSystemUsed, prettyDates));
594             pw.printPair("inactiveTime",
595                     formatElapsedTime(screenOnTime - usageStats.mBeginIdleTime, prettyDates));
596             pw.println();
597         }
598         pw.decreaseIndent();
599
600         pw.println("configurations");
601         pw.increaseIndent();
602         final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
603         final int configCount = configStats.size();
604         for (int i = 0; i < configCount; i++) {
605             final ConfigurationStats config = configStats.valueAt(i);
606             pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
607             pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
608             pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
609             pw.printPair("count", config.mActivationCount);
610             pw.println();
611         }
612         pw.decreaseIndent();
613
614         pw.println("events");
615         pw.increaseIndent();
616         final TimeSparseArray<UsageEvents.Event> events = stats.events;
617         final int eventCount = events != null ? events.size() : 0;
618         for (int i = 0; i < eventCount; i++) {
619             final UsageEvents.Event event = events.valueAt(i);
620             pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
621             pw.printPair("type", eventToString(event.mEventType));
622             pw.printPair("package", event.mPackage);
623             if (event.mClass != null) {
624                 pw.printPair("class", event.mClass);
625             }
626             if (event.mConfiguration != null) {
627                 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
628             }
629             pw.println();
630         }
631         pw.decreaseIndent();
632         pw.decreaseIndent();
633     }
634
635     private static String intervalToString(int interval) {
636         switch (interval) {
637             case UsageStatsManager.INTERVAL_DAILY:
638                 return "daily";
639             case UsageStatsManager.INTERVAL_WEEKLY:
640                 return "weekly";
641             case UsageStatsManager.INTERVAL_MONTHLY:
642                 return "monthly";
643             case UsageStatsManager.INTERVAL_YEARLY:
644                 return "yearly";
645             default:
646                 return "?";
647         }
648     }
649
650     private static String eventToString(int eventType) {
651         switch (eventType) {
652             case UsageEvents.Event.NONE:
653                 return "NONE";
654             case UsageEvents.Event.MOVE_TO_BACKGROUND:
655                 return "MOVE_TO_BACKGROUND";
656             case UsageEvents.Event.MOVE_TO_FOREGROUND:
657                 return "MOVE_TO_FOREGROUND";
658             case UsageEvents.Event.END_OF_DAY:
659                 return "END_OF_DAY";
660             case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
661                 return "CONTINUE_PREVIOUS_DAY";
662             case UsageEvents.Event.CONFIGURATION_CHANGE:
663                 return "CONFIGURATION_CHANGE";
664             case UsageEvents.Event.SYSTEM_INTERACTION:
665                 return "SYSTEM_INTERACTION";
666             case UsageEvents.Event.USER_INTERACTION:
667                 return "USER_INTERACTION";
668             default:
669                 return "UNKNOWN";
670         }
671     }
672 }