Home | History | Annotate | Download | only in usage
      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.EventList;
     21 import android.app.usage.EventStats;
     22 import android.app.usage.UsageEvents;
     23 import android.app.usage.UsageStats;
     24 import android.app.usage.UsageStatsManager;
     25 import android.content.res.Configuration;
     26 import android.os.SystemClock;
     27 import android.content.Context;
     28 import android.text.format.DateUtils;
     29 import android.util.ArrayMap;
     30 import android.util.ArraySet;
     31 import android.util.Slog;
     32 
     33 import com.android.internal.util.IndentingPrintWriter;
     34 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
     35 
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.text.SimpleDateFormat;
     39 import java.util.ArrayList;
     40 import java.util.Arrays;
     41 import java.util.List;
     42 
     43 /**
     44  * A per-user UsageStatsService. All methods are meant to be called with the main lock held
     45  * in UsageStatsService.
     46  */
     47 class UserUsageStatsService {
     48     private static final String TAG = "UsageStatsService";
     49     private static final boolean DEBUG = UsageStatsService.DEBUG;
     50     private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     51     private static final int sDateFormatFlags =
     52             DateUtils.FORMAT_SHOW_DATE
     53             | DateUtils.FORMAT_SHOW_TIME
     54             | DateUtils.FORMAT_SHOW_YEAR
     55             | DateUtils.FORMAT_NUMERIC_DATE;
     56 
     57     private final Context mContext;
     58     private final UsageStatsDatabase mDatabase;
     59     private final IntervalStats[] mCurrentStats;
     60     private boolean mStatsChanged = false;
     61     private final UnixCalendar mDailyExpiryDate;
     62     private final StatsUpdatedListener mListener;
     63     private final String mLogPrefix;
     64     private String mLastBackgroundedPackage;
     65     private final int mUserId;
     66 
     67     private static final long[] INTERVAL_LENGTH = new long[] {
     68             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
     69             UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
     70     };
     71 
     72     interface StatsUpdatedListener {
     73         void onStatsUpdated();
     74         void onStatsReloaded();
     75         /**
     76          * Callback that a system update was detected
     77          * @param mUserId user that needs to be initialized
     78          */
     79         void onNewUpdate(int mUserId);
     80     }
     81 
     82     UserUsageStatsService(Context context, int userId, File usageStatsDir,
     83             StatsUpdatedListener listener) {
     84         mContext = context;
     85         mDailyExpiryDate = new UnixCalendar(0);
     86         mDatabase = new UsageStatsDatabase(usageStatsDir);
     87         mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
     88         mListener = listener;
     89         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
     90         mUserId = userId;
     91     }
     92 
     93     void init(final long currentTimeMillis) {
     94         mDatabase.init(currentTimeMillis);
     95 
     96         int nullCount = 0;
     97         for (int i = 0; i < mCurrentStats.length; i++) {
     98             mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
     99             if (mCurrentStats[i] == null) {
    100                 // Find out how many intervals we don't have data for.
    101                 // Ideally it should be all or none.
    102                 nullCount++;
    103             }
    104         }
    105 
    106         if (nullCount > 0) {
    107             if (nullCount != mCurrentStats.length) {
    108                 // This is weird, but we shouldn't fail if something like this
    109                 // happens.
    110                 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
    111             } else {
    112                 // This must be first boot.
    113             }
    114 
    115             // By calling loadActiveStats, we will
    116             // generate new stats for each bucket.
    117             loadActiveStats(currentTimeMillis);
    118         } else {
    119             // Set up the expiry date to be one day from the latest daily stat.
    120             // This may actually be today and we will rollover on the first event
    121             // that is reported.
    122             updateRolloverDeadline();
    123         }
    124 
    125         // Now close off any events that were open at the time this was saved.
    126         for (IntervalStats stat : mCurrentStats) {
    127             final int pkgCount = stat.packageStats.size();
    128             for (int i = 0; i < pkgCount; i++) {
    129                 UsageStats pkgStats = stat.packageStats.valueAt(i);
    130                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
    131                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
    132                     stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
    133                             UsageEvents.Event.END_OF_DAY);
    134                     notifyStatsChanged();
    135                 }
    136             }
    137 
    138             stat.updateConfigurationStats(null, stat.lastTimeSaved);
    139         }
    140 
    141         if (mDatabase.isNewUpdate()) {
    142             notifyNewUpdate();
    143         }
    144     }
    145 
    146     void onTimeChanged(long oldTime, long newTime) {
    147         persistActiveStats();
    148         mDatabase.onTimeChanged(newTime - oldTime);
    149         loadActiveStats(newTime);
    150     }
    151 
    152     void reportEvent(UsageEvents.Event event) {
    153         if (DEBUG) {
    154             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
    155                     + "[" + event.mTimeStamp + "]: "
    156                     + eventToString(event.mEventType));
    157         }
    158 
    159         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
    160             // Need to rollover
    161             rolloverStats(event.mTimeStamp);
    162         }
    163 
    164         final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
    165 
    166         final Configuration newFullConfig = event.mConfiguration;
    167         if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
    168                 currentDailyStats.activeConfiguration != null) {
    169             // Make the event configuration a delta.
    170             event.mConfiguration = Configuration.generateDelta(
    171                     currentDailyStats.activeConfiguration, newFullConfig);
    172         }
    173 
    174         // Add the event to the daily list.
    175         if (currentDailyStats.events == null) {
    176             currentDailyStats.events = new EventList();
    177         }
    178         if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
    179             currentDailyStats.events.insert(event);
    180         }
    181 
    182         boolean incrementAppLaunch = false;
    183         if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
    184             if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
    185                 incrementAppLaunch = true;
    186             }
    187         } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
    188             if (event.mPackage != null) {
    189                 mLastBackgroundedPackage = event.mPackage;
    190             }
    191         }
    192 
    193         for (IntervalStats stats : mCurrentStats) {
    194             switch (event.mEventType) {
    195                 case UsageEvents.Event.CONFIGURATION_CHANGE: {
    196                     stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
    197                 } break;
    198                 case UsageEvents.Event.CHOOSER_ACTION: {
    199                     stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
    200                     String[] annotations = event.mContentAnnotations;
    201                     if (annotations != null) {
    202                         for (String annotation : annotations) {
    203                             stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
    204                         }
    205                     }
    206                 } break;
    207                 case UsageEvents.Event.SCREEN_INTERACTIVE: {
    208                     stats.updateScreenInteractive(event.mTimeStamp);
    209                 } break;
    210                 case UsageEvents.Event.SCREEN_NON_INTERACTIVE: {
    211                     stats.updateScreenNonInteractive(event.mTimeStamp);
    212                 } break;
    213                 case UsageEvents.Event.KEYGUARD_SHOWN: {
    214                     stats.updateKeyguardShown(event.mTimeStamp);
    215                 } break;
    216                 case UsageEvents.Event.KEYGUARD_HIDDEN: {
    217                     stats.updateKeyguardHidden(event.mTimeStamp);
    218                 } break;
    219                 default: {
    220                     stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
    221                     if (incrementAppLaunch) {
    222                         stats.incrementAppLaunchCount(event.mPackage);
    223                     }
    224                 } break;
    225             }
    226         }
    227 
    228         notifyStatsChanged();
    229     }
    230 
    231     private static final StatCombiner<UsageStats> sUsageStatsCombiner =
    232             new StatCombiner<UsageStats>() {
    233                 @Override
    234                 public void combine(IntervalStats stats, boolean mutable,
    235                                     List<UsageStats> accResult) {
    236                     if (!mutable) {
    237                         accResult.addAll(stats.packageStats.values());
    238                         return;
    239                     }
    240 
    241                     final int statCount = stats.packageStats.size();
    242                     for (int i = 0; i < statCount; i++) {
    243                         accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
    244                     }
    245                 }
    246             };
    247 
    248     private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
    249             new StatCombiner<ConfigurationStats>() {
    250                 @Override
    251                 public void combine(IntervalStats stats, boolean mutable,
    252                                     List<ConfigurationStats> accResult) {
    253                     if (!mutable) {
    254                         accResult.addAll(stats.configurations.values());
    255                         return;
    256                     }
    257 
    258                     final int configCount = stats.configurations.size();
    259                     for (int i = 0; i < configCount; i++) {
    260                         accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
    261                     }
    262                 }
    263             };
    264 
    265     private static final StatCombiner<EventStats> sEventStatsCombiner =
    266             new StatCombiner<EventStats>() {
    267                 @Override
    268                 public void combine(IntervalStats stats, boolean mutable,
    269                         List<EventStats> accResult) {
    270                     stats.addEventStatsTo(accResult);
    271                 }
    272             };
    273 
    274     /**
    275      * Generic query method that selects the appropriate IntervalStats for the specified time range
    276      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
    277      * provided to select the stats to use from the IntervalStats object.
    278      */
    279     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
    280             StatCombiner<T> combiner) {
    281         if (intervalType == UsageStatsManager.INTERVAL_BEST) {
    282             intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
    283             if (intervalType < 0) {
    284                 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
    285                 // occurred.
    286                 intervalType = UsageStatsManager.INTERVAL_DAILY;
    287             }
    288         }
    289 
    290         if (intervalType < 0 || intervalType >= mCurrentStats.length) {
    291             if (DEBUG) {
    292                 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
    293             }
    294             return null;
    295         }
    296 
    297         final IntervalStats currentStats = mCurrentStats[intervalType];
    298 
    299         if (DEBUG) {
    300             Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
    301                     + beginTime + " AND endTime < " + endTime);
    302         }
    303 
    304         if (beginTime >= currentStats.endTime) {
    305             if (DEBUG) {
    306                 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
    307                         + currentStats.endTime);
    308             }
    309             // Nothing newer available.
    310             return null;
    311         }
    312 
    313         // Truncate the endTime to just before the in-memory stats. Then, we'll append the
    314         // in-memory stats to the results (if necessary) so as to avoid writing to disk too
    315         // often.
    316         final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
    317 
    318         // Get the stats from disk.
    319         List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
    320                 truncatedEndTime, combiner);
    321         if (DEBUG) {
    322             Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
    323             Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
    324                     " endTime=" + currentStats.endTime);
    325         }
    326 
    327         // Now check if the in-memory stats match the range and add them if they do.
    328         if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
    329             if (DEBUG) {
    330                 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
    331             }
    332 
    333             if (results == null) {
    334                 results = new ArrayList<>();
    335             }
    336             combiner.combine(currentStats, true, results);
    337         }
    338 
    339         if (DEBUG) {
    340             Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
    341         }
    342         return results;
    343     }
    344 
    345     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
    346         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
    347     }
    348 
    349     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
    350         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
    351     }
    352 
    353     List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) {
    354         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
    355     }
    356 
    357     UsageEvents queryEvents(final long beginTime, final long endTime,
    358             boolean obfuscateInstantApps) {
    359         final ArraySet<String> names = new ArraySet<>();
    360         List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
    361                 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
    362                     @Override
    363                     public void combine(IntervalStats stats, boolean mutable,
    364                             List<UsageEvents.Event> accumulatedResult) {
    365                         if (stats.events == null) {
    366                             return;
    367                         }
    368 
    369                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
    370                         final int size = stats.events.size();
    371                         for (int i = startIndex; i < size; i++) {
    372                             if (stats.events.get(i).mTimeStamp >= endTime) {
    373                                 return;
    374                             }
    375 
    376                             UsageEvents.Event event = stats.events.get(i);
    377                             if (obfuscateInstantApps) {
    378                                 event = event.getObfuscatedIfInstantApp();
    379                             }
    380                             names.add(event.mPackage);
    381                             if (event.mClass != null) {
    382                                 names.add(event.mClass);
    383                             }
    384                             accumulatedResult.add(event);
    385                         }
    386                     }
    387                 });
    388 
    389         if (results == null || results.isEmpty()) {
    390             return null;
    391         }
    392 
    393         String[] table = names.toArray(new String[names.size()]);
    394         Arrays.sort(table);
    395         return new UsageEvents(results, table);
    396     }
    397 
    398     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
    399             final String packageName) {
    400         final ArraySet<String> names = new ArraySet<>();
    401         names.add(packageName);
    402         final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
    403                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
    404                     if (stats.events == null) {
    405                         return;
    406                     }
    407 
    408                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
    409                     final int size = stats.events.size();
    410                     for (int i = startIndex; i < size; i++) {
    411                         if (stats.events.get(i).mTimeStamp >= endTime) {
    412                             return;
    413                         }
    414 
    415                         final UsageEvents.Event event = stats.events.get(i);
    416                         if (!packageName.equals(event.mPackage)) {
    417                             continue;
    418                         }
    419                         if (event.mClass != null) {
    420                             names.add(event.mClass);
    421                         }
    422                         accumulatedResult.add(event);
    423                     }
    424                 });
    425 
    426         if (results == null || results.isEmpty()) {
    427             return null;
    428         }
    429 
    430         final String[] table = names.toArray(new String[names.size()]);
    431         Arrays.sort(table);
    432         return new UsageEvents(results, table);
    433     }
    434 
    435     void persistActiveStats() {
    436         if (mStatsChanged) {
    437             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
    438             try {
    439                 for (int i = 0; i < mCurrentStats.length; i++) {
    440                     mDatabase.putUsageStats(i, mCurrentStats[i]);
    441                 }
    442                 mStatsChanged = false;
    443             } catch (IOException e) {
    444                 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
    445             }
    446         }
    447     }
    448 
    449     private void rolloverStats(final long currentTimeMillis) {
    450         final long startTime = SystemClock.elapsedRealtime();
    451         Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
    452 
    453         // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
    454         // need a new CONTINUE_PREVIOUS_DAY entry.
    455         final Configuration previousConfig =
    456                 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
    457         ArraySet<String> continuePreviousDay = new ArraySet<>();
    458         for (IntervalStats stat : mCurrentStats) {
    459             final int pkgCount = stat.packageStats.size();
    460             for (int i = 0; i < pkgCount; i++) {
    461                 UsageStats pkgStats = stat.packageStats.valueAt(i);
    462                 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
    463                         pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
    464                     continuePreviousDay.add(pkgStats.mPackageName);
    465                     stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
    466                             UsageEvents.Event.END_OF_DAY);
    467                     notifyStatsChanged();
    468                 }
    469             }
    470 
    471             stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
    472             stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1);
    473         }
    474 
    475         persistActiveStats();
    476         mDatabase.prune(currentTimeMillis);
    477         loadActiveStats(currentTimeMillis);
    478 
    479         final int continueCount = continuePreviousDay.size();
    480         for (int i = 0; i < continueCount; i++) {
    481             String name = continuePreviousDay.valueAt(i);
    482             final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
    483             for (IntervalStats stat : mCurrentStats) {
    484                 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
    485                 stat.updateConfigurationStats(previousConfig, beginTime);
    486                 notifyStatsChanged();
    487             }
    488         }
    489         persistActiveStats();
    490 
    491         final long totalTime = SystemClock.elapsedRealtime() - startTime;
    492         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
    493                 + " milliseconds");
    494     }
    495 
    496     private void notifyStatsChanged() {
    497         if (!mStatsChanged) {
    498             mStatsChanged = true;
    499             mListener.onStatsUpdated();
    500         }
    501     }
    502 
    503     private void notifyNewUpdate() {
    504         mListener.onNewUpdate(mUserId);
    505     }
    506 
    507     private void loadActiveStats(final long currentTimeMillis) {
    508         for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
    509             final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
    510             if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
    511                     currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
    512                 if (DEBUG) {
    513                     Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
    514                             sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
    515                             ") for interval " + intervalType);
    516                 }
    517                 mCurrentStats[intervalType] = stats;
    518             } else {
    519                 // No good fit remains.
    520                 if (DEBUG) {
    521                     Slog.d(TAG, "Creating new stats @ " +
    522                             sDateFormat.format(currentTimeMillis) + "(" +
    523                             currentTimeMillis + ") for interval " + intervalType);
    524                 }
    525 
    526                 mCurrentStats[intervalType] = new IntervalStats();
    527                 mCurrentStats[intervalType].beginTime = currentTimeMillis;
    528                 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
    529             }
    530         }
    531 
    532         mStatsChanged = false;
    533         updateRolloverDeadline();
    534 
    535         // Tell the listener that the stats reloaded, which may have changed idle states.
    536         mListener.onStatsReloaded();
    537     }
    538 
    539     private void updateRolloverDeadline() {
    540         mDailyExpiryDate.setTimeInMillis(
    541                 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
    542         mDailyExpiryDate.addDays(1);
    543         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
    544                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
    545                 mDailyExpiryDate.getTimeInMillis() + ")");
    546     }
    547 
    548     //
    549     // -- DUMP related methods --
    550     //
    551 
    552     void checkin(final IndentingPrintWriter pw) {
    553         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
    554             @Override
    555             public boolean checkin(IntervalStats stats) {
    556                 printIntervalStats(pw, stats, false, false, null);
    557                 return true;
    558             }
    559         });
    560     }
    561 
    562     void dump(IndentingPrintWriter pw, String pkg) {
    563         dump(pw, pkg, false);
    564     }
    565     void dump(IndentingPrintWriter pw, String pkg, boolean compact) {
    566         printLast24HrEvents(pw, !compact, pkg);
    567         for (int interval = 0; interval < mCurrentStats.length; interval++) {
    568             pw.print("In-memory ");
    569             pw.print(intervalToString(interval));
    570             pw.println(" stats");
    571             printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkg);
    572         }
    573     }
    574 
    575     private String formatDateTime(long dateTime, boolean pretty) {
    576         if (pretty) {
    577             return "\"" + sDateFormat.format(dateTime)+ "\"";
    578         }
    579         return Long.toString(dateTime);
    580     }
    581 
    582     private String formatElapsedTime(long elapsedTime, boolean pretty) {
    583         if (pretty) {
    584             return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
    585         }
    586         return Long.toString(elapsedTime);
    587     }
    588 
    589 
    590     void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) {
    591         pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
    592         pw.printPair("type", eventToString(event.mEventType));
    593         pw.printPair("package", event.mPackage);
    594         if (event.mClass != null) {
    595             pw.printPair("class", event.mClass);
    596         }
    597         if (event.mConfiguration != null) {
    598             pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
    599         }
    600         if (event.mShortcutId != null) {
    601             pw.printPair("shortcutId", event.mShortcutId);
    602         }
    603         if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
    604             pw.printPair("standbyBucket", event.getStandbyBucket());
    605             pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
    606         }
    607         pw.printHexPair("flags", event.mFlags);
    608         pw.println();
    609     }
    610 
    611     void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final String pkg) {
    612         final long endTime = System.currentTimeMillis();
    613         UnixCalendar yesterday = new UnixCalendar(endTime);
    614         yesterday.addDays(-1);
    615 
    616         final long beginTime = yesterday.getTimeInMillis();
    617 
    618         List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY,
    619                 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
    620                     @Override
    621                     public void combine(IntervalStats stats, boolean mutable,
    622                             List<UsageEvents.Event> accumulatedResult) {
    623                         if (stats.events == null) {
    624                             return;
    625                         }
    626 
    627                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
    628                         final int size = stats.events.size();
    629                         for (int i = startIndex; i < size; i++) {
    630                             if (stats.events.get(i).mTimeStamp >= endTime) {
    631                                 return;
    632                             }
    633 
    634                             UsageEvents.Event event = stats.events.get(i);
    635                             if (pkg != null && !pkg.equals(event.mPackage)) {
    636                                 continue;
    637                             }
    638                             accumulatedResult.add(event);
    639                         }
    640                     }
    641                 });
    642 
    643         pw.print("Last 24 hour events (");
    644         if (prettyDates) {
    645             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
    646                     beginTime, endTime, sDateFormatFlags) + "\"");
    647         } else {
    648             pw.printPair("beginTime", beginTime);
    649             pw.printPair("endTime", endTime);
    650         }
    651         pw.println(")");
    652         if (events != null) {
    653             pw.increaseIndent();
    654             for (UsageEvents.Event event : events) {
    655                 printEvent(pw, event, prettyDates);
    656             }
    657             pw.decreaseIndent();
    658         }
    659     }
    660 
    661     void printEventAggregation(IndentingPrintWriter pw, String label,
    662             IntervalStats.EventTracker tracker, boolean prettyDates) {
    663         if (tracker.count != 0 || tracker.duration != 0) {
    664             pw.print(label);
    665             pw.print(": ");
    666             pw.print(tracker.count);
    667             pw.print("x for ");
    668             pw.print(formatElapsedTime(tracker.duration, prettyDates));
    669             if (tracker.curStartTime != 0) {
    670                 pw.print(" (now running, started at ");
    671                 formatDateTime(tracker.curStartTime, prettyDates);
    672                 pw.print(")");
    673             }
    674             pw.println();
    675         }
    676     }
    677 
    678     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
    679             boolean prettyDates, boolean skipEvents, String pkg) {
    680         if (prettyDates) {
    681             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
    682                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
    683         } else {
    684             pw.printPair("beginTime", stats.beginTime);
    685             pw.printPair("endTime", stats.endTime);
    686         }
    687         pw.println();
    688         pw.increaseIndent();
    689         pw.println("packages");
    690         pw.increaseIndent();
    691         final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
    692         final int pkgCount = pkgStats.size();
    693         for (int i = 0; i < pkgCount; i++) {
    694             final UsageStats usageStats = pkgStats.valueAt(i);
    695             if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
    696                 continue;
    697             }
    698             pw.printPair("package", usageStats.mPackageName);
    699             pw.printPair("totalTime",
    700                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
    701             pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
    702             pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
    703             pw.println();
    704         }
    705         pw.decreaseIndent();
    706 
    707         pw.println();
    708         pw.println("ChooserCounts");
    709         pw.increaseIndent();
    710         for (UsageStats usageStats : pkgStats.values()) {
    711             if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
    712                 continue;
    713             }
    714             pw.printPair("package", usageStats.mPackageName);
    715             if (usageStats.mChooserCounts != null) {
    716                 final int chooserCountSize = usageStats.mChooserCounts.size();
    717                 for (int i = 0; i < chooserCountSize; i++) {
    718                     final String action = usageStats.mChooserCounts.keyAt(i);
    719                     final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
    720                     final int annotationSize = counts.size();
    721                     for (int j = 0; j < annotationSize; j++) {
    722                         final String key = counts.keyAt(j);
    723                         final int count = counts.valueAt(j);
    724                         if (count != 0) {
    725                             pw.printPair("ChooserCounts", action + ":" + key + " is " +
    726                                     Integer.toString(count));
    727                             pw.println();
    728                         }
    729                     }
    730                 }
    731             }
    732             pw.println();
    733         }
    734         pw.decreaseIndent();
    735 
    736         if (pkg == null) {
    737             pw.println("configurations");
    738             pw.increaseIndent();
    739             final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
    740             final int configCount = configStats.size();
    741             for (int i = 0; i < configCount; i++) {
    742                 final ConfigurationStats config = configStats.valueAt(i);
    743                 pw.printPair("config", Configuration.resourceQualifierString(
    744                         config.mConfiguration));
    745                 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
    746                 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
    747                 pw.printPair("count", config.mActivationCount);
    748                 pw.println();
    749             }
    750             pw.decreaseIndent();
    751             pw.println("event aggregations");
    752             pw.increaseIndent();
    753             printEventAggregation(pw, "screen-interactive", stats.interactiveTracker,
    754                     prettyDates);
    755             printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker,
    756                     prettyDates);
    757             printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker,
    758                     prettyDates);
    759             printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker,
    760                     prettyDates);
    761             pw.decreaseIndent();
    762         }
    763 
    764         // The last 24 hours of events is already printed in the non checkin dump
    765         // No need to repeat here.
    766         if (!skipEvents) {
    767             pw.println("events");
    768             pw.increaseIndent();
    769             final EventList events = stats.events;
    770             final int eventCount = events != null ? events.size() : 0;
    771             for (int i = 0; i < eventCount; i++) {
    772                 final UsageEvents.Event event = events.get(i);
    773                 if (pkg != null && !pkg.equals(event.mPackage)) {
    774                     continue;
    775                 }
    776                 printEvent(pw, event, prettyDates);
    777             }
    778             pw.decreaseIndent();
    779         }
    780         pw.decreaseIndent();
    781     }
    782 
    783     private static String intervalToString(int interval) {
    784         switch (interval) {
    785             case UsageStatsManager.INTERVAL_DAILY:
    786                 return "daily";
    787             case UsageStatsManager.INTERVAL_WEEKLY:
    788                 return "weekly";
    789             case UsageStatsManager.INTERVAL_MONTHLY:
    790                 return "monthly";
    791             case UsageStatsManager.INTERVAL_YEARLY:
    792                 return "yearly";
    793             default:
    794                 return "?";
    795         }
    796     }
    797 
    798     private static String eventToString(int eventType) {
    799         switch (eventType) {
    800             case UsageEvents.Event.NONE:
    801                 return "NONE";
    802             case UsageEvents.Event.MOVE_TO_BACKGROUND:
    803                 return "MOVE_TO_BACKGROUND";
    804             case UsageEvents.Event.MOVE_TO_FOREGROUND:
    805                 return "MOVE_TO_FOREGROUND";
    806             case UsageEvents.Event.END_OF_DAY:
    807                 return "END_OF_DAY";
    808             case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
    809                 return "CONTINUE_PREVIOUS_DAY";
    810             case UsageEvents.Event.CONFIGURATION_CHANGE:
    811                 return "CONFIGURATION_CHANGE";
    812             case UsageEvents.Event.SYSTEM_INTERACTION:
    813                 return "SYSTEM_INTERACTION";
    814             case UsageEvents.Event.USER_INTERACTION:
    815                 return "USER_INTERACTION";
    816             case UsageEvents.Event.SHORTCUT_INVOCATION:
    817                 return "SHORTCUT_INVOCATION";
    818             case UsageEvents.Event.CHOOSER_ACTION:
    819                 return "CHOOSER_ACTION";
    820             case UsageEvents.Event.NOTIFICATION_SEEN:
    821                 return "NOTIFICATION_SEEN";
    822             case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
    823                 return "STANDBY_BUCKET_CHANGED";
    824             case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
    825                 return "NOTIFICATION_INTERRUPTION";
    826             case UsageEvents.Event.SLICE_PINNED:
    827                 return "SLICE_PINNED";
    828             case UsageEvents.Event.SLICE_PINNED_PRIV:
    829                 return "SLICE_PINNED_PRIV";
    830             case UsageEvents.Event.SCREEN_INTERACTIVE:
    831                 return "SCREEN_INTERACTIVE";
    832             case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
    833                 return "SCREEN_NON_INTERACTIVE";
    834             default:
    835                 return "UNKNOWN";
    836         }
    837     }
    838 
    839     byte[] getBackupPayload(String key){
    840         return mDatabase.getBackupPayload(key);
    841     }
    842 
    843     void applyRestoredPayload(String key, byte[] payload){
    844         mDatabase.applyRestoredPayload(key, payload);
    845     }
    846 }
    847