InstalledAppProvider: store APK hash and last update time

The APK hash is useful for comparing whether something is exactly the same
file as something else.  For example, to compare whether the installed APK
matches something that f-droid.org hosts.  The "last update time" is a fast
way to check whether the information is current.
This commit is contained in:
Hans-Christoph Steiner 2016-05-27 16:52:54 +02:00
parent 906a26414a
commit 90467bf8bf
5 changed files with 69 additions and 16 deletions

View File

@ -38,6 +38,7 @@ public class MockInstallablePackageManager extends MockPackageManager {
p.versionCode = version;
p.versionName = versionName;
p.applicationInfo = new MockApplicationInfo(p);
p.lastUpdateTime = System.currentTimeMillis();
info.add(p);
}
}

View File

@ -2,6 +2,8 @@ package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.net.Uri;
import mock.MockContextSwappableComponents;
import mock.MockInstallablePackageManager;
@ -81,6 +83,39 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
}
public void testLastUpdateTime() {
String packageName = "com.example.app";
insertInstalledApp(packageName, 10, "1.0");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(packageName, 10, "1.0");
Uri uri = InstalledAppProvider.getAppUri(packageName);
String[] projection = {
InstalledAppProvider.DataColumns.PACKAGE_NAME,
InstalledAppProvider.DataColumns.LAST_UPDATE_TIME,
};
Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME));
assertTrue(lastUpdateTime > 0);
assertTrue(lastUpdateTime < System.currentTimeMillis());
cursor.close();
insertInstalledApp(packageName, 11, "1.1");
cursor = getMockContentResolver().query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)));
cursor.close();
}
public void testDelete() {
insertInstalledApp("com.example.app1", 10, "1.0");
@ -156,6 +191,9 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
values.put(InstalledAppProvider.DataColumns.SIGNATURE, "");
values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, System.currentTimeMillis());
values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "sha256");
values.put(InstalledAppProvider.DataColumns.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe");
return values;
}

View File

@ -103,11 +103,14 @@ class DBHelper extends SQLiteOpenHelper {
+ InstalledAppProvider.DataColumns.VERSION_CODE + " INT NOT NULL, "
+ InstalledAppProvider.DataColumns.VERSION_NAME + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.SIGNATURE + " TEXT NOT NULL "
+ InstalledAppProvider.DataColumns.SIGNATURE + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.LAST_UPDATE_TIME + " INTEGER NOT NULL DEFAULT 0, "
+ InstalledAppProvider.DataColumns.HASH_TYPE + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.HASH + " TEXT NOT NULL"
+ " );";
private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + TABLE_INSTALLED_APP + ";";
private static final int DB_VERSION = 55;
private static final int DB_VERSION = 56;
private final Context context;
@ -199,7 +202,7 @@ class DBHelper extends SQLiteOpenHelper {
public void onCreate(SQLiteDatabase db) {
createAppApk(db);
createInstalledApp(db);
db.execSQL(CREATE_TABLE_INSTALLED_APP);
db.execSQL(CREATE_TABLE_REPO);
insertRepo(
@ -287,16 +290,15 @@ class DBHelper extends SQLiteOpenHelper {
addLastUpdatedToRepo(db, oldVersion);
renameRepoId(db, oldVersion);
populateRepoNames(db, oldVersion);
if (oldVersion < 43) createInstalledApp(db);
addIsSwapToRepo(db, oldVersion);
addChangelogToApp(db, oldVersion);
addIconUrlLargeToApp(db, oldVersion);
updateIconUrlLarge(db, oldVersion);
recreateInstalledCache(db, oldVersion);
addCredentialsToRepo(db, oldVersion);
addAuthorToApp(db, oldVersion);
useMaxValueInMaxSdkVersion(db, oldVersion);
requireTimestampInRepos(db, oldVersion);
recreateInstalledAppTable(db, oldVersion);
}
/**
@ -555,19 +557,19 @@ class DBHelper extends SQLiteOpenHelper {
db.execSQL("create index apk_id on " + TABLE_APK + " (id);");
}
private void createInstalledApp(SQLiteDatabase db) {
Utils.debugLog(TAG, "Creating 'installed app' database table.");
db.execSQL(CREATE_TABLE_INSTALLED_APP);
}
// If any column was added or removed, just drop the table, create it
// again and let the cache be filled from scratch again.
private void recreateInstalledCache(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 51) {
/**
* If any column was added or removed, just drop the table, create it again
* and let the cache be filled from scratch by {@link InstalledAppProviderService}
* For DB versions older than 43, this will create the {@link InstalledAppProvider}
* table for the first time.
*/
private void recreateInstalledAppTable(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 57) {
return;
}
Utils.debugLog(TAG, "(re)creating 'installed app' database table.");
db.execSQL(DROP_TABLE_INSTALLED_APP);
createInstalledApp(db);
db.execSQL(CREATE_TABLE_INSTALLED_APP);
}
private static boolean columnExists(SQLiteDatabase db,

View File

@ -64,10 +64,13 @@ public class InstalledAppProvider extends FDroidProvider {
String VERSION_NAME = "versionName";
String APPLICATION_LABEL = "applicationLabel";
String SIGNATURE = "sig";
String LAST_UPDATE_TIME = "lastUpdateTime";
String HASH_TYPE = "hashType";
String HASH = "hash";
String[] ALL = {
_ID, PACKAGE_NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
SIGNATURE,
SIGNATURE, LAST_UPDATE_TIME, HASH_TYPE, HASH,
};
}
@ -89,6 +92,9 @@ public class InstalledAppProvider extends FDroidProvider {
return Uri.parse("content://" + getAuthority());
}
/**
* @return the {@link Uri} that points to a specific installed app
*/
public static Uri getAppUri(String packageName) {
return Uri.withAppendedPath(getContentUri(), packageName);
}

View File

@ -155,6 +155,12 @@ public class InstalledAppProviderService extends IntentService {
InstalledAppProvider.getApplicationLabel(context, packageInfo.packageName));
contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE,
InstalledAppProvider.getPackageSig(packageInfo));
contentValues.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, packageInfo.lastUpdateTime);
String hashType = "sha256";
String hash = Utils.getBinaryHash(new File(packageInfo.applicationInfo.publicSourceDir), hashType);
contentValues.put(InstalledAppProvider.DataColumns.HASH_TYPE, hashType);
contentValues.put(InstalledAppProvider.DataColumns.HASH, hash);
context.getContentResolver().insert(uri, contentValues);
}