Merge branch 'issue-564--filter-anti-features--refactor-and-test-in-prep' into 'master'

Tests + Refactorings in preperation for #564  (Filter anti features)

As described in #564, there is a small amount of ground work to be done in order to support a UI for filtering anti features. This is the first stage of that. A subsequent MR will add a database migration to put anti features in their own table, and have a join table between apps and anti features. See commit messages for more detailed descriptions.

See merge request !339
This commit is contained in:
Daniel Martí 2016-06-20 14:53:20 +00:00
commit ef403928cf
13 changed files with 179 additions and 141 deletions

View File

@ -266,7 +266,7 @@ public class AppDetails extends AppCompatActivity {
} }
if (Preferences.get().expertMode() && apk.nativecode != null) { if (Preferences.get().expertMode() && apk.nativecode != null) {
holder.nativecode.setText(apk.nativecode.toString().replaceAll(",", " ")); holder.nativecode.setText(TextUtils.join(" ", apk.nativecode));
holder.nativecode.setVisibility(View.VISIBLE); holder.nativecode.setVisibility(View.VISIBLE);
} else { } else {
holder.nativecode.setVisibility(View.GONE); holder.nativecode.setVisibility(View.GONE);
@ -276,7 +276,7 @@ public class AppDetails extends AppCompatActivity {
holder.incompatibleReasons.setText( holder.incompatibleReasons.setText(
getResources().getString( getResources().getString(
R.string.requires_features, R.string.requires_features,
apk.incompatibleReasons.toPrettyString())); TextUtils.join(", ", apk.incompatibleReasons)));
holder.incompatibleReasons.setVisibility(View.VISIBLE); holder.incompatibleReasons.setVisibility(View.VISIBLE);
} else { } else {
holder.incompatibleReasons.setVisibility(View.GONE); holder.incompatibleReasons.setVisibility(View.GONE);
@ -1323,7 +1323,7 @@ public class AppDetails extends AppCompatActivity {
// Categories TextView // Categories TextView
final TextView categories = (TextView) view.findViewById(R.id.categories); final TextView categories = (TextView) view.findViewById(R.id.categories);
if (prefs.expertMode() && app.categories != null) { if (prefs.expertMode() && app.categories != null) {
categories.setText(app.categories.toString().replaceAll(",", ", ")); categories.setText(TextUtils.join(", ", app.categories));
} else { } else {
categories.setVisibility(View.GONE); categories.setVisibility(View.GONE);
} }

View File

@ -25,12 +25,15 @@ public class AppFilter {
// Return true if the given app should be filtered out based on user // Return true if the given app should be filtered out based on user
// preferences, and false otherwise. // preferences, and false otherwise.
public boolean filter(App app) { public boolean filter(App app) {
if (app.requirements == null) { if (app.requirements != null && !Preferences.get().filterAppsRequiringRoot()) {
return false; for (String requirement : app.requirements) {
if ("root".equals(requirement)) {
return true;
}
}
} }
return !Preferences.get().filterAppsRequiringRoot() return false;
&& app.requirements.contains("root");
} }
} }

View File

@ -6,6 +6,8 @@ import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.fdroid.fdroid.compat.SupportedArchitectures; import org.fdroid.fdroid.compat.SupportedArchitectures;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
@ -69,13 +71,16 @@ public class CompatibilityChecker {
cpuAbisDesc = builder.toString(); cpuAbisDesc = builder.toString();
} }
private boolean compatibleApi(Utils.CommaSeparatedList nativecode) { private boolean compatibleApi(@Nullable String[] nativecode) {
if (nativecode == null) { if (nativecode == null) {
return true; return true;
} }
for (final String cpuAbi : cpuAbis) { for (final String cpuAbi : cpuAbis) {
if (nativecode.contains(cpuAbi)) { for (String code : nativecode) {
return true; if (code.equals(cpuAbi)) {
return true;
}
} }
} }
return false; return false;
@ -108,11 +113,9 @@ public class CompatibilityChecker {
} }
} }
if (!compatibleApi(apk.nativecode)) { if (!compatibleApi(apk.nativecode)) {
for (final String code : apk.nativecode) { Collections.addAll(incompatibleReasons, apk.nativecode);
incompatibleReasons.add(code);
}
Utils.debugLog(TAG, apk.packageName + " vercode " + apk.versionCode Utils.debugLog(TAG, apk.packageName + " vercode " + apk.versionCode
+ " only supports " + Utils.CommaSeparatedList.str(apk.nativecode) + " only supports " + TextUtils.join(", ", apk.nativecode)
+ " while your architectures are " + cpuAbisDesc); + " while your architectures are " + cpuAbisDesc);
} }

View File

@ -142,13 +142,13 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.added = Utils.parseDate(str, null); curapk.added = Utils.parseDate(str, null);
break; break;
case "permissions": case "permissions":
curapk.permissions = Utils.CommaSeparatedList.make(str); curapk.permissions = Utils.parseCommaSeparatedString(str);
break; break;
case "features": case "features":
curapk.features = Utils.CommaSeparatedList.make(str); curapk.features = Utils.parseCommaSeparatedString(str);
break; break;
case "nativecode": case "nativecode":
curapk.nativecode = Utils.CommaSeparatedList.make(str); curapk.nativecode = Utils.parseCommaSeparatedString(str);
break; break;
} }
} else if (curapp != null) { } else if (curapp != null) {
@ -218,13 +218,13 @@ public class RepoXMLHandler extends DefaultHandler {
curapp.upstreamVersionCode = Utils.parseInt(str, -1); curapp.upstreamVersionCode = Utils.parseInt(str, -1);
break; break;
case "categories": case "categories":
curapp.categories = Utils.CommaSeparatedList.make(str); curapp.categories = Utils.parseCommaSeparatedString(str);
break; break;
case "antifeatures": case "antifeatures":
curapp.antiFeatures = Utils.CommaSeparatedList.make(str); curapp.antiFeatures = Utils.parseCommaSeparatedString(str);
break; break;
case "requirements": case "requirements":
curapp.requirements = Utils.CommaSeparatedList.make(str); curapp.requirements = Utils.parseCommaSeparatedString(str);
break; break;
} }
} else if ("description".equals(localName)) { } else if ("description".equals(localName)) {

View File

@ -57,11 +57,8 @@ import java.security.cert.CertificateEncodingException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Formatter; import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.Locale; import java.util.Locale;
public final class Utils { public final class Utils {
@ -402,87 +399,6 @@ public final class Utils {
return new Locale(languageTag); return new Locale(languageTag);
} }
public static final class CommaSeparatedList implements Iterable<String> {
private final String value;
private CommaSeparatedList(String list) {
value = list;
}
public static CommaSeparatedList make(List<String> list) {
if (list == null || list.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
sb.append(',');
}
sb.append(list.get(i));
}
return new CommaSeparatedList(sb.toString());
}
public static CommaSeparatedList make(String[] list) {
if (list == null || list.length == 0) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(list[i]);
}
return new CommaSeparatedList(sb.toString());
}
@Nullable
public static CommaSeparatedList make(@Nullable String list) {
if (TextUtils.isEmpty(list)) {
return null;
}
return new CommaSeparatedList(list);
}
public static String str(CommaSeparatedList instance) {
return instance == null ? null : instance.toString();
}
@Override
public String toString() {
return value;
}
public String toPrettyString() {
return value.replaceAll(",", ", ");
}
@Override
public Iterator<String> iterator() {
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
splitter.setString(value);
return splitter.iterator();
}
public ArrayList<String> toArrayList() {
ArrayList<String> out = new ArrayList<>();
for (String element : this) {
out.add(element);
}
return out;
}
public boolean contains(String v) {
for (final String s : this) {
if (s.equals(v)) {
return true;
}
}
return false;
}
}
public static DisplayImageOptions.Builder getImageLoadingOptions() { public static DisplayImageOptions.Builder getImageLoadingOptions() {
return new DisplayImageOptions.Builder() return new DisplayImageOptions.Builder()
.cacheInMemory(true) .cacheInMemory(true)
@ -560,6 +476,16 @@ public final class Utils {
return result; return result;
} }
@Nullable
public static String[] parseCommaSeparatedString(String values) {
return values == null || values.length() == 0 ? null : values.split(",");
}
@Nullable
public static String serializeCommaSeparatedString(@Nullable String[] values) {
return values == null || values.length == 0 ? null : TextUtils.join(",", values);
}
private static Date parseDateFormat(DateFormat format, String str, Date fallback) { private static Date parseDateFormat(DateFormat format, String str, Date fallback) {
if (str == null || str.length() == 0) { if (str == null || str.length() == 0) {
return fallback; return fallback;

View File

@ -29,11 +29,11 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public int targetSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown public int targetSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown
public int maxSdkVersion = SDK_VERSION_MAX_VALUE; // "infinity" if not set public int maxSdkVersion = SDK_VERSION_MAX_VALUE; // "infinity" if not set
public Date added; public Date added;
public Utils.CommaSeparatedList permissions; // null if empty or public String[] permissions; // null if empty or
// unknown // unknown
public Utils.CommaSeparatedList features; // null if empty or unknown public String[] features; // null if empty or unknown
public Utils.CommaSeparatedList nativecode; // null if empty or unknown public String[] nativecode; // null if empty or unknown
/** /**
* ID (md5 sum of public key) of signature. Might be null, in the * ID (md5 sum of public key) of signature. Might be null, in the
@ -58,7 +58,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public int repoVersion; public int repoVersion;
public String repoAddress; public String repoAddress;
public Utils.CommaSeparatedList incompatibleReasons; public String[] incompatibleReasons;
public Apk() { public Apk() {
} }
@ -83,7 +83,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
added = Utils.parseDate(cursor.getString(i), null); added = Utils.parseDate(cursor.getString(i), null);
break; break;
case ApkProvider.DataColumns.FEATURES: case ApkProvider.DataColumns.FEATURES:
features = Utils.CommaSeparatedList.make(cursor.getString(i)); features = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case ApkProvider.DataColumns.PACKAGE_NAME: case ApkProvider.DataColumns.PACKAGE_NAME:
packageName = cursor.getString(i); packageName = cursor.getString(i);
@ -104,13 +104,13 @@ public class Apk extends ValueObject implements Comparable<Apk> {
apkName = cursor.getString(i); apkName = cursor.getString(i);
break; break;
case ApkProvider.DataColumns.PERMISSIONS: case ApkProvider.DataColumns.PERMISSIONS:
permissions = Utils.CommaSeparatedList.make(cursor.getString(i)); permissions = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case ApkProvider.DataColumns.NATIVE_CODE: case ApkProvider.DataColumns.NATIVE_CODE:
nativecode = Utils.CommaSeparatedList.make(cursor.getString(i)); nativecode = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case ApkProvider.DataColumns.INCOMPATIBLE_REASONS: case ApkProvider.DataColumns.INCOMPATIBLE_REASONS:
incompatibleReasons = Utils.CommaSeparatedList.make(cursor.getString(i)); incompatibleReasons = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case ApkProvider.DataColumns.REPO_ID: case ApkProvider.DataColumns.REPO_ID:
repo = cursor.getInt(i); repo = cursor.getInt(i);
@ -205,10 +205,10 @@ public class Apk extends ValueObject implements Comparable<Apk> {
values.put(ApkProvider.DataColumns.TARGET_SDK_VERSION, targetSdkVersion); values.put(ApkProvider.DataColumns.TARGET_SDK_VERSION, targetSdkVersion);
values.put(ApkProvider.DataColumns.MAX_SDK_VERSION, maxSdkVersion); values.put(ApkProvider.DataColumns.MAX_SDK_VERSION, maxSdkVersion);
values.put(ApkProvider.DataColumns.ADDED_DATE, Utils.formatDate(added, "")); values.put(ApkProvider.DataColumns.ADDED_DATE, Utils.formatDate(added, ""));
values.put(ApkProvider.DataColumns.PERMISSIONS, Utils.CommaSeparatedList.str(permissions)); values.put(ApkProvider.DataColumns.PERMISSIONS, Utils.serializeCommaSeparatedString(permissions));
values.put(ApkProvider.DataColumns.FEATURES, Utils.CommaSeparatedList.str(features)); values.put(ApkProvider.DataColumns.FEATURES, Utils.serializeCommaSeparatedString(features));
values.put(ApkProvider.DataColumns.NATIVE_CODE, Utils.CommaSeparatedList.str(nativecode)); values.put(ApkProvider.DataColumns.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode));
values.put(ApkProvider.DataColumns.INCOMPATIBLE_REASONS, Utils.CommaSeparatedList.str(incompatibleReasons)); values.put(ApkProvider.DataColumns.INCOMPATIBLE_REASONS, Utils.serializeCommaSeparatedString(incompatibleReasons));
values.put(ApkProvider.DataColumns.IS_COMPATIBLE, compatible ? 1 : 0); values.put(ApkProvider.DataColumns.IS_COMPATIBLE, compatible ? 1 : 0);
return values; return values;
} }

View File

@ -88,17 +88,17 @@ public class App extends ValueObject implements Comparable<App> {
/** /**
* List of categories (as defined in the metadata documentation) or null if there aren't any. * List of categories (as defined in the metadata documentation) or null if there aren't any.
*/ */
public Utils.CommaSeparatedList categories; public String[] categories;
/** /**
* List of anti-features (as defined in the metadata documentation) or null if there aren't any. * List of anti-features (as defined in the metadata documentation) or null if there aren't any.
*/ */
public Utils.CommaSeparatedList antiFeatures; public String[] antiFeatures;
/** /**
* List of special requirements (such as root privileges) or null if there aren't any. * List of special requirements (such as root privileges) or null if there aren't any.
*/ */
public Utils.CommaSeparatedList requirements; public String[] requirements;
/** /**
* True if all updates for this app are to be ignored * True if all updates for this app are to be ignored
@ -221,13 +221,13 @@ public class App extends ValueObject implements Comparable<App> {
lastUpdated = Utils.parseDate(cursor.getString(i), null); lastUpdated = Utils.parseDate(cursor.getString(i), null);
break; break;
case AppProvider.DataColumns.CATEGORIES: case AppProvider.DataColumns.CATEGORIES:
categories = Utils.CommaSeparatedList.make(cursor.getString(i)); categories = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case AppProvider.DataColumns.ANTI_FEATURES: case AppProvider.DataColumns.ANTI_FEATURES:
antiFeatures = Utils.CommaSeparatedList.make(cursor.getString(i)); antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case AppProvider.DataColumns.REQUIREMENTS: case AppProvider.DataColumns.REQUIREMENTS:
requirements = Utils.CommaSeparatedList.make(cursor.getString(i)); requirements = Utils.parseCommaSeparatedString(cursor.getString(i));
break; break;
case AppProvider.DataColumns.IGNORE_ALLUPDATES: case AppProvider.DataColumns.IGNORE_ALLUPDATES:
ignoreAllUpdates = cursor.getInt(i) == 1; ignoreAllUpdates = cursor.getInt(i) == 1;
@ -334,7 +334,7 @@ public class App extends ValueObject implements Comparable<App> {
apk.targetSdkVersion = minTargetMax[1]; apk.targetSdkVersion = minTargetMax[1];
apk.maxSdkVersion = minTargetMax[2]; apk.maxSdkVersion = minTargetMax[2];
apk.packageName = this.packageName; apk.packageName = this.packageName;
apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions); apk.permissions = packageInfo.requestedPermissions;
apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk"; apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk";
apk.installedFile = apkFile; apk.installedFile = apkFile;
@ -348,15 +348,14 @@ public class App extends ValueObject implements Comparable<App> {
abis.add(matcher.group(1)); abis.add(matcher.group(1));
} }
} }
apk.nativecode = Utils.CommaSeparatedList.make(abis.toArray(new String[abis.size()])); apk.nativecode = abis.toArray(new String[abis.size()]);
final FeatureInfo[] features = packageInfo.reqFeatures; final FeatureInfo[] features = packageInfo.reqFeatures;
if (features != null && features.length > 0) { if (features != null && features.length > 0) {
final String[] featureNames = new String[features.length]; apk.features = new String[features.length];
for (int i = 0; i < features.length; i++) { for (int i = 0; i < features.length; i++) {
featureNames[i] = features[i].name; apk.features[i] = features[i].name;
} }
apk.features = Utils.CommaSeparatedList.make(featureNames);
} }
final JarEntry aSignedEntry = (JarEntry) apkJar.getEntry("AndroidManifest.xml"); final JarEntry aSignedEntry = (JarEntry) apkJar.getEntry("AndroidManifest.xml");
@ -462,9 +461,9 @@ public class App extends ValueObject implements Comparable<App> {
values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVersionCode); values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVersionCode);
values.put(AppProvider.DataColumns.UPSTREAM_VERSION_NAME, upstreamVersionName); values.put(AppProvider.DataColumns.UPSTREAM_VERSION_NAME, upstreamVersionName);
values.put(AppProvider.DataColumns.UPSTREAM_VERSION_CODE, upstreamVersionCode); values.put(AppProvider.DataColumns.UPSTREAM_VERSION_CODE, upstreamVersionCode);
values.put(AppProvider.DataColumns.CATEGORIES, Utils.CommaSeparatedList.str(categories)); values.put(AppProvider.DataColumns.CATEGORIES, Utils.serializeCommaSeparatedString(categories));
values.put(AppProvider.DataColumns.ANTI_FEATURES, Utils.CommaSeparatedList.str(antiFeatures)); values.put(AppProvider.DataColumns.ANTI_FEATURES, Utils.serializeCommaSeparatedString(antiFeatures));
values.put(AppProvider.DataColumns.REQUIREMENTS, Utils.CommaSeparatedList.str(requirements)); values.put(AppProvider.DataColumns.REQUIREMENTS, Utils.serializeCommaSeparatedString(requirements));
values.put(AppProvider.DataColumns.IS_COMPATIBLE, compatible ? 1 : 0); values.put(AppProvider.DataColumns.IS_COMPATIBLE, compatible ? 1 : 0);
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAllUpdates ? 1 : 0); values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAllUpdates ? 1 : 0);
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreThisUpdate); values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreThisUpdate);

View File

@ -98,11 +98,9 @@ public class AppProvider extends FDroidProvider {
cursor.moveToFirst(); cursor.moveToFirst();
while (!cursor.isAfterLast()) { while (!cursor.isAfterLast()) {
final String categoriesString = cursor.getString(0); final String categoriesString = cursor.getString(0);
Utils.CommaSeparatedList categoriesList = Utils.CommaSeparatedList.make(categoriesString); String[] categoriesList = Utils.parseCommaSeparatedString(categoriesString);
if (categoriesList != null) { if (categoriesList != null) {
for (final String s : categoriesList) { Collections.addAll(categorySet, categoriesList);
categorySet.add(s);
}
} }
cursor.moveToNext(); cursor.moveToNext();
} }

View File

@ -284,7 +284,7 @@ public class RepoPersister {
final List<String> reasons = checker.getIncompatibleReasons(apk); final List<String> reasons = checker.getIncompatibleReasons(apk);
if (reasons.size() > 0) { if (reasons.size() > 0) {
apk.compatible = false; apk.compatible = false;
apk.incompatibleReasons = Utils.CommaSeparatedList.make(reasons); apk.incompatibleReasons = reasons.toArray(new String[reasons.size()]);
} else { } else {
apk.compatible = true; apk.compatible = true;
apk.incompatibleReasons = null; apk.incompatibleReasons = null;

View File

@ -454,7 +454,7 @@ public final class LocalRepoManager {
private void tagFeatures(App app) throws IOException { private void tagFeatures(App app) throws IOException {
serializer.startTag("", "features"); serializer.startTag("", "features");
if (app.installedApk.features != null) { if (app.installedApk.features != null) {
serializer.text(Utils.CommaSeparatedList.str(app.installedApk.features)); serializer.text(TextUtils.join(",", app.installedApk.features));
} }
serializer.endTag("", "features"); serializer.endTag("", "features");
} }
@ -462,7 +462,7 @@ public final class LocalRepoManager {
private void tagNativecode(App app) throws IOException { private void tagNativecode(App app) throws IOException {
if (app.installedApk.nativecode != null) { if (app.installedApk.nativecode != null) {
serializer.startTag("", "nativecode"); serializer.startTag("", "nativecode");
serializer.text(Utils.CommaSeparatedList.str(app.installedApk.nativecode)); serializer.text(TextUtils.join(",", app.installedApk.nativecode));
serializer.endTag("", "nativecode"); serializer.endTag("", "nativecode");
} }
} }

View File

@ -21,7 +21,11 @@ import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
@ -30,6 +34,8 @@ import javax.xml.parsers.SAXParserFactory;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@Config(constants = BuildConfig.class) @Config(constants = BuildConfig.class)
@ -112,6 +118,65 @@ public class RepoXMLHandlerTest {
expectedRepo.timestamp = 1412746769; expectedRepo.timestamp = 1412746769;
RepoDetails actualDetails = getFromFile("largeRepo.xml"); RepoDetails actualDetails = getFromFile("largeRepo.xml");
handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12); handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12);
// Generated using something like the following:
// sed 's,<application,\n<application,g' largeRepo.xml | grep "antifeatures" | sed 's,.*id="\(.*\)".*<antifeatures>\(.*\)</antifeatures>.*,\1 \2,p' | sort | uniq
Map<String, List<String>> expectedAntiFeatures = new HashMap<>();
expectedAntiFeatures.put("org.fdroid.fdroid", new ArrayList<String>());
expectedAntiFeatures.put("org.adblockplus.android", Arrays.asList("Tracking", "Ads"));
expectedAntiFeatures.put("org.microg.nlp.backend.apple", Arrays.asList("Tracking", "NonFreeNet"));
expectedAntiFeatures.put("com.ds.avare", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.miracleas.bitcoin_spinner", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("de.Cherubin7th.blackscreenpresentationremote", Collections.singletonList("Ads"));
expectedAntiFeatures.put("budo.budoist", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("no.rkkc.bysykkel", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.jadn.cc", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.atai.TessUI", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.zephyrsoft.checknetwork", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.bashtian.dashclocksunrise", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("org.geometerplus.zlibrary.ui.android", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.mozilla.firefox", Arrays.asList("NonFreeAdd", "Tracking"));
expectedAntiFeatures.put("com.gmail.charleszq", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("it.andreascarpino.forvodroid", Arrays.asList("NonFreeNet", "NonFreeDep"));
expectedAntiFeatures.put("de.b0nk.fp1_epo_autoupdate", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.blogspot.tonyatkins.freespeech", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.frostwire.android", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.namsor.api.samples.gendre", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.github.mobile", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.cradle.iitc_mobile", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.matteopacini.katana", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.enaikoon.android.keypadmapper3", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.linphone", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("ch.rrelmy.android.locationcachemap", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.powerpoint45.lucidbrowser", Arrays.asList("Ads", "NonFreeDep"));
expectedAntiFeatures.put("org.mixare", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("apps.droidnotify", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.numix.calculator", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.numix.icons_circle", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.gh4a", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("at.tomtasche.reader", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.uni_potsdam.hpi.openmensa", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("net.osmand.plus", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("byrne.utilities.pasteedroid", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.bwx.bequick", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("be.geecko.QuickLyric", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.wanghaus.remembeer", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("cri.sanity", Collections.singletonList("Ads"));
expectedAntiFeatures.put("com.showmehills", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.akop.bach", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.dmfs.tasks", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.telegram.messenger", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.danvelazco.fbwrapper", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.zephyrsoft.trackworktime", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.transdroid", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.lonepulse.travisjr", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.twsitedapps.homemanager", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.zeitgeist.movement", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("net.wigle.wigleandroid", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.nick.wwwjdic", Collections.singletonList("Tracking"));
checkAntiFeatures(actualDetails.apps, expectedAntiFeatures);
/* /*
* 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'
@ -599,6 +664,22 @@ public class RepoXMLHandlerTest {
}); });
} }
private void checkAntiFeatures(List<App> apps, Map<String, List<String>> expectedAntiFeatures) {
for (App app : apps) {
if (expectedAntiFeatures.containsKey(app.packageName)) {
List<String> antiFeatures = expectedAntiFeatures.get(app.packageName);
if (antiFeatures.size() == 0) {
assertNull(app.antiFeatures);
} else {
List<String> actualAntiFeatures = new ArrayList<>();
Collections.addAll(actualAntiFeatures, app.antiFeatures);
assertTrue(actualAntiFeatures.containsAll(antiFeatures));
assertTrue(antiFeatures.containsAll(actualAntiFeatures));
}
}
}
}
private void checkIncludedApps(List<App> actualApps, String[] expctedAppIds) { private void checkIncludedApps(List<App> actualApps, String[] expctedAppIds) {
assertNotNull(actualApps); assertNotNull(actualApps);
assertNotNull(expctedAppIds); assertNotNull(expctedAppIds);

View File

@ -15,6 +15,8 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class) @Config(constants = BuildConfig.class)
@ -49,6 +51,29 @@ public class UtilsTest {
String fingerprintLongByOneFingerprint = "59050C8155DCA377F23D5A15B77D37134000CDBD8B42FBFBE0E3F38096E68CECE"; String fingerprintLongByOneFingerprint = "59050C8155DCA377F23D5A15B77D37134000CDBD8B42FBFBE0E3F38096E68CECE";
String fingerprintLongByOnePubkey = "308203c5308202ada00302010202047b7cf549300d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85"; String fingerprintLongByOnePubkey = "308203c5308202ada00302010202047b7cf549300d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85";
@Test
public void commaSeparatedStrings() {
assertNull(Utils.parseCommaSeparatedString(null));
assertNull(Utils.parseCommaSeparatedString(""));
String[] singleValue = Utils.parseCommaSeparatedString("single");
assertNotNull(singleValue);
assertEquals(1, singleValue.length);
assertEquals("single", singleValue[0]);
String[] tripleValue = Utils.parseCommaSeparatedString("One,TWO,three");
assertNotNull(tripleValue);
assertEquals(3, tripleValue.length);
assertEquals("One", tripleValue[0]);
assertEquals("TWO", tripleValue[1]);
assertEquals("three", tripleValue[2]);
assertNull(Utils.serializeCommaSeparatedString(null));
assertNull(Utils.serializeCommaSeparatedString(new String[] {}));
assertEquals("Single", Utils.serializeCommaSeparatedString(new String[] {"Single"}));
assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[] {"One", "TWO", "three"}));
}
@Test @Test
public void testFormatFingerprint() { public void testFormatFingerprint() {
Context context = RuntimeEnvironment.application; Context context = RuntimeEnvironment.application;

View File

@ -7,7 +7,6 @@ import android.net.Uri;
import org.fdroid.fdroid.Assert; import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.mock.MockApk; import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockApp; import org.fdroid.fdroid.mock.MockApp;
import org.fdroid.fdroid.mock.MockRepo; import org.fdroid.fdroid.mock.MockRepo;
@ -289,7 +288,8 @@ public class ApkProviderTest extends FDroidProviderTest {
assertEquals(0, apk.repoVersion); assertEquals(0, apk.repoVersion);
// But this should have saved correctly... // But this should have saved correctly...
assertEquals("Some features", apk.features.toString()); assertEquals(1, apk.features.length);
assertEquals("Some features", apk.features[0]);
assertEquals("com.example.com", apk.packageName); assertEquals("com.example.com", apk.packageName);
assertEquals(1, apk.versionCode); assertEquals(1, apk.versionCode);
assertEquals(10, apk.repo); assertEquals(10, apk.repo);
@ -409,7 +409,7 @@ public class ApkProviderTest extends FDroidProviderTest {
assertNull(apk.added); assertNull(apk.added);
assertNull(apk.hashType); assertNull(apk.hashType);
apk.features = Utils.CommaSeparatedList.make("one,two,three"); apk.features = new String[] {"one", "two", "three" };
long dateTimestamp = System.currentTimeMillis(); long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp); apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type"; apk.hashType = "i'm a hash type";
@ -435,7 +435,10 @@ public class ApkProviderTest extends FDroidProviderTest {
assertNotNull(updatedApk.added); assertNotNull(updatedApk.added);
assertNotNull(updatedApk.hashType); assertNotNull(updatedApk.hashType);
assertEquals("one,two,three", updatedApk.features.toString()); assertEquals(3, updatedApk.features.length);
assertEquals("one", updatedApk.features[0]);
assertEquals("two", updatedApk.features[1]);
assertEquals("three", updatedApk.features[2]);
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear()); assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth()); assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay()); assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());