Merge "Switch PackageStatusStorage to XML-based storage" am: 4e77ae6d1a

am: 4a7e3de5e7

Change-Id: I5db1d32b6141653e7971c4f1dd9931d4042bb19b
This commit is contained in:
Neil Fuller
2017-06-28 17:20:30 +00:00
committed by android-build-merger
4 changed files with 266 additions and 213 deletions

View File

@@ -16,73 +16,83 @@
package com.android.server.timezone; package com.android.server.timezone;
import android.content.ContentValues; import com.android.internal.util.FastXmlSerializer;
import android.content.Context;
import android.database.Cursor; import org.xmlpull.v1.XmlPullParser;
import android.database.sqlite.SQLiteDatabase; import org.xmlpull.v1.XmlPullParserException;
import android.database.sqlite.SQLiteOpenHelper; import org.xmlpull.v1.XmlSerializer;
import android.util.AtomicFile;
import android.util.Slog; import android.util.Slog;
import android.util.Xml;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE; import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS; import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
import static com.android.server.timezone.PackageStatus.CHECK_STARTED; import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
/** /**
* Storage logic for accessing/mutating the Android system's persistent state related to time zone * Storage logic for accessing/mutating the Android system's persistent state related to time zone
* update checking. There is expected to be a single instance and all methods synchronized on * update checking. There is expected to be a single instance. All non-private methods are thread
* {@code this} for thread safety. * safe.
*/ */
final class PackageStatusStorage { final class PackageStatusStorage {
private static final String TAG = "timezone.PackageStatusStorage"; private static final String LOG_TAG = "timezone.PackageStatusStorage";
private static final String DATABASE_NAME = "timezonepackagestatus.db"; private static final String TAG_PACKAGE_STATUS = "PackageStatus";
private static final int DATABASE_VERSION = 1;
/** The table name. It will have a single row with _id == {@link #SINGLETON_ID} */
private static final String TABLE = "status";
private static final String COLUMN_ID = "_id";
/** /**
* Column that stores a monotonically increasing lock ID, used to detect concurrent update * Attribute that stores a monotonically increasing lock ID, used to detect concurrent update
* issues without on-line locks. Incremented on every write. * issues without on-line locks. Incremented on every write.
*/ */
private static final String COLUMN_OPTIMISTIC_LOCK_ID = "optimistic_lock_id"; private static final String ATTRIBUTE_OPTIMISTIC_LOCK_ID = "optimisticLockId";
/** /**
* Column that stores the current "check status" of the time zone update application packages. * Attribute that stores the current "check status" of the time zone update application
* packages.
*/ */
private static final String COLUMN_CHECK_STATUS = "check_status"; private static final String ATTRIBUTE_CHECK_STATUS = "checkStatus";
/** /**
* Column that stores the version of the time zone rules update application being checked / last * Attribute that stores the version of the time zone rules update application being checked
* checked. * / last checked.
*/ */
private static final String COLUMN_UPDATE_APP_VERSION = "update_app_package_version"; private static final String ATTRIBUTE_UPDATE_APP_VERSION = "updateAppPackageVersion";
/** /**
* Column that stores the version of the time zone rules data application being checked / last * Attribute that stores the version of the time zone rules data application being checked
* checked. * / last checked.
*/ */
private static final String COLUMN_DATA_APP_VERSION = "data_app_package_version"; private static final String ATTRIBUTE_DATA_APP_VERSION = "dataAppPackageVersion";
/**
* The ID of the one row.
*/
private static final int SINGLETON_ID = 1;
private static final int UNKNOWN_PACKAGE_VERSION = -1; private static final int UNKNOWN_PACKAGE_VERSION = -1;
private final DatabaseHelper mDatabaseHelper; private final AtomicFile mPackageStatusFile;
PackageStatusStorage(Context context) { PackageStatusStorage(File storageDir) {
mDatabaseHelper = new DatabaseHelper(context); mPackageStatusFile = new AtomicFile(new File(storageDir, "packageStatus.xml"));
if (!mPackageStatusFile.getBaseFile().exists()) {
try {
insertInitialPackageStatus();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
} }
void deleteDatabaseForTests() { void deleteFileForTests() {
SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile()); synchronized(this) {
mPackageStatusFile.delete();
}
} }
/** /**
@@ -93,48 +103,60 @@ final class PackageStatusStorage {
synchronized (this) { synchronized (this) {
try { try {
return getPackageStatusInternal(); return getPackageStatusInternal();
} catch (IllegalArgumentException e) { } catch (ParseException e) {
// This means that data exists in the table but it was bad. // This means that data exists in the file but it was bad.
Slog.e(TAG, "Package status invalid, resetting and retrying", e); Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e);
// Reset the storage so it is in a good state again. // Reset the storage so it is in a good state again.
mDatabaseHelper.recoverFromBadData(); recoverFromBadData(e);
return getPackageStatusInternal(); try {
return getPackageStatusInternal();
} catch (ParseException e2) {
throw new IllegalStateException("Recovery from bad file failed", e2);
}
} }
} }
} }
private PackageStatus getPackageStatusInternal() { private PackageStatus getPackageStatusInternal() throws ParseException {
String[] columns = { try (FileInputStream fis = mPackageStatusFile.openRead()) {
COLUMN_CHECK_STATUS, COLUMN_UPDATE_APP_VERSION, COLUMN_DATA_APP_VERSION XmlPullParser parser = parseToPackageStatusTag(fis);
}; Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS);
Cursor cursor = mDatabaseHelper.getReadableDatabase() if (checkStatus == null) {
.query(TABLE, columns, COLUMN_ID + " = ?", return null;
new String[] { Integer.toString(SINGLETON_ID) }, }
null /* groupBy */, null /* having */, null /* orderBy */); int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION);
if (cursor.getCount() != 1) { int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION);
Slog.e(TAG, "Unable to find package status from package status row. Rows returned: " return new PackageStatus(checkStatus,
+ cursor.getCount()); new PackageVersions(updateAppVersion, dataAppVersion));
return null; } catch (IOException e) {
ParseException e2 = new ParseException("Error reading package status", 0);
e2.initCause(e);
throw e2;
} }
cursor.moveToFirst(); }
// Determine check status. // Callers should be synchronized(this).
if (cursor.isNull(0)) { private int recoverFromBadData(Exception cause) {
// This is normal the first time getPackageStatus() is called, or after mPackageStatusFile.delete();
// resetCheckState(). try {
return null; return insertInitialPackageStatus();
} catch (IOException e) {
IllegalStateException fatal = new IllegalStateException(e);
fatal.addSuppressed(cause);
throw fatal;
} }
int checkStatus = cursor.getInt(0); }
// Determine package version. /** Insert the initial data, returning the optimistic lock ID */
if (cursor.isNull(1) || cursor.isNull(2)) { private int insertInitialPackageStatus() throws IOException {
Slog.e(TAG, "Package version information unexpectedly null"); // Doesn't matter what it is, but we avoid the obvious starting value each time the data
return null; // is reset to ensure that old tokens are unlikely to work.
} final int initialOptimisticLockId = (int) System.currentTimeMillis();
PackageVersions packageVersions = new PackageVersions(cursor.getInt(1), cursor.getInt(2));
return new PackageStatus(checkStatus, packageVersions); writePackageStatusInternal(null /* status */, initialOptimisticLockId,
null /* packageVersions */);
return initialOptimisticLockId;
} }
/** /**
@@ -147,23 +169,29 @@ final class PackageStatusStorage {
} }
synchronized (this) { synchronized (this) {
Integer optimisticLockId = getCurrentOptimisticLockId(); int optimisticLockId;
if (optimisticLockId == null) { try {
Slog.w(TAG, "Unable to find optimistic lock ID from package status row"); optimisticLockId = getCurrentOptimisticLockId();
} catch (ParseException e) {
Slog.w(LOG_TAG, "Unable to find optimistic lock ID from package status");
// Recover. // Recover.
optimisticLockId = mDatabaseHelper.recoverFromBadData(); optimisticLockId = recoverFromBadData(e);
} }
int newOptimisticLockId = optimisticLockId + 1; int newOptimisticLockId = optimisticLockId + 1;
boolean statusRowUpdated = writeStatusRow( try {
optimisticLockId, newOptimisticLockId, CHECK_STARTED, currentInstalledVersions); boolean statusUpdated = writePackageStatusWithOptimisticLockCheck(
if (!statusRowUpdated) { optimisticLockId, newOptimisticLockId, CHECK_STARTED,
Slog.e(TAG, "Unable to update status to CHECK_STARTED in package status row." currentInstalledVersions);
+ " synchronization failure?"); if (!statusUpdated) {
return null; throw new IllegalStateException("Unable to update status to CHECK_STARTED."
+ " synchronization failure?");
}
return new CheckToken(newOptimisticLockId, currentInstalledVersions);
} catch (IOException e) {
throw new IllegalStateException(e);
} }
return new CheckToken(newOptimisticLockId, currentInstalledVersions);
} }
} }
@@ -172,19 +200,25 @@ final class PackageStatusStorage {
*/ */
void resetCheckState() { void resetCheckState() {
synchronized(this) { synchronized(this) {
Integer optimisticLockId = getCurrentOptimisticLockId(); int optimisticLockId;
if (optimisticLockId == null) { try {
Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package" optimisticLockId = getCurrentOptimisticLockId();
+ " status row"); } catch (ParseException e) {
Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+ " status");
// Attempt to recover the storage state. // Attempt to recover the storage state.
optimisticLockId = mDatabaseHelper.recoverFromBadData(); optimisticLockId = recoverFromBadData(e);
} }
int newOptimisticLockId = optimisticLockId + 1; int newOptimisticLockId = optimisticLockId + 1;
if (!writeStatusRow(optimisticLockId, newOptimisticLockId, try {
null /* status */, null /* packageVersions */)) { if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId,
Slog.e(TAG, "resetCheckState: Unable to reset package status row," newOptimisticLockId, null /* status */, null /* packageVersions */)) {
+ " newOptimisticLockId=" + newOptimisticLockId); throw new IllegalStateException("resetCheckState: Unable to reset package"
+ " status, newOptimisticLockId=" + newOptimisticLockId);
}
} catch (IOException e) {
throw new IllegalStateException(e);
} }
} }
} }
@@ -199,138 +233,146 @@ final class PackageStatusStorage {
int optimisticLockId = checkToken.mOptimisticLockId; int optimisticLockId = checkToken.mOptimisticLockId;
int newOptimisticLockId = optimisticLockId + 1; int newOptimisticLockId = optimisticLockId + 1;
int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE; int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
return writeStatusRow(optimisticLockId, newOptimisticLockId, try {
status, checkToken.mPackageVersions); return writePackageStatusWithOptimisticLockCheck(optimisticLockId,
} newOptimisticLockId, status, checkToken.mPackageVersions);
} } catch (IOException e) {
throw new IllegalStateException(e);
// Caller should be synchronized(this)
private Integer getCurrentOptimisticLockId() {
final String[] columns = { COLUMN_OPTIMISTIC_LOCK_ID };
final String querySelection = COLUMN_ID + " = ?";
final String[] querySelectionArgs = { Integer.toString(SINGLETON_ID) };
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
try (Cursor cursor = database.query(TABLE, columns, querySelection, querySelectionArgs,
null /* groupBy */, null /* having */, null /* orderBy */)) {
if (cursor.getCount() != 1) {
Slog.w(TAG, cursor.getCount() + " rows returned, expected exactly one.");
return null;
} }
cursor.moveToFirst();
return cursor.getInt(0);
} }
} }
// Caller should be synchronized(this) // Caller should be synchronized(this).
private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status, private int getCurrentOptimisticLockId() throws ParseException {
PackageVersions packageVersions) { try (FileInputStream fis = mPackageStatusFile.openRead()) {
XmlPullParser parser = parseToPackageStatusTag(fis);
return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID);
} catch (IOException e) {
ParseException e2 = new ParseException("Unable to read file", 0);
e2.initCause(e);
throw e2;
}
}
/** Returns a parser or throws ParseException, never returns null. */
private static XmlPullParser parseToPackageStatusTag(FileInputStream fis)
throws ParseException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, StandardCharsets.UTF_8.name());
int type;
while ((type = parser.next()) != END_DOCUMENT) {
final String tag = parser.getName();
if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) {
return parser;
}
}
throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0);
} catch (XmlPullParserException e) {
throw new IllegalStateException("Unable to configure parser", e);
} catch (IOException e) {
ParseException e2 = new ParseException("Error reading XML", 0);
e.initCause(e);
throw e2;
}
}
// Caller should be synchronized(this).
private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId,
int newOptimisticLockId, Integer status, PackageVersions packageVersions)
throws IOException {
int currentOptimisticLockId;
try {
currentOptimisticLockId = getCurrentOptimisticLockId();
if (currentOptimisticLockId != optimisticLockId) {
return false;
}
} catch (ParseException e) {
recoverFromBadData(e);
return false;
}
writePackageStatusInternal(status, newOptimisticLockId, packageVersions);
return true;
}
// Caller should be synchronized(this).
private void writePackageStatusInternal(Integer status, int optimisticLockId,
PackageVersions packageVersions) throws IOException {
if ((status == null) != (packageVersions == null)) { if ((status == null) != (packageVersions == null)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Provide both status and packageVersions, or neither."); "Provide both status and packageVersions, or neither.");
} }
SQLiteDatabase database = mDatabaseHelper.getWritableDatabase(); FileOutputStream fos = null;
ContentValues values = new ContentValues(); try {
values.put(COLUMN_OPTIMISTIC_LOCK_ID, newOptimisticLockId); fos = mPackageStatusFile.startWrite();
if (status == null) { XmlSerializer serializer = new FastXmlSerializer();
values.putNull(COLUMN_CHECK_STATUS); serializer.setOutput(fos, StandardCharsets.UTF_8.name());
values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION); serializer.startDocument(null /* encoding */, true /* standalone */);
values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION); final String namespace = null;
} else { serializer.startTag(namespace, TAG_PACKAGE_STATUS);
values.put(COLUMN_CHECK_STATUS, status); String statusAttributeValue = status == null ? "" : Integer.toString(status);
values.put(COLUMN_UPDATE_APP_VERSION, packageVersions.mUpdateAppVersion); serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue);
values.put(COLUMN_DATA_APP_VERSION, packageVersions.mDataAppVersion); serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID,
Integer.toString(optimisticLockId));
int updateAppVersion = status == null
? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion;
serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION,
Integer.toString(updateAppVersion));
int dataAppVersion = status == null
? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion;
serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION,
Integer.toString(dataAppVersion));
serializer.endTag(namespace, TAG_PACKAGE_STATUS);
serializer.endDocument();
serializer.flush();
mPackageStatusFile.finishWrite(fos);
} catch (IOException e) {
if (fos != null) {
mPackageStatusFile.failWrite(fos);
}
throw e;
} }
String updateSelection = COLUMN_ID + " = ? AND " + COLUMN_OPTIMISTIC_LOCK_ID + " = ?";
String[] updateSelectionArgs = {
Integer.toString(SINGLETON_ID), Integer.toString(optimisticLockId)
};
int count = database.update(TABLE, values, updateSelection, updateSelectionArgs);
if (count > 1) {
// This has to be because of corruption: there should only ever be one row.
Slog.w(TAG, "writeStatusRow: " + count + " rows updated, expected exactly one.");
// Reset the table.
mDatabaseHelper.recoverFromBadData();
}
// 1 is the success case. 0 rows updated means the row is missing or the optimistic lock ID
// was not as expected, this could be because of corruption but is most likely due to an
// optimistic lock failure. Callers can decide on a case-by-case basis.
return count == 1;
}
/** Only used during tests to force an empty table. */
void deleteRowForTests() {
mDatabaseHelper.getWritableDatabase().delete(TABLE, null, null);
} }
/** Only used during tests to force a known table state. */ /** Only used during tests to force a known table state. */
public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) { public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
int optimisticLockId = getCurrentOptimisticLockId(); synchronized (this) {
writeStatusRow(optimisticLockId, optimisticLockId, checkStatus, packageVersions); try {
int optimisticLockId = getCurrentOptimisticLockId();
writePackageStatusWithOptimisticLockCheck(optimisticLockId, optimisticLockId,
checkStatus, packageVersions);
} catch (IOException | ParseException e) {
throw new IllegalStateException(e);
}
}
} }
static class DatabaseHelper extends SQLiteOpenHelper { private static Integer getNullableIntAttribute(XmlPullParser parser, String attributeName)
throws ParseException {
private final Context mContext; String attributeValue = parser.getAttributeValue(null, attributeName);
try {
public DatabaseHelper(Context context) { if (attributeValue == null) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); throw new ParseException("Attribute " + attributeName + " missing", 0);
mContext = context; } else if (attributeValue.isEmpty()) {
} return null;
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE + " (" +
"_id INTEGER PRIMARY KEY," +
COLUMN_OPTIMISTIC_LOCK_ID + " INTEGER NOT NULL," +
COLUMN_CHECK_STATUS + " INTEGER," +
COLUMN_UPDATE_APP_VERSION + " INTEGER NOT NULL," +
COLUMN_DATA_APP_VERSION + " INTEGER NOT NULL" +
");");
insertInitialRowState(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
// no-op: nothing to upgrade
}
/** Recover the initial data row state, returning the new current optimistic lock ID */
int recoverFromBadData() {
// Delete the table content.
SQLiteDatabase writableDatabase = getWritableDatabase();
writableDatabase.delete(TABLE, null /* whereClause */, null /* whereArgs */);
// Insert the initial content.
return insertInitialRowState(writableDatabase);
}
/** Insert the initial data row, returning the optimistic lock ID */
private static int insertInitialRowState(SQLiteDatabase db) {
// Doesn't matter what it is, but we avoid the obvious starting value each time the row
// is reset to ensure that old tokens are unlikely to work.
final int initialOptimisticLockId = (int) System.currentTimeMillis();
// Insert the one row.
ContentValues values = new ContentValues();
values.put(COLUMN_ID, SINGLETON_ID);
values.put(COLUMN_OPTIMISTIC_LOCK_ID, initialOptimisticLockId);
values.putNull(COLUMN_CHECK_STATUS);
values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
long id = db.insert(TABLE, null, values);
if (id == -1) {
Slog.w(TAG, "insertInitialRow: could not insert initial row, id=" + id);
return -1;
} }
return initialOptimisticLockId; return Integer.parseInt(attributeValue);
} catch (NumberFormatException e) {
throw new ParseException(
"Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0);
} }
}
File getDatabaseFile() { private static int getIntAttribute(XmlPullParser parser, String attributeName)
return mContext.getDatabasePath(DATABASE_NAME); throws ParseException {
Integer value = getNullableIntAttribute(parser, attributeName);
if (value == null) {
throw new ParseException("Missing attribute " + attributeName, 0);
} }
return value;
} }
} }

View File

@@ -21,9 +21,12 @@ import com.android.internal.annotations.VisibleForTesting;
import android.app.timezone.RulesUpdaterContract; import android.app.timezone.RulesUpdaterContract;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Environment;
import android.provider.TimeZoneRulesDataContract; import android.provider.TimeZoneRulesDataContract;
import android.util.Slog; import android.util.Slog;
import java.io.File;
/** /**
* Monitors the installed applications associated with time zone updates. If the app packages are * Monitors the installed applications associated with time zone updates. If the app packages are
* updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted * updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
@@ -81,11 +84,17 @@ public class PackageTracker implements IntentHelper.Listener {
/** Creates the {@link PackageTracker} for normal use. */ /** Creates the {@link PackageTracker} for normal use. */
static PackageTracker create(Context context) { static PackageTracker create(Context context) {
PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context); PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
// TODO(nfuller): Switch to FileUtils.createDir() when available. http://b/31008728
File storageDir = new File(Environment.getDataSystemDirectory(), "timezone");
if (!storageDir.exists()) {
storageDir.mkdir();
}
return new PackageTracker( return new PackageTracker(
helperImpl /* clock */, helperImpl /* clock */,
helperImpl /* configHelper */, helperImpl /* configHelper */,
helperImpl /* packageManagerHelper */, helperImpl /* packageManagerHelper */,
new PackageStatusStorage(context), new PackageStatusStorage(storageDir),
new IntentHelperImpl(context)); new IntentHelperImpl(context));
} }

View File

@@ -21,10 +21,11 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import java.io.File;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -40,15 +41,16 @@ public class PackageStatusStorageTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
Context context = InstrumentationRegistry.getContext(); Context context = InstrumentationRegistry.getContext();
File dataDir = context.getFilesDir();
// Using the instrumentation context means the database is created in a test app-specific // Using the instrumentation context means the database is created in a test app-specific
// directory. // directory.
mPackageStatusStorage = new PackageStatusStorage(context); mPackageStatusStorage = new PackageStatusStorage(dataDir);
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
mPackageStatusStorage.deleteDatabaseForTests(); mPackageStatusStorage.deleteFileForTests();
} }
@Test @Test
@@ -90,7 +92,7 @@ public class PackageStatusStorageTest {
} }
@Test @Test
public void generateCheckToken_missingRowBehavior() { public void generateCheckToken_missingFileBehavior() {
// Assert initial state. // Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus()); assertNull(mPackageStatusStorage.getPackageStatus());
@@ -100,15 +102,15 @@ public class PackageStatusStorageTest {
// There should now be state. // There should now be state.
assertNotNull(mPackageStatusStorage.getPackageStatus()); assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the table by removing the one row. // Corrupt the data by removing the file.
mPackageStatusStorage.deleteRowForTests(); mPackageStatusStorage.deleteFileForTests();
// Check that generateCheckToken recovers. // Check that generateCheckToken recovers.
assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS)); assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
} }
@Test @Test
public void getPackageStatus_missingRowBehavior() { public void getPackageStatus_missingFileBehavior() {
// Assert initial state. // Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus()); assertNull(mPackageStatusStorage.getPackageStatus());
@@ -118,14 +120,14 @@ public class PackageStatusStorageTest {
// There should now be a state. // There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus()); assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the table by removing the one row. // Corrupt the data by removing the file.
mPackageStatusStorage.deleteRowForTests(); mPackageStatusStorage.deleteFileForTests();
assertNull(mPackageStatusStorage.getPackageStatus()); assertNull(mPackageStatusStorage.getPackageStatus());
} }
@Test @Test
public void markChecked_missingRowBehavior() { public void markChecked_missingFileBehavior() {
// Assert initial state. // Assert initial state.
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS); CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertNotNull(token1); assertNotNull(token1);
@@ -133,10 +135,10 @@ public class PackageStatusStorageTest {
// There should now be a state. // There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus()); assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the table by removing the one row. // Corrupt the data by removing the file.
mPackageStatusStorage.deleteRowForTests(); mPackageStatusStorage.deleteFileForTests();
// The missing row should mean token1 is now considered invalid, so we should get a false. // The missing file should mean token1 is now considered invalid, so we should get a false.
assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */)); assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
// The storage should have recovered and we should be able to carry on like before. // The storage should have recovered and we should be able to carry on like before.

View File

@@ -71,7 +71,7 @@ public class PackageTrackerTest {
// Using the instrumentation context means the database is created in a test app-specific // Using the instrumentation context means the database is created in a test app-specific
// directory. We can use the real thing for this test. // directory. We can use the real thing for this test.
mPackageStatusStorage = new PackageStatusStorage(context); mPackageStatusStorage = new PackageStatusStorage(context.getFilesDir());
// For other interactions with the Android framework we create a fake object. // For other interactions with the Android framework we create a fake object.
mFakeIntentHelper = new FakeIntentHelper(); mFakeIntentHelper = new FakeIntentHelper();
@@ -88,7 +88,7 @@ public class PackageTrackerTest {
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
if (mPackageStatusStorage != null) { if (mPackageStatusStorage != null) {
mPackageStatusStorage.deleteDatabaseForTests(); mPackageStatusStorage.deleteFileForTests();
} }
} }