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:
parent
b34853a776
commit
1d951e7689
@ -46,7 +46,6 @@ import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Responsible for updating an individual repository. This will:
|
||||
* * Download the index.jar
|
||||
* * 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.
|
||||
*/
|
||||
private static final String[] APP_FIELDS_TO_IGNORE = {
|
||||
AppProvider.DataColumns.IGNORE_ALLUPDATES,
|
||||
AppProvider.DataColumns.IGNORE_THISUPDATE
|
||||
AppProvider.DataColumns.IGNORE_ALLUPDATES,
|
||||
AppProvider.DataColumns.IGNORE_THISUPDATE,
|
||||
};
|
||||
|
||||
@NonNull protected final Context context;
|
||||
@NonNull protected final Repo repo;
|
||||
protected boolean hasChanged = false;
|
||||
@Nullable protected ProgressListener progressListener;
|
||||
@NonNull
|
||||
protected final Context context;
|
||||
@NonNull
|
||||
protected final Repo repo;
|
||||
protected boolean hasChanged;
|
||||
@Nullable
|
||||
protected ProgressListener progressListener;
|
||||
private String cacheTag;
|
||||
private X509Certificate signingCertFromJar;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public RepoUpdater(@NonNull Context context, @NonNull Repo repo) {
|
||||
@ -160,8 +163,8 @@ public class RepoUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private ContentValues repoDetailsToSave = null;
|
||||
private String signingCertFromIndexXml = null;
|
||||
private ContentValues repoDetailsToSave;
|
||||
private String signingCertFromIndexXml;
|
||||
|
||||
private RepoXMLHandler.IndexReceiver createIndexReceiver() {
|
||||
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:
|
||||
* * 25 = 37 seconds
|
||||
* * 50 = 33 seconds
|
||||
* * 100 = 30 seconds
|
||||
* * 200 = 32 seconds
|
||||
* Raising this means more memory consumption, so we'd like it to be low, but not
|
||||
* so low that it takes too long.
|
||||
* * 25 = 37 seconds
|
||||
* * 50 = 33 seconds
|
||||
* * 100 = 30 seconds
|
||||
* * 200 = 32 seconds
|
||||
* Raising this means more memory consumption, so we'd like it to be low, but not
|
||||
* so low that it takes too long.
|
||||
*/
|
||||
private static final int MAX_APP_BUFFER = 50;
|
||||
|
||||
@ -242,7 +245,7 @@ public class RepoUpdater {
|
||||
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -320,7 +323,7 @@ public class RepoUpdater {
|
||||
* array.
|
||||
*/
|
||||
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);
|
||||
return found != null;
|
||||
}
|
||||
@ -429,7 +432,7 @@ public class RepoUpdater {
|
||||
JarFile jarFile = new JarFile(downloadedFile, true);
|
||||
JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
|
||||
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
|
||||
progressListener, repo, (int) indexEntry.getSize());
|
||||
progressListener, repo, (int) indexEntry.getSize());
|
||||
|
||||
// Process the index...
|
||||
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
@ -558,7 +561,7 @@ public class RepoUpdater {
|
||||
* check that the signing certificate in the jar matches that fingerprint.
|
||||
*/
|
||||
private void verifyAndStoreTOFUCerts(String certFromIndexXml, X509Certificate rawCertFromJar)
|
||||
throws SigningException {
|
||||
throws SigningException {
|
||||
if (repo.pubkey != null)
|
||||
return; // there is a repo.pubkey already, nothing to TOFU
|
||||
|
||||
@ -570,7 +573,7 @@ public class RepoUpdater {
|
||||
String fingerprintFromIndexXml = Utils.calcFingerprint(certFromIndexXml);
|
||||
String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
|
||||
if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml)
|
||||
|| !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
|
||||
|| !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
|
||||
throw new SigningException(repo, "Supplied certificate fingerprint does not match!");
|
||||
}
|
||||
} // 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
|
||||
if (TextUtils.isEmpty(repo.pubkey)
|
||||
|| TextUtils.isEmpty(certFromJar)
|
||||
|| TextUtils.isEmpty(certFromIndexXml))
|
||||
|| TextUtils.isEmpty(certFromJar)
|
||||
|| TextUtils.isEmpty(certFromIndexXml))
|
||||
throw new SigningException(repo, "A empty repo or signing certificate is invalid!");
|
||||
|
||||
// though its called repo.pubkey, its actually a X509 certificate
|
||||
if (repo.pubkey.equals(certFromJar)
|
||||
&& repo.pubkey.equals(certFromIndexXml)
|
||||
&& certFromIndexXml.equals(certFromJar)) {
|
||||
&& repo.pubkey.equals(certFromIndexXml)
|
||||
&& certFromIndexXml.equals(certFromJar)) {
|
||||
return; // we have a match!
|
||||
}
|
||||
throw new SigningException(repo, "Signing certificate does not match!");
|
||||
|
@ -45,12 +45,12 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
private App curapp;
|
||||
private Apk curapk;
|
||||
|
||||
private String currentApkHashType = null;
|
||||
private String currentApkHashType;
|
||||
|
||||
// After processing the XML, these will be -1 if the index didn't specify
|
||||
// them - otherwise it will be the value specified.
|
||||
private int repoMaxAge = -1;
|
||||
private int repoVersion = 0;
|
||||
private int repoVersion;
|
||||
private String repoDescription;
|
||||
private String repoName;
|
||||
|
||||
@ -61,6 +61,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
interface IndexReceiver {
|
||||
void receiveRepo(String name, String description, String signingCert, int maxage, int version);
|
||||
|
||||
void receiveApp(App app, List<Apk> packages);
|
||||
}
|
||||
|
||||
@ -78,7 +79,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName)
|
||||
throws SAXException {
|
||||
throws SAXException {
|
||||
|
||||
if ("application".equals(localName) && curapp != null) {
|
||||
onApplicationParsed();
|
||||
@ -94,53 +95,53 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
final String str = curchars.toString().trim();
|
||||
if (curapk != null) {
|
||||
switch (localName) {
|
||||
case "version":
|
||||
curapk.version = str;
|
||||
break;
|
||||
case "versioncode":
|
||||
curapk.vercode = Utils.parseInt(str, -1);
|
||||
break;
|
||||
case "size":
|
||||
curapk.size = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "hash":
|
||||
if (currentApkHashType == null || currentApkHashType.equals("md5")) {
|
||||
if (curapk.hash == null) {
|
||||
case "version":
|
||||
curapk.version = str;
|
||||
break;
|
||||
case "versioncode":
|
||||
curapk.vercode = Utils.parseInt(str, -1);
|
||||
break;
|
||||
case "size":
|
||||
curapk.size = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "hash":
|
||||
if (currentApkHashType == null || "md5".equals(currentApkHashType)) {
|
||||
if (curapk.hash == null) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
} else if ("sha256".equals(currentApkHashType)) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
} else if (currentApkHashType.equals("sha256")) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
break;
|
||||
case "sig":
|
||||
curapk.sig = str;
|
||||
break;
|
||||
case "srcname":
|
||||
curapk.srcname = str;
|
||||
break;
|
||||
case "apkname":
|
||||
curapk.apkName = str;
|
||||
break;
|
||||
case "sdkver":
|
||||
curapk.minSdkVersion = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "maxsdkver":
|
||||
curapk.maxSdkVersion = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "added":
|
||||
curapk.added = Utils.parseDate(str, null);
|
||||
break;
|
||||
case "permissions":
|
||||
curapk.permissions = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
case "features":
|
||||
curapk.features = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
case "nativecode":
|
||||
curapk.nativecode = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
break;
|
||||
case "sig":
|
||||
curapk.sig = str;
|
||||
break;
|
||||
case "srcname":
|
||||
curapk.srcname = str;
|
||||
break;
|
||||
case "apkname":
|
||||
curapk.apkName = str;
|
||||
break;
|
||||
case "sdkver":
|
||||
curapk.minSdkVersion = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "maxsdkver":
|
||||
curapk.maxSdkVersion = Utils.parseInt(str, 0);
|
||||
break;
|
||||
case "added":
|
||||
curapk.added = Utils.parseDate(str, null);
|
||||
break;
|
||||
case "permissions":
|
||||
curapk.permissions = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
case "features":
|
||||
curapk.features = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
case "nativecode":
|
||||
curapk.nativecode = Utils.CommaSeparatedList.make(str);
|
||||
break;
|
||||
}
|
||||
} else if (curapp != null) {
|
||||
switch (localName) {
|
||||
@ -237,7 +238,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attributes) throws SAXException {
|
||||
Attributes attributes) throws SAXException {
|
||||
super.startElement(uri, localName, qName, attributes);
|
||||
|
||||
if ("repo".equals(localName)) {
|
||||
|
@ -101,7 +101,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
public static void schedule(Context ctx) {
|
||||
|
||||
SharedPreferences prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(ctx);
|
||||
.getDefaultSharedPreferences(ctx);
|
||||
String sint = prefs.getString(Preferences.PREF_UPD_INTERVAL, "0");
|
||||
int interval = Integer.parseInt(sint);
|
||||
|
||||
@ -109,12 +109,12 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
PendingIntent pending = PendingIntent.getService(ctx, 0, intent, 0);
|
||||
|
||||
AlarmManager alarm = (AlarmManager) ctx
|
||||
.getSystemService(Context.ALARM_SERVICE);
|
||||
.getSystemService(Context.ALARM_SERVICE);
|
||||
alarm.cancel(pending);
|
||||
if (interval > 0) {
|
||||
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + 5000,
|
||||
AlarmManager.INTERVAL_HOUR, pending);
|
||||
SystemClock.elapsedRealtime() + 5000,
|
||||
AlarmManager.INTERVAL_HOUR, pending);
|
||||
Utils.debugLog(TAG, "Update scheduler alarm set");
|
||||
} else {
|
||||
Utils.debugLog(TAG, "Update scheduler alarm not set");
|
||||
@ -128,18 +128,18 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(downloadProgressReceiver,
|
||||
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
|
||||
new IntentFilter(Downloader.LOCAL_ACTION_PROGRESS));
|
||||
localBroadcastManager.registerReceiver(updateStatusReceiver,
|
||||
new IntentFilter(LOCAL_ACTION_STATUS));
|
||||
new IntentFilter(LOCAL_ACTION_STATUS));
|
||||
|
||||
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
notificationBuilder = new NotificationCompat.Builder(this)
|
||||
.setSmallIcon(R.drawable.ic_refresh_white)
|
||||
.setOngoing(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentTitle(getString(R.string.update_notification_title));
|
||||
|
||||
.setSmallIcon(R.drawable.ic_refresh_white)
|
||||
.setOngoing(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentTitle(getString(R.string.update_notification_title));
|
||||
|
||||
// Android docs are a little sketchy, however it seems that Gingerbread is the last
|
||||
// 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) {
|
||||
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));
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
switch (resultCode) {
|
||||
case STATUS_INFO:
|
||||
notificationBuilder.setContentText(message)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
if (progress != -1) {
|
||||
notificationBuilder.setProgress(100, progress, false);
|
||||
}
|
||||
@ -243,8 +243,8 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
case STATUS_ERROR_GLOBAL:
|
||||
text = context.getString(R.string.global_error_updating_repos, message);
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_alert);
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_alert);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
@ -261,8 +261,8 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
}
|
||||
text = msgBuilder.toString();
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info);
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
@ -271,7 +271,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
case STATUS_COMPLETE_AND_SAME:
|
||||
text = context.getString(R.string.repos_unchanged);
|
||||
notificationBuilder.setContentText(text)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE);
|
||||
notificationManager.notify(NOTIFY_ID_UPDATING, notificationBuilder.build());
|
||||
break;
|
||||
}
|
||||
@ -281,10 +281,11 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
/**
|
||||
* Check whether it is time to run the scheduled update.
|
||||
* We don't want to run if:
|
||||
* - The time between scheduled runs is set to zero (though don't know
|
||||
* when that would occur)
|
||||
* - Last update was too recent
|
||||
* - Not on wifi, but the property for "Only auto update on wifi" is set.
|
||||
* - The time between scheduled runs is set to zero (though don't know
|
||||
* when that would occur)
|
||||
* - Last update was too recent
|
||||
* - Not on wifi, but the property for "Only auto update on wifi" is set.
|
||||
*
|
||||
* @return True if we are due for a scheduled update.
|
||||
*/
|
||||
private boolean verifyIsTimeForScheduledRun() {
|
||||
@ -299,7 +300,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
long elapsed = System.currentTimeMillis() - lastUpdate;
|
||||
if (elapsed < interval * 60 * 60 * 1000) {
|
||||
Log.i(TAG, "Skipping update - done " + elapsed
|
||||
+ "ms ago, interval is " + interval + " hours");
|
||||
+ "ms ago, interval is " + interval + " hours");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -319,7 +320,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
@ -432,9 +433,9 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
private void performUpdateNotification() {
|
||||
Cursor cursor = getContentResolver().query(
|
||||
AppProvider.getCanUpdateUri(),
|
||||
AppProvider.DataColumns.ALL,
|
||||
null, null, null);
|
||||
AppProvider.getCanUpdateUri(),
|
||||
AppProvider.DataColumns.ALL,
|
||||
null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
showAppUpdatesNotification(cursor);
|
||||
@ -446,8 +447,8 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
private PendingIntent createNotificationIntent() {
|
||||
Intent notifyIntent = new Intent(this, FDroid.class).putExtra(FDroid.EXTRA_TAB_UPDATE, true);
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder
|
||||
.create(this).addParentStack(FDroid.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
.create(this).addParentStack(FDroid.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
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) {
|
||||
|
||||
final String contentText = hasUpdates.getCount() > 1
|
||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
||||
: getString(R.string.one_update_available);
|
||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
||||
: getString(R.string.one_update_available);
|
||||
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
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 String contentText = hasUpdates.getCount() > 1
|
||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
||||
: getString(R.string.one_update_available);
|
||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
||||
: getString(R.string.one_update_available);
|
||||
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(getString(R.string.fdroid_updates_available))
|
||||
.setSmallIcon(icon)
|
||||
.setContentIntent(createNotificationIntent())
|
||||
.setContentText(contentText)
|
||||
.setStyle(createNotificationBigStyle(hasUpdates));
|
||||
new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(getString(R.string.fdroid_updates_available))
|
||||
.setSmallIcon(icon)
|
||||
.setContentIntent(createNotificationIntent())
|
||||
.setContentText(contentText)
|
||||
.setStyle(createNotificationBigStyle(hasUpdates));
|
||||
|
||||
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
|
||||
}
|
||||
|
@ -229,10 +229,10 @@ public class ApkProvider extends FDroidProvider {
|
||||
private static final int CODE_REPO_APPS = CODE_APKS + 1;
|
||||
protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1;
|
||||
|
||||
private static final String PROVIDER_NAME = "ApkProvider";
|
||||
protected static final String PATH_APK = "apk";
|
||||
private static final String PATH_APKS = "apks";
|
||||
private static final String PATH_APP = "app";
|
||||
private static final String PROVIDER_NAME = "ApkProvider";
|
||||
protected static final String PATH_APK = "apk";
|
||||
private static final String PATH_APKS = "apks";
|
||||
private static final String PATH_APP = "app";
|
||||
private static final String PATH_REPO = "repo";
|
||||
private static final String PATH_REPO_APPS = "repo-apps";
|
||||
protected static final String PATH_REPO_APK = "repo-apk";
|
||||
@ -411,7 +411,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
return new QuerySelection(selection, args);
|
||||
}
|
||||
|
||||
private QuerySelection queryRepo(long repoId) {
|
||||
protected QuerySelection queryRepo(long repoId) {
|
||||
final String selection = DataColumns.REPO_ID + " = ? ";
|
||||
final String[] args = {Long.toString(repoId)};
|
||||
return new QuerySelection(selection, args);
|
||||
@ -421,7 +421,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
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(",");
|
||||
if (apkDetails.length > MAX_APKS_TO_QUERY) {
|
||||
throw new IllegalArgumentException(
|
||||
@ -473,7 +473,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
query = query.add(queryRepoApps(Long.parseLong(pathSegments.get(1)), 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);
|
||||
@ -535,7 +535,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
case CODE_APKS:
|
||||
query = query.add(queryApks(uri.getLastPathSegment()));
|
||||
break;
|
||||
|
||||
|
||||
// TODO: Add tests for this.
|
||||
case CODE_REPO_APK:
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
@ -561,11 +561,13 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
protected int performUpdateUnchecked(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||
validateFields(DataColumns.ALL, values);
|
||||
removeRepoFields(values);
|
||||
|
||||
@ -577,7 +579,6 @@ public class ApkProvider extends FDroidProvider {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
return numRows;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 requiresInstalledTable;
|
||||
@ -366,14 +366,14 @@ public class AppProvider extends FDroidProvider {
|
||||
if (field.equals(DataColumns.CATEGORIES)) {
|
||||
categoryFieldAdded = true;
|
||||
}
|
||||
appendField(field, "fdroid_app");
|
||||
appendField(field, getTableName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void appendCountField() {
|
||||
countFieldAppended = true;
|
||||
appendField("COUNT( DISTINCT fdroid_app.id ) AS " + DataColumns._COUNT);
|
||||
appendField("COUNT( DISTINCT " + getTableName() + ".id ) AS " + DataColumns._COUNT);
|
||||
}
|
||||
|
||||
private void addSuggestedApkVersionField() {
|
||||
@ -388,7 +388,7 @@ public class AppProvider extends FDroidProvider {
|
||||
leftJoin(
|
||||
DBHelper.TABLE_APK,
|
||||
"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);
|
||||
}
|
||||
@ -570,15 +570,15 @@ public class AppProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
private AppQuerySelection queryCanUpdate() {
|
||||
final String ignoreCurrent = " fdroid_app.ignoreThisUpdate != fdroid_app.suggestedVercode ";
|
||||
final String ignoreAll = " fdroid_app.ignoreAllUpdates != 1 ";
|
||||
final String ignoreCurrent = getTableName() + ".ignoreThisUpdate != " + getTableName() + ".suggestedVercode ";
|
||||
final String ignoreAll = getTableName() + ".ignoreAllUpdates != 1 ";
|
||||
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();
|
||||
}
|
||||
|
||||
private AppQuerySelection queryRepo(long repoId) {
|
||||
final String selection = " fdroid_apk.repo = ? ";
|
||||
final String selection = DBHelper.TABLE_APK + ".repo = ? ";
|
||||
final String[] args = {String.valueOf(repoId)};
|
||||
return new AppQuerySelection(selection, args);
|
||||
}
|
||||
@ -589,10 +589,10 @@ public class AppProvider extends FDroidProvider {
|
||||
|
||||
private AppQuerySelection querySearch(String query) {
|
||||
final String[] columns = {
|
||||
"fdroid_app.id",
|
||||
"fdroid_app.name",
|
||||
"fdroid_app.summary",
|
||||
"fdroid_app.description",
|
||||
getTableName() + ".id",
|
||||
getTableName() + ".name",
|
||||
getTableName() + ".summary",
|
||||
getTableName() + ".description",
|
||||
};
|
||||
|
||||
// Remove duplicates, surround in % for case insensitive searching
|
||||
@ -632,15 +632,16 @@ public class AppProvider extends FDroidProvider {
|
||||
return new AppQuerySelection(selection.toString(), selectionKeywords);
|
||||
}
|
||||
|
||||
private AppQuerySelection querySingle(String id) {
|
||||
final String selection = "fdroid_app.id = ?";
|
||||
protected AppQuerySelection querySingle(String id) {
|
||||
final String selection = getTableName() + ".id = ?";
|
||||
final String[] args = {id};
|
||||
return new AppQuerySelection(selection, args);
|
||||
}
|
||||
|
||||
private AppQuerySelection queryIgnored() {
|
||||
final String selection = "fdroid_app.ignoreAllUpdates = 1 OR " +
|
||||
"fdroid_app.ignoreThisUpdate >= fdroid_app.suggestedVercode";
|
||||
final String table = getTableName();
|
||||
final String selection = table + ".ignoreAllUpdates = 1 OR " +
|
||||
table + ".ignoreThisUpdate >= " + table + ".suggestedVercode";
|
||||
return new AppQuerySelection(selection);
|
||||
}
|
||||
|
||||
@ -653,13 +654,13 @@ public class AppProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
private AppQuerySelection queryNewlyAdded() {
|
||||
final String selection = "fdroid_app.added > ?";
|
||||
final String selection = getTableName() + ".added > ?";
|
||||
final String[] args = {Utils.formatDate(Preferences.get().calcMaxHistory(), "")};
|
||||
return new AppQuerySelection(selection, args);
|
||||
}
|
||||
|
||||
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(), "")};
|
||||
return new AppQuerySelection(selection, args);
|
||||
}
|
||||
@ -668,10 +669,10 @@ public class AppProvider extends FDroidProvider {
|
||||
// TODO: In the future, add a new table for categories,
|
||||
// so we can join onto it.
|
||||
final String selection =
|
||||
" fdroid_app.categories = ? OR " + // Only category e.g. "internet"
|
||||
" fdroid_app.categories LIKE ? OR " + // First category e.g. "internet,%"
|
||||
" fdroid_app.categories LIKE ? OR " + // Last category e.g. "%,internet"
|
||||
" fdroid_app.categories LIKE ? "; // One of many categories e.g. "%,internet,%"
|
||||
getTableName() + ".categories = ? OR " + // Only category e.g. "internet"
|
||||
getTableName() + ".categories LIKE ? OR " + // First category e.g. "internet,%"
|
||||
getTableName() + ".categories LIKE ? OR " + // Last category e.g. "%,internet"
|
||||
getTableName() + ".categories LIKE ? "; // One of many categories e.g. "%,internet,%"
|
||||
final String[] args = {
|
||||
category,
|
||||
category + ",%",
|
||||
@ -682,7 +683,7 @@ public class AppProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -692,8 +693,8 @@ public class AppProvider extends FDroidProvider {
|
||||
return new AppQuerySelection(selection, args);
|
||||
}
|
||||
|
||||
private static AppQuerySelection queryApps(String appIds) {
|
||||
return queryApps(appIds, "fdroid_app.id");
|
||||
private AppQuerySelection queryApps(String appIds) {
|
||||
return queryApps(appIds, getTableName() + ".id");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -754,13 +755,13 @@ public class AppProvider extends FDroidProvider {
|
||||
break;
|
||||
|
||||
case RECENTLY_UPDATED:
|
||||
sortOrder = " fdroid_app.lastUpdated DESC";
|
||||
sortOrder = getTableName() + ".lastUpdated DESC";
|
||||
selection = selection.add(queryRecentlyUpdated());
|
||||
includeSwap = false;
|
||||
break;
|
||||
|
||||
case NEWLY_ADDED:
|
||||
sortOrder = " fdroid_app.added DESC";
|
||||
sortOrder = getTableName() + ".added DESC";
|
||||
selection = selection.add(queryNewlyAdded());
|
||||
includeSwap = false;
|
||||
break;
|
||||
@ -775,7 +776,7 @@ public class AppProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
if (AppProvider.DataColumns.NAME.equals(sortOrder)) {
|
||||
sortOrder = " fdroid_app." + sortOrder + " COLLATE LOCALIZED ";
|
||||
sortOrder = getTableName() + sortOrder + " COLLATE LOCALIZED ";
|
||||
}
|
||||
|
||||
Query query = new Query();
|
||||
|
@ -88,7 +88,6 @@ public class TempApkProvider extends ApkProvider {
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
int code = matcher.match(uri);
|
||||
|
||||
if (code == CODE_INIT) {
|
||||
initTable();
|
||||
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() {
|
||||
write().execSQL("DROP TABLE IF EXISTS " + getTableName());
|
||||
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());
|
||||
write().execSQL("DELETE FROM " + DBHelper.TABLE_APK);
|
||||
write().execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + getTableName());
|
||||
getContext().getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
write().execSQL("DROP TABLE IF EXISTS " + getTableName());
|
||||
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());
|
||||
write().execSQL("DELETE FROM " + DBHelper.TABLE_APP);
|
||||
write().execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + getTableName());
|
||||
getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.TempApkProvider;
|
||||
import org.fdroid.fdroid.data.TempAppProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
@ -82,6 +84,8 @@ public class MultiRepoUpdaterTest extends InstrumentationTestCase {
|
||||
resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider()));
|
||||
resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider()));
|
||||
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) {
|
||||
|
@ -8,7 +8,6 @@ import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RepoUpdaterTest extends InstrumentationTestCase {
|
||||
private static final String TAG = "RepoUpdaterTest";
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@ -8,6 +9,7 @@ import android.util.Log;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.mock.MockRepo;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
@ -25,34 +27,7 @@ import javax.xml.parsers.SAXParserFactory;
|
||||
public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
private static final String TAG = "RepoXMLHandlerTest";
|
||||
|
||||
private final Repo actualRepo = new Repo();
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
private static final String FAKE_PUBKEY = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
|
||||
|
||||
public RepoXMLHandlerTest() {
|
||||
}
|
||||
@ -67,10 +42,8 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
expectedRepo.name = "F-Droid";
|
||||
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.";
|
||||
processFile("simpleIndex.xml");
|
||||
handlerTestSuite(expectedRepo, 0, 0);
|
||||
assertEquals(actualRepo.maxage, -1);
|
||||
assertEquals(actualRepo.version, 12);
|
||||
RepoDetails actualDetails = getFromFile("simpleIndex.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 0, 0, -1, 12);
|
||||
}
|
||||
|
||||
public void testSmallRepo() {
|
||||
@ -78,11 +51,9 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
expectedRepo.name = "Android-Nexus-7-20139453 on UNSET";
|
||||
expectedRepo.pubkey = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c";
|
||||
expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453";
|
||||
processFile("smallRepo.xml");
|
||||
handlerTestSuite(expectedRepo, 12, 12);
|
||||
assertEquals(actualRepo.maxage, 14);
|
||||
assertEquals(actualRepo.version, -1);
|
||||
checkIncludedApps(new String[] {
|
||||
RepoDetails actualDetails = getFromFile("smallRepo.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 12, 12, 14, -1);
|
||||
checkIncludedApps(actualDetails.apps, new String[]{
|
||||
"org.mozilla.firefox",
|
||||
"com.koushikdutta.superuser",
|
||||
"info.guardianproject.courier",
|
||||
@ -103,11 +74,9 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
expectedRepo.name = "Guardian Project Official Releases";
|
||||
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.";
|
||||
processFile("mediumRepo.xml");
|
||||
handlerTestSuite(expectedRepo, 15, 36);
|
||||
assertEquals(expectedRepo.maxage, 60);
|
||||
assertEquals(expectedRepo.version, 12);
|
||||
checkIncludedApps(new String[] {
|
||||
RepoDetails actualDetails = getFromFile("mediumRepo.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 15, 36, 60, 12);
|
||||
checkIncludedApps(actualDetails.apps, new String[]{
|
||||
"info.guardianproject.cacert",
|
||||
"info.guardianproject.otr.app.im",
|
||||
"info.guardianproject.soundrecorder",
|
||||
@ -131,15 +100,13 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
expectedRepo.name = "F-Droid";
|
||||
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.";
|
||||
processFile("largeRepo.xml");
|
||||
handlerTestSuite(expectedRepo, 1211, 2381);
|
||||
assertEquals("Repo max age", 14, expectedRepo.maxage);
|
||||
assertEquals("Repo version", 12, expectedRepo.version);
|
||||
RepoDetails actualDetails = getFromFile("largeRepo.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12);
|
||||
/*
|
||||
* generated using: sed 's,<application,\n<application,g' largeRepo.xml
|
||||
* | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p'
|
||||
*/
|
||||
checkIncludedApps(new String[]{
|
||||
checkIncludedApps(actualDetails.apps, new String[]{
|
||||
"org.zeroxlab.zeroxbenchmark", "com.uberspot.a2048", "com.traffar.a24game",
|
||||
"info.staticfree.android.twentyfourhour", "nerd.tuxmobil.fahrplan.congress",
|
||||
"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(packageNames);
|
||||
assertEquals(actualApps.size(), packageNames.length);
|
||||
for (String id : packageNames) {
|
||||
assertNotNull(expctedAppIds);
|
||||
assertEquals(actualApps.size(), expctedAppIds.length);
|
||||
for (String id : expctedAppIds) {
|
||||
boolean thisAppMissing = true;
|
||||
for (App app : actualApps) {
|
||||
if (TextUtils.equals(app.id, id)) {
|
||||
@ -638,53 +605,84 @@ public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerTestSuite(Repo expectedRepo, int appCount, int apkCount) {
|
||||
assertFalse(TextUtils.isEmpty(actualRepo.pubkey));
|
||||
assertEquals(expectedRepo.pubkey.length(), actualRepo.pubkey.length());
|
||||
assertEquals(expectedRepo.pubkey, actualRepo.pubkey);
|
||||
assertFalse(FAKE_PUBKEY.equals(actualRepo.pubkey));
|
||||
private void handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails, int appCount, int apkCount, int maxAge, int version) {
|
||||
assertNotNull(actualDetails);
|
||||
assertFalse(TextUtils.isEmpty(actualDetails.signingCert));
|
||||
assertEquals(expectedRepo.pubkey.length(), actualDetails.signingCert.length());
|
||||
assertEquals(expectedRepo.pubkey, actualDetails.signingCert);
|
||||
assertFalse(FAKE_PUBKEY.equals(actualDetails.signingCert));
|
||||
|
||||
assertFalse(TextUtils.isEmpty(actualRepo.name));
|
||||
assertEquals(expectedRepo.name.length(), actualRepo.name.length());
|
||||
assertEquals(expectedRepo.name, actualRepo.name);
|
||||
assertFalse(TextUtils.isEmpty(actualDetails.name));
|
||||
assertEquals(expectedRepo.name.length(), actualDetails.name.length());
|
||||
assertEquals(expectedRepo.name, actualDetails.name);
|
||||
|
||||
assertFalse(TextUtils.isEmpty(actualRepo.description));
|
||||
assertEquals(expectedRepo.description.length(), actualRepo.description.length());
|
||||
assertEquals(expectedRepo.description, actualRepo.description);
|
||||
assertFalse(TextUtils.isEmpty(actualDetails.description));
|
||||
assertEquals(expectedRepo.description.length(), actualDetails.description.length());
|
||||
assertEquals(expectedRepo.description, actualDetails.description);
|
||||
|
||||
assertNotNull(actualApps);
|
||||
assertEquals(actualApps.size(), appCount);
|
||||
assertEquals(actualDetails.maxAge, maxAge);
|
||||
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);
|
||||
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
|
||||
public long getId() {
|
||||
return 10000;
|
||||
public void receiveRepo(String name, String description, String signingCert, int maxage, int version) {
|
||||
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;
|
||||
try {
|
||||
parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
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);
|
||||
String resName = "assets/" + indexFilename;
|
||||
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));
|
||||
InputStream input = getClass().getClassLoader().getResourceAsStream(resName);
|
||||
InputSource is = new InputSource(new BufferedInputStream(input));
|
||||
reader.parse(is);
|
||||
return handler;
|
||||
return repoDetails;
|
||||
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
|
||||
// Satisfies the compiler, but fail() will always throw a runtime exception so we never
|
||||
// reach this return statement.
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user