Fixed repo updater tests. Fix to temp app/apk providers.

The repo xml handler now has a different mechanism for returning
data about the parsed xml file. This is done via a callback, rather
than storing the data in member variables. The tests now deal with
this correctly.

The update/delete operations of the TempAp[pk]Provider's didn't
work, so that has now been fixed.
This commit is contained in:
Peter Serwylo 2015-09-06 08:15:19 +10:00
parent b34853a776
commit 1d951e7689
10 changed files with 286 additions and 224 deletions

View File

@ -46,7 +46,6 @@ import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
/** /**
*
* Responsible for updating an individual repository. This will: * Responsible for updating an individual repository. This will:
* * Download the index.jar * * Download the index.jar
* * Verify that it is signed correctly and by the correct certificate * * Verify that it is signed correctly and by the correct certificate
@ -74,19 +73,23 @@ public class RepoUpdater {
* is essentially completely transient, and can be nuked at any time. * is essentially completely transient, and can be nuked at any time.
*/ */
private static final String[] APP_FIELDS_TO_IGNORE = { private static final String[] APP_FIELDS_TO_IGNORE = {
AppProvider.DataColumns.IGNORE_ALLUPDATES, AppProvider.DataColumns.IGNORE_ALLUPDATES,
AppProvider.DataColumns.IGNORE_THISUPDATE AppProvider.DataColumns.IGNORE_THISUPDATE,
}; };
@NonNull protected final Context context; @NonNull
@NonNull protected final Repo repo; protected final Context context;
protected boolean hasChanged = false; @NonNull
@Nullable protected ProgressListener progressListener; protected final Repo repo;
protected boolean hasChanged;
@Nullable
protected ProgressListener progressListener;
private String cacheTag; private String cacheTag;
private X509Certificate signingCertFromJar; private X509Certificate signingCertFromJar;
/** /**
* Updates an app repo as read out of the database into a {@link Repo} instance. * Updates an app repo as read out of the database into a {@link Repo} instance.
*
* @param repo A {@link Repo} read out of the local database * @param repo A {@link Repo} read out of the local database
*/ */
public RepoUpdater(@NonNull Context context, @NonNull Repo repo) { public RepoUpdater(@NonNull Context context, @NonNull Repo repo) {
@ -160,8 +163,8 @@ public class RepoUpdater {
} }
} }
private ContentValues repoDetailsToSave = null; private ContentValues repoDetailsToSave;
private String signingCertFromIndexXml = null; private String signingCertFromIndexXml;
private RepoXMLHandler.IndexReceiver createIndexReceiver() { private RepoXMLHandler.IndexReceiver createIndexReceiver() {
return new RepoXMLHandler.IndexReceiver() { return new RepoXMLHandler.IndexReceiver() {
@ -184,12 +187,12 @@ public class RepoUpdater {
/** /**
* My crappy benchmark with a Nexus 4, Android 5.0 on a fairly crappy internet connection I get: * My crappy benchmark with a Nexus 4, Android 5.0 on a fairly crappy internet connection I get:
* * 25 = 37 seconds * * 25 = 37 seconds
* * 50 = 33 seconds * * 50 = 33 seconds
* * 100 = 30 seconds * * 100 = 30 seconds
* * 200 = 32 seconds * * 200 = 32 seconds
* Raising this means more memory consumption, so we'd like it to be low, but not * Raising this means more memory consumption, so we'd like it to be low, but not
* so low that it takes too long. * so low that it takes too long.
*/ */
private static final int MAX_APP_BUFFER = 50; private static final int MAX_APP_BUFFER = 50;
@ -242,7 +245,7 @@ public class RepoUpdater {
try { try {
context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations); context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations);
} catch (RemoteException|OperationApplicationException e) { } catch (RemoteException | OperationApplicationException e) {
throw new UpdateException(repo, "An internal error occured while updating the database", e); throw new UpdateException(repo, "An internal error occured while updating the database", e);
} }
} }
@ -320,7 +323,7 @@ public class RepoUpdater {
* array. * array.
*/ */
private boolean isAppInDatabase(App app) { private boolean isAppInDatabase(App app) {
String[] fields = { AppProvider.DataColumns.APP_ID }; String[] fields = {AppProvider.DataColumns.APP_ID};
App found = AppProvider.Helper.findById(context.getContentResolver(), app.id, fields); App found = AppProvider.Helper.findById(context.getContentResolver(), app.id, fields);
return found != null; return found != null;
} }
@ -429,7 +432,7 @@ public class RepoUpdater {
JarFile jarFile = new JarFile(downloadedFile, true); JarFile jarFile = new JarFile(downloadedFile, true);
JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml"); JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry), indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
progressListener, repo, (int) indexEntry.getSize()); progressListener, repo, (int) indexEntry.getSize());
// Process the index... // Process the index...
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
@ -558,7 +561,7 @@ public class RepoUpdater {
* check that the signing certificate in the jar matches that fingerprint. * check that the signing certificate in the jar matches that fingerprint.
*/ */
private void verifyAndStoreTOFUCerts(String certFromIndexXml, X509Certificate rawCertFromJar) private void verifyAndStoreTOFUCerts(String certFromIndexXml, X509Certificate rawCertFromJar)
throws SigningException { throws SigningException {
if (repo.pubkey != null) if (repo.pubkey != null)
return; // there is a repo.pubkey already, nothing to TOFU return; // there is a repo.pubkey already, nothing to TOFU
@ -570,7 +573,7 @@ public class RepoUpdater {
String fingerprintFromIndexXml = Utils.calcFingerprint(certFromIndexXml); String fingerprintFromIndexXml = Utils.calcFingerprint(certFromIndexXml);
String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar); String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml) if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml)
|| !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) { || !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
throw new SigningException(repo, "Supplied certificate fingerprint does not match!"); throw new SigningException(repo, "Supplied certificate fingerprint does not match!");
} }
} // else - no info to check things are valid, so just Trust On First Use } // else - no info to check things are valid, so just Trust On First Use
@ -600,14 +603,14 @@ public class RepoUpdater {
// repo and repo.pubkey must be pre-loaded from the database // repo and repo.pubkey must be pre-loaded from the database
if (TextUtils.isEmpty(repo.pubkey) if (TextUtils.isEmpty(repo.pubkey)
|| TextUtils.isEmpty(certFromJar) || TextUtils.isEmpty(certFromJar)
|| TextUtils.isEmpty(certFromIndexXml)) || TextUtils.isEmpty(certFromIndexXml))
throw new SigningException(repo, "A empty repo or signing certificate is invalid!"); throw new SigningException(repo, "A empty repo or signing certificate is invalid!");
// though its called repo.pubkey, its actually a X509 certificate // though its called repo.pubkey, its actually a X509 certificate
if (repo.pubkey.equals(certFromJar) if (repo.pubkey.equals(certFromJar)
&& repo.pubkey.equals(certFromIndexXml) && repo.pubkey.equals(certFromIndexXml)
&& certFromIndexXml.equals(certFromJar)) { && certFromIndexXml.equals(certFromJar)) {
return; // we have a match! return; // we have a match!
} }
throw new SigningException(repo, "Signing certificate does not match!"); throw new SigningException(repo, "Signing certificate does not match!");

View File

@ -45,12 +45,12 @@ public class RepoXMLHandler extends DefaultHandler {
private App curapp; private App curapp;
private Apk curapk; private Apk curapk;
private String currentApkHashType = null; private String currentApkHashType;
// After processing the XML, these will be -1 if the index didn't specify // After processing the XML, these will be -1 if the index didn't specify
// them - otherwise it will be the value specified. // them - otherwise it will be the value specified.
private int repoMaxAge = -1; private int repoMaxAge = -1;
private int repoVersion = 0; private int repoVersion;
private String repoDescription; private String repoDescription;
private String repoName; private String repoName;
@ -61,6 +61,7 @@ public class RepoXMLHandler extends DefaultHandler {
interface IndexReceiver { interface IndexReceiver {
void receiveRepo(String name, String description, String signingCert, int maxage, int version); void receiveRepo(String name, String description, String signingCert, int maxage, int version);
void receiveApp(App app, List<Apk> packages); void receiveApp(App app, List<Apk> packages);
} }
@ -78,7 +79,7 @@ public class RepoXMLHandler extends DefaultHandler {
@Override @Override
public void endElement(String uri, String localName, String qName) public void endElement(String uri, String localName, String qName)
throws SAXException { throws SAXException {
if ("application".equals(localName) && curapp != null) { if ("application".equals(localName) && curapp != null) {
onApplicationParsed(); onApplicationParsed();
@ -94,53 +95,53 @@ public class RepoXMLHandler extends DefaultHandler {
final String str = curchars.toString().trim(); final String str = curchars.toString().trim();
if (curapk != null) { if (curapk != null) {
switch (localName) { switch (localName) {
case "version": case "version":
curapk.version = str; curapk.version = str;
break; break;
case "versioncode": case "versioncode":
curapk.vercode = Utils.parseInt(str, -1); curapk.vercode = Utils.parseInt(str, -1);
break; break;
case "size": case "size":
curapk.size = Utils.parseInt(str, 0); curapk.size = Utils.parseInt(str, 0);
break; break;
case "hash": case "hash":
if (currentApkHashType == null || currentApkHashType.equals("md5")) { if (currentApkHashType == null || "md5".equals(currentApkHashType)) {
if (curapk.hash == null) { if (curapk.hash == null) {
curapk.hash = str;
curapk.hashType = "SHA-256";
}
} else if ("sha256".equals(currentApkHashType)) {
curapk.hash = str; curapk.hash = str;
curapk.hashType = "SHA-256"; curapk.hashType = "SHA-256";
} }
} else if (currentApkHashType.equals("sha256")) { break;
curapk.hash = str; case "sig":
curapk.hashType = "SHA-256"; curapk.sig = str;
} break;
break; case "srcname":
case "sig": curapk.srcname = str;
curapk.sig = str; break;
break; case "apkname":
case "srcname": curapk.apkName = str;
curapk.srcname = str; break;
break; case "sdkver":
case "apkname": curapk.minSdkVersion = Utils.parseInt(str, 0);
curapk.apkName = str; break;
break; case "maxsdkver":
case "sdkver": curapk.maxSdkVersion = Utils.parseInt(str, 0);
curapk.minSdkVersion = Utils.parseInt(str, 0); break;
break; case "added":
case "maxsdkver": curapk.added = Utils.parseDate(str, null);
curapk.maxSdkVersion = Utils.parseInt(str, 0); break;
break; case "permissions":
case "added": curapk.permissions = Utils.CommaSeparatedList.make(str);
curapk.added = Utils.parseDate(str, null); break;
break; case "features":
case "permissions": curapk.features = Utils.CommaSeparatedList.make(str);
curapk.permissions = Utils.CommaSeparatedList.make(str); break;
break; case "nativecode":
case "features": curapk.nativecode = Utils.CommaSeparatedList.make(str);
curapk.features = Utils.CommaSeparatedList.make(str); break;
break;
case "nativecode":
curapk.nativecode = Utils.CommaSeparatedList.make(str);
break;
} }
} else if (curapp != null) { } else if (curapp != null) {
switch (localName) { switch (localName) {
@ -237,7 +238,7 @@ public class RepoXMLHandler extends DefaultHandler {
@Override @Override
public void startElement(String uri, String localName, String qName, public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException { Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes); super.startElement(uri, localName, qName, attributes);
if ("repo".equals(localName)) { if ("repo".equals(localName)) {

View File

@ -101,7 +101,7 @@ public class UpdateService extends IntentService implements ProgressListener {
public static void schedule(Context ctx) { public static void schedule(Context ctx) {
SharedPreferences prefs = PreferenceManager SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(ctx); .getDefaultSharedPreferences(ctx);
String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0"); String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0");
int interval = Integer.parseInt(sint); int interval = Integer.parseInt(sint);
@ -109,12 +109,12 @@ public class UpdateService extends IntentService implements ProgressListener {
PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0); PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0);
AlarmManager alarm = (AlarmManager) ctx AlarmManager alarm = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE); .getSystemService(Context.ALARM_SERVICE);
alarm.cancel(pending); alarm.cancel(pending);
if (interval > 0) { if (interval > 0) {
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 5000, SystemClock.elapsedRealtime() + 5000,
AlarmManager.INTERVAL_HOUR, pending); AlarmManager.INTERVAL_HOUR, pending);
Utils.debugLog(TAG, "Update scheduler alarm set"); Utils.debugLog(TAG, "Update scheduler alarm set");
} else { } else {
Utils.debugLog(TAG, "Update scheduler alarm not set"); Utils.debugLog(TAG, "Update scheduler alarm not set");
@ -128,17 +128,17 @@ public class UpdateService extends IntentService implements ProgressListener {
localBroadcastManager = LocalBroadcastManager.getInstance(this); localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(downloadProgressReceiver, localBroadcastManager.registerReceiver(downloadProgressReceiver,
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS)); new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
localBroadcastManager.registerReceiver(updateStatusReceiver, localBroadcastManager.registerReceiver(updateStatusReceiver,
new IntentFilter(LOCAL_ACTION_STATUS)); new IntentFilter(LOCAL_ACTION_STATUS));
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = new NotificationCompat.Builder(this) notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_refresh_white) .setSmallIcon(R.drawable.ic_refresh_white)
.setOngoing(true) .setOngoing(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE) .setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentTitle(getString(R.string.update_notification_title)); .setContentTitle(getString(R.string.update_notification_title));
// Android docs are a little sketchy, however it seems that Gingerbread is the last // Android docs are a little sketchy, however it seems that Gingerbread is the last
// sdk that made a content intent mandatory: // sdk that made a content intent mandatory:
@ -147,7 +147,7 @@ public class UpdateService extends IntentService implements ProgressListener {
// //
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
Intent pendingIntent = new Intent(this, FDroid.class); Intent pendingIntent = new Intent(this, FDroid.class);
pendingIntent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); pendingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, pendingIntent, PendingIntent.FLAG_UPDATE_CURRENT)); notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, pendingIntent, PendingIntent.FLAG_UPDATE_CURRENT));
} }
@ -234,7 +234,7 @@ public class UpdateService extends IntentService implements ProgressListener {
switch (resultCode) { switch (resultCode) {
case STATUS_INFO: case STATUS_INFO:
notificationBuilder.setContentText(message) notificationBuilder.setContentText(message)
.setCategory(NotificationCompat.CATEGORY_SERVICE); .setCategory(NotificationCompat.CATEGORY_SERVICE);
if (progress != -1) { if (progress != -1) {
notificationBuilder.setProgress(100, progress, false); notificationBuilder.setProgress(100, progress, false);
} }
@ -243,8 +243,8 @@ public class UpdateService extends IntentService implements ProgressListener {
case STATUS_ERROR_GLOBAL: case STATUS_ERROR_GLOBAL:
text = context.getString(R.string.global_error_updating_repos, message); text = context.getString(R.string.global_error_updating_repos, message);
notificationBuilder.setContentText(text) notificationBuilder.setContentText(text)
.setCategory(NotificationCompat.CATEGORY_ERROR) .setCategory(NotificationCompat.CATEGORY_ERROR)
.setSmallIcon(android.R.drawable.ic_dialog_alert); .setSmallIcon(android.R.drawable.ic_dialog_alert);
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build()); notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
Toast.makeText(context, text, Toast.LENGTH_LONG).show(); Toast.makeText(context, text, Toast.LENGTH_LONG).show();
break; break;
@ -261,8 +261,8 @@ public class UpdateService extends IntentService implements ProgressListener {
} }
text = msgBuilder.toString(); text = msgBuilder.toString();
notificationBuilder.setContentText(text) notificationBuilder.setContentText(text)
.setCategory(NotificationCompat.CATEGORY_ERROR) .setCategory(NotificationCompat.CATEGORY_ERROR)
.setSmallIcon(android.R.drawable.ic_dialog_info); .setSmallIcon(android.R.drawable.ic_dialog_info);
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build()); notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
Toast.makeText(context, text, Toast.LENGTH_LONG).show(); Toast.makeText(context, text, Toast.LENGTH_LONG).show();
break; break;
@ -271,7 +271,7 @@ public class UpdateService extends IntentService implements ProgressListener {
case STATUS_COMPLETE_AND_SAME: case STATUS_COMPLETE_AND_SAME:
text = context.getString(R.string.repos_unchanged); text = context.getString(R.string.repos_unchanged);
notificationBuilder.setContentText(text) notificationBuilder.setContentText(text)
.setCategory(NotificationCompat.CATEGORY_SERVICE); .setCategory(NotificationCompat.CATEGORY_SERVICE);
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build()); notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
break; break;
} }
@ -281,10 +281,11 @@ public class UpdateService extends IntentService implements ProgressListener {
/** /**
* Check whether it is time to run the scheduled update. * Check whether it is time to run the scheduled update.
* We don't want to run if: * We don't want to run if:
* - The time between scheduled runs is set to zero (though don't know * - The time between scheduled runs is set to zero (though don't know
* when that would occur) * when that would occur)
* - Last update was too recent * - Last update was too recent
* - Not on wifi, but the property for "Only auto update on wifi" is set. * - Not on wifi, but the property for "Only auto update on wifi" is set.
*
* @return True if we are due for a scheduled update. * @return True if we are due for a scheduled update.
*/ */
private boolean verifyIsTimeForScheduledRun() { private boolean verifyIsTimeForScheduledRun() {
@ -299,7 +300,7 @@ public class UpdateService extends IntentService implements ProgressListener {
long elapsed = System.currentTimeMillis() - lastUpdate; long elapsed = System.currentTimeMillis() - lastUpdate;
if (elapsed < interval * 60 * 60 * 1000) { if (elapsed < interval * 60 * 60 * 1000) {
Log.i(TAG, "Skipping update - done " + elapsed Log.i(TAG, "Skipping update - done " + elapsed
+ "ms ago, interval is " + interval + " hours"); + "ms ago, interval is " + interval + " hours");
return false; return false;
} }
@ -319,7 +320,7 @@ public class UpdateService extends IntentService implements ProgressListener {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (activeNetwork.getType() != ConnectivityManager.TYPE_WIFI if (activeNetwork.getType() != ConnectivityManager.TYPE_WIFI
&& prefs.getBoolean(Preferences.PREF_UPD_WIFI_ONLY, false)) { && prefs.getBoolean(Preferences.PREF_UPD_WIFI_ONLY, false)) {
Log.i(TAG, "Skipping update - wifi not available"); Log.i(TAG, "Skipping update - wifi not available");
return false; return false;
} }
@ -432,9 +433,9 @@ public class UpdateService extends IntentService implements ProgressListener {
private void performUpdateNotification() { private void performUpdateNotification() {
Cursor cursor = getContentResolver().query( Cursor cursor = getContentResolver().query(
AppProvider.getCanUpdateUri(), AppProvider.getCanUpdateUri(),
AppProvider.DataColumns.ALL, AppProvider.DataColumns.ALL,
null, null, null); null, null, null);
if (cursor != null) { if (cursor != null) {
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
showAppUpdatesNotification(cursor); showAppUpdatesNotification(cursor);
@ -446,8 +447,8 @@ public class UpdateService extends IntentService implements ProgressListener {
private PendingIntent createNotificationIntent() { private PendingIntent createNotificationIntent() {
Intent notifyIntent = new Intent(this, FDroid.class).putExtra(FDroid.EXTRA_TAB_UPDATE, true); Intent notifyIntent = new Intent(this, FDroid.class).putExtra(FDroid.EXTRA_TAB_UPDATE, true);
TaskStackBuilder stackBuilder = TaskStackBuilder TaskStackBuilder stackBuilder = TaskStackBuilder
.create(this).addParentStack(FDroid.class) .create(this).addParentStack(FDroid.class)
.addNextIntent(notifyIntent); .addNextIntent(notifyIntent);
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
} }
@ -456,8 +457,8 @@ public class UpdateService extends IntentService implements ProgressListener {
private NotificationCompat.Style createNotificationBigStyle(Cursor hasUpdates) { private NotificationCompat.Style createNotificationBigStyle(Cursor hasUpdates) {
final String contentText = hasUpdates.getCount() > 1 final String contentText = hasUpdates.getCount() > 1
? getString(R.string.many_updates_available, hasUpdates.getCount()) ? getString(R.string.many_updates_available, hasUpdates.getCount())
: getString(R.string.one_update_available); : getString(R.string.one_update_available);
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.setBigContentTitle(contentText); inboxStyle.setBigContentTitle(contentText);
@ -482,17 +483,17 @@ public class UpdateService extends IntentService implements ProgressListener {
final int icon = Build.VERSION.SDK_INT >= 11 ? R.drawable.ic_stat_notify_updates : R.drawable.ic_launcher; final int icon = Build.VERSION.SDK_INT >= 11 ? R.drawable.ic_stat_notify_updates : R.drawable.ic_launcher;
final String contentText = hasUpdates.getCount() > 1 final String contentText = hasUpdates.getCount() > 1
? getString(R.string.many_updates_available, hasUpdates.getCount()) ? getString(R.string.many_updates_available, hasUpdates.getCount())
: getString(R.string.one_update_available); : getString(R.string.one_update_available);
NotificationCompat.Builder builder = NotificationCompat.Builder builder =
new NotificationCompat.Builder(this) new NotificationCompat.Builder(this)
.setAutoCancel(true) .setAutoCancel(true)
.setContentTitle(getString(R.string.fdroid_updates_available)) .setContentTitle(getString(R.string.fdroid_updates_available))
.setSmallIcon(icon) .setSmallIcon(icon)
.setContentIntent(createNotificationIntent()) .setContentIntent(createNotificationIntent())
.setContentText(contentText) .setContentText(contentText)
.setStyle(createNotificationBigStyle(hasUpdates)); .setStyle(createNotificationBigStyle(hasUpdates));
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build()); notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
} }

View File

@ -229,10 +229,10 @@ public class ApkProvider extends FDroidProvider {
private static final int CODE_REPO_APPS = CODE_APKS + 1; private static final int CODE_REPO_APPS = CODE_APKS + 1;
protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1; protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1;
private static final String PROVIDER_NAME = "ApkProvider"; private static final String PROVIDER_NAME = "ApkProvider";
protected static final String PATH_APK = "apk"; protected static final String PATH_APK = "apk";
private static final String PATH_APKS = "apks"; private static final String PATH_APKS = "apks";
private static final String PATH_APP = "app"; private static final String PATH_APP = "app";
private static final String PATH_REPO = "repo"; private static final String PATH_REPO = "repo";
private static final String PATH_REPO_APPS = "repo-apps"; private static final String PATH_REPO_APPS = "repo-apps";
protected static final String PATH_REPO_APK = "repo-apk"; protected static final String PATH_REPO_APK = "repo-apk";
@ -411,7 +411,7 @@ public class ApkProvider extends FDroidProvider {
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
} }
private QuerySelection queryRepo(long repoId) { protected QuerySelection queryRepo(long repoId) {
final String selection = DataColumns.REPO_ID + " = ? "; final String selection = DataColumns.REPO_ID + " = ? ";
final String[] args = {Long.toString(repoId)}; final String[] args = {Long.toString(repoId)};
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
@ -421,7 +421,7 @@ public class ApkProvider extends FDroidProvider {
return queryRepo(repoId).add(AppProvider.queryApps(appIds, DataColumns.APK_ID)); return queryRepo(repoId).add(AppProvider.queryApps(appIds, DataColumns.APK_ID));
} }
private QuerySelection queryApks(String apkKeys) { protected QuerySelection queryApks(String apkKeys) {
final String[] apkDetails = apkKeys.split(","); final String[] apkDetails = apkKeys.split(",");
if (apkDetails.length > MAX_APKS_TO_QUERY) { if (apkDetails.length > MAX_APKS_TO_QUERY) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -561,11 +561,13 @@ public class ApkProvider extends FDroidProvider {
@Override @Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
if (matcher.match(uri) != CODE_SINGLE) { if (matcher.match(uri) != CODE_SINGLE) {
throw new UnsupportedOperationException("Cannot update anything other than a single apk."); throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
} }
return performUpdateUnchecked(uri, values, where, whereArgs);
}
protected int performUpdateUnchecked(Uri uri, ContentValues values, String where, String[] whereArgs) {
validateFields(DataColumns.ALL, values); validateFields(DataColumns.ALL, values);
removeRepoFields(values); removeRepoFields(values);
@ -577,7 +579,6 @@ public class ApkProvider extends FDroidProvider {
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
} }
return numRows; return numRows;
} }
} }

View File

@ -286,7 +286,7 @@ public class AppProvider extends FDroidProvider {
} }
private static class Query extends QueryBuilder { private class Query extends QueryBuilder {
private boolean isSuggestedApkTableAdded; private boolean isSuggestedApkTableAdded;
private boolean requiresInstalledTable; private boolean requiresInstalledTable;
@ -366,14 +366,14 @@ public class AppProvider extends FDroidProvider {
if (field.equals(DataColumns.CATEGORIES)) { if (field.equals(DataColumns.CATEGORIES)) {
categoryFieldAdded = true; categoryFieldAdded = true;
} }
appendField(field, "fdroid_app"); appendField(field, getTableName());
break; break;
} }
} }
private void appendCountField() { private void appendCountField() {
countFieldAppended = true; countFieldAppended = true;
appendField("COUNT( DISTINCT fdroid_app.id ) AS " + DataColumns._COUNT); appendField("COUNT( DISTINCT " + getTableName() + ".id ) AS " + DataColumns._COUNT);
} }
private void addSuggestedApkVersionField() { private void addSuggestedApkVersionField() {
@ -388,7 +388,7 @@ public class AppProvider extends FDroidProvider {
leftJoin( leftJoin(
DBHelper.TABLE_APK, DBHelper.TABLE_APK,
"suggestedApk", "suggestedApk",
"fdroid_app.suggestedVercode = suggestedApk.vercode AND fdroid_app.id = suggestedApk.id"); getTableName() + ".suggestedVercode = suggestedApk.vercode AND " + getTableName() + ".id = suggestedApk.id");
} }
appendField(fieldName, "suggestedApk", alias); appendField(fieldName, "suggestedApk", alias);
} }
@ -570,15 +570,15 @@ public class AppProvider extends FDroidProvider {
} }
private AppQuerySelection queryCanUpdate() { private AppQuerySelection queryCanUpdate() {
final String ignoreCurrent = " fdroid_app.ignoreThisUpdate != fdroid_app.suggestedVercode "; final String ignoreCurrent = getTableName() + ".ignoreThisUpdate != " + getTableName() + ".suggestedVercode ";
final String ignoreAll = " fdroid_app.ignoreAllUpdates != 1 "; final String ignoreAll = getTableName() + ".ignoreAllUpdates != 1 ";
final String ignore = " ( " + ignoreCurrent + " AND " + ignoreAll + " ) "; final String ignore = " ( " + ignoreCurrent + " AND " + ignoreAll + " ) ";
final String where = ignore + " AND fdroid_app." + DataColumns.SUGGESTED_VERSION_CODE + " > installed.versionCode"; final String where = ignore + " AND " + getTableName() + "." + DataColumns.SUGGESTED_VERSION_CODE + " > installed.versionCode";
return new AppQuerySelection(where).requireNaturalInstalledTable(); return new AppQuerySelection(where).requireNaturalInstalledTable();
} }
private AppQuerySelection queryRepo(long repoId) { private AppQuerySelection queryRepo(long repoId) {
final String selection = " fdroid_apk.repo = ? "; final String selection = DBHelper.TABLE_APK + ".repo = ? ";
final String[] args = {String.valueOf(repoId)}; final String[] args = {String.valueOf(repoId)};
return new AppQuerySelection(selection, args); return new AppQuerySelection(selection, args);
} }
@ -589,10 +589,10 @@ public class AppProvider extends FDroidProvider {
private AppQuerySelection querySearch(String query) { private AppQuerySelection querySearch(String query) {
final String[] columns = { final String[] columns = {
"fdroid_app.id", getTableName() + ".id",
"fdroid_app.name", getTableName() + ".name",
"fdroid_app.summary", getTableName() + ".summary",
"fdroid_app.description", getTableName() + ".description",
}; };
// Remove duplicates, surround in % for case insensitive searching // Remove duplicates, surround in % for case insensitive searching
@ -632,15 +632,16 @@ public class AppProvider extends FDroidProvider {
return new AppQuerySelection(selection.toString(), selectionKeywords); return new AppQuerySelection(selection.toString(), selectionKeywords);
} }
private AppQuerySelection querySingle(String id) { protected AppQuerySelection querySingle(String id) {
final String selection = "fdroid_app.id = ?"; final String selection = getTableName() + ".id = ?";
final String[] args = {id}; final String[] args = {id};
return new AppQuerySelection(selection, args); return new AppQuerySelection(selection, args);
} }
private AppQuerySelection queryIgnored() { private AppQuerySelection queryIgnored() {
final String selection = "fdroid_app.ignoreAllUpdates = 1 OR " + final String table = getTableName();
"fdroid_app.ignoreThisUpdate >= fdroid_app.suggestedVercode"; final String selection = table + ".ignoreAllUpdates = 1 OR " +
table + ".ignoreThisUpdate >= " + table + ".suggestedVercode";
return new AppQuerySelection(selection); return new AppQuerySelection(selection);
} }
@ -653,13 +654,13 @@ public class AppProvider extends FDroidProvider {
} }
private AppQuerySelection queryNewlyAdded() { private AppQuerySelection queryNewlyAdded() {
final String selection = "fdroid_app.added > ?"; final String selection = getTableName() + ".added > ?";
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")}; final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
return new AppQuerySelection(selection, args); return new AppQuerySelection(selection, args);
} }
private AppQuerySelection queryRecentlyUpdated() { private AppQuerySelection queryRecentlyUpdated() {
final String selection = "fdroid_app.added != fdroid_app.lastUpdated AND fdroid_app.lastUpdated > ?"; final String selection = getTableName() + ".added != fdroid_app.lastUpdated AND fdroid_app.lastUpdated > ?";
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")}; final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
return new AppQuerySelection(selection, args); return new AppQuerySelection(selection, args);
} }
@ -668,10 +669,10 @@ public class AppProvider extends FDroidProvider {
// TODO: In the future, add a new table for categories, // TODO: In the future, add a new table for categories,
// so we can join onto it. // so we can join onto it.
final String selection = final String selection =
" fdroid_app.categories = ? OR " + // Only category e.g. "internet" getTableName() + ".categories = ? OR " + // Only category e.g. "internet"
" fdroid_app.categories LIKE ? OR " + // First category e.g. "internet,%" getTableName() + ".categories LIKE ? OR " + // First category e.g. "internet,%"
" fdroid_app.categories LIKE ? OR " + // Last category e.g. "%,internet" getTableName() + ".categories LIKE ? OR " + // Last category e.g. "%,internet"
" fdroid_app.categories LIKE ? "; // One of many categories e.g. "%,internet,%" getTableName() + ".categories LIKE ? "; // One of many categories e.g. "%,internet,%"
final String[] args = { final String[] args = {
category, category,
category + ",%", category + ",%",
@ -682,7 +683,7 @@ public class AppProvider extends FDroidProvider {
} }
private AppQuerySelection queryNoApks() { private AppQuerySelection queryNoApks() {
String selection = "(SELECT COUNT(*) FROM fdroid_apk WHERE fdroid_apk.id = fdroid_app.id) = 0"; String selection = "(SELECT COUNT(*) FROM " + DBHelper.TABLE_APK + " WHERE " + DBHelper.TABLE_APK + ".id = " + getTableName() + ".id) = 0";
return new AppQuerySelection(selection); return new AppQuerySelection(selection);
} }
@ -692,8 +693,8 @@ public class AppProvider extends FDroidProvider {
return new AppQuerySelection(selection, args); return new AppQuerySelection(selection, args);
} }
private static AppQuerySelection queryApps(String appIds) { private AppQuerySelection queryApps(String appIds) {
return queryApps(appIds, "fdroid_app.id"); return queryApps(appIds, getTableName() + ".id");
} }
@Override @Override
@ -754,13 +755,13 @@ public class AppProvider extends FDroidProvider {
break; break;
case RECENTLY_UPDATED: case RECENTLY_UPDATED:
sortOrder = " fdroid_app.lastUpdated DESC"; sortOrder = getTableName() + ".lastUpdated DESC";
selection = selection.add(queryRecentlyUpdated()); selection = selection.add(queryRecentlyUpdated());
includeSwap = false; includeSwap = false;
break; break;
case NEWLY_ADDED: case NEWLY_ADDED:
sortOrder = " fdroid_app.added DESC"; sortOrder = getTableName() + ".added DESC";
selection = selection.add(queryNewlyAdded()); selection = selection.add(queryNewlyAdded());
includeSwap = false; includeSwap = false;
break; break;
@ -775,7 +776,7 @@ public class AppProvider extends FDroidProvider {
} }
if (AppProvider.DataColumns.NAME.equals(sortOrder)) { if (AppProvider.DataColumns.NAME.equals(sortOrder)) {
sortOrder = " fdroid_app." + sortOrder + " COLLATE LOCALIZED "; sortOrder = getTableName() + sortOrder + " COLLATE LOCALIZED ";
} }
Query query = new Query(); Query query = new Query();

View File

@ -88,7 +88,6 @@ public class TempApkProvider extends ApkProvider {
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
int code = matcher.match(uri); int code = matcher.match(uri);
if (code == CODE_INIT) { if (code == CODE_INIT) {
initTable(); initTable();
return null; return null;
@ -100,6 +99,40 @@ public class TempApkProvider extends ApkProvider {
} }
} }
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
if (matcher.match(uri) != CODE_SINGLE) {
throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
}
return performUpdateUnchecked(uri, values, where, whereArgs);
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
QuerySelection query = new QuerySelection(where, whereArgs);
switch (matcher.match(uri)) {
case CODE_REPO_APK:
List<String> pathSegments = uri.getPathSegments();
query = query.add(queryRepo(Long.parseLong(pathSegments.get(1)))).add(queryApks(pathSegments.get(2)));
break;
default:
Log.e(TAG, "Invalid URI for apk content provider: " + uri);
throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri);
}
int rowsAffected = write().delete(getTableName(), query.getSelection(), query.getArgs());
if (!isApplyingBatch()) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsAffected;
}
private void initTable() { private void initTable() {
write().execSQL("DROP TABLE IF EXISTS " + getTableName()); write().execSQL("DROP TABLE IF EXISTS " + getTableName());
write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK);
@ -109,5 +142,6 @@ public class TempApkProvider extends ApkProvider {
Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APK + " so they can be copied from " + getTableName()); Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APK + " so they can be copied from " + getTableName());
write().execSQL("DELETE FROM " + DBHelper.TABLE_APK); write().execSQL("DELETE FROM " + DBHelper.TABLE_APK);
write().execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + getTableName()); write().execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + getTableName());
getContext().getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
} }
} }

View File

@ -83,6 +83,25 @@ public class TempAppProvider extends AppProvider {
} }
} }
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
QuerySelection query = new QuerySelection(where, whereArgs);
switch (matcher.match(uri)) {
case CODE_SINGLE:
query = query.add(querySingle(uri.getLastPathSegment()));
break;
default:
throw new UnsupportedOperationException("Update not supported for " + uri + ".");
}
int count = write().update(getTableName(), values, query.getSelection(), query.getArgs());
if (!isApplyingBatch()) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
private void initTable() { private void initTable() {
write().execSQL("DROP TABLE IF EXISTS " + getTableName()); write().execSQL("DROP TABLE IF EXISTS " + getTableName());
write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP);
@ -92,5 +111,6 @@ public class TempAppProvider extends AppProvider {
Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APP + " so they can be copied from " + getTableName()); Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APP + " so they can be copied from " + getTableName());
write().execSQL("DELETE FROM " + DBHelper.TABLE_APP); write().execSQL("DELETE FROM " + DBHelper.TABLE_APP);
write().execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + getTableName()); write().execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + getTableName());
getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
} }
} }

View File

@ -20,6 +20,8 @@ import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.TempApkProvider;
import org.fdroid.fdroid.data.TempAppProvider;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
@ -82,6 +84,8 @@ public class MultiRepoUpdaterTest extends InstrumentationTestCase {
resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider())); resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider()));
resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider())); resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider()));
resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider())); resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider()));
resolver.addProvider(TempAppProvider.getAuthority(), prepareProvider(new TempAppProvider()));
resolver.addProvider(TempApkProvider.getAuthority(), prepareProvider(new TempApkProvider()));
} }
private ContentProvider prepareProvider(ContentProvider provider) { private ContentProvider prepareProvider(ContentProvider provider) {

View File

@ -8,7 +8,6 @@ import org.fdroid.fdroid.RepoUpdater.UpdateException;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import java.io.File; import java.io.File;
import java.util.UUID;
public class RepoUpdaterTest extends InstrumentationTestCase { public class RepoUpdaterTest extends InstrumentationTestCase {
private static final String TAG = "RepoUpdaterTest"; private static final String TAG = "RepoUpdaterTest";

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import android.support.annotation.NonNull;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -8,6 +9,7 @@ import android.util.Log;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.MockRepo;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.XMLReader; import org.xml.sax.XMLReader;
@ -25,34 +27,7 @@ import javax.xml.parsers.SAXParserFactory;
public class RepoXMLHandlerTest extends AndroidTestCase { public class RepoXMLHandlerTest extends AndroidTestCase {
private static final String TAG = "RepoXMLHandlerTest"; private static final String TAG = "RepoXMLHandlerTest";
private final Repo actualRepo = new Repo(); private static final String FAKE_PUBKEY = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
public final List<App> actualApps = new ArrayList<>();
public final List<Apk> actualApks = new ArrayList<>();
private final static String FAKE_PUBKEY = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
private RepoXMLHandler.IndexReceiver indexReceiver = new RepoXMLHandler.IndexReceiver() {
private boolean hasReceivedRepo;
@Override
public void receiveRepo(String name, String description, String signingCert, int maxage, int version) {
assertFalse("Repo XML contains more than one <repo>.", hasReceivedRepo);
actualRepo.name = name;
actualRepo.description = description;
actualRepo.pubkey = signingCert;
actualRepo.maxage = maxage;
actualRepo.version = version;
hasReceivedRepo = true;
}
@Override
public void receiveApp(App app, List<Apk> packages) {
actualApps.add(app);
actualApks.addAll(packages);
}
};
public RepoXMLHandlerTest() { public RepoXMLHandlerTest() {
} }
@ -67,10 +42,8 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
expectedRepo.name = "F-Droid"; expectedRepo.name = "F-Droid";
expectedRepo.pubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; expectedRepo.pubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid."; expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid.";
processFile("simpleIndex.xml"); RepoDetails actualDetails = getFromFile("simpleIndex.xml");
handlerTestSuite(expectedRepo, 0, 0); handlerTestSuite(expectedRepo, actualDetails, 0, 0, -1, 12);
assertEquals(actualRepo.maxage, -1);
assertEquals(actualRepo.version, 12);
} }
public void testSmallRepo() { public void testSmallRepo() {
@ -78,11 +51,9 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
expectedRepo.name = "Android-Nexus-7-20139453 on UNSET"; expectedRepo.name = "Android-Nexus-7-20139453 on UNSET";
expectedRepo.pubkey = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c"; expectedRepo.pubkey = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c";
expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453"; expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453";
processFile("smallRepo.xml"); RepoDetails actualDetails = getFromFile("smallRepo.xml");
handlerTestSuite(expectedRepo, 12, 12); handlerTestSuite(expectedRepo, actualDetails, 12, 12, 14, -1);
assertEquals(actualRepo.maxage, 14); checkIncludedApps(actualDetails.apps, new String[]{
assertEquals(actualRepo.version, -1);
checkIncludedApps(new String[] {
"org.mozilla.firefox", "org.mozilla.firefox",
"com.koushikdutta.superuser", "com.koushikdutta.superuser",
"info.guardianproject.courier", "info.guardianproject.courier",
@ -103,11 +74,9 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
expectedRepo.name = "Guardian Project Official Releases"; expectedRepo.name = "Guardian Project Official Releases";
expectedRepo.pubkey = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f"; expectedRepo.pubkey = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f";
expectedRepo.description = "The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store."; expectedRepo.description = "The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store.";
processFile("mediumRepo.xml"); RepoDetails actualDetails = getFromFile("mediumRepo.xml");
handlerTestSuite(expectedRepo, 15, 36); handlerTestSuite(expectedRepo, actualDetails, 15, 36, 60, 12);
assertEquals(expectedRepo.maxage, 60); checkIncludedApps(actualDetails.apps, new String[]{
assertEquals(expectedRepo.version, 12);
checkIncludedApps(new String[] {
"info.guardianproject.cacert", "info.guardianproject.cacert",
"info.guardianproject.otr.app.im", "info.guardianproject.otr.app.im",
"info.guardianproject.soundrecorder", "info.guardianproject.soundrecorder",
@ -131,15 +100,13 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
expectedRepo.name = "F-Droid"; expectedRepo.name = "F-Droid";
expectedRepo.pubkey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; expectedRepo.pubkey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
expectedRepo.description = "The official FDroid repository. Applications in this repository are mostly built directory from the source code. Some are official binaries built by the original application developers - these will be replaced by source-built versions over time."; expectedRepo.description = "The official FDroid repository. Applications in this repository are mostly built directory from the source code. Some are official binaries built by the original application developers - these will be replaced by source-built versions over time.";
processFile("largeRepo.xml"); RepoDetails actualDetails = getFromFile("largeRepo.xml");
handlerTestSuite(expectedRepo, 1211, 2381); handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12);
assertEquals("Repo max age", 14, expectedRepo.maxage);
assertEquals("Repo version", 12, expectedRepo.version);
/* /*
* generated using: sed 's,<application,\n<application,g' largeRepo.xml * generated using: sed 's,<application,\n<application,g' largeRepo.xml
* | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p' * | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p'
*/ */
checkIncludedApps(new String[]{ checkIncludedApps(actualDetails.apps, new String[]{
"org.zeroxlab.zeroxbenchmark", "com.uberspot.a2048", "com.traffar.a24game", "org.zeroxlab.zeroxbenchmark", "com.uberspot.a2048", "com.traffar.a24game",
"info.staticfree.android.twentyfourhour", "nerd.tuxmobil.fahrplan.congress", "info.staticfree.android.twentyfourhour", "nerd.tuxmobil.fahrplan.congress",
"com.jecelyin.editor", "com.markuspage.android.atimetracker", "a2dp.Vol", "com.jecelyin.editor", "com.markuspage.android.atimetracker", "a2dp.Vol",
@ -622,11 +589,11 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
}); });
} }
private void checkIncludedApps(String[] packageNames) { private void checkIncludedApps(List<App> actualApps, String[] expctedAppIds) {
assertNotNull(actualApps); assertNotNull(actualApps);
assertNotNull(packageNames); assertNotNull(expctedAppIds);
assertEquals(actualApps.size(), packageNames.length); assertEquals(actualApps.size(), expctedAppIds.length);
for (String id : packageNames) { for (String id : expctedAppIds) {
boolean thisAppMissing = true; boolean thisAppMissing = true;
for (App app : actualApps) { for (App app : actualApps) {
if (TextUtils.equals(app.id, id)) { if (TextUtils.equals(app.id, id)) {
@ -638,53 +605,84 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
} }
} }
private void handlerTestSuite(Repo expectedRepo, int appCount, int apkCount) { private void handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails, int appCount, int apkCount, int maxAge, int version) {
assertFalse(TextUtils.isEmpty(actualRepo.pubkey)); assertNotNull(actualDetails);
assertEquals(expectedRepo.pubkey.length(), actualRepo.pubkey.length()); assertFalse(TextUtils.isEmpty(actualDetails.signingCert));
assertEquals(expectedRepo.pubkey, actualRepo.pubkey); assertEquals(expectedRepo.pubkey.length(), actualDetails.signingCert.length());
assertFalse(FAKE_PUBKEY.equals(actualRepo.pubkey)); assertEquals(expectedRepo.pubkey, actualDetails.signingCert);
assertFalse(FAKE_PUBKEY.equals(actualDetails.signingCert));
assertFalse(TextUtils.isEmpty(actualRepo.name)); assertFalse(TextUtils.isEmpty(actualDetails.name));
assertEquals(expectedRepo.name.length(), actualRepo.name.length()); assertEquals(expectedRepo.name.length(), actualDetails.name.length());
assertEquals(expectedRepo.name, actualRepo.name); assertEquals(expectedRepo.name, actualDetails.name);
assertFalse(TextUtils.isEmpty(actualRepo.description)); assertFalse(TextUtils.isEmpty(actualDetails.description));
assertEquals(expectedRepo.description.length(), actualRepo.description.length()); assertEquals(expectedRepo.description.length(), actualDetails.description.length());
assertEquals(expectedRepo.description, actualRepo.description); assertEquals(expectedRepo.description, actualDetails.description);
assertNotNull(actualApps); assertEquals(actualDetails.maxAge, maxAge);
assertEquals(actualApps.size(), appCount); assertEquals(actualDetails.version, version);
List<Apk> apks = actualApks; List<App> apps = actualDetails.apps;
assertNotNull(apps);
assertEquals(apps.size(), appCount);
List<Apk> apks = actualDetails.apks;
assertNotNull(apks); assertNotNull(apks);
assertEquals(apks.size(), apkCount); assertEquals(apks.size(), apkCount);
} }
private static class MockRepo extends Repo { private static class RepoDetails implements RepoXMLHandler.IndexReceiver {
public String name;
public String description;
public String signingCert;
public int maxAge;
public int version;
public List<Apk> apks = new ArrayList<>();
public List<App> apps = new ArrayList<>();
@Override @Override
public long getId() { public void receiveRepo(String name, String description, String signingCert, int maxage, int version) {
return 10000; this.name = name;
this.description = description;
this.signingCert = signingCert;
this.maxAge = maxage;
this.version = version;
} }
@Override
public void receiveApp(App app, List<Apk> packages) {
apks.addAll(packages);
apps.add(app);
}
} }
private RepoXMLHandler processFile(String indexFilename) { @NonNull
private RepoDetails getFromFile(String indexFilename) {
SAXParser parser; SAXParser parser;
try { try {
parser = SAXParserFactory.newInstance().newSAXParser(); parser = SAXParserFactory.newInstance().newSAXParser();
XMLReader reader = parser.getXMLReader(); XMLReader reader = parser.getXMLReader();
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(), indexReceiver); RepoDetails repoDetails = new RepoDetails();
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails);
reader.setContentHandler(handler); reader.setContentHandler(handler);
String resName = "assets/" + indexFilename; String resName = "assets/" + indexFilename;
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName)); Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));
InputStream input = getClass().getClassLoader().getResourceAsStream(resName); InputStream input = getClass().getClassLoader().getResourceAsStream(resName);
InputSource is = new InputSource(new BufferedInputStream(input)); InputSource is = new InputSource(new BufferedInputStream(input));
reader.parse(is); reader.parse(is);
return handler; return repoDetails;
} catch (ParserConfigurationException | SAXException | IOException e) { } catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace(); e.printStackTrace();
fail(); fail();
// Satisfies the compiler, but fail() will always throw a runtime exception so we never
// reach this return statement.
return null;
} }
return null;
} }
} }