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