Merge changes from topic "UsageStatsDatabaseUpgradeAttempt2"
* changes: Add ProtoInputStream and UsageStatsProto to art profiles Upgrade UsageStatsDatabase to version 4 (attempt 2) Move UsageStatsDatabase upgrade backup to seperate folder
This commit is contained in:
committed by
Android (Google) Code Review
commit
0e1ce140a2
@@ -6414,6 +6414,38 @@ HPLandroid/util/proto/EncodedBuffer;->startEditing()V
|
||||
HPLandroid/util/proto/EncodedBuffer;->writeFromThisBuffer(II)V
|
||||
HPLandroid/util/proto/EncodedBuffer;->writeRawBuffer([BII)V
|
||||
HPLandroid/util/proto/EncodedBuffer;->writeRawFixed64(J)V
|
||||
HPLandroid/util/proto/ProtoInputStream;-><init>(Ljava/io/InputStream;I)V
|
||||
HPLandroid/util/proto/ProtoInputStream;-><init>(Ljava/io/InputStream;)V
|
||||
HPLandroid/util/proto/ProtoInputStream;-><init>([B)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->getFieldNumber()I
|
||||
HPLandroid/util/proto/ProtoInputStream;->getWireType()I
|
||||
HPLandroid/util/proto/ProtoInputStream;->getOffset()I
|
||||
HPLandroid/util/proto/ProtoInputStream;->nextField()I
|
||||
HPLandroid/util/proto/ProtoInputStream;->isNextField(J)Z
|
||||
HPLandroid/util/proto/ProtoInputStream;->readDouble(J)D
|
||||
HPLandroid/util/proto/ProtoInputStream;->readFloat(J)F
|
||||
HPLandroid/util/proto/ProtoInputStream;->readInt(J)I
|
||||
HPLandroid/util/proto/ProtoInputStream;->readLong(J)J
|
||||
HPLandroid/util/proto/ProtoInputStream;->readBoolean(J)Z
|
||||
HPLandroid/util/proto/ProtoInputStream;->readString(J)Ljava/lang/String;
|
||||
HPLandroid/util/proto/ProtoInputStream;->readBytes(J)[B
|
||||
HPLandroid/util/proto/ProtoInputStream;->start(J)J
|
||||
HPLandroid/util/proto/ProtoInputStream;->end(J)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->readTag()V
|
||||
HPLandroid/util/proto/ProtoInputStream;->decodeZigZag32(I)I
|
||||
HPLandroid/util/proto/ProtoInputStream;->decodeZigZag64(J)J
|
||||
HPLandroid/util/proto/ProtoInputStream;->readVarint()J
|
||||
HPLandroid/util/proto/ProtoInputStream;->readFixed32()I
|
||||
HPLandroid/util/proto/ProtoInputStream;->readFixed64()J
|
||||
HPLandroid/util/proto/ProtoInputStream;->readRawBytes(I)[B
|
||||
HPLandroid/util/proto/ProtoInputStream;->readRawString(I)Ljava/lang/String;
|
||||
HPLandroid/util/proto/ProtoInputStream;->fillBuffer()V
|
||||
HPLandroid/util/proto/ProtoInputStream;->skip()V
|
||||
HPLandroid/util/proto/ProtoInputStream;->incOffset(I)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->checkPacked(J)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->assertFieldNumber(J)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->assertWireType(I)V
|
||||
HPLandroid/util/proto/ProtoInputStream;->assertFreshData()V
|
||||
HPLandroid/util/proto/ProtoOutputStream;-><init>(Ljava/io/FileDescriptor;)V
|
||||
HPLandroid/util/proto/ProtoOutputStream;-><init>(Ljava/io/OutputStream;)V
|
||||
HPLandroid/util/proto/ProtoOutputStream;->compactIfNecessary()V
|
||||
@@ -58580,6 +58612,7 @@ Landroid/util/apk/WrappedX509Certificate;
|
||||
Landroid/util/apk/ZipUtils;
|
||||
Landroid/util/jar/StrictJarFile;
|
||||
Landroid/util/proto/EncodedBuffer;
|
||||
Landroid/util/proto/ProtoInputStream;
|
||||
Landroid/util/proto/ProtoOutputStream;
|
||||
Landroid/view/-$$Lambda$FocusFinder$FocusSorter$h0f2ZYL6peSaaEeCCkAoYs_YZvU;
|
||||
Landroid/view/-$$Lambda$FocusFinder$FocusSorter$kW7K1t9q7Y62V38r-7g6xRzqqq8;
|
||||
|
||||
@@ -2082,6 +2082,22 @@ HPLcom/android/server/usage/StorageStatsService;->queryStatsForPackage(Ljava/lan
|
||||
HPLcom/android/server/usage/StorageStatsService;->queryStatsForUid(Ljava/lang/String;ILjava/lang/String;)Landroid/app/usage/StorageStats;
|
||||
HPLcom/android/server/usage/UnixCalendar;->getTimeInMillis()J
|
||||
HPLcom/android/server/usage/UsageStatsDatabase$CheckinAction;->checkin(Lcom/android/server/usage/IntervalStats;)Z
|
||||
HPLcom/android/server/usage/UsageStatsProto;->readStringPool(Landroid/util/proto/ProtoInputStream;)Ljava/util/List;
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadUsageStats(Landroid/util/proto/ProtoInputStream;Lcom/android/server/usage/IntervalStats;Ljava/util/List;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadCountAndTime(Landroid/util/proto/ProtoInputStream;JLcom/android/server/usage/IntervalStats;$EventTracker)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadChooserCounts(Landroid/util/proto/ProtoInputStream;Landroid/app/usage/UsageStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadCountsForAction(Landroid/util/proto/ProtoInputStream;Landroid/util/ArrayMap;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadConfigStats(Landroid/util/proto/ProtoInputStream;JLcom/android/server/usage/IntervalStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->loadEvent(Landroid/util/proto/ProtoInputStream;JLcom/android/server/usage/IntervalStats;Ljava/util/List;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeStringPool(Landroid/util/proto/ProtoOutputStream;Lcom/android/server/usage/IntervalStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeUsageStats(Landroid/util/proto/ProtoOutputStream;JLcom/android/server/usage/IntervalStats;Landroid/app/usage/UsageStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeCountAndTime(Landroid/util/proto/ProtoOutputStream;JIJ)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeChooserCounts(Landroid/util/proto/ProtoOutputStream;Landroid/app/usage/UsageStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeCountsForAction(Landroid/util/proto/ProtoOutputStream;Landroid/util/ArrayMap;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeConfigStats(Landroid/util/proto/ProtoOutputStream;JLcom/android/server/usage/IntervalStats;Landroid/app/usage/ConfigurationStats;Z)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->writeEvent(Landroid/util/proto/ProtoOutputStream;JLcom/android/server/usage/IntervalStats;Landroid/app/usage/UsageEvents$Event;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->read(Ljava/io/InputStream;Lcom/android/server/usage/IntervalStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsProto;->write(Ljava/io/OutputStream;Lcom/android/server/usage/IntervalStats;)V
|
||||
HPLcom/android/server/usage/UsageStatsService$2;->onUidStateChanged(IIJ)V
|
||||
HPLcom/android/server/usage/UsageStatsService$BinderService;->hasPermission(Ljava/lang/String;)Z
|
||||
HPLcom/android/server/usage/UsageStatsService$BinderService;->isAppInactive(Ljava/lang/String;I)Z
|
||||
|
||||
@@ -51,6 +51,10 @@ public class UsageStatsDatabaseTest {
|
||||
private IntervalStats mIntervalStats = new IntervalStats();
|
||||
private long mEndTime = 0;
|
||||
|
||||
// Key under which the payload blob is stored
|
||||
// same as UsageStatsBackupHelper.KEY_USAGE_STATS
|
||||
static final String KEY_USAGE_STATS = "usage_stats";
|
||||
|
||||
private static final UsageStatsDatabase.StatCombiner<IntervalStats> mIntervalStatsVerifier =
|
||||
new UsageStatsDatabase.StatCombiner<IntervalStats>() {
|
||||
@Override
|
||||
@@ -104,10 +108,11 @@ public class UsageStatsDatabaseTest {
|
||||
|
||||
private void populateIntervalStats() {
|
||||
final int numberOfEvents = 3000;
|
||||
long time = 1;
|
||||
final int timeProgression = 23;
|
||||
long time = System.currentTimeMillis() - (numberOfEvents*timeProgression);
|
||||
mIntervalStats = new IntervalStats();
|
||||
|
||||
mIntervalStats.beginTime = 1;
|
||||
mIntervalStats.beginTime = time;
|
||||
mIntervalStats.interactiveTracker.count = 2;
|
||||
mIntervalStats.interactiveTracker.duration = 111111;
|
||||
mIntervalStats.nonInteractiveTracker.count = 3;
|
||||
@@ -158,7 +163,7 @@ public class UsageStatsDatabaseTest {
|
||||
mIntervalStats.events.insert(event);
|
||||
mIntervalStats.update(event.mPackage, event.mTimeStamp, event.mEventType);
|
||||
|
||||
time += 23; // Arbitrary progression of time
|
||||
time += timeProgression; // Arbitrary progression of time
|
||||
}
|
||||
mEndTime = time;
|
||||
|
||||
@@ -286,9 +291,19 @@ public class UsageStatsDatabaseTest {
|
||||
}
|
||||
assertEquals(stats1.activeConfiguration, stats2.activeConfiguration);
|
||||
|
||||
assertEquals(stats1.events.size(), stats2.events.size());
|
||||
for (int i = 0; i < stats1.events.size(); i++) {
|
||||
compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
|
||||
if (stats1.events == null) {
|
||||
// If stats1 events are null, stats2 should be null or empty
|
||||
if (stats2.events != null) {
|
||||
assertEquals(stats2.events.size(), 0);
|
||||
}
|
||||
} else if (stats2.events == null) {
|
||||
// If stats2 events are null, stats1 should be null or empty
|
||||
assertEquals(stats1.events.size(), 0);
|
||||
} else {
|
||||
assertEquals(stats1.events.size(), stats2.events.size());
|
||||
for (int i = 0; i < stats1.events.size(); i++) {
|
||||
compareUsageEvent(stats1.events.get(i), stats2.events.get(i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +354,47 @@ public class UsageStatsDatabaseTest {
|
||||
compareIntervalStats(mIntervalStats, stats.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Backup and Restore tests.
|
||||
* Will write the generated IntervalStat to a database and create a backup in the specified
|
||||
* version's format. The database will then be restored from the blob and the restored
|
||||
* interval stats will be compared to the generated stats.
|
||||
*/
|
||||
void runBackupRestoreTest(int version) throws IOException {
|
||||
UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir);
|
||||
prevDB.init(1);
|
||||
prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats);
|
||||
// Create a backup with a specific version
|
||||
byte[] blob = prevDB.getBackupPayload(KEY_USAGE_STATS, version);
|
||||
|
||||
clearUsageStatsFiles();
|
||||
|
||||
UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir);
|
||||
newDB.init(1);
|
||||
// Attempt to restore the usage stats from the backup
|
||||
newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
|
||||
List<IntervalStats> stats = newDB.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, mEndTime,
|
||||
mIntervalStatsVerifier);
|
||||
|
||||
|
||||
if (version > newDB.BACKUP_VERSION || version < 1) {
|
||||
if (stats != null && stats.size() != 0) {
|
||||
fail("UsageStatsDatabase should ne be able to restore from unknown data versions");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(1, stats.size());
|
||||
|
||||
// Clear non backed up data from expected IntervalStats
|
||||
mIntervalStats.activeConfiguration = null;
|
||||
mIntervalStats.configurations.clear();
|
||||
if (mIntervalStats.events != null) mIntervalStats.events.clear();
|
||||
|
||||
// The written and read IntervalStats should match
|
||||
compareIntervalStats(mIntervalStats, stats.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the version upgrade from 3 to 4
|
||||
*/
|
||||
@@ -349,4 +405,18 @@ public class UsageStatsDatabaseTest {
|
||||
runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_MONTHLY);
|
||||
runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_YEARLY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the version upgrade from 3 to 4
|
||||
*/
|
||||
@Test
|
||||
public void testBackupRestore() throws IOException {
|
||||
runBackupRestoreTest(1);
|
||||
runBackupRestoreTest(4);
|
||||
|
||||
// test invalid backup versions as well
|
||||
runBackupRestoreTest(0);
|
||||
runBackupRestoreTest(99999);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,31 +58,31 @@ import java.util.List;
|
||||
* When the UsageStatsDatabase version is upgraded, the files on disk are migrated to the new
|
||||
* version on init. The steps of migration are as follows:
|
||||
* 1) Check if version upgrade breadcrumb exists on disk, if so skip to step 4.
|
||||
* 2) Copy current files to versioned backup files.
|
||||
* 3) Write a temporary breadcrumb file with some info about the backed up files.
|
||||
* 4) Deserialize a versioned backup file using the info written to the breadcrumb for the
|
||||
* correct deserialization methodology.
|
||||
* 2) Move current files to a timestamped backup directory.
|
||||
* 3) Write a temporary breadcrumb file with some info about the backup directory.
|
||||
* 4) Deserialize the backup files in the timestamped backup folder referenced by the breadcrumb.
|
||||
* 5) Reserialize the data read from the file with the new version format and replace the old files
|
||||
* 6) Repeat Step 3 and 4 for each versioned backup file matching the breadcrumb file.
|
||||
* 6) Repeat Step 3 and 4 for each file in the backup folder.
|
||||
* 7) Update the version file with the new version and build fingerprint.
|
||||
* 8) Delete the versioned backup files (unless flagged to be kept).
|
||||
* 8) Delete the time stamped backup folder (unless flagged to be kept).
|
||||
* 9) Delete the breadcrumb file.
|
||||
*
|
||||
* Performing the upgrade steps in this order, protects against unexpected shutdowns mid upgrade
|
||||
*
|
||||
* A versioned backup file is simply a copy of a Usage Stats file with some extra info embedded in
|
||||
* the file name. The structure of the versioned backup filename is as followed:
|
||||
* (original file name).(backup timestamp).(original file version).vak
|
||||
*
|
||||
* During the version upgrade process, the new upgraded file will have it's name set to the original
|
||||
* file name. The backup timestamp helps distinguish between versioned backups if multiple upgrades
|
||||
* and downgrades have taken place. The original file version denotes how to parse the file.
|
||||
* The backup directory will contain directories with timestamp names. If the upgrade breadcrumb
|
||||
* exists on disk, it will contain a timestamp which will match one of the backup directories. The
|
||||
* breadcrumb will also contain a version number which will denote how the files in the backup
|
||||
* directory should be deserialized.
|
||||
*/
|
||||
public class UsageStatsDatabase {
|
||||
private static final int DEFAULT_CURRENT_VERSION = 3;
|
||||
|
||||
// Current version of the backup schema
|
||||
static final int BACKUP_VERSION = 1;
|
||||
private static final int DEFAULT_CURRENT_VERSION = 4;
|
||||
/**
|
||||
* Current version of the backup schema
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final int BACKUP_VERSION = 4;
|
||||
|
||||
// Key under which the payload blob is stored
|
||||
// same as UsageStatsBackupHelper.KEY_USAGE_STATS
|
||||
@@ -91,13 +91,12 @@ public class UsageStatsDatabase {
|
||||
// Persist versioned backup files.
|
||||
// Should be false, except when testing new versions
|
||||
// STOPSHIP: b/111422946 this should be false on launch
|
||||
static final boolean KEEP_VAK_FILES = true;
|
||||
static final boolean KEEP_BACKUP_DIR = true;
|
||||
|
||||
private static final String TAG = "UsageStatsDatabase";
|
||||
// STOPSHIP: b/111422946 this should be boolean DEBUG = UsageStatsService.DEBUG; on launch
|
||||
private static final boolean DEBUG = true;
|
||||
private static final String BAK_SUFFIX = ".bak";
|
||||
private static final String VERSIONED_BAK_SUFFIX = ".vak";
|
||||
private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
|
||||
private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention";
|
||||
private static final int SELECTION_LOG_RETENTION_LEN =
|
||||
@@ -108,12 +107,13 @@ public class UsageStatsDatabase {
|
||||
private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
|
||||
private final UnixCalendar mCal;
|
||||
private final File mVersionFile;
|
||||
private final File mBackupsDir;
|
||||
// If this file exists on disk, UsageStatsDatabase is in the middle of migrating files to a new
|
||||
// version. If this file exists on boot, the upgrade was interrupted and needs to be picked up
|
||||
// where it left off.
|
||||
private final File mUpdateBreadcrumb;
|
||||
// Current version of the database files schema
|
||||
private final int mCurrentVersion;
|
||||
private int mCurrentVersion;
|
||||
private boolean mFirstUpdate;
|
||||
private boolean mNewUpdate;
|
||||
|
||||
@@ -133,6 +133,7 @@ public class UsageStatsDatabase {
|
||||
};
|
||||
mCurrentVersion = version;
|
||||
mVersionFile = new File(dir, "version");
|
||||
mBackupsDir = new File(dir, "backups");
|
||||
mUpdateBreadcrumb = new File(dir, "breadcrumb");
|
||||
mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
|
||||
mCal = new UnixCalendar(0);
|
||||
@@ -251,7 +252,7 @@ public class UsageStatsDatabase {
|
||||
final FilenameFilter backupFileFilter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return !name.endsWith(BAK_SUFFIX) && !name.endsWith(VERSIONED_BAK_SUFFIX);
|
||||
return !name.endsWith(BAK_SUFFIX);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -316,24 +317,33 @@ public class UsageStatsDatabase {
|
||||
if (version != mCurrentVersion) {
|
||||
Slog.i(TAG, "Upgrading from version " + version + " to " + mCurrentVersion);
|
||||
if (!mUpdateBreadcrumb.exists()) {
|
||||
doUpgradeLocked(version);
|
||||
try {
|
||||
doUpgradeLocked(version);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG,
|
||||
"Failed to upgrade from version " + version + " to " + mCurrentVersion,
|
||||
e);
|
||||
// Fallback to previous version.
|
||||
mCurrentVersion = version;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Slog.i(TAG, "Version upgrade breadcrumb found on disk! Continuing version upgrade");
|
||||
}
|
||||
}
|
||||
|
||||
if (mUpdateBreadcrumb.exists()) {
|
||||
int previousVersion;
|
||||
long token;
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new FileReader(mUpdateBreadcrumb))) {
|
||||
token = Long.parseLong(reader.readLine());
|
||||
previousVersion = Integer.parseInt(reader.readLine());
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
Slog.e(TAG, "Failed read version upgrade breadcrumb");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
continueUpgradeLocked(previousVersion, token);
|
||||
if (mUpdateBreadcrumb.exists()) {
|
||||
int previousVersion;
|
||||
long token;
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new FileReader(mUpdateBreadcrumb))) {
|
||||
token = Long.parseLong(reader.readLine());
|
||||
previousVersion = Integer.parseInt(reader.readLine());
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
Slog.e(TAG, "Failed read version upgrade breadcrumb");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
continueUpgradeLocked(previousVersion, token);
|
||||
}
|
||||
|
||||
if (version != mCurrentVersion || mNewUpdate) {
|
||||
@@ -351,11 +361,12 @@ public class UsageStatsDatabase {
|
||||
|
||||
if (mUpdateBreadcrumb.exists()) {
|
||||
// Files should be up to date with current version. Clear the version update breadcrumb
|
||||
if (!KEEP_VAK_FILES) {
|
||||
removeVersionedBackupFiles();
|
||||
}
|
||||
mUpdateBreadcrumb.delete();
|
||||
}
|
||||
|
||||
if (mBackupsDir.exists() && !KEEP_BACKUP_DIR) {
|
||||
deleteDirectory(mBackupsDir);
|
||||
}
|
||||
}
|
||||
|
||||
private String getBuildFingerprint() {
|
||||
@@ -378,22 +389,36 @@ public class UsageStatsDatabase {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Turn all current usage stats files into versioned backup files
|
||||
// Create a dir in backups based on current timestamp
|
||||
final long token = System.currentTimeMillis();
|
||||
final FilenameFilter backupFileFilter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return !name.endsWith(BAK_SUFFIX) && !name.endsWith(VERSIONED_BAK_SUFFIX);
|
||||
}
|
||||
};
|
||||
final File backupDir = new File(mBackupsDir, Long.toString(token));
|
||||
backupDir.mkdirs();
|
||||
if (!backupDir.exists()) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to create backup directory " + backupDir.getAbsolutePath());
|
||||
}
|
||||
try {
|
||||
Files.copy(mVersionFile.toPath(),
|
||||
new File(backupDir, mVersionFile.getName()).toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to back up version file : " + mVersionFile.toString());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mIntervalDirs.length; i++) {
|
||||
File[] files = mIntervalDirs[i].listFiles(backupFileFilter);
|
||||
final File backupIntervalDir = new File(backupDir, mIntervalDirs[i].getName());
|
||||
backupIntervalDir.mkdir();
|
||||
|
||||
if (!backupIntervalDir.exists()) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to create interval backup directory "
|
||||
+ backupIntervalDir.getAbsolutePath());
|
||||
}
|
||||
File[] files = mIntervalDirs[i].listFiles();
|
||||
if (files != null) {
|
||||
for (int j = 0; j < files.length; j++) {
|
||||
final File backupFile = new File(
|
||||
files[j].toString() + "." + Long.toString(token) + "."
|
||||
+ Integer.toString(thisVersion) + VERSIONED_BAK_SUFFIX);
|
||||
final File backupFile = new File(backupIntervalDir, files[j].getName());
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating versioned (" + Integer.toString(thisVersion)
|
||||
+ ") backup of " + files[j].toString()
|
||||
@@ -403,9 +428,8 @@ public class UsageStatsDatabase {
|
||||
|
||||
try {
|
||||
// Backup file should not already exist, but make sure it doesn't
|
||||
Files.deleteIfExists(backupFile.toPath());
|
||||
Files.move(files[j].toPath(), backupFile.toPath(),
|
||||
StandardCopyOption.ATOMIC_MOVE);
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to back up file : " + files[j].toString());
|
||||
throw new RuntimeException(e);
|
||||
@@ -414,8 +438,7 @@ public class UsageStatsDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
// Leave a breadcrumb behind noting that all the usage stats have been copied to a
|
||||
// versioned backup.
|
||||
// Leave a breadcrumb behind noting that all the usage stats have been moved to a backup
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(mUpdateBreadcrumb));
|
||||
@@ -434,18 +457,13 @@ public class UsageStatsDatabase {
|
||||
}
|
||||
|
||||
private void continueUpgradeLocked(int version, long token) {
|
||||
// Read all the backed ups for the specified version and rewrite them with the current
|
||||
// version's file format.
|
||||
final FilenameFilter versionedBackupFileFilter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.endsWith("." + Long.toString(token) + "." + Integer.toString(version)
|
||||
+ VERSIONED_BAK_SUFFIX);
|
||||
}
|
||||
};
|
||||
final File backupDir = new File(mBackupsDir, Long.toString(token));
|
||||
|
||||
// Read each file in the backup according to the version and write to the interval
|
||||
// directories in the current versions format
|
||||
for (int i = 0; i < mIntervalDirs.length; i++) {
|
||||
File[] files = mIntervalDirs[i].listFiles(versionedBackupFileFilter);
|
||||
final File backedUpInterval = new File(backupDir, mIntervalDirs[i].getName());
|
||||
File[] files = backedUpInterval.listFiles();
|
||||
if (files != null) {
|
||||
for (int j = 0; j < files.length; j++) {
|
||||
if (DEBUG) {
|
||||
@@ -459,34 +477,9 @@ public class UsageStatsDatabase {
|
||||
readLocked(new AtomicFile(files[j]), stats, version);
|
||||
writeLocked(new AtomicFile(new File(mIntervalDirs[i],
|
||||
Long.toString(stats.beginTime))), stats, mCurrentVersion);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG,
|
||||
"Failed to upgrade versioned backup file : " + files[j].toString());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeVersionedBackupFiles() {
|
||||
final FilenameFilter versionedBackupFileFilter = new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.endsWith(VERSIONED_BAK_SUFFIX);
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < mIntervalDirs.length; i++) {
|
||||
File[] files = mIntervalDirs[i].listFiles(versionedBackupFileFilter);
|
||||
if (files != null) {
|
||||
for (int j = 0; j < files.length; j++) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG,
|
||||
"Removing " + files[j].toString() + " for interval " + i);
|
||||
}
|
||||
if (!files[j].delete()) {
|
||||
Slog.e(TAG, "Failed to delete file : " + files[j].toString());
|
||||
} catch (Exception e) {
|
||||
// This method is called on boot, log the exception and move on
|
||||
Slog.e(TAG, "Failed to upgrade backup file : " + files[j].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -761,7 +754,7 @@ public class UsageStatsDatabase {
|
||||
}
|
||||
}
|
||||
writeLocked(af, stats);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to delete chooser counts from usage stats file", e);
|
||||
}
|
||||
}
|
||||
@@ -961,7 +954,7 @@ public class UsageStatsDatabase {
|
||||
sb.append("\nError found in:\n");
|
||||
sb.append(file.getBaseFile().getAbsolutePath());
|
||||
sb.append("\nPlease go to b/115429334 to help root cause this issue");
|
||||
Slog.wtf(TAG,sb.toString());
|
||||
Slog.wtf(TAG, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1013,40 +1006,53 @@ public class UsageStatsDatabase {
|
||||
|
||||
/* Backup/Restore Code */
|
||||
byte[] getBackupPayload(String key) {
|
||||
return getBackupPayload(key, BACKUP_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public byte[] getBackupPayload(String key, int version) {
|
||||
synchronized (mLock) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (KEY_USAGE_STATS.equals(key)) {
|
||||
prune(System.currentTimeMillis());
|
||||
DataOutputStream out = new DataOutputStream(baos);
|
||||
try {
|
||||
out.writeInt(BACKUP_VERSION);
|
||||
out.writeInt(version);
|
||||
|
||||
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size());
|
||||
|
||||
for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].size();
|
||||
i++) {
|
||||
writeIntervalStatsToStream(out,
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].valueAt(i));
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY].valueAt(i),
|
||||
version);
|
||||
}
|
||||
|
||||
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size());
|
||||
for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].size();
|
||||
i++) {
|
||||
writeIntervalStatsToStream(out,
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].valueAt(i));
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_WEEKLY].valueAt(i),
|
||||
version);
|
||||
}
|
||||
|
||||
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size());
|
||||
for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].size();
|
||||
i++) {
|
||||
writeIntervalStatsToStream(out,
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].valueAt(i));
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_MONTHLY].valueAt(i),
|
||||
version);
|
||||
}
|
||||
|
||||
out.writeInt(mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size());
|
||||
for (int i = 0; i < mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].size();
|
||||
i++) {
|
||||
writeIntervalStatsToStream(out,
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].valueAt(i));
|
||||
mSortedStatFiles[UsageStatsManager.INTERVAL_YEARLY].valueAt(i),
|
||||
version);
|
||||
}
|
||||
if (DEBUG) Slog.i(TAG, "Written " + baos.size() + " bytes of data");
|
||||
} catch (IOException ioe) {
|
||||
@@ -1059,7 +1065,11 @@ public class UsageStatsDatabase {
|
||||
|
||||
}
|
||||
|
||||
void applyRestoredPayload(String key, byte[] payload) {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void applyRestoredPayload(String key, byte[] payload) {
|
||||
synchronized (mLock) {
|
||||
if (KEY_USAGE_STATS.equals(key)) {
|
||||
// Read stats files for the current device configs
|
||||
@@ -1087,28 +1097,32 @@ public class UsageStatsDatabase {
|
||||
|
||||
int fileCount = in.readInt();
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
|
||||
backupDataVersion);
|
||||
stats = mergeStats(stats, dailyConfigSource);
|
||||
putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats);
|
||||
}
|
||||
|
||||
fileCount = in.readInt();
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
|
||||
backupDataVersion);
|
||||
stats = mergeStats(stats, weeklyConfigSource);
|
||||
putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats);
|
||||
}
|
||||
|
||||
fileCount = in.readInt();
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
|
||||
backupDataVersion);
|
||||
stats = mergeStats(stats, monthlyConfigSource);
|
||||
putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats);
|
||||
}
|
||||
|
||||
fileCount = in.readInt();
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in));
|
||||
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
|
||||
backupDataVersion);
|
||||
stats = mergeStats(stats, yearlyConfigSource);
|
||||
putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats);
|
||||
}
|
||||
@@ -1135,7 +1149,7 @@ public class UsageStatsDatabase {
|
||||
return beingRestored;
|
||||
}
|
||||
|
||||
private void writeIntervalStatsToStream(DataOutputStream out, AtomicFile statsFile)
|
||||
private void writeIntervalStatsToStream(DataOutputStream out, AtomicFile statsFile, int version)
|
||||
throws IOException {
|
||||
IntervalStats stats = new IntervalStats();
|
||||
try {
|
||||
@@ -1146,7 +1160,7 @@ public class UsageStatsDatabase {
|
||||
return;
|
||||
}
|
||||
sanitizeIntervalStatsForBackup(stats);
|
||||
byte[] data = serializeIntervalStats(stats);
|
||||
byte[] data = serializeIntervalStats(stats, version);
|
||||
out.writeInt(data.length);
|
||||
out.write(data);
|
||||
}
|
||||
@@ -1165,26 +1179,26 @@ public class UsageStatsDatabase {
|
||||
if (stats.events != null) stats.events.clear();
|
||||
}
|
||||
|
||||
private byte[] serializeIntervalStats(IntervalStats stats) {
|
||||
private byte[] serializeIntervalStats(IntervalStats stats, int version) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(baos);
|
||||
try {
|
||||
out.writeLong(stats.beginTime);
|
||||
writeLocked(out, stats);
|
||||
} catch (IOException ioe) {
|
||||
writeLocked(out, stats, version);
|
||||
} catch (Exception ioe) {
|
||||
Slog.d(TAG, "Serializing IntervalStats Failed", ioe);
|
||||
baos.reset();
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private IntervalStats deserializeIntervalStats(byte[] data) {
|
||||
private IntervalStats deserializeIntervalStats(byte[] data, int version) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
DataInputStream in = new DataInputStream(bais);
|
||||
IntervalStats stats = new IntervalStats();
|
||||
try {
|
||||
stats.beginTime = in.readLong();
|
||||
readLocked(in, stats);
|
||||
readLocked(in, stats, version);
|
||||
} catch (IOException ioe) {
|
||||
Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe);
|
||||
stats = null;
|
||||
|
||||
Reference in New Issue
Block a user