Don't implement 'update' for installed apps, use replace (Fixes #14)

There were some weird edge cases that couldn't quite be pinned down,
whereby installing an app would result in a unique key violation being
hit. One example was when somebody was installing an apk from a file
manager. It seems that this doesn't trigger a PACKAGE_CHANGED, but
rather a PACKAGE_INSTALLED. The end result is that it attempts to insert
a record that already exists in the installed apps table. Because we
have a unique key constraing on the appId, it breaks.

This commit changes the way that we insert installed app details.
Instead of inserting some times, and updating other times, we always
insert. If we hit a unique key violation, the row is deleted, and then
the new values are reinserted.
This commit is contained in:
Peter Serwylo 2014-04-24 07:58:19 +09:30
parent d573bac5b0
commit 71db322b6d
4 changed files with 22 additions and 46 deletions

View File

@ -39,11 +39,12 @@ public class PackageUpgradedReceiver extends PackageReceiver {
Log.d("FDroid", "Updating installed app info for '" + appId + "' to v" + info.versionCode + " (" + info.versionName + ")");
Uri uri = InstalledAppProvider.getAppUri(appId);
Uri uri = InstalledAppProvider.getContentUri();
ContentValues values = new ContentValues(1);
values.put(InstalledAppProvider.DataColumns.APP_ID, info.packageName);
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, info.versionCode);
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName);
context.getContentResolver().update(uri, values, null, null);
context.getContentResolver().insert(uri, values);
}
}

View File

@ -27,7 +27,6 @@ public class InstalledAppCacheUpdater {
private Context context;
private List<PackageInfo> toInsert = new ArrayList<PackageInfo>();
private List<PackageInfo> toUpdate = new ArrayList<PackageInfo>();
private List<String> toDelete = new ArrayList<String>();
protected InstalledAppCacheUpdater(Context context) {
@ -85,14 +84,13 @@ public class InstalledAppCacheUpdater {
* then the cache has changed.
*/
private boolean hasChanged() {
return toInsert.size() > 0 || toUpdate.size() > 0 || toDelete.size() > 0;
return toInsert.size() > 0 || toDelete.size() > 0;
}
private void updateCache() {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.addAll(deleteFromCache(toDelete));
ops.addAll(updateCachedValues(toUpdate));
ops.addAll(insertIntoCache(toInsert));
if (ops.size() > 0) {
@ -114,12 +112,8 @@ public class InstalledAppCacheUpdater {
List<PackageInfo> installedPackages = context.getPackageManager().getInstalledPackages(0);
for (PackageInfo appInfo : installedPackages) {
if (!cachedInfo.containsKey(appInfo.packageName)) {
toInsert.add(appInfo);
} else {
if (cachedInfo.get(appInfo.packageName) < appInfo.versionCode) {
toUpdate.add(appInfo);
}
if (cachedInfo.containsKey(appInfo.packageName)) {
cachedInfo.remove(appInfo.packageName);
}
}
@ -148,22 +142,6 @@ public class InstalledAppCacheUpdater {
return ops;
}
private List<ContentProviderOperation> updateCachedValues(List<PackageInfo> appsToUpdate) {
List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(appsToUpdate.size());
if (appsToUpdate.size() > 0) {
Log.d(TAG, "Preparing to update installed app cache for " + appsToUpdate.size() + " apps.");
for (PackageInfo info : appsToUpdate) {
Uri uri = InstalledAppProvider.getAppUri(info.packageName);
ContentProviderOperation op = ContentProviderOperation.newUpdate(uri)
.withValue(InstalledAppProvider.DataColumns.VERSION_CODE, info.versionCode)
.withValue(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName)
.build();
ops.add(op);
}
}
return ops;
}
private List<ContentProviderOperation> deleteFromCache(List<String> appIds) {
List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(appIds.size());
if (appIds.size() > 0) {

View File

@ -140,7 +140,7 @@ public class InstalledAppProvider extends FDroidProvider {
}
verifyVersionNameNotNull(values);
write().insertOrThrow(getTableName(), null, values);
write().replaceOrThrow(getTableName(), null, values);
if (!isApplyingBatch()) {
getContext().getContentResolver().notifyChange(uri, null);
}
@ -149,20 +149,7 @@ public class InstalledAppProvider extends FDroidProvider {
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
if (matcher.match(uri) != CODE_SINGLE) {
throw new UnsupportedOperationException("Update not supported for " + uri + ".");
}
QuerySelection query = new QuerySelection(where, whereArgs);
query = query.add(queryApp(uri.getLastPathSegment()));
verifyVersionNameNotNull(values);
int count = write().update(getTableName(), values, query.getSelection(), query.getArgs());
if (!isApplyingBatch()) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
throw new UnsupportedOperationException("\"Update' not supported for installed appp provider. Instead, you should insert, and it will overwrite the relevant rows if one exists.");
}
/**

View File

@ -60,11 +60,21 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
try {
getMockContentResolver().update(
InstalledAppProvider.getAppUri("com.example.app2"),
createContentValues(11, "1.1"),
null, null
);
fail();
} catch (UnsupportedOperationException e) {
// We expect this to happen, because we should be using insert() instead.
}
getMockContentResolver().insert(
InstalledAppProvider.getContentUri(),
createContentValues("com.example.app2", 11, "1.1")
);
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app2", 11, "1.1");