Refactored code which inserts apps into database to make testable.

Putting it in the UpdateService made it a little tricky to test, so
I moved it out to a separate class called `RepoPersister`.
This commit is contained in:
Peter Serwylo 2015-11-03 00:57:51 +11:00
parent c6a8bd2139
commit b6218c6d05
2 changed files with 344 additions and 288 deletions

View File

@ -0,0 +1,329 @@
package org.fdroid.fdroid;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.util.Log;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Saves app and apk information to the database after a {@link RepoUpdater} has processed the
* relevant index file.
*/
public class RepoPersister {
private static final String TAG = "RepoPersister";
/**
* When an app already exists in the db, and we are updating it on the off chance that some
* values changed in the index, some fields should not be updated. Rather, they should be
* ignored, because they were explicitly set by the user, and hence can't be automatically
* overridden by the index.
*
* NOTE: In the future, these attributes will be moved to a join table, so that the app table
* is essentially completely transient, and can be nuked at any time.
*/
private static final String[] APP_FIELDS_TO_IGNORE = {
AppProvider.DataColumns.IGNORE_ALLUPDATES,
AppProvider.DataColumns.IGNORE_THISUPDATE,
};
@NonNull
private final Context context;
private Map<String, App> appsToUpdate = new HashMap<>();
private List<Apk> apksToUpdate = new ArrayList<>();
private List<Repo> repos = new ArrayList<>();
public RepoPersister(@NonNull Context context) {
this.context = context;
}
public RepoPersister queueUpdater(RepoUpdater updater) {
queueApps(updater.getApps());
queueApks(updater.getApks());
repos.add(updater.repo);
return this;
}
private void queueApps(List<App> apps) {
for (final App app : apps) {
appsToUpdate.put(app.id, app);
}
}
private void queueApks(List<Apk> apks) {
apksToUpdate.addAll(apks);
}
public void save(List<Repo> disabledRepos) {
List<App> listOfAppsToUpdate = new ArrayList<>();
listOfAppsToUpdate.addAll(appsToUpdate.values());
calcApkCompatibilityFlags(apksToUpdate);
// Need to do this BEFORE updating the apks, otherwise when it continually
// calls "get existing apks for repo X" then it will be getting the newly
// created apks, rather than those from the fresh, juicy index we just processed.
removeApksNoLongerInRepo(apksToUpdate, repos);
int totalInsertsUpdates = listOfAppsToUpdate.size() + apksToUpdate.size();
updateOrInsertApps(listOfAppsToUpdate, totalInsertsUpdates, 0);
updateOrInsertApks(apksToUpdate, totalInsertsUpdates, listOfAppsToUpdate.size());
removeApksFromRepos(disabledRepos);
removeAppsWithoutApks();
// This will sort out the icon urls, compatibility flags. and suggested version
// for each app. It used to happen here in Java code, but was moved to SQL when
// it became apparant we don't always have enough info (depending on which repos
// were updated).
AppProvider.Helper.calcDetailsFromIndex(context);
}
/**
* This cannot be offloaded to the database (as we did with the query which
* updates apps, depending on whether their apks are compatible or not).
* The reason is that we need to interact with the CompatibilityChecker
* in order to see if, and why an apk is not compatible.
*/
private void calcApkCompatibilityFlags(List<Apk> apks) {
final CompatibilityChecker checker = new CompatibilityChecker(context);
for (final Apk apk : apks) {
final List<String> reasons = checker.getIncompatibleReasons(apk);
if (reasons.size() > 0) {
apk.compatible = false;
apk.incompatibleReasons = Utils.CommaSeparatedList.make(reasons);
} else {
apk.compatible = true;
apk.incompatibleReasons = null;
}
}
}
/**
* If a repo was updated (i.e. it is in use, and the index has changed
* since last time we did an update), then we want to remove any apks that
* belong to the repo which are not in the current list of apks that were
* retrieved.
*/
private void removeApksNoLongerInRepo(List<Apk> apksToUpdate, List<Repo> updatedRepos) {
long startTime = System.currentTimeMillis();
List<Apk> toRemove = new ArrayList<>();
final String[] fields = {
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION_CODE,
ApkProvider.DataColumns.VERSION,
};
for (final Repo repo : updatedRepos) {
final List<Apk> existingApks = ApkProvider.Helper.findByRepo(context, repo, fields);
for (final Apk existingApk : existingApks) {
if (!isApkToBeUpdated(existingApk, apksToUpdate)) {
toRemove.add(existingApk);
}
}
}
long duration = System.currentTimeMillis() - startTime;
Utils.debugLog(TAG, "Found " + toRemove.size() + " apks no longer in the updated repos (took " + duration + "ms)");
if (toRemove.size() > 0) {
ApkProvider.Helper.deleteApks(context, toRemove);
}
}
private void updateOrInsertApps(List<App> appsToUpdate, int totalUpdateCount, int currentCount) {
List<ContentProviderOperation> operations = new ArrayList<>();
List<String> knownAppIds = getKnownAppIds(appsToUpdate);
for (final App app : appsToUpdate) {
if (knownAppIds.contains(app.id)) {
operations.add(updateExistingApp(app));
} else {
operations.add(insertNewApp(app));
}
}
Utils.debugLog(TAG, "Updating/inserting " + operations.size() + " apps.");
try {
executeBatchWithStatus(AppProvider.getAuthority(), operations, currentCount, totalUpdateCount);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Could not update or insert apps", e);
}
}
private void executeBatchWithStatus(String providerAuthority,
List<ContentProviderOperation> operations,
int currentCount,
int totalUpdateCount)
throws RemoteException, OperationApplicationException {
int i = 0;
while (i < operations.size()) {
int count = Math.min(operations.size() - i, 100);
ArrayList<ContentProviderOperation> o = new ArrayList<>(operations.subList(i, i + count));
UpdateService.sendStatus(context, UpdateService.STATUS_INFO, context.getString(
R.string.status_inserting,
(int) ((double) (currentCount + i) / totalUpdateCount * 100)));
context.getContentResolver().applyBatch(providerAuthority, o);
i += 100;
}
}
/**
* Return list of apps from the "apks" argument which are already in the database.
*/
private List<Apk> getKnownApks(List<Apk> apks) {
final String[] fields = {
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION,
ApkProvider.DataColumns.VERSION_CODE,
};
return ApkProvider.Helper.knownApks(context, apks, fields);
}
private void updateOrInsertApks(List<Apk> apksToUpdate, int totalApksAppsCount, int currentCount) {
List<ContentProviderOperation> operations = new ArrayList<>();
List<Apk> knownApks = getKnownApks(apksToUpdate);
for (final Apk apk : apksToUpdate) {
boolean known = false;
for (final Apk knownApk : knownApks) {
if (knownApk.id.equals(apk.id) && knownApk.vercode == apk.vercode) {
known = true;
break;
}
}
if (known) {
operations.add(updateExistingApk(apk));
} else {
operations.add(insertNewApk(apk));
knownApks.add(apk); // In case another repo has the same version/id combo for this apk.
}
}
Utils.debugLog(TAG, "Updating/inserting " + operations.size() + " apks.");
try {
executeBatchWithStatus(ApkProvider.getAuthority(), operations, currentCount, totalApksAppsCount);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Could not update/insert apps", e);
}
}
private ContentProviderOperation updateExistingApk(final Apk apk) {
Uri uri = ApkProvider.getContentUri(apk);
ContentValues values = apk.toContentValues();
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
}
private ContentProviderOperation insertNewApk(final Apk apk) {
ContentValues values = apk.toContentValues();
Uri uri = ApkProvider.getContentUri();
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
private ContentProviderOperation updateExistingApp(App app) {
Uri uri = AppProvider.getContentUri(app);
ContentValues values = app.toContentValues();
for (final String toIgnore : APP_FIELDS_TO_IGNORE) {
if (values.containsKey(toIgnore)) {
values.remove(toIgnore);
}
}
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
}
private ContentProviderOperation insertNewApp(App app) {
ContentValues values = app.toContentValues();
Uri uri = AppProvider.getContentUri();
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
private static boolean isApkToBeUpdated(Apk existingApk, List<Apk> apksToUpdate) {
for (final Apk apkToUpdate : apksToUpdate) {
if (apkToUpdate.vercode == existingApk.vercode && apkToUpdate.id.equals(existingApk.id)) {
return true;
}
}
return false;
}
private void removeApksFromRepos(List<Repo> repos) {
for (final Repo repo : repos) {
Uri uri = ApkProvider.getRepoUri(repo.getId());
int numDeleted = context.getContentResolver().delete(uri, null, null);
Utils.debugLog(TAG, "Removing " + numDeleted + " apks from repo " + repo.address);
}
}
private void removeAppsWithoutApks() {
int numDeleted = context.getContentResolver().delete(AppProvider.getNoApksUri(), null, null);
Utils.debugLog(TAG, "Removing " + numDeleted + " apks that don't have any apks");
}
private List<String> getKnownAppIds(List<App> apps) {
List<String> knownAppIds = new ArrayList<>();
if (apps.isEmpty()) {
return knownAppIds;
}
if (apps.size() > AppProvider.MAX_APPS_TO_QUERY) {
int middle = apps.size() / 2;
List<App> apps1 = apps.subList(0, middle);
List<App> apps2 = apps.subList(middle, apps.size());
knownAppIds.addAll(getKnownAppIds(apps1));
knownAppIds.addAll(getKnownAppIds(apps2));
} else {
knownAppIds.addAll(getKnownAppIdsFromProvider(apps));
}
return knownAppIds;
}
/**
* Looks in the database to see which apps we already know about. Only
* returns ids of apps that are in the database if they are in the "apps"
* array.
*/
private List<String> getKnownAppIdsFromProvider(List<App> apps) {
final Uri uri = AppProvider.getContentUri(apps);
final String[] fields = {AppProvider.DataColumns.APP_ID};
Cursor cursor = context.getContentResolver().query(uri, fields, null, null, null);
int knownIdCount = cursor != null ? cursor.getCount() : 0;
List<String> knownIds = new ArrayList<>(knownIdCount);
if (cursor != null) {
if (knownIdCount > 0) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
knownIds.add(cursor.getString(0));
cursor.moveToNext();
}
}
cursor.close();
}
return knownIds;
}
}

View File

@ -23,19 +23,14 @@ import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.RemoteException;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
@ -45,7 +40,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
@ -54,9 +48,7 @@ import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.net.Downloader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UpdateService extends IntentService implements ProgressListener {
@ -89,20 +81,6 @@ public class UpdateService extends IntentService implements ProgressListener {
super("UpdateService");
}
/**
* When an app already exists in the db, and we are updating it on the off chance that some
* values changed in the index, some fields should not be updated. Rather, they should be
* ignored, because they were explicitly set by the user, and hence can't be automatically
* overridden by the index.
*
* NOTE: In the future, these attributes will be moved to a join table, so that the app table
* is essentially completely transient, and can be nuked at any time.
*/
private static final String[] APP_FIELDS_TO_IGNORE = {
AppProvider.DataColumns.IGNORE_ALLUPDATES,
AppProvider.DataColumns.IGNORE_THISUPDATE,
};
public static void updateNow(Context context) {
updateRepoNow(null, context);
}
@ -178,16 +156,16 @@ public class UpdateService extends IntentService implements ProgressListener {
localBroadcastManager.unregisterReceiver(updateStatusReceiver);
}
protected void sendStatus(int statusCode) {
sendStatus(statusCode, null);
protected static void sendStatus(Context context, int statusCode) {
sendStatus(context, statusCode, null);
}
protected void sendStatus(int statusCode, String message) {
protected static void sendStatus(Context context, int statusCode, String message) {
Intent intent = new Intent(LOCAL_ACTION_STATUS);
intent.putExtra(EXTRA_STATUS_CODE, statusCode);
if (!TextUtils.isEmpty(message))
intent.putExtra(EXTRA_MESSAGE, message);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
protected void sendRepoErrorStatus(int statusCode, ArrayList<CharSequence> repoErrors) {
@ -219,7 +197,7 @@ public class UpdateService extends IntentService implements ProgressListener {
String totalSizeFriendly = Utils.getFriendlySize(totalSize);
message = getString(R.string.status_download, repoAddress, downloadedSizeFriendly, totalSizeFriendly, percent);
}
sendStatus(STATUS_INFO, message);
sendStatus(context, STATUS_INFO, message);
}
};
@ -354,8 +332,8 @@ public class UpdateService extends IntentService implements ProgressListener {
List<Repo> repos = RepoProvider.Helper.all(this);
// Process each repo...
Map<String, App> appsToUpdate = new HashMap<>();
List<Apk> apksToUpdate = new ArrayList<>();
RepoPersister appSaver = new RepoPersister(this);
//List<Repo> swapRepos = new ArrayList<>();
List<Repo> unchangedRepos = new ArrayList<>();
List<Repo> updatedRepos = new ArrayList<>();
@ -380,16 +358,13 @@ public class UpdateService extends IntentService implements ProgressListener {
continue;
}
sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
RepoUpdater updater = new RepoUpdater(getBaseContext(), repo);
updater.setProgressListener(this);
try {
updater.update();
if (updater.hasChanged()) {
for (final App app : updater.getApps()) {
appsToUpdate.put(app.id, app);
}
apksToUpdate.addAll(updater.getApks());
appSaver.queueUpdater(updater);
updatedRepos.add(repo);
changes = true;
repoUpdateRememberers.add(updater.getRememberer());
@ -406,29 +381,9 @@ public class UpdateService extends IntentService implements ProgressListener {
if (!changes) {
Utils.debugLog(TAG, "Not checking app details or compatibility, because all repos were up to date.");
} else {
sendStatus(STATUS_INFO, getString(R.string.status_checking_compatibility));
sendStatus(this, STATUS_INFO, getString(R.string.status_checking_compatibility));
List<App> listOfAppsToUpdate = new ArrayList<>();
listOfAppsToUpdate.addAll(appsToUpdate.values());
calcApkCompatibilityFlags(this, apksToUpdate);
// Need to do this BEFORE updating the apks, otherwise when it continually
// calls "get existing apks for repo X" then it will be getting the newly
// created apks, rather than those from the fresh, juicy index we just processed.
removeApksNoLongerInRepo(apksToUpdate, updatedRepos);
int totalInsertsUpdates = listOfAppsToUpdate.size() + apksToUpdate.size();
updateOrInsertApps(listOfAppsToUpdate, totalInsertsUpdates, 0);
updateOrInsertApks(apksToUpdate, totalInsertsUpdates, listOfAppsToUpdate.size());
removeApksFromRepos(disabledRepos);
removeAppsWithoutApks();
// This will sort out the icon urls, compatibility flags. and suggested version
// for each app. It used to happen here in Java code, but was moved to SQL when
// it became apparant we don't always have enough info (depending on which repos
// were updated).
AppProvider.Helper.calcDetailsFromIndex(this);
appSaver.save(disabledRepos);
notifyContentProviders();
@ -448,9 +403,9 @@ public class UpdateService extends IntentService implements ProgressListener {
if (errorRepos.isEmpty()) {
if (changes) {
sendStatus(STATUS_COMPLETE_WITH_CHANGES);
sendStatus(this, STATUS_COMPLETE_WITH_CHANGES);
} else {
sendStatus(STATUS_COMPLETE_AND_SAME);
sendStatus(this, STATUS_COMPLETE_AND_SAME);
}
} else {
if (updatedRepos.size() + unchangedRepos.size() == 0) {
@ -461,7 +416,7 @@ public class UpdateService extends IntentService implements ProgressListener {
}
} catch (Exception e) {
Log.e(TAG, "Exception during update processing", e);
sendStatus(STATUS_ERROR_GLOBAL, e.getMessage());
sendStatus(this, STATUS_ERROR_GLOBAL, e.getMessage());
}
}
@ -470,26 +425,6 @@ public class UpdateService extends IntentService implements ProgressListener {
getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
}
/**
* This cannot be offloaded to the database (as we did with the query which
* updates apps, depending on whether their apks are compatible or not).
* The reason is that we need to interact with the CompatibilityChecker
* in order to see if, and why an apk is not compatible.
*/
private static void calcApkCompatibilityFlags(Context context, List<Apk> apks) {
final CompatibilityChecker checker = new CompatibilityChecker(context);
for (final Apk apk : apks) {
final List<String> reasons = checker.getIncompatibleReasons(apk);
if (reasons.size() > 0) {
apk.compatible = false;
apk.incompatibleReasons = Utils.CommaSeparatedList.make(reasons);
} else {
apk.compatible = true;
apk.incompatibleReasons = null;
}
}
}
private void performUpdateNotification() {
Cursor cursor = getContentResolver().query(
AppProvider.getCanUpdateUri(),
@ -557,214 +492,6 @@ public class UpdateService extends IntentService implements ProgressListener {
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
}
private List<String> getKnownAppIds(List<App> apps) {
List<String> knownAppIds = new ArrayList<>();
if (apps.isEmpty()) {
return knownAppIds;
}
if (apps.size() > AppProvider.MAX_APPS_TO_QUERY) {
int middle = apps.size() / 2;
List<App> apps1 = apps.subList(0, middle);
List<App> apps2 = apps.subList(middle, apps.size());
knownAppIds.addAll(getKnownAppIds(apps1));
knownAppIds.addAll(getKnownAppIds(apps2));
} else {
knownAppIds.addAll(getKnownAppIdsFromProvider(apps));
}
return knownAppIds;
}
/**
* Looks in the database to see which apps we already know about. Only
* returns ids of apps that are in the database if they are in the "apps"
* array.
*/
private List<String> getKnownAppIdsFromProvider(List<App> apps) {
final Uri uri = AppProvider.getContentUri(apps);
final String[] fields = {AppProvider.DataColumns.APP_ID};
Cursor cursor = getContentResolver().query(uri, fields, null, null, null);
int knownIdCount = cursor != null ? cursor.getCount() : 0;
List<String> knownIds = new ArrayList<>(knownIdCount);
if (cursor != null) {
if (knownIdCount > 0) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
knownIds.add(cursor.getString(0));
cursor.moveToNext();
}
}
cursor.close();
}
return knownIds;
}
private void updateOrInsertApps(List<App> appsToUpdate, int totalUpdateCount, int currentCount) {
List<ContentProviderOperation> operations = new ArrayList<>();
List<String> knownAppIds = getKnownAppIds(appsToUpdate);
for (final App app : appsToUpdate) {
if (knownAppIds.contains(app.id)) {
operations.add(updateExistingApp(app));
} else {
operations.add(insertNewApp(app));
}
}
Utils.debugLog(TAG, "Updating/inserting " + operations.size() + " apps.");
try {
executeBatchWithStatus(AppProvider.getAuthority(), operations, currentCount, totalUpdateCount);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Could not update or insert apps", e);
}
}
private void executeBatchWithStatus(String providerAuthority,
List<ContentProviderOperation> operations,
int currentCount,
int totalUpdateCount)
throws RemoteException, OperationApplicationException {
int i = 0;
while (i < operations.size()) {
int count = Math.min(operations.size() - i, 100);
ArrayList<ContentProviderOperation> o = new ArrayList<>(operations.subList(i, i + count));
sendStatus(STATUS_INFO, getString(
R.string.status_inserting,
(int) ((double) (currentCount + i) / totalUpdateCount * 100)));
getContentResolver().applyBatch(providerAuthority, o);
i += 100;
}
}
/**
* Return list of apps from the "apks" argument which are already in the database.
*/
private List<Apk> getKnownApks(List<Apk> apks) {
final String[] fields = {
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION,
ApkProvider.DataColumns.VERSION_CODE,
};
return ApkProvider.Helper.knownApks(this, apks, fields);
}
private void updateOrInsertApks(List<Apk> apksToUpdate, int totalApksAppsCount, int currentCount) {
List<ContentProviderOperation> operations = new ArrayList<>();
List<Apk> knownApks = getKnownApks(apksToUpdate);
for (final Apk apk : apksToUpdate) {
boolean known = false;
for (final Apk knownApk : knownApks) {
if (knownApk.id.equals(apk.id) && knownApk.vercode == apk.vercode) {
known = true;
break;
}
}
if (known) {
operations.add(updateExistingApk(apk));
} else {
operations.add(insertNewApk(apk));
knownApks.add(apk); // In case another repo has the same version/id combo for this apk.
}
}
Utils.debugLog(TAG, "Updating/inserting " + operations.size() + " apks.");
try {
executeBatchWithStatus(ApkProvider.getAuthority(), operations, currentCount, totalApksAppsCount);
} catch (RemoteException | OperationApplicationException e) {
Log.e(TAG, "Could not update/insert apps", e);
}
}
private ContentProviderOperation updateExistingApk(final Apk apk) {
Uri uri = ApkProvider.getContentUri(apk);
ContentValues values = apk.toContentValues();
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
}
private ContentProviderOperation insertNewApk(final Apk apk) {
ContentValues values = apk.toContentValues();
Uri uri = ApkProvider.getContentUri();
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
private ContentProviderOperation updateExistingApp(App app) {
Uri uri = AppProvider.getContentUri(app);
ContentValues values = app.toContentValues();
for (final String toIgnore : APP_FIELDS_TO_IGNORE) {
if (values.containsKey(toIgnore)) {
values.remove(toIgnore);
}
}
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
}
private ContentProviderOperation insertNewApp(App app) {
ContentValues values = app.toContentValues();
Uri uri = AppProvider.getContentUri();
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
/**
* If a repo was updated (i.e. it is in use, and the index has changed
* since last time we did an update), then we want to remove any apks that
* belong to the repo which are not in the current list of apks that were
* retrieved.
*/
private void removeApksNoLongerInRepo(List<Apk> apksToUpdate, List<Repo> updatedRepos) {
long startTime = System.currentTimeMillis();
List<Apk> toRemove = new ArrayList<>();
final String[] fields = {
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION_CODE,
ApkProvider.DataColumns.VERSION,
};
for (final Repo repo : updatedRepos) {
final List<Apk> existingApks = ApkProvider.Helper.findByRepo(this, repo, fields);
for (final Apk existingApk : existingApks) {
if (!isApkToBeUpdated(existingApk, apksToUpdate)) {
toRemove.add(existingApk);
}
}
}
long duration = System.currentTimeMillis() - startTime;
Utils.debugLog(TAG, "Found " + toRemove.size() + " apks no longer in the updated repos (took " + duration + "ms)");
if (toRemove.size() > 0) {
ApkProvider.Helper.deleteApks(this, toRemove);
}
}
private static boolean isApkToBeUpdated(Apk existingApk, List<Apk> apksToUpdate) {
for (final Apk apkToUpdate : apksToUpdate) {
if (apkToUpdate.vercode == existingApk.vercode && apkToUpdate.id.equals(existingApk.id)) {
return true;
}
}
return false;
}
private void removeApksFromRepos(List<Repo> repos) {
for (final Repo repo : repos) {
Uri uri = ApkProvider.getRepoUri(repo.getId());
int numDeleted = getContentResolver().delete(uri, null, null);
Utils.debugLog(TAG, "Removing " + numDeleted + " apks from repo " + repo.address);
}
}
private void removeAppsWithoutApks() {
int numDeleted = getContentResolver().delete(AppProvider.getNoApksUri(), null, null);
Utils.debugLog(TAG, "Removing " + numDeleted + " apks that don't have any apks");
}
/**
* Received progress event from the RepoXMLHandler. It could be progress
* downloading from the repo, or perhaps processing the info from the repo.
@ -783,6 +510,6 @@ public class UpdateService extends IntentService implements ProgressListener {
message = getString(R.string.status_processing_xml_percent, repoAddress, downloadedSize, totalSize, percent);
break;
}
sendStatus(STATUS_INFO, message);
sendStatus(this, STATUS_INFO, message);
}
}