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 }