• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
UsageStatsDatabase(File dir)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      */
init(long currentTimeMillis)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 {
checkin(IntervalStats stats)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      */
checkinDailyFiles(CheckinAction checkinAction)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 
indexFilesLocked()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      */
isFirstUpdate()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      */
isNewUpdate()227     boolean isNewUpdate() {
228         return mNewUpdate;
229     }
230 
checkVersionAndBuildLocked()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 
getBuildFingerprint()269     private String getBuildFingerprint() {
270         return Build.VERSION.RELEASE + ";"
271                 + Build.VERSION.CODENAME + ";"
272                 + Build.VERSION.INCREMENTAL;
273     }
274 
doUpgradeLocked(int thisVersion)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 
onTimeChanged(long timeDiffMillis)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      */
getLatestUsageStats(int intervalType)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          */
combine(IntervalStats stats, boolean mutable, List<T> accumulatedResult)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      */
queryUsageStats(int intervalType, long beginTime, long endTime, StatCombiner<T> combiner)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      */
findBestFitBucket(long beginTimeStamp, long endTimeStamp)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      */
prune(final long currentTimeMillis)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 
pruneFilesOlderThan(File dir, long expiryTime)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      */
putUsageStats(int intervalType, IntervalStats stats)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 */
getBackupPayload(String key)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 
applyRestoredPayload(String key, byte[] payload)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      */
mergeStats(IntervalStats beingRestored, IntervalStats onDevice)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 
writeIntervalStatsToStream(DataOutputStream out, AtomicFile statsFile)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 
getIntervalStatsBytes(DataInputStream in)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 
sanitizeIntervalStatsForBackup(IntervalStats stats)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 
serializeIntervalStats(IntervalStats stats)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 
deserializeIntervalStats(byte[] data)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 
deleteDirectoryContents(File directory)738     private static void deleteDirectoryContents(File directory) {
739         File[] files = directory.listFiles();
740         for (File file : files) {
741             deleteDirectory(file);
742         }
743     }
744 
deleteDirectory(File directory)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 }