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.TimeSparseArray;
     20 import android.app.usage.UsageStatsManager;
     21 import android.os.Build;
     22 import android.util.AtomicFile;
     23 import android.util.Slog;
     24 import android.util.TimeUtils;
     25 
     26 import java.io.BufferedReader;
     27 import java.io.BufferedWriter;
     28 import java.io.ByteArrayInputStream;
     29 import java.io.ByteArrayOutputStream;
     30 import java.io.DataInputStream;
     31 import java.io.DataOutputStream;
     32 import java.io.File;
     33 import java.io.FileReader;
     34 import java.io.FileWriter;
     35 import java.io.FilenameFilter;
     36 import java.io.IOException;
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 
     40 /**
     41  * Provides an interface to query for UsageStat data from an XML database.
     42  */
     43 class UsageStatsDatabase {
     44     private static final int CURRENT_VERSION = 3;
     45 
     46     // Current version of the backup schema
     47     static final int BACKUP_VERSION = 1;
     48 
     49     // Key under which the payload blob is stored
     50     // same as UsageStatsBackupHelper.KEY_USAGE_STATS
     51     static final String KEY_USAGE_STATS = "usage_stats";
     52 
     53 
     54     private static final String TAG = "UsageStatsDatabase";
     55     private static final boolean DEBUG = UsageStatsService.DEBUG;
     56     private static final String BAK_SUFFIX = ".bak";
     57     private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
     58 
     59     private final Object mLock = new Object();
     60     private final File[] mIntervalDirs;
     61     private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
     62     private final UnixCalendar mCal;
     63     private final File mVersionFile;
     64     private boolean mFirstUpdate;
     65     private boolean mNewUpdate;
     66 
     67     public UsageStatsDatabase(File dir) {
     68         mIntervalDirs = new File[] {
     69                 new File(dir, "daily"),
     70                 new File(dir, "weekly"),
     71                 new File(dir, "monthly"),
     72                 new File(dir, "yearly"),
     73         };
     74         mVersionFile = new File(dir, "version");
     75         mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
     76         mCal = new UnixCalendar(0);
     77     }
     78 
     79     /**
     80      * Initialize any directories required and index what stats are available.
     81      */
     82     public void init(long currentTimeMillis) {
     83         synchronized (mLock) {
     84             for (File f : mIntervalDirs) {
     85                 f.mkdirs();
     86                 if (!f.exists()) {
     87                     throw new IllegalStateException("Failed to create directory "
     88                             + f.getAbsolutePath());
     89                 }
     90             }
     91 
     92             checkVersionAndBuildLocked();
     93             indexFilesLocked();
     94 
     95             // Delete files that are in the future.
     96             for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) {
     97                 final int startIndex = files.closestIndexOnOrAfter(currentTimeMillis);
     98                 if (startIndex < 0) {
     99                     continue;
    100                 }
    101 
    102                 final int fileCount = files.size();
    103                 for (int i = startIndex; i < fileCount; i++) {
    104                     files.valueAt(i).delete();
    105                 }
    106 
    107                 // Remove in a separate loop because any accesses (valueAt)
    108                 // will cause a gc in the SparseArray and mess up the order.
    109                 for (int i = startIndex; i < fileCount; i++) {
    110                     files.removeAt(i);
    111                 }
    112             }
    113         }
    114     }
    115 
    116     public interface CheckinAction {
    117         boolean checkin(IntervalStats stats);
    118     }
    119 
    120     /**
    121      * Calls {@link CheckinAction#checkin(IntervalStats)} on the given {@link CheckinAction}
    122      * for all {@link IntervalStats} that haven't been checked-in.
    123      * If any of the calls to {@link CheckinAction#checkin(IntervalStats)} returns false or throws
    124      * an exception, the check-in will be aborted.
    125      *
    126      * @param checkinAction The callback to run when checking-in {@link IntervalStats}.
    127      * @return true if the check-in succeeded.
    128      */
    129     public boolean checkinDailyFiles(CheckinAction checkinAction) {
    130         synchronized (mLock) {
    131             final TimeSparseArray<AtomicFile> files =
    132                     mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY];
    133             final int fileCount = files.size();
    134 
    135             // We may have holes in the checkin (if there was an error)
    136             // so find the last checked-in file and go from there.
    137             int lastCheckin = -1;
    138             for (int i = 0; i < fileCount - 1; i++) {
    139                 if (files.valueAt(i).getBaseFile().getPath().endsWith(CHECKED_IN_SUFFIX)) {
    140                     lastCheckin = i;
    141                 }
    142             }
    143 
    144             final int start = lastCheckin + 1;
    145             if (start == fileCount - 1) {
    146                 return true;
    147             }
    148 
    149             try {
    150                 IntervalStats stats = new IntervalStats();
    151                 for (int i = start; i < fileCount - 1; i++) {
    152                     UsageStatsXml.read(files.valueAt(i), stats);
    153                     if (!checkinAction.checkin(stats)) {
    154                         return false;
    155                     }
    156                 }
    157             } catch (IOException e) {
    158                 Slog.e(TAG, "Failed to check-in", e);
    159                 return false;
    160             }
    161 
    162             // We have successfully checked-in the stats, so rename the files so that they
    163             // are marked as checked-in.
    164             for (int i = start; i < fileCount - 1; i++) {
    165                 final AtomicFile file = files.valueAt(i);
    166                 final File checkedInFile = new File(
    167                         file.getBaseFile().getPath() + CHECKED_IN_SUFFIX);
    168                 if (!file.getBaseFile().renameTo(checkedInFile)) {
    169                     // We must return success, as we've already marked some files as checked-in.
    170                     // It's better to repeat ourselves than to lose data.
    171                     Slog.e(TAG, "Failed to mark file " + file.getBaseFile().getPath()
    172                             + " as checked-in");
    173                     return true;
    174                 }
    175 
    176                 // AtomicFile needs to set a new backup path with the same -c extension, so
    177                 // we replace the old AtomicFile with the updated one.
    178                 files.setValueAt(i, new AtomicFile(checkedInFile));
    179             }
    180         }
    181         return true;
    182     }
    183 
    184     private void indexFilesLocked() {
    185         final FilenameFilter backupFileFilter = new FilenameFilter() {
    186             @Override
    187             public boolean accept(File dir, String name) {
    188                 return !name.endsWith(BAK_SUFFIX);
    189             }
    190         };
    191 
    192         // Index the available usage stat files on disk.
    193         for (int i = 0; i < mSortedStatFiles.length; i++) {
    194             if (mSortedStatFiles[i] == null) {
    195                 mSortedStatFiles[i] = new TimeSparseArray<>();
    196             } else {
    197                 mSortedStatFiles[i].clear();
    198             }
    199             File[] files = mIntervalDirs[i].listFiles(backupFileFilter);
    200             if (files != null) {
    201                 if (DEBUG) {
    202                     Slog.d(TAG, "Found " + files.length + " stat files for interval " + i);
    203                 }
    204 
    205                 for (File f : files) {
    206                     final AtomicFile af = new AtomicFile(f);
    207                     try {
    208                         mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af);
    209                     } catch (IOException e) {
    210                         Slog.e(TAG, "failed to index file: " + f, e);
    211                     }
    212                 }
    213             }
    214         }
    215     }
    216 
    217     /**
    218      * Is this the first update to the system from L to M?
    219      */
    220     boolean isFirstUpdate() {
    221         return mFirstUpdate;
    222     }
    223 
    224     /**
    225      * Is this a system update since we started tracking build fingerprint in the version file?
    226      */
    227     boolean isNewUpdate() {
    228         return mNewUpdate;
    229     }
    230 
    231     private void checkVersionAndBuildLocked() {
    232         int version;
    233         String buildFingerprint;
    234         String currentFingerprint = getBuildFingerprint();
    235         mFirstUpdate = true;
    236         mNewUpdate = true;
    237         try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) {
    238             version = Integer.parseInt(reader.readLine());
    239             buildFingerprint = reader.readLine();
    240             if (buildFingerprint != null) {
    241                 mFirstUpdate = false;
    242             }
    243             if (currentFingerprint.equals(buildFingerprint)) {
    244                 mNewUpdate = false;
    245             }
    246         } catch (NumberFormatException | IOException e) {
    247             version = 0;
    248         }
    249 
    250         if (version != CURRENT_VERSION) {
    251             Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION);
    252             doUpgradeLocked(version);
    253         }
    254 
    255         if (version != CURRENT_VERSION || mNewUpdate) {
    256             try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
    257                 writer.write(Integer.toString(CURRENT_VERSION));
    258                 writer.write("\n");
    259                 writer.write(currentFingerprint);
    260                 writer.write("\n");
    261                 writer.flush();
    262             } catch (IOException e) {
    263                 Slog.e(TAG, "Failed to write new version");
    264                 throw new RuntimeException(e);
    265             }
    266         }
    267     }
    268 
    269     private String getBuildFingerprint() {
    270         return Build.VERSION.RELEASE + ";"
    271                 + Build.VERSION.CODENAME + ";"
    272                 + Build.VERSION.INCREMENTAL;
    273     }
    274 
    275     private void doUpgradeLocked(int thisVersion) {
    276         if (thisVersion < 2) {
    277             // Delete all files if we are version 0. This is a pre-release version,
    278             // so this is fine.
    279             Slog.i(TAG, "Deleting all usage stats files");
    280             for (int i = 0; i < mIntervalDirs.length; i++) {
    281                 File[] files = mIntervalDirs[i].listFiles();
    282                 if (files != null) {
    283                     for (File f : files) {
    284                         f.delete();
    285                     }
    286                 }
    287             }
    288         }
    289     }
    290 
    291     public void onTimeChanged(long timeDiffMillis) {
    292         synchronized (mLock) {
    293             StringBuilder logBuilder = new StringBuilder();
    294             logBuilder.append("Time changed by ");
    295             TimeUtils.formatDuration(timeDiffMillis, logBuilder);
    296             logBuilder.append(".");
    297 
    298             int filesDeleted = 0;
    299             int filesMoved = 0;
    300 
    301             for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) {
    302                 final int fileCount = files.size();
    303                 for (int i = 0; i < fileCount; i++) {
    304                     final AtomicFile file = files.valueAt(i);
    305                     final long newTime = files.keyAt(i) + timeDiffMillis;
    306                     if (newTime < 0) {
    307                         filesDeleted++;
    308                         file.delete();
    309                     } else {
    310                         try {
    311                             file.openRead().close();
    312                         } catch (IOException e) {
    313                             // Ignore, this is just to make sure there are no backups.
    314                         }
    315 
    316                         String newName = Long.toString(newTime);
    317                         if (file.getBaseFile().getName().endsWith(CHECKED_IN_SUFFIX)) {
    318                             newName = newName + CHECKED_IN_SUFFIX;
    319                         }
    320 
    321                         final File newFile = new File(file.getBaseFile().getParentFile(), newName);
    322                         filesMoved++;
    323                         file.getBaseFile().renameTo(newFile);
    324                     }
    325                 }
    326                 files.clear();
    327             }
    328 
    329             logBuilder.append(" files deleted: ").append(filesDeleted);
    330             logBuilder.append(" files moved: ").append(filesMoved);
    331             Slog.i(TAG, logBuilder.toString());
    332 
    333             // Now re-index the new files.
    334             indexFilesLocked();
    335         }
    336     }
    337 
    338     /**
    339      * Get the latest stats that exist for this interval type.
    340      */
    341     public IntervalStats getLatestUsageStats(int intervalType) {
    342         synchronized (mLock) {
    343             if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
    344                 throw new IllegalArgumentException("Bad interval type " + intervalType);
    345             }
    346 
    347             final int fileCount = mSortedStatFiles[intervalType].size();
    348             if (fileCount == 0) {
    349                 return null;
    350             }
    351 
    352             try {
    353                 final AtomicFile f = mSortedStatFiles[intervalType].valueAt(fileCount - 1);
    354                 IntervalStats stats = new IntervalStats();
    355                 UsageStatsXml.read(f, stats);
    356                 return stats;
    357             } catch (IOException e) {
    358                 Slog.e(TAG, "Failed to read usage stats file", e);
    359             }
    360         }
    361         return null;
    362     }
    363 
    364     /**
    365      * Figures out what to extract from the given IntervalStats object.
    366      */
    367     interface StatCombiner<T> {
    368 
    369         /**
    370          * Implementations should extract interesting from <code>stats</code> and add it
    371          * to the <code>accumulatedResult</code> list.
    372          *
    373          * If the <code>stats</code> object is mutable, <code>mutable</code> will be true,
    374          * which means you should make a copy of the data before adding it to the
    375          * <code>accumulatedResult</code> list.
    376          *
    377          * @param stats The {@link IntervalStats} object selected.
    378          * @param mutable Whether or not the data inside the stats object is mutable.
    379          * @param accumulatedResult The list to which to add extracted data.
    380          */
    381         void combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult);
    382     }
    383 
    384     /**
    385      * Find all {@link IntervalStats} for the given range and interval type.
    386      */
    387     public <T> List<T> queryUsageStats(int intervalType, long beginTime, long endTime,
    388             StatCombiner<T> combiner) {
    389         synchronized (mLock) {
    390             if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
    391                 throw new IllegalArgumentException("Bad interval type " + intervalType);
    392             }
    393 
    394             final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType];
    395 
    396             if (endTime <= beginTime) {
    397                 if (DEBUG) {
    398                     Slog.d(TAG, "endTime(" + endTime + ") <= beginTime(" + beginTime + ")");
    399                 }
    400                 return null;
    401             }
    402 
    403             int startIndex = intervalStats.closestIndexOnOrBefore(beginTime);
    404             if (startIndex < 0) {
    405                 // All the stats available have timestamps after beginTime, which means they all
    406                 // match.
    407                 startIndex = 0;
    408             }
    409 
    410             int endIndex = intervalStats.closestIndexOnOrBefore(endTime);
    411             if (endIndex < 0) {
    412                 // All the stats start after this range ends, so nothing matches.
    413                 if (DEBUG) {
    414                     Slog.d(TAG, "No results for this range. All stats start after.");
    415                 }
    416                 return null;
    417             }
    418 
    419             if (intervalStats.keyAt(endIndex) == endTime) {
    420                 // The endTime is exclusive, so if we matched exactly take the one before.
    421                 endIndex--;
    422                 if (endIndex < 0) {
    423                     // All the stats start after this range ends, so nothing matches.
    424                     if (DEBUG) {
    425                         Slog.d(TAG, "No results for this range. All stats start after.");
    426                     }
    427                     return null;
    428                 }
    429             }
    430 
    431             final IntervalStats stats = new IntervalStats();
    432             final ArrayList<T> results = new ArrayList<>();
    433             for (int i = startIndex; i <= endIndex; i++) {
    434                 final AtomicFile f = intervalStats.valueAt(i);
    435 
    436                 if (DEBUG) {
    437                     Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
    438                 }
    439 
    440                 try {
    441                     UsageStatsXml.read(f, stats);
    442                     if (beginTime < stats.endTime) {
    443                         combiner.combine(stats, false, results);
    444                     }
    445                 } catch (IOException e) {
    446                     Slog.e(TAG, "Failed to read usage stats file", e);
    447                     // We continue so that we return results that are not
    448                     // corrupt.
    449                 }
    450             }
    451             return results;
    452         }
    453     }
    454 
    455     /**
    456      * Find the interval that best matches this range.
    457      *
    458      * TODO(adamlesinski): Use endTimeStamp in best fit calculation.
    459      */
    460     public int findBestFitBucket(long beginTimeStamp, long endTimeStamp) {
    461         synchronized (mLock) {
    462             int bestBucket = -1;
    463             long smallestDiff = Long.MAX_VALUE;
    464             for (int i = mSortedStatFiles.length - 1; i >= 0; i--) {
    465                 final int index = mSortedStatFiles[i].closestIndexOnOrBefore(beginTimeStamp);
    466                 int size = mSortedStatFiles[i].size();
    467                 if (index >= 0 && index < size) {
    468                     // We have some results here, check if they are better than our current match.
    469                     long diff = Math.abs(mSortedStatFiles[i].keyAt(index) - beginTimeStamp);
    470                     if (diff < smallestDiff) {
    471                         smallestDiff = diff;
    472                         bestBucket = i;
    473                     }
    474                 }
    475             }
    476             return bestBucket;
    477         }
    478     }
    479 
    480     /**
    481      * Remove any usage stat files that are too old.
    482      */
    483     public void prune(final long currentTimeMillis) {
    484         synchronized (mLock) {
    485             mCal.setTimeInMillis(currentTimeMillis);
    486             mCal.addYears(-3);
    487             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY],
    488                     mCal.getTimeInMillis());
    489 
    490             mCal.setTimeInMillis(currentTimeMillis);
    491             mCal.addMonths(-6);
    492             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY],
    493                     mCal.getTimeInMillis());
    494 
    495             mCal.setTimeInMillis(currentTimeMillis);
    496             mCal.addWeeks(-4);
    497             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY],
    498                     mCal.getTimeInMillis());
    499 
    500             mCal.setTimeInMillis(currentTimeMillis);
    501             mCal.addDays(-7);
    502             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
    503                     mCal.getTimeInMillis());
    504 
    505             // We must re-index our file list or we will be trying to read
    506             // deleted files.
    507             indexFilesLocked();
    508         }
    509     }
    510 
    511     private static void pruneFilesOlderThan(File dir, long expiryTime) {
    512         File[] files = dir.listFiles();
    513         if (files != null) {
    514             for (File f : files) {
    515                 String path = f.getPath();
    516                 if (path.endsWith(BAK_SUFFIX)) {
    517                     f = new File(path.substring(0, path.length() - BAK_SUFFIX.length()));
    518                 }
    519 
    520                 long beginTime;
    521                 try {
    522                     beginTime = UsageStatsXml.parseBeginTime(f);
    523                 } catch (IOException e) {
    524                     beginTime = 0;
    525                 }
    526 
    527                 if (beginTime < expiryTime) {
    528                     new AtomicFile(f).delete();
    529                 }
    530             }
    531         }
    532     }
    533 
    534     /**
    535      * Update the stats in the database. They may not be written to disk immediately.
    536      */
    537     public void putUsageStats(int intervalType, IntervalStats stats) throws IOException {
    538         if (stats == null) return;
    539         synchronized (mLock) {
    540             if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
    541                 throw new IllegalArgumentException("Bad interval type " + intervalType);
    542             }
    543 
    544             AtomicFile f = mSortedStatFiles[intervalType].get(stats.beginTime);
    545             if (f == null) {
    546                 f = new AtomicFile(new File(mIntervalDirs[intervalType],
    547                         Long.toString(stats.beginTime)));
    548                 mSortedStatFiles[intervalType].put(stats.beginTime, f);
    549             }
    550 
    551             UsageStatsXml.write(f, stats);
    552             stats.lastTimeSaved = f.getLastModifiedTime();
    553         }
    554     }
    555 
    556 
    557     /* Backup/Restore Code */
    558     byte[] getBackupPayload(String key) {
    559         synchronized (mLock) {
    560             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    561             if (KEY_USAGE_STATS.equals(key)) {
    562                 prune(System.currentTimeMillis());
    563                 DataOutputStream out = new DataOutputStream(baos);
    564                 try {
    565                     out.writeInt(BACKUP_VERSION);
    566 
    567                     out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size());
    568                     for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size();
    569                             i++) {
    570                         writeIntervalStatsToStream(out,
    571                                 mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].valueAt(i));
    572                     }
    573 
    574                     out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size());
    575                     for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size();
    576                             i++) {
    577                         writeIntervalStatsToStream(out,
    578                                 mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].valueAt(i));
    579                     }
    580 
    581                     out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size());
    582                     for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size();
    583                             i++) {
    584                         writeIntervalStatsToStream(out,
    585                                 mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].valueAt(i));
    586                     }
    587 
    588                     out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size());
    589                     for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size();
    590                             i++) {
    591                         writeIntervalStatsToStream(out,
    592                                 mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].valueAt(i));
    593                     }
    594                     if (DEBUG) Slog.i(TAG, "Written " + baos.size() + " bytes of data");
    595                 } catch (IOException ioe) {
    596                     Slog.d(TAG, "Failed to write data to output stream", ioe);
    597                     baos.reset();
    598                 }
    599             }
    600             return baos.toByteArray();
    601         }
    602 
    603     }
    604 
    605     void applyRestoredPayload(String key, byte[] payload) {
    606         synchronized (mLock) {
    607             if (KEY_USAGE_STATS.equals(key)) {
    608                 // Read stats files for the current device configs
    609                 IntervalStats dailyConfigSource =
    610                         getLatestUsageStats(UsageStatsManager.INTERVAL_DAILY);
    611                 IntervalStats weeklyConfigSource =
    612                         getLatestUsageStats(UsageStatsManager.INTERVAL_WEEKLY);
    613                 IntervalStats monthlyConfigSource =
    614                         getLatestUsageStats(UsageStatsManager.INTERVAL_MONTHLY);
    615                 IntervalStats yearlyConfigSource =
    616                         getLatestUsageStats(UsageStatsManager.INTERVAL_YEARLY);
    617 
    618                 try {
    619                     DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
    620                     int backupDataVersion = in.readInt();
    621 
    622                     // Can't handle this backup set
    623                     if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) return;
    624 
    625                     // Delete all stats files
    626                     // Do this after reading version and before actually restoring
    627                     for (int i = 0; i < mIntervalDirs.length; i++) {
    628                         deleteDirectoryContents(mIntervalDirs[i]);
    629                     }
    630 
    631                     int fileCount = in.readInt();
    632                     for (int i = 0; i < fileCount; i++) {
    633                         IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
    634                         stats = mergeStats(stats, dailyConfigSource);
    635                         putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats);
    636                     }
    637 
    638                     fileCount = in.readInt();
    639                     for (int i = 0; i < fileCount; i++) {
    640                         IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
    641                         stats = mergeStats(stats, weeklyConfigSource);
    642                         putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats);
    643                     }
    644 
    645                     fileCount = in.readInt();
    646                     for (int i = 0; i < fileCount; i++) {
    647                         IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
    648                         stats = mergeStats(stats, monthlyConfigSource);
    649                         putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats);
    650                     }
    651 
    652                     fileCount = in.readInt();
    653                     for (int i = 0; i < fileCount; i++) {
    654                         IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
    655                         stats = mergeStats(stats, yearlyConfigSource);
    656                         putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats);
    657                     }
    658                     if (DEBUG) Slog.i(TAG, "Completed Restoring UsageStats");
    659                 } catch (IOException ioe) {
    660                     Slog.d(TAG, "Failed to read data from input stream", ioe);
    661                 } finally {
    662                     indexFilesLocked();
    663                 }
    664             }
    665         }
    666     }
    667 
    668     /**
    669      * Get the Configuration Statistics from the current device statistics and merge them
    670      * with the backed up usage statistics.
    671      */
    672     private IntervalStats mergeStats(IntervalStats beingRestored, IntervalStats onDevice) {
    673         if (onDevice == null) return beingRestored;
    674         if (beingRestored == null) return null;
    675         beingRestored.activeConfiguration = onDevice.activeConfiguration;
    676         beingRestored.configurations.putAll(onDevice.configurations);
    677         beingRestored.events = onDevice.events;
    678         return beingRestored;
    679     }
    680 
    681     private void writeIntervalStatsToStream(DataOutputStream out, AtomicFile statsFile)
    682             throws IOException {
    683         IntervalStats stats = new IntervalStats();
    684         try {
    685             UsageStatsXml.read(statsFile, stats);
    686         } catch (IOException e) {
    687             Slog.e(TAG, "Failed to read usage stats file", e);
    688             out.writeInt(0);
    689             return;
    690         }
    691         sanitizeIntervalStatsForBackup(stats);
    692         byte[] data = serializeIntervalStats(stats);
    693         out.writeInt(data.length);
    694         out.write(data);
    695     }
    696 
    697     private static byte[] getIntervalStatsBytes(DataInputStream in) throws IOException {
    698         int length = in.readInt();
    699         byte[] buffer = new byte[length];
    700         in.read(buffer, 0, length);
    701         return buffer;
    702     }
    703 
    704     private static void sanitizeIntervalStatsForBackup(IntervalStats stats) {
    705         if (stats == null) return;
    706         stats.activeConfiguration = null;
    707         stats.configurations.clear();
    708         if (stats.events != null) stats.events.clear();
    709     }
    710 
    711     private static byte[] serializeIntervalStats(IntervalStats stats) {
    712         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    713         DataOutputStream out = new DataOutputStream(baos);
    714         try {
    715             out.writeLong(stats.beginTime);
    716             UsageStatsXml.write(out, stats);
    717         } catch (IOException ioe) {
    718             Slog.d(TAG, "Serializing IntervalStats Failed", ioe);
    719             baos.reset();
    720         }
    721         return baos.toByteArray();
    722     }
    723 
    724     private static IntervalStats deserializeIntervalStats(byte[] data) {
    725         ByteArrayInputStream bais = new ByteArrayInputStream(data);
    726         DataInputStream in = new DataInputStream(bais);
    727         IntervalStats stats = new IntervalStats();
    728         try {
    729             stats.beginTime = in.readLong();
    730             UsageStatsXml.read(in, stats);
    731         } catch (IOException ioe) {
    732             Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe);
    733             stats = null;
    734         }
    735         return stats;
    736     }
    737 
    738     private static void deleteDirectoryContents(File directory) {
    739         File[] files = directory.listFiles();
    740         for (File file : files) {
    741             deleteDirectory(file);
    742         }
    743     }
    744 
    745     private static void deleteDirectory(File directory) {
    746         File[] files = directory.listFiles();
    747         if (files != null) {
    748             for (File file : files) {
    749                 if (!file.isDirectory()) {
    750                     file.delete();
    751                 } else {
    752                     deleteDirectory(file);
    753                 }
    754             }
    755         }
    756         directory.delete();
    757     }
    758 }