import static org.testng.Assert.assertEquals;
import android.app.usage.EventList;
+import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.res.Configuration;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
runBackupRestoreTest(0);
runBackupRestoreTest(99999);
}
+
+ /**
+ * Test the pruning in indexFilesLocked() that only allow up to 100 daily files, 50 weekly files
+ * , 12 monthly files, 10 yearly files.
+ */
+ @Test
+ public void testMaxFiles() throws IOException {
+ final File[] intervalDirs = new File[]{
+ new File(mTestDir, "daily"),
+ new File(mTestDir, "weekly"),
+ new File(mTestDir, "monthly"),
+ new File(mTestDir, "yearly"),
+ };
+ // Create 10 extra files under each interval dir.
+ final int extra = 10;
+ final int length = intervalDirs.length;
+ for (int i = 0; i < length; i++) {
+ final int numFiles = UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra;
+ for (int f = 0; f < numFiles; f++) {
+ final AtomicFile file = new AtomicFile(new File(intervalDirs[i], Long.toString(f)));
+ FileOutputStream fos = file.startWrite();
+ fos.write(1);
+ file.finishWrite(fos);
+ }
+ }
+ // indexFilesLocked() list files under each interval dir, if number of files are more than
+ // the max allowed files for each interval type, it deletes the lowest numbered files.
+ mUsageStatsDatabase.forceIndexFiles();
+ final int len = mUsageStatsDatabase.mSortedStatFiles.length;
+ for (int i = 0; i < len; i++) {
+ final TimeSparseArray<AtomicFile> files = mUsageStatsDatabase.mSortedStatFiles[i];
+ // The stats file for each interval type equals to max allowed.
+ assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i],
+ files.size());
+ // The highest numbered file,
+ assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i] + extra - 1,
+ files.keyAt(files.size() - 1));
+ // The lowest numbered file:
+ assertEquals(extra, files.keyAt(0));
+ }
+ }
}
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@VisibleForTesting
public static final int BACKUP_VERSION = 4;
+ @VisibleForTesting
+ static final int[] MAX_FILES_PER_INTERVAL_TYPE = new int[]{100, 50, 12, 10};
+
// Key under which the payload blob is stored
// same as UsageStatsBackupHelper.KEY_USAGE_STATS
static final String KEY_USAGE_STATS = "usage_stats";
private final Object mLock = new Object();
private final File[] mIntervalDirs;
- private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
+ @VisibleForTesting
+ final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
private final UnixCalendar mCal;
private final File mVersionFile;
private final File mBackupsDir;
@VisibleForTesting
public UsageStatsDatabase(File dir, int version) {
mIntervalDirs = new File[]{
- new File(dir, "daily"),
- new File(dir, "weekly"),
- new File(dir, "monthly"),
- new File(dir, "yearly"),
+ new File(dir, "daily"),
+ new File(dir, "weekly"),
+ new File(dir, "monthly"),
+ new File(dir, "yearly"),
};
mCurrentVersion = version;
mVersionFile = new File(dir, "version");
return true;
}
+ /** @hide */
+ @VisibleForTesting
+ void forceIndexFiles() {
+ synchronized (mLock) {
+ indexFilesLocked();
+ }
+ }
+
private void indexFilesLocked() {
final FilenameFilter backupFileFilter = new FilenameFilter() {
@Override
return !name.endsWith(BAK_SUFFIX);
}
};
-
// Index the available usage stat files on disk.
for (int i = 0; i < mSortedStatFiles.length; i++) {
if (mSortedStatFiles[i] == null) {
if (DEBUG) {
Slog.d(TAG, "Found " + files.length + " stat files for interval " + i);
}
-
- for (File f : files) {
+ final int len = files.length;
+ for (int j = 0; j < len; j++) {
+ final File f = files[j];
final AtomicFile af = new AtomicFile(f);
try {
mSortedStatFiles[i].put(parseBeginTime(af), af);
Slog.e(TAG, "failed to index file: " + f, e);
}
}
+
+ // only keep the max allowed number of files for each interval type.
+ final int toDelete = mSortedStatFiles[i].size() - MAX_FILES_PER_INTERVAL_TYPE[i];
+ if (toDelete > 0) {
+ for (int j = 0; j < toDelete; j++) {
+ mSortedStatFiles[i].valueAt(0).delete();
+ mSortedStatFiles[i].removeAt(0);
+ }
+ Slog.d(TAG, "Deleted " + toDelete + " stat files for interval " + i);
+ }
}
}
}