[app] Fix or remove UI tests

Tests for v0 index have been removed with their assets.

AntiFeaturesTest is not needed anymore, because filtering doesn't happen in new DB

PanicResponderActivityTest can not be tested as easily anymore

MultiIndexUpdaterTests are now spread over various database tests

LocaleSelectionTest is now in org.fdroid.database.BestLocaleTest

Most tests org.fdroid.fdroid.data now have equivalents in the new database library
This commit is contained in:
Torsten Grote
2022-06-01 09:36:59 -03:00
committed by Hans-Christoph Steiner
parent d61ecbfa08
commit cf913ffefa
71 changed files with 138 additions and 6153 deletions

View File

@@ -103,9 +103,6 @@ android {
showStandardStreams = true
}
systemProperty 'robolectric.dependency.repo.url', 'https://repo1.maven.org/maven2'
// hack to avoid memory leak crashes
forkEvery = 1
}
}
}

View File

@@ -108,50 +108,50 @@ public class SwapRepoEmulatorTest {
assertFalse(TextUtils.isEmpty(signingCert));
assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert)));
Repo repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
while (repoToDelete != null) {
Log.d(TAG, "Removing old test swap repo matching this one: " + repoToDelete.address);
RepoProvider.Helper.remove(context, repoToDelete.getId());
repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
}
// Repo repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
// while (repoToDelete != null) {
// Log.d(TAG, "Removing old test swap repo matching this one: " + repoToDelete.address);
// RepoProvider.Helper.remove(context, repoToDelete.getId());
// repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
// }
//
// ContentValues values = new ContentValues(4);
// values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
// values.put(Schema.RepoTable.Cols.ADDRESS, FDroidApp.repo.getAddress());
// values.put(Schema.RepoTable.Cols.NAME, "");
// values.put(Schema.RepoTable.Cols.IS_SWAP, true);
// final String lastEtag = UUID.randomUUID().toString();
// values.put(Schema.RepoTable.Cols.LAST_ETAG, lastEtag);
// RepoProvider.Helper.insert(context, values);
// Repo repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
// assertTrue(repo.isSwap);
// assertNotEquals(-1, repo.getId());
// assertEquals(lastEtag, repo.lastetag);
// assertNull(repo.lastUpdated);
//
// assertTrue(isPortInUse(FDroidApp.ipAddressString, FDroidApp.port));
// Thread.sleep(100);
// IndexUpdater updater = new IndexUpdater(context, repo);
// updater.update();
// assertTrue(updater.hasChanged());
//
// repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
// final Date lastUpdated = repo.lastUpdated;
// assertTrue("repo lastUpdated should be updated", new Date(2019, 5, 13).compareTo(repo.lastUpdated) > 0);
ContentValues values = new ContentValues(4);
values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
values.put(Schema.RepoTable.Cols.ADDRESS, FDroidApp.repo.getAddress());
values.put(Schema.RepoTable.Cols.NAME, "");
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
final String lastEtag = UUID.randomUUID().toString();
values.put(Schema.RepoTable.Cols.LAST_ETAG, lastEtag);
RepoProvider.Helper.insert(context, values);
Repo repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
assertTrue(repo.isSwap);
assertNotEquals(-1, repo.getId());
assertEquals(lastEtag, repo.lastetag);
assertNull(repo.lastUpdated);
// App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(),
// context.getPackageName(), repo.getId());
// assertEquals(context.getPackageName(), app.packageName);
assertTrue(isPortInUse(FDroidApp.ipAddressString, FDroidApp.port));
Thread.sleep(100);
IndexUpdater updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.getAddress());
final Date lastUpdated = repo.lastUpdated;
assertTrue("repo lastUpdated should be updated", new Date(2019, 5, 13).compareTo(repo.lastUpdated) > 0);
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(),
context.getPackageName(), repo.getId());
assertEquals(context.getPackageName(), app.packageName);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals(1, apks.size());
for (Apk apk : apks) {
Log.i(TAG, "Apk: " + apk);
assertEquals(context.getPackageName(), apk.packageName);
assertEquals(BuildConfig.VERSION_NAME, apk.versionName);
assertEquals(BuildConfig.VERSION_CODE, apk.versionCode);
assertEquals(app.repoId, apk.repoId);
}
// List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
// assertEquals(1, apks.size());
// for (Apk apk : apks) {
// Log.i(TAG, "Apk: " + apk);
// assertEquals(context.getPackageName(), apk.packageName);
// assertEquals(BuildConfig.VERSION_NAME, apk.versionName);
// assertEquals(BuildConfig.VERSION_CODE, apk.versionCode);
// assertEquals(app.repoId, apk.repoId);
// }
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -165,14 +165,14 @@ public class SwapRepoEmulatorTest {
}
LocalRepoService.runProcess(context, packageNames.toArray(new String[0]));
updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
assertTrue("repo lastUpdated should be updated", lastUpdated.compareTo(repo.lastUpdated) < 0);
for (String packageName : packageNames) {
assertNotNull(ApkProvider.Helper.findByPackageName(context, packageName));
}
// updater = new IndexUpdater(context, repo);
// updater.update();
// assertTrue(updater.hasChanged());
// assertTrue("repo lastUpdated should be updated", lastUpdated.compareTo(repo.lastUpdated) < 0);
//
// for (String packageName : packageNames) {
// assertNotNull(ApkProvider.Helper.findByPackageName(context, packageName));
// }
} finally {
if (localHttpd != null) {
localHttpd.stop();

View File

@@ -1,143 +0,0 @@
package org.fdroid.fdroid;
import android.app.Application;
import android.content.ContentValues;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppPrefs;
import org.fdroid.fdroid.data.AppPrefsProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.InstalledAppTestUtils;
import org.fdroid.fdroid.data.Schema;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class AntiFeaturesTest extends FDroidProviderTest {
private App notVuln;
private App allVuln;
private App vulnAtV2;
@Before
public void setup() {
Preferences.setupForTests(context);
ContentValues vulnValues = new ContentValues(1);
vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
insertApk(vulnAtV2, 1, false);
insertApk(vulnAtV2, 2, true);
insertApk(vulnAtV2, 3, false);
notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
insertApk(notVuln, 5, false);
insertApk(notVuln, 10, false);
insertApk(notVuln, 15, false);
allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
insertApk(allVuln, 100, true);
insertApk(allVuln, 101, true);
insertApk(allVuln, 105, true);
AppProvider.Helper.recalculatePreferredMetadata(context);
}
private static String generateHash(String packageName, int versionCode) {
return packageName + "-" + versionCode;
}
private void insertApk(App app, int versionCode, boolean isVuln) {
ContentValues values = new ContentValues();
values.put(Schema.ApkTable.Cols.HASH, generateHash(app.packageName, versionCode));
if (isVuln) {
values.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
}
Assert.insertApk(context, app, versionCode, values);
}
private void install(App app, int versionCode) {
String hash = generateHash(app.packageName, versionCode);
InstalledAppTestUtils.install(context, app.packageName, versionCode, "v" + versionCode, null, hash);
}
@Test
public void noVulnerableApps() {
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void futureVersionIsVulnerable() {
install(vulnAtV2, 1);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void vulnerableAndAbleToBeUpdated() {
install(vulnAtV2, 2);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(1, installed.size());
assertEquals(vulnAtV2.packageName, installed.get(0).packageName);
}
@Test
public void vulnerableButUpToDate() {
install(vulnAtV2, 3);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void allVulnerableButIgnored() {
install(allVuln, 101);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(1, installed.size());
App app = installed.get(0);
AppPrefs prefs = app.getPrefs(context);
prefs.ignoreVulnerabilities = true;
AppPrefsProvider.Helper.update(context, app, prefs);
List<App> installedButIgnored = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installedButIgnored.size());
}
@Test
public void antiFeaturesSaveCorrectly() {
List<Apk> notVulnApks = ApkProvider.Helper.findByPackageName(context, notVuln.packageName);
assertEquals(3, notVulnApks.size());
List<Apk> allVulnApks = ApkProvider.Helper.findByPackageName(context, allVuln.packageName);
assertEquals(3, allVulnApks.size());
for (Apk apk : allVulnApks) {
assertArrayEquals(new String[]{"KnownVuln", "ContainsGreenButtons"}, apk.antiFeatures);
}
List<Apk> vulnAtV2Apks = ApkProvider.Helper.findByPackageName(context, vulnAtV2.packageName);
assertEquals(3, vulnAtV2Apks.size());
for (Apk apk : vulnAtV2Apks) {
if (apk.versionCode == 2) {
assertArrayEquals(new String[]{"KnownVuln", "ContainsGreenButtons"}, apk.antiFeatures);
} else {
assertNull(apk.antiFeatures);
}
}
}
}

View File

@@ -1,267 +0,0 @@
package org.fdroid.fdroid;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import junit.framework.AssertionFailedError;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.Schema.ApkTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
public class Assert {
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, List<T> expectedList) {
List<T> actualList = new ArrayList<>(actualArray.length);
Collections.addAll(actualList, actualArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualArray, expectedList);
}
public static <T> String listToString(List<T> list) {
String string = "[";
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
string += ", ";
}
string += "'" + list.get(i) + "'";
}
string += "]";
return string;
}
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, List<T> expectedContains) {
if (actualList.size() != expectedContains.size()) {
String message =
"List sizes don't match.\n" +
"Expected: " +
listToString(expectedContains) + "\n" +
"Actual: " +
listToString(actualList);
throw new AssertionFailedError(message);
}
for (T required : expectedContains) {
boolean containsRequired = false;
for (T itemInList : actualList) {
if (required.equals(itemInList)) {
containsRequired = true;
break;
}
}
if (!containsRequired) {
String message =
"List doesn't contain \"" + required + "\".\n" +
"Expected: " +
listToString(expectedContains) + "\n" +
"Actual: " +
listToString(actualList);
throw new AssertionFailedError(message);
}
}
}
public static void assertCantDelete(ContentResolver resolver, Uri uri) {
try {
resolver.delete(uri, null, null);
fail();
} catch (UnsupportedOperationException e) {
// Successful condition
} catch (Exception e) {
fail();
}
}
public static void assertCantUpdate(ContentResolver resolver, Uri uri) {
try {
resolver.update(uri, new ContentValues(), null, null);
fail();
} catch (UnsupportedOperationException e) {
// Successful condition
} catch (Exception e) {
fail();
}
}
public static void assertInvalidUri(ContentResolver resolver, String uri) {
assertInvalidUri(resolver, Uri.parse(uri));
}
public static void assertValidUri(ContentResolver resolver, String uri, String[] projection) {
assertValidUri(resolver, Uri.parse(uri), projection);
}
public static void assertInvalidUri(ContentResolver resolver, Uri uri) {
Cursor cursor = resolver.query(uri, new String[]{}, null, null, null);
assertNull(cursor);
}
public static void assertValidUri(ContentResolver resolver, Uri uri, String[] projection) {
Cursor cursor = resolver.query(uri, projection, null, null, null);
assertNotNull(cursor);
cursor.close();
}
public static void assertValidUri(ContentResolver resolver, Uri actualUri, String expectedUri,
String[] projection) {
assertValidUri(resolver, actualUri, projection);
assertEquals(expectedUri, actualUri.toString());
}
public static void assertResultCount(ContentResolver resolver, int expectedCount, Uri uri) {
assertResultCount(resolver, expectedCount, uri, new String[]{});
}
public static void assertResultCount(ContentResolver resolver, int expectedCount, Uri uri,
String[] projection) {
Cursor cursor = resolver.query(uri, projection, null, null, null);
assertResultCount(expectedCount, cursor);
cursor.close();
}
public static void assertResultCount(int expectedCount, List items) {
assertNotNull(items);
assertEquals(expectedCount, items.size());
}
public static void assertResultCount(int expectedCount, Cursor result) {
assertNotNull(result);
assertEquals(expectedCount, result.getCount());
}
public static void assertIsInstalledVersionInDb(ContentResolver resolver,
String appId, int versionCode, String versionName) {
Uri uri = InstalledAppProvider.getAppUri(appId);
String[] projection = {
InstalledAppTable.Cols.Package.NAME,
InstalledAppTable.Cols.VERSION_CODE,
InstalledAppTable.Cols.VERSION_NAME,
InstalledAppTable.Cols.APPLICATION_LABEL,
};
Cursor cursor = resolver.query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME)));
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_CODE)));
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_NAME)));
cursor.close();
}
public static App insertApp(Context context, String packageName, String name) {
return insertApp(context, packageName, name, new ContentValues());
}
public static App insertApp(Context context, String packageName, String name, Repo repo) {
ContentValues values = new ContentValues();
values.put(AppMetadataTable.Cols.REPO_ID, repo.getId());
return insertApp(context, packageName, name, values);
}
public static App insertApp(Context context, String packageName, String name, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(AppMetadataTable.Cols.REPO_ID, 1);
values.put(AppMetadataTable.Cols.Package.PACKAGE_NAME, packageName);
values.put(AppMetadataTable.Cols.NAME, name);
// Required fields (NOT NULL in the database).
values.put(AppMetadataTable.Cols.SUMMARY, "test summary");
values.put(AppMetadataTable.Cols.DESCRIPTION, "test description");
values.put(AppMetadataTable.Cols.LICENSE, "GPL?");
values.put(AppMetadataTable.Cols.IS_COMPATIBLE, 1);
values.putAll(additionalValues);
// Don't hard code to 1, let consumers override it in additionalValues then ask for it back.
int repoId = values.getAsInteger(AppMetadataTable.Cols.REPO_ID);
Uri uri = AppProvider.getContentUri();
context.getContentResolver().insert(uri, values);
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName,
repoId, AppMetadataTable.Cols.ALL);
assertNotNull(app);
return app;
}
public static App ensureApp(Context context, String packageName) {
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1,
AppMetadataTable.Cols.ALL);
if (app == null) {
insertApp(context, packageName, packageName);
app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1,
AppMetadataTable.Cols.ALL);
}
assertNotNull(app);
return app;
}
public static Uri insertApk(Context context, String packageName, int versionCode) {
return insertApk(context, ensureApp(context, packageName), versionCode);
}
public static Uri insertApk(Context context, String packageName, int versionCode,
ContentValues additionalValues) {
return insertApk(context, ensureApp(context, packageName), versionCode, additionalValues);
}
public static Uri insertApk(Context context, App app, int versionCode) {
return insertApk(context, app, versionCode, new ContentValues());
}
public static Uri insertApk(Context context, App app, int versionCode, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(ApkTable.Cols.APP_ID, app.getId());
values.put(ApkTable.Cols.VERSION_CODE, versionCode);
// Required fields (NOT NULL in the database).
values.put(ApkTable.Cols.REPO_ID, 1);
values.put(ApkTable.Cols.VERSION_NAME, "The good one");
values.put(ApkTable.Cols.HASH, "11111111aaaaaaaa");
values.put(ApkTable.Cols.NAME, "Test Apk");
values.put(ApkTable.Cols.SIZE, 10000);
values.put(ApkTable.Cols.IS_COMPATIBLE, 1);
values.putAll(additionalValues);
Uri uri = ApkProvider.getContentUri();
return context.getContentResolver().insert(uri, values);
}
}

View File

@@ -20,7 +20,6 @@
package org.fdroid.fdroid;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.MockApk;
import org.junit.Before;
@@ -30,9 +29,14 @@ import org.robolectric.RobolectricTestRunner;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@RunWith(RobolectricTestRunner.class)
public class RepoUrlsTest extends FDroidProviderTest {
public static final String TAG = "RepoUrlsTest";
public class RepoUrlsTest {
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Private class describing a repository URL we're going to test, and
@@ -40,9 +44,9 @@ public class RepoUrlsTest extends FDroidProviderTest {
*/
private static class TestRepo {
// Repo URL for the test case
public String repoUrl;
String repoUrl;
// String format pattern for generating file URLs, should contain a single %s for the filename
public String fileUrlPattern;
String fileUrlPattern;
TestRepo(String repoUrl, String fileUrlPattern) {
this.repoUrl = repoUrl;
@@ -115,38 +119,29 @@ public class RepoUrlsTest extends FDroidProviderTest {
@Test
public void testIndexUrls() {
testReposWithFile(IndexUpdater.SIGNED_FILE_NAME, new GetFileFromRepo() {
@Override
public String get(TestRepo tr) {
Repo repo = new Repo();
repo.address = tr.repoUrl;
IndexUpdater updater = new IndexUpdater(context, repo);
return updater.getIndexUrl(repo);
}
testReposWithFile(IndexUpdater.SIGNED_FILE_NAME, tr -> {
Repo repo = new Repo();
repo.address = tr.repoUrl;
IndexUpdater updater = new IndexUpdater(context, repo);
return updater.getIndexUrl(repo);
});
}
@Test
public void testIndexV1Urls() {
testReposWithFile(IndexV1Updater.SIGNED_FILE_NAME, new GetFileFromRepo() {
@Override
public String get(TestRepo tr) {
Repo repo = new Repo();
repo.address = tr.repoUrl;
IndexV1Updater updater = new IndexV1Updater(context, repo);
return updater.getIndexUrl(repo);
}
testReposWithFile(IndexV1Updater.SIGNED_FILE_NAME, tr -> {
Repo repo = new Repo();
repo.address = tr.repoUrl;
IndexV1Updater updater = new IndexV1Updater(context, repo);
return updater.getIndexUrl(repo);
});
}
@Test
public void testApkUrls() {
testReposWithFile(APK_NAME, new GetFileFromRepo() {
@Override
public String get(TestRepo tr) {
Apk apk = new MockApk(APK_NAME, 1, tr.repoUrl, APK_NAME);
return apk.getCanonicalUrl();
}
testReposWithFile(APK_NAME, tr -> {
Apk apk = new MockApk(APK_NAME, 1, tr.repoUrl, APK_NAME);
return apk.getCanonicalUrl();
});
}
}

View File

@@ -1,42 +1,18 @@
package org.fdroid.fdroid;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import org.apache.commons.io.IOUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.RepoProviderTest;
import org.fdroid.fdroid.data.Schema;
import org.mockito.AdditionalAnswers;
import org.robolectric.Robolectric;
import org.robolectric.android.controller.ContentProviderController;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import androidx.test.core.app.ApplicationProvider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
public class TestUtils {
@@ -47,13 +23,11 @@ public class TestUtils {
* This is the F-Droid signature used to sign the AdAway binaries used for the multiRepo.*.jar
* repos used by some tests.
*/
public static final String FDROID_CERT = "3082033c30820224a00302010202044e9c4ba6300d06092a864886f70d01010505003060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f6964301e170d3131313031373135333731305a170d3339303330343135333731305a3060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f696430820122300d06092a864886f70d01010105000382010f003082010a0282010100981b0aac96f1c66be3c21e773327ee8c4d3b18c75c548243f4cfedbe8ef0d3c6cc1b3b7b094ddd39cdf71d034ef2cd2d1e7bdca458801b04a531cbe7106a3575151375cb32177b017f81cc508f981a1809d0a417c6f3d59ddfa876c3d91874b1d59e08eaf757da13fb82f7e6f7340abc56f0ab672f02e957d446585931388b1affb6f43a16efc7f060df9c8da17c86899b19495114cc5939decd521e172b48e68c6ec03bc58776acd6a52fd61fd839d2a404df25ae79c2ccec2d9a07c9a1751c341e5e9b706b8e713bec2149e16f5ca15a1d6fe67d52ebb210995ee03d9416118fa9434f65ffe6d43dddfe3e2b0c54b94ea8e5a1031ed41856cd369da41dc6790203010001300d06092a864886f70d0101050500038201010080951aa68b5a2c7ac464b66078afd4826df96e2c10b612a441036e43aa923bfa55f26c61b5d94c2132877a3801c2394328f70b322f6308dbea6ed4f0f4897d73d13af9498277f60685239acd8922275544334d295b07245ef0ec924e1c35e8004d8d268d97c957078149cc5635f8977ce432a56278a03664a45a6be51319b0b5f3e27b2372ae859215e3f3d0f5c8b86d1a42f742abe4d224870d419600966e46d83ce41df04e315353f334378f0f994732a6c05d351b1bea66efc62471762d0f752d379966e8293fc5fe4150665427b0f3fb3a1b64c3b75128abadc02c3efa44c06e2d22ba8f1c3f4b782ac2da0d56307173093fde31215d26ab05714a12d696"; // NOCHECKSTYLE LineLength
public static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
public static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
private static final String FDROID_CERT = "3082033c30820224a00302010202044e9c4ba6300d06092a864886f70d01010505003060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f6964301e170d3131313031373135333731305a170d3339303330343135333731305a3060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f696430820122300d06092a864886f70d01010105000382010f003082010a0282010100981b0aac96f1c66be3c21e773327ee8c4d3b18c75c548243f4cfedbe8ef0d3c6cc1b3b7b094ddd39cdf71d034ef2cd2d1e7bdca458801b04a531cbe7106a3575151375cb32177b017f81cc508f981a1809d0a417c6f3d59ddfa876c3d91874b1d59e08eaf757da13fb82f7e6f7340abc56f0ab672f02e957d446585931388b1affb6f43a16efc7f060df9c8da17c86899b19495114cc5939decd521e172b48e68c6ec03bc58776acd6a52fd61fd839d2a404df25ae79c2ccec2d9a07c9a1751c341e5e9b706b8e713bec2149e16f5ca15a1d6fe67d52ebb210995ee03d9416118fa9434f65ffe6d43dddfe3e2b0c54b94ea8e5a1031ed41856cd369da41dc6790203010001300d06092a864886f70d0101050500038201010080951aa68b5a2c7ac464b66078afd4826df96e2c10b612a441036e43aa923bfa55f26c61b5d94c2132877a3801c2394328f70b322f6308dbea6ed4f0f4897d73d13af9498277f60685239acd8922275544334d295b07245ef0ec924e1c35e8004d8d268d97c957078149cc5635f8977ce432a56278a03664a45a6be51319b0b5f3e27b2372ae859215e3f3d0f5c8b86d1a42f742abe4d224870d419600966e46d83ce41df04e315353f334378f0f994732a6c05d351b1bea66efc62471762d0f752d379966e8293fc5fe4150665427b0f3fb3a1b64c3b75128abadc02c3efa44c06e2d22ba8f1c3f4b782ac2da0d56307173093fde31215d26ab05714a12d696"; // NOCHECKSTYLE LineLength
private static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
public static final String FDROID_SIG;
public static final String UPSTREAM_SIG;
public static final String THIRD_PARTY_SIG;
static {
// Some code requires the full certificate (e.g. when we mock PackageInfo to give to the
@@ -62,43 +36,11 @@ public class TestUtils {
try {
FDROID_SIG = new Hasher("MD5", FDROID_CERT.getBytes()).getHash();
UPSTREAM_SIG = new Hasher("MD5", UPSTREAM_CERT.getBytes()).getHash();
THIRD_PARTY_SIG = new Hasher("MD5", THIRD_PARTY_CERT.getBytes()).getHash();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
private static String formatSigForDebugging(String sig) {
String suffix;
// Can't use a switch statement here because *_SIG is not a constant, despite beign static final.
if (sig.equals(FDROID_SIG)) {
suffix = "F-Droid";
} else if (sig.equals(UPSTREAM_SIG)) {
suffix = "Upstream";
} else if (sig.equals(THIRD_PARTY_SIG)) {
suffix = "3rd Party";
} else {
suffix = "Unknown";
}
return sig + " [" + suffix + "]";
}
public static void assertSignaturesMatch(String message, String expected, String actual) {
assertEquals(message, formatSigForDebugging(expected), formatSigForDebugging(actual));
}
public static Apk insertApk(Context context, App app, int versionCode, String signature) {
ContentValues values = new ContentValues();
values.put(Schema.ApkTable.Cols.SIGNATURE, signature);
long repoId = app.repoId > 0 ? app.repoId : 1;
values.put(Schema.ApkTable.Cols.REPO_ID, repoId);
Uri uri = Assert.insertApk(context, app, versionCode, values);
return ApkProvider.Helper.findByUri(context, uri, Schema.ApkTable.Cols.ALL);
}
public static Apk getApk(long appId, int versionCode) {
return getApk(appId, versionCode, "signature", null);
}
@@ -132,37 +74,6 @@ public class TestUtils {
return app;
}
public static App insertApp(Context context, String packageName, String appName, int suggestedVersionCode,
String repoUrl, String preferredSigner) {
Repo repo = ensureRepo(context, repoUrl);
return insertApp(context, packageName, appName, suggestedVersionCode, repo, preferredSigner);
}
public static App insertApp(Context context, String packageName, String appName, int suggestedVersionCode,
Repo repo, String preferredSigner) {
ContentValues values = new ContentValues();
values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId());
values.put(Schema.AppMetadataTable.Cols.SUGGESTED_VERSION_CODE, suggestedVersionCode);
values.put(Schema.AppMetadataTable.Cols.PREFERRED_SIGNER, preferredSigner);
return Assert.insertApp(context, packageName, appName, values);
}
public static Repo ensureRepo(Context context, String repoUrl) {
Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl);
if (existing != null) {
return existing;
}
return RepoProviderTest.insertRepo(context, repoUrl, "", "", "");
}
public static <T extends ContentProvider> ContentProviderController<T> registerContentProvider(
String authority, Class<T> providerClass) {
ProviderInfo info = new ProviderInfo();
info.authority = authority;
return Robolectric.buildContentProvider(providerClass).create(info);
}
public static File copyResourceToTempFile(String resourceName) {
File tempFile = null;
InputStream input = null;
@@ -185,59 +96,4 @@ public class TestUtils {
}
return tempFile;
}
/**
* The way that Robolectric has to implement shadows for Android classes
* such as {@link android.content.ContentProvider} is by using a special
* annotation that means the classes will implement the correct methods at
* runtime. However this means that the shadow of a content provider does
* not actually extend {@link android.content.ContentProvider}. As such,
* we need to do some special mocking using Mockito in order to provide a
* {@link ContextWrapper} which is able to return a proper content
* resolver that delegates to the Robolectric shadow object.
*/
public static ContextWrapper createContextWithContentResolver(ContentResolver contentResolver) {
final ContentResolver resolver = mock(ContentResolver.class, AdditionalAnswers.delegatesTo(contentResolver));
return new ContextWrapper(ApplicationProvider.getApplicationContext()) {
@Override
public ContentResolver getContentResolver() {
return resolver;
}
};
}
/**
* Normally apps/apks are only added to the database in response to a repo update.
* At the end of a repo update, the {@link AppProvider} updates the suggested apks and
* recalculates the preferred metadata for each app. Because we are adding apps/apks
* directly to the database, we need to simulate this update after inserting stuff.
*/
public static void updateDbAfterInserting(Context context) {
AppProvider.Helper.calcSuggestedApks(context);
AppProvider.Helper.recalculatePreferredMetadata(context);
}
/**
* Set a static final field through reflection
*/
public static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
public static void ls(File dir) {
Process p = null;
try {
p = Runtime.getRuntime().exec("ls -l " + dir.getAbsolutePath());
p.waitFor();
for (String line : IOUtils.readLines(p.getInputStream())) {
System.out.println(line);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@@ -2,8 +2,6 @@
package org.fdroid.fdroid;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.database.Cursor;
import org.fdroid.fdroid.data.AppProvider;
@@ -18,12 +16,10 @@ import org.robolectric.RobolectricTestRunner;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
import androidx.loader.content.CursorLoader;
import androidx.test.core.app.ApplicationProvider;
import vendored.org.apache.commons.codec.digest.DigestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -183,9 +179,6 @@ public class UtilsTest {
"6e8a584e004c6cd26d3822a04b0591e355dc5d07b5a3d0f8e309443f47ad1208", SHA_256));
assertTrue(Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("install_history_all"),
"4ad118d4a600dcc104834635d248a89e337fc91b173163d646996b9c54d77372", SHA_256));
assertFalse("wrong sha256 value",
Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("simpleIndex.jar"),
"6e8a584e004c6cd26d3822a04b0591e355dc5d07b5a3d0f8e309443f47ad1208", SHA_256));
File f = TestUtils.copyResourceToTempFile("additional_repos.xml");
assertTrue(Utils.isFileMatchingHash(f,
@@ -253,19 +246,6 @@ public class UtilsTest {
@Test
public void testGetFileHexDigest() throws IOException {
File f = TestUtils.copyResourceToTempFile("largeRepo.xml");
assertEquals("df1754aa4b56c86c06d7842dfd02064f0781c1f740f489d3fc158bb541c8d197",
Utils.getFileHexDigest(f, "sha256"));
f = TestUtils.copyResourceToTempFile("masterKeyIndex.jar");
assertEquals("625d5aedcd0499fe04ebab81f3c7ae30c236cee653a914ffb587d890198f3aba",
Utils.getFileHexDigest(f, "sha256"));
f = TestUtils.copyResourceToTempFile("index.fdroid.2016-10-30.jar");
assertEquals("c138b503c6475aa749585d0e3ad4dba3546b6d33ec485efd8ac8bd603d93fedb",
Utils.getFileHexDigest(f, "sha-256"));
f = TestUtils.copyResourceToTempFile("index.fdroid.2016-11-10.jar");
assertEquals("93bea45814fd8955cabb957e7a3f8790d6c568eaa16fa30425c2d26c60490bde",
Utils.getFileHexDigest(f, "SHA-256"));
// zero size file should have a stable hex digest file
assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
Utils.getFileHexDigest(File.createTempFile("asdf", "asdf"), SHA_256));
@@ -308,42 +288,6 @@ public class UtilsTest {
}
}
/**
* Test the replacement for the ancient fingerprint algorithm.
*
* @see org.fdroid.fdroid.data.Apk#sig
*/
@Test
public void testGetsig() {
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
for (int length : new int[]{256, 345, 1233, 4032, 12092}) {
byte[] rawCertBytes = new byte[length];
new Random().nextBytes(rawCertBytes);
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
String sig = DigestUtils.md5Hex(fdroidSig);
assertEquals(sig, Utils.getsig(rawCertBytes));
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{new Signature(rawCertBytes)};
assertEquals(sig, Utils.getPackageSig(packageInfo));
}
}
@Test
public void testGetAntifeatureSQLFilterWithNone() {
Context context = ApplicationProvider.getApplicationContext();

View File

@@ -1,442 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
import org.fdroid.fdroid.data.Schema.RepoTable;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockRepo;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertCantDelete;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.fdroid.fdroid.Assert.insertApp;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class ApkProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL;
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void testAppApks() {
App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid");
App exampleApp = insertApp(context, "com.example", "Example");
for (int i = 1; i <= 10; i++) {
Assert.insertApk(context, fdroidApp, i);
Assert.insertApk(context, exampleApp, i);
}
assertTotalApkCount(20);
Cursor fdroidApks = contentResolver.query(
ApkProvider.getAppUri("org.fdroid.fdroid"),
PROJ,
null, null, null);
assertResultCount(10, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
fdroidApks.close();
Cursor exampleApks = contentResolver.query(
ApkProvider.getAppUri("com.example"),
PROJ,
null, null, null);
assertResultCount(10, exampleApks);
assertBelongsToApp(exampleApks, "com.example");
exampleApks.close();
}
@Test
public void testInvalidDeleteUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
assertCantDelete(contentResolver, ApkProvider.getContentUri());
assertCantDelete(contentResolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 10, null));
assertCantDelete(contentResolver, ApkProvider.getApkFromAnyRepoUri(apk));
assertCantDelete(contentResolver, Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
}
private static final long REPO_KEEP = 1;
private static final long REPO_DELETE = 2;
@Test
public void testRepoApks() {
// Insert apks into two repos, one of which we will later purge the
// the apks from.
for (int i = 1; i <= 5; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
insertApkForRepo("com.example." + i, 1, REPO_DELETE);
}
for (int i = 6; i <= 10; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
insertApkForRepo("com.example." + i, 1, REPO_KEEP);
}
assertTotalApkCount(20);
Cursor cursor = contentResolver.query(
ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
assertResultCount(10, cursor);
assertBelongsToRepo(cursor, REPO_DELETE);
cursor.close();
int count = ApkProvider.Helper.deleteApksByRepo(context, new MockRepo(REPO_DELETE));
assertEquals(10, count);
assertTotalApkCount(10);
cursor = contentResolver.query(
ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
assertResultCount(0, cursor);
cursor.close();
// The only remaining apks should be those from REPO_KEEP.
assertBelongsToRepo(queryAllApks(), REPO_KEEP);
}
@Test
public void testQuery() {
Cursor cursor = queryAllApks();
assertNotNull(cursor);
cursor.close();
}
@Test
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
Apk apk = new MockApk("org.fdroid.fdroid", 13);
// Insert a new record...
Assert.insertApk(context, apk.packageName, apk.versionCode);
cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// And now we should be able to recover these values from the apk
// value object (because the queryAllApks() helper asks for VERSION_CODE and
// PACKAGE_NAME.
cursor.moveToFirst();
Apk toCheck = new Apk(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", toCheck.packageName);
assertEquals(13, toCheck.versionCode);
}
@Test(expected = IllegalArgumentException.class)
public void testCursorMustMoveToFirst() {
Assert.insertApk(context, "org.example.test", 12);
Cursor cursor = queryAllApks();
new Apk(cursor);
}
@Test
public void testCount() {
String[] projectionCount = new String[]{Cols._COUNT};
for (int i = 0; i < 13; i++) {
Assert.insertApk(context, "com.example", i);
}
Uri all = ApkProvider.getContentUri();
Cursor allWithFields = contentResolver.query(all, PROJ, null, null, null);
Cursor allWithCount = contentResolver.query(all, projectionCount, null, null, null);
assertResultCount(13, allWithFields);
allWithFields.close();
assertResultCount(1, allWithCount);
allWithCount.moveToFirst();
int countColumn = allWithCount.getColumnIndex(Cols._COUNT);
assertEquals(13, allWithCount.getInt(countColumn));
allWithCount.close();
}
@Test(expected = IllegalArgumentException.class)
public void testInsertWithInvalidExtraFieldDescription() {
assertInvalidExtraField(RepoTable.Cols.DESCRIPTION);
}
@Test(expected = IllegalArgumentException.class)
public void testInsertWithInvalidExtraFieldAddress() {
assertInvalidExtraField(RepoTable.Cols.ADDRESS);
}
@Test(expected = IllegalArgumentException.class)
public void testInsertWithInvalidExtraFieldFingerprint() {
assertInvalidExtraField(RepoTable.Cols.FINGERPRINT);
}
@Test(expected = IllegalArgumentException.class)
public void testInsertWithInvalidExtraFieldName() {
assertInvalidExtraField(RepoTable.Cols.NAME);
}
@Test(expected = IllegalArgumentException.class)
public void testInsertWithInvalidExtraFieldSigningCert() {
assertInvalidExtraField(RepoTable.Cols.SIGNING_CERT);
}
public void assertInvalidExtraField(String field) {
ContentValues invalidRepo = new ContentValues();
invalidRepo.put(field, "Test data");
Assert.insertApk(context, "org.fdroid.fdroid", 10, invalidRepo);
}
@Test
public void testInsertWithValidExtraFields() {
assertResultCount(0, queryAllApks());
ContentValues values = new ContentValues();
values.put(Cols.REPO_ID, 10);
values.put(Cols.Repo.ADDRESS, "http://example.com");
values.put(Cols.Repo.VERSION, 3);
values.put(Cols.FEATURES, "Some features");
Uri uri = Assert.insertApk(context, "com.example.com", 1, values);
assertResultCount(1, queryAllApks());
String[] projections = Cols.ALL;
Cursor cursor = contentResolver.query(uri, projections, null, null, null);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
// These should have quietly been dropped when we tried to save them,
// because the provider only knows how to query them (not update them).
assertEquals(null, apk.repoAddress);
assertEquals(0, apk.repoVersion);
// But this should have saved correctly...
assertEquals(1, apk.features.length);
assertEquals("Some features", apk.features[0]);
assertEquals("com.example.com", apk.packageName);
assertEquals(1, apk.versionCode);
assertEquals(10, apk.repoId);
}
@Test
public void testFindByApp() {
for (int i = 0; i < 7; i++) {
Assert.insertApk(context, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
Assert.insertApk(context, "org.example", i);
}
for (int i = 0; i < 3; i++) {
Assert.insertApk(context, "com.example", i);
}
Assert.insertApk(context, "com.apk.thingo", 1);
assertTotalApkCount(7 + 9 + 3 + 1);
List<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(context, "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(context, "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(context, "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> thingoApks = ApkProvider.Helper.findByPackageName(context, "com.apk.thingo");
assertResultCount(1, thingoApks);
assertBelongsToApp(thingoApks, "com.apk.thingo");
}
@Test
public void findApksForAppInSpecificRepo() {
Repo fdroidRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/repo");
Repo swapRepo = RepoProviderTest.insertRepo(context, "http://192.168.1.3/fdroid/repo", "", "22", "", true);
App officialFDroid = insertApp(context, "org.fdroid.fdroid", "F-Droid (Official)", fdroidRepo);
TestUtils.insertApk(context, officialFDroid, 4, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, officialFDroid, 5, TestUtils.FDROID_SIG);
App debugSwapFDroid = insertApp(context, "org.fdroid.fdroid", "F-Droid (Debug)", swapRepo);
TestUtils.insertApk(context, debugSwapFDroid, 6, TestUtils.THIRD_PARTY_SIG);
List<Apk> foundOfficialApks = ApkProvider.Helper.findAppVersionsByRepo(context, officialFDroid, fdroidRepo);
assertEquals(2, foundOfficialApks.size());
List<Apk> debugSwapApks = ApkProvider.Helper.findAppVersionsByRepo(context, officialFDroid, swapRepo);
assertEquals(1, debugSwapApks.size());
assertEquals(debugSwapFDroid.getId(), debugSwapApks.get(0).appId);
assertEquals(6, debugSwapApks.get(0).versionCode);
}
@Test
public void testUpdate() {
Uri apkUri = Assert.insertApk(context, "com.example", 10);
String[] allFields = Cols.ALL;
Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null);
assertResultCount(1, cursor);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
assertEquals("com.example", apk.packageName);
assertEquals(10, apk.versionCode);
assertNull(apk.antiFeatures);
assertNull(apk.features);
assertNull(apk.added);
assertNull(apk.hashType);
apk.antiFeatures = new String[]{"KnownVuln", "Other anti feature"};
apk.features = new String[]{"one", "two", "three"};
apk.hashType = "i'm a hash type";
Date testTime = Utils.parseDate(Utils.formatTime(new Date(System.currentTimeMillis()), null), null);
apk.added = testTime;
ApkProvider.Helper.update(context, apk);
// Should not have inserted anything else, just updated the already existing apk.
Cursor allCursor = contentResolver.query(ApkProvider.getContentUri(), allFields, null, null, null);
assertResultCount(1, allCursor);
allCursor.close();
Cursor updatedCursor = contentResolver.query(apkUri, allFields, null, null, null);
assertResultCount(1, updatedCursor);
updatedCursor.moveToFirst();
Apk updatedApk = new Apk(updatedCursor);
updatedCursor.close();
assertEquals("com.example", updatedApk.packageName);
assertEquals(10, updatedApk.versionCode);
assertArrayEquals(new String[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(testTime.getMonth(), updatedApk.added.getMonth());
assertEquals(testTime.getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType);
}
@Test
public void testFind() {
// Insert some random apks either side of the "com.example", so that
// the Helper.find() method doesn't stumble upon the app we are interested
// in by shear dumb luck...
for (int i = 0; i < 10; i++) {
Assert.insertApk(context, "org.fdroid.apk." + i, i);
}
App exampleApp = insertApp(context, "com.example", "Example");
ContentValues values = new ContentValues();
values.put(Cols.VERSION_NAME, "v1.1");
values.put(Cols.HASH, "xxxxyyyy");
values.put(Cols.HASH_TYPE, "a hash type");
Assert.insertApk(context, exampleApp, 11, values);
// ...and a few more for good measure...
for (int i = 15; i < 20; i++) {
Assert.insertApk(context, "com.other.thing." + i, i);
}
Apk apk = ApkProvider.Helper.findApkFromAnyRepo(context, "com.example", 11);
assertNotNull(apk);
// The find() method populates ALL fields if you don't specify any,
// so we expect to find each of the ones we inserted above...
assertEquals("com.example", apk.packageName);
assertEquals(11, apk.versionCode);
assertEquals("v1.1", apk.versionName);
assertEquals("xxxxyyyy", apk.hash);
assertEquals("a hash type", apk.hashType);
Apk notFound = ApkProvider.Helper.findApkFromAnyRepo(context, "com.doesnt.exist", 1000);
assertNull(notFound);
}
protected final Cursor queryAllApks() {
return contentResolver.query(ApkProvider.getContentUri(), PROJ, null, null, null);
}
protected void assertContains(List<Apk> apks, Apk apk) {
boolean found = false;
for (Apk a : apks) {
if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) {
found = true;
break;
}
}
if (!found) {
fail("Apk [" + apk + "] not found in " + Assert.listToString(apks));
}
}
protected void assertBelongsToApp(Cursor apks, String appId) {
assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
}
protected void assertBelongsToApp(List<Apk> apks, String appId) {
for (Apk apk : apks) {
assertEquals(appId, apk.packageName);
}
}
protected void assertTotalApkCount(int expected) {
assertResultCount(expected, queryAllApks());
}
protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
assertEquals(repoId, apk.repoId);
}
}
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(Cols.REPO_ID, repoId);
Uri uri = Assert.insertApk(context, id, versionCode, additionalValues);
return ApkProvider.Helper.get(context, uri);
}
}

View File

@@ -26,13 +26,11 @@ import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
public class ApkTest {
public static final String TAG = "ApkTest";
private static ContextWrapper context;
private final ContextWrapper context = ApplicationProvider.getApplicationContext();
@Before
public final void setUp() {
context = ApplicationProvider.getApplicationContext();
ShadowMimeTypeMap mimeTypeMap = Shadows.shadowOf(MimeTypeMap.getSingleton());
mimeTypeMap.addExtensionMimeTypMapping("apk", "application/vnd.android.package-archive");
mimeTypeMap.addExtensionMimeTypMapping("obf", "application/octet-stream");

View File

@@ -1,65 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import org.fdroid.fdroid.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class AppPrefsProviderTest extends FDroidProviderTest {
@SuppressWarnings({"PMD.EqualsNull", "EqualsWithItself", "EqualsBetweenInconvertibleTypes", "ObjectEqualsNull"})
@Test
public void prefEquality() {
AppPrefs original = new AppPrefs(101, true, true);
assertTrue(original.equals(new AppPrefs(101, true, true)));
assertTrue(original.equals(original));
assertFalse(original.equals(null));
assertFalse(original.equals("String"));
assertFalse(original.equals(new AppPrefs(102, true, true)));
assertFalse(original.equals(new AppPrefs(101, false, true)));
assertFalse(original.equals(new AppPrefs(100, false, true)));
assertFalse(original.equals(new AppPrefs(102, true, false)));
assertFalse(original.equals(new AppPrefs(101, false, false)));
assertFalse(original.equals(new AppPrefs(100, false, false)));
}
@Test
public void newPreferences() {
App withPrefs = Assert.insertApp(context, "com.example.withPrefs", "With Prefs");
App withoutPrefs = Assert.insertApp(context, "com.example.withoutPrefs", "Without Prefs");
assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withPrefs));
assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withoutPrefs));
AppPrefs defaultPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
assertEquals(0, defaultPrefs.ignoreThisUpdate);
assertFalse(defaultPrefs.ignoreAllUpdates);
assertFalse(defaultPrefs.ignoreVulnerabilities);
AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(12, false, false));
AppPrefs newPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
assertEquals(12, newPrefs.ignoreThisUpdate);
assertFalse(newPrefs.ignoreAllUpdates);
assertFalse(newPrefs.ignoreVulnerabilities);
AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(14, true, true));
AppPrefs evenNewerPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
assertEquals(14, evenNewerPrefs.ignoreThisUpdate);
assertTrue(evenNewerPrefs.ignoreAllUpdates);
assertTrue(evenNewerPrefs.ignoreVulnerabilities);
assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withoutPrefs));
}
}

View File

@@ -1,357 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.fdroid.fdroid.Assert.insertApk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class AppProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL;
private static Locale defaultLocale;
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Before
public void setup() {
defaultLocale = Locale.getDefault();
Preferences.setupForTests(context);
}
@After
public void teardown() {
Locale.setDefault(defaultLocale);
}
/**
* Although this doesn't directly relate to the {@link AppProvider}, it is here because
* the {@link AppProvider} used to stumble across this bug when asking for installed apps,
* and the device had over 1000 apps installed.
*/
@Ignore("takes a long time and covers a rare situation (over 1000 apps installed)")
@Test
public void testMaxSqliteParams() {
insertApp("com.example.app1", "App 1");
insertApp("com.example.app100", "App 100");
insertApp("com.example.app1000", "App 1000");
for (int i = 0; i < 50; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 1, AppProvider.getInstalledUri(), PROJ);
for (int i = 50; i < 500; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 2, AppProvider.getInstalledUri(), PROJ);
for (int i = 500; i < 1100; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 3, AppProvider.getInstalledUri(), PROJ);
}
@Test
public void testCantFindApp() {
assertNull(AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.example.doesnt-exist", 1, Cols.ALL));
}
@Test
public void testQuery() {
Cursor cursor = queryAllApps();
assertNotNull(cursor);
cursor.close();
}
private void insertApps(int count) {
for (int i = 0; i < count; i++) {
insertApp("com.example.test." + i, "Test app " + i);
}
}
private void insertAndInstallApp(
String packageName, int installedVercode, int suggestedVercode,
boolean ignoreAll, int ignoreVercode) {
App app = insertApp(contentResolver, context, packageName, "App: " + packageName, new ContentValues());
AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll, false));
ContentValues certValue = new ContentValues(1);
certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG);
// Make sure that the relevant apks are also in the DB, or else the `install` method below will
// not be able to correctly calculate the suggested version o the apk.
insertApk(context, packageName, installedVercode, certValue);
if (installedVercode != suggestedVercode) {
insertApk(context, packageName, suggestedVercode, certValue);
}
InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode, TestUtils.FDROID_CERT);
}
@Test
public void testCanUpdate() {
insertApp("not installed", "not installed");
insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
ContentResolver r = context.getContentResolver();
// Can't "update", although can "install"...
App notInstalled = AppProvider.Helper.findSpecificApp(r, "not installed", 1, Cols.ALL);
assertFalse(notInstalled.canAndWantToUpdate(context));
assertResultCount(contentResolver, 2, AppProvider.getCanUpdateUri(), PROJ);
assertResultCount(contentResolver, 9, AppProvider.getInstalledUri(), PROJ);
App installedOnlyOneVersionAvailable = AppProvider.Helper.findSpecificApp(r, "installed, only one version available", 1, Cols.ALL);
App installedAlreadyLatestNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, already latest, no ignore", 1, Cols.ALL);
App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore all", 1, Cols.ALL);
App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore latest", 1, Cols.ALL);
App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore old", 1, Cols.ALL);
assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context));
App installedOldNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, old version, no ignore", 1, Cols.ALL);
App installedOldIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore all", 1, Cols.ALL);
App installedOldIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore latest", 1, Cols.ALL);
App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore newer, but not latest", 1, Cols.ALL);
assertTrue(installedOldNoIgnore.canAndWantToUpdate(context));
assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context));
assertFalse(installedOldIgnoreLatest.canAndWantToUpdate(context));
assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate(context));
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), Cols.ALL, null, null, null);
assertNotNull(canUpdateCursor);
canUpdateCursor.moveToFirst();
List<String> canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
while (!canUpdateCursor.isAfterLast()) {
canUpdateIds.add(new App(canUpdateCursor).packageName);
canUpdateCursor.moveToNext();
}
canUpdateCursor.close();
String[] expectedUpdateableIds = {
"installed, old version, no ignore",
"installed, old version, ignore newer, but not latest",
};
assertContainsOnly(expectedUpdateableIds, canUpdateIds);
}
@Test
public void testIgnored() {
insertApp("not installed", "not installed");
insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ);
String[] projection = {Cols.Package.PACKAGE_NAME};
List<App> canUpdateApps = AppProvider.Helper.findCanUpdate(context, projection);
String[] expectedCanUpdate = {
"installed, old version, no ignore",
"installed, old version, ignore newer, but not latest",
// These are ignored because they don't have updates available:
// "installed, only one version available",
// "installed, already latest, no ignore",
// "installed, already latest, ignore old",
// "not installed",
// These four should be ignored due to the app preferences:
// "installed, already latest, ignore all",
// "installed, already latest, ignore latest",
// "installed, old version, ignore all",
// "installed, old version, ignore latest",
};
assertContainsOnlyIds(canUpdateApps, expectedCanUpdate);
}
public static void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
List<String> actualIds = new ArrayList<>(actualApps.size());
for (App app : actualApps) {
actualIds.add(app.packageName);
}
assertContainsOnly(actualIds, expectedIds);
}
@Test
public void testInstalled() {
insertApps(100);
assertResultCount(contentResolver, 100, AppProvider.getContentUri(), PROJ);
assertResultCount(contentResolver, 0, AppProvider.getCanUpdateUri(), PROJ);
assertResultCount(contentResolver, 0, AppProvider.getInstalledUri(), PROJ);
for (int i = 10; i < 20; i++) {
InstalledAppTestUtils.install(context, "com.example.test." + i, i, "v1");
}
assertResultCount(contentResolver, 0, AppProvider.getCanUpdateUri(), PROJ);
assertResultCount(contentResolver, 10, AppProvider.getInstalledUri(), PROJ);
}
@Test
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
// Insert a new record...
insertApp("org.fdroid.fdroid", "F-Droid");
cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// And now we should be able to recover these values from the app
// value object (because the queryAllApps() helper asks for NAME and
// PACKAGE_NAME.
cursor.moveToFirst();
App app = new App(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", app.packageName);
assertEquals("F-Droid", app.name);
App otherApp = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.fdroid.fdroid", 1, Cols.ALL);
assertNotNull(otherApp);
assertEquals("org.fdroid.fdroid", otherApp.packageName);
assertEquals("F-Droid", otherApp.name);
}
@Test
public void testInsertTrimsNamesAndSummary() {
// Insert a new record with unwanted newlines...
App app = insertApp("org.fdroid.trimmer", "Trim me\n", "Trim me too\n");
assertEquals("org.fdroid.trimmer", app.packageName);
assertEquals("Trim me", app.name);
assertEquals("Trim me too", app.summary);
}
/**
* We intentionally throw an IllegalArgumentException if you haven't
* yet called cursor.move*().
*/
@Test(expected = IllegalArgumentException.class)
public void testCursorMustMoveToFirst() {
insertApp("org.fdroid.fdroid", "F-Droid");
Cursor cursor = queryAllApps();
new App(cursor);
}
private Cursor queryAllApps() {
String[] projection = new String[]{
Cols._ID,
Cols.NAME,
Cols.Package.PACKAGE_NAME,
};
return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null);
}
// =======================================================================
// Misc helper functions
// (to be used by any tests in this suite)
// =======================================================================
private void insertApp(String id, String name) {
insertApp(contentResolver, context, id, name, new ContentValues());
}
private App insertApp(String id, String name, String summary) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(Cols.SUMMARY, summary);
return insertApp(contentResolver, context, id, name, additionalValues);
}
public static App insertApp(ContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues) {
return insertApp(contentResolver, context, id, name, additionalValues, 1);
}
public static App insertApp(ContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues, long repoId) {
ContentValues values = new ContentValues();
values.put(Cols.Package.PACKAGE_NAME, id);
values.put(Cols.REPO_ID, repoId);
values.put(Cols.NAME, name);
// Required fields (NOT NULL in the database).
values.put(Cols.SUMMARY, "test summary");
values.put(Cols.DESCRIPTION, "test description");
values.put(Cols.LICENSE, "GPL?");
values.put(Cols.IS_COMPATIBLE, 1);
values.put(Cols.PREFERRED_SIGNER, "eaa1d713b9c2a0475234a86d6539f910");
values.putAll(additionalValues);
Uri uri = AppProvider.getContentUri();
contentResolver.insert(uri, values);
AppProvider.Helper.recalculatePreferredMetadata(context);
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), id, repoId, Cols.ALL);
}
}

View File

@@ -1,302 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.fdroid.fdroid.mock.MockRepo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class CategoryProviderTest extends FDroidProviderTest {
/**
* Different repositories can specify a different set of categories for the same package.
* In this case, only the repository with the highest priority should get to choose which
* category the app goes in.
*/
@Test
public void onlyHighestPriorityMetadataDefinesCategories() {
long mainRepo = 1;
long gpRepo = 3;
insertAppWithCategory("info.guardianproject.notepadbot", "NoteCipher", "Writing,Security", mainRepo);
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable", mainRepo);
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo);
String[] expectedFDroid = new String[]{
"Animal",
"Mineral",
"Security",
"Vegetable",
"Writing",
};
String[] expectedGP = new String[]{
"GuardianProject",
"Office",
};
// We overwrite "Security" + "Writing" with "GuardianProject" + "Office"
String[] expectedBoth = new String[]{
"Animal",
"Mineral",
"Vegetable",
"GuardianProject",
"Office",
};
assertContainsOnly(categories(), expectedFDroid);
insertAppWithCategory("info.guardianproject.notepadbot", "NoteCipher", "Office,GuardianProject", gpRepo);
assertContainsOnly(categories(), expectedBoth);
RepoProvider.Helper.purgeApps(context, new MockRepo(mainRepo));
List<String> categoriesAfterPurge = categories();
assertContainsOnly(categoriesAfterPurge, expectedGP);
}
@Test
public void queryFreeTextAndCategories() {
insertAppWithCategory("com.dog", "Dog", "Animal");
insertAppWithCategory("com.cat", "Cat", "Animal");
insertAppWithCategory("com.crow", "Crow", "Animal,Bird");
insertAppWithCategory("com.chicken", "Chicken", "Animal,Bird,Food");
insertAppWithCategory("com.dog-statue", "Dog Statue", "Animal,Mineral");
insertAppWithCategory("com.rock", "Rock", "Mineral");
insertAppWithCategory("com.banana", "Banana", "Food");
insertAppWithCategory("com.dog-food", "Dog Food", "Food");
assertPackagesInUri(AppProvider.getSearchUri("dog", "Animal"), new String[]{
"com.dog",
"com.dog-statue",
});
assertPackagesInUri(AppProvider.getSearchUri("dog", "Food"), new String[]{
"com.dog-food",
});
assertPackagesInUri(AppProvider.getSearchUri("dog", null), new String[]{
"com.dog",
"com.dog-statue",
"com.dog-food",
});
}
@Test
public void queryAppsInCategories() {
insertAppWithCategory("com.dog", "Dog", "Animal");
insertAppWithCategory("com.cat", "Cat", "Animal");
insertAppWithCategory("com.crow", "Crow", "Animal,Bird");
insertAppWithCategory("com.chicken", "Chicken", "Animal,Bird,Food");
insertAppWithCategory("com.bird-statue", "Bird Statue", "Bird,Mineral");
insertAppWithCategory("com.rock", "Rock", "Mineral");
insertAppWithCategory("com.banana", "Banana", "Food");
assertPackagesInCategory("Animal", new String[]{
"com.dog",
"com.cat",
"com.crow",
"com.chicken",
});
assertPackagesInCategory("animal", new String[]{
"com.dog",
"com.cat",
"com.crow",
"com.chicken",
});
assertPackagesInCategory("Bird", new String[]{
"com.crow",
"com.chicken",
"com.bird-statue",
});
assertPackagesInCategory("Food", new String[]{
"com.chicken",
"com.banana",
});
assertPackagesInCategory("Mineral", new String[]{
"com.rock",
"com.bird-statue",
});
assertNoPackagesInUri(AppProvider.getCategoryUri("Not a category"));
}
private void assertNoPackagesInUri(Uri uri) {
Cursor noApps = contentResolver.query(uri, Cols.ALL, null, null, null);
assertEquals(noApps.getCount(), 0);
}
private void assertPackagesInCategory(String category, String[] expectedPackages) {
assertPackagesInUri(AppProvider.getCategoryUri(category), expectedPackages);
}
private void assertPackagesInUri(Uri uri, String[] expectedPackages) {
List<App> apps = AppProvider.Helper.cursorToList(contentResolver.query(uri, Cols.ALL, null, null, null));
AppProviderTest.assertContainsOnlyIds(apps, expectedPackages);
}
/**
* This does not include {@code sortOrder} since that is defined in
* {@link org.fdroid.fdroid.views.categories.CategoryController#onCreateLoader(int, android.os.Bundle)}
* so these results are sorted by the default sort.
*/
@Test
public void topAppsFromCategory() {
insertAppWithCategory("com.dog", "Dog", "Animal", new Date(2017, 2, 6));
insertAppWithCategory("com.cat", "Cat", "Animal", new Date(2017, 2, 5));
insertAppWithCategory("com.bird", "Bird", "Animal", new Date(2017, 2, 4));
insertAppWithCategory("com.snake", "Snake", "Animal", new Date(2017, 2, 3));
insertAppWithCategory("com.rat", "Rat", "Animal", new Date(2017, 2, 2));
insertAppWithCategory("com.rock", "Rock", "Mineral", new Date(2017, 1, 4));
insertAppWithCategory("com.stone", "Stone", "Mineral", new Date(2017, 1, 3));
insertAppWithCategory("com.boulder", "Boulder", "Mineral", new Date(2017, 1, 2));
insertAppWithCategory("com.banana", "Banana", "Vegetable", new Date(2015, 1, 1));
insertAppWithCategory("com.tomato", "Tomato", "Vegetable", new Date(2017, 4, 4));
assertArrayEquals(new String[]{"com.bird", "com.cat", "com.dog"}, getTopAppsFromCategory("Animal", 3));
assertArrayEquals(new String[]{"com.bird", "com.cat"}, getTopAppsFromCategory("Animal", 2));
assertArrayEquals(new String[]{"com.bird"}, getTopAppsFromCategory("Animal", 1));
assertArrayEquals(new String[]{"com.boulder", "com.rock"}, getTopAppsFromCategory("Mineral", 2));
assertArrayEquals(new String[]{"com.banana", "com.tomato"}, getTopAppsFromCategory("Vegetable", 10));
}
public String[] getTopAppsFromCategory(String category, int numToGet) {
List<App> apps = AppProvider.Helper.cursorToList(contentResolver
.query(AppProvider.getTopFromCategoryUri(category, numToGet), Cols.ALL, null, null, Cols.NAME));
String[] packageNames = new String[apps.size()];
for (int i = 0; i < apps.size(); i++) {
packageNames[i] = apps.get(i).packageName;
}
return packageNames;
}
@Test
public void testCategoriesSingle() {
insertAppWithCategory("com.dog", "Dog", "Animal");
insertAppWithCategory("com.rock", "Rock", "Mineral");
insertAppWithCategory("com.banana", "Banana", "Vegetable");
List<String> categories = categories();
String[] expected = new String[]{
"Animal",
"Mineral",
"Vegetable",
};
assertContainsOnly(categories, expected);
}
@Test
public void testCategoriesMultiple() {
long mainRepo = 1;
insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal", mainRepo);
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable", mainRepo);
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo);
List<String> categories = categories();
String[] expected = new String[]{
"Animal",
"Mineral",
"Vegetable",
};
assertContainsOnly(categories, expected);
int additionalRepo = 2;
insertAppWithCategory("com.example.game", "Game",
"Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
"The quick brown fox jumps over the lazy dog,With apostrophe's", additionalRepo);
List<String> categoriesLonger = categories();
String[] expectedLonger = new String[]{
"Animal",
"Mineral",
"Vegetable",
"Running",
"Shooting",
"Jumping",
"Bleh",
"Sneh",
"Pleh",
"Blah",
"Test category",
"The quick brown fox jumps over the lazy dog",
"With apostrophe's",
};
assertContainsOnly(categoriesLonger, expectedLonger);
RepoProvider.Helper.purgeApps(context, new MockRepo(additionalRepo));
List<String> categoriesAfterPurge = categories();
assertContainsOnly(categoriesAfterPurge, expected);
}
private void insertAppWithCategory(String id, String name, String categories) {
insertAppWithCategory(id, name, categories, new Date(), 1);
}
private void insertAppWithCategory(String id, String name, String categories, Date lastUpdated) {
insertAppWithCategory(id, name, categories, lastUpdated, 1);
}
private void insertAppWithCategory(String id, String name, String categories, long repoId) {
insertAppWithCategory(id, name, categories, new Date(), repoId);
}
private void insertAppWithCategory(String id, String name, String categories, Date lastUpdated, long repoId) {
ContentValues values = new ContentValues(2);
values.put(Cols.ForWriting.Categories.CATEGORIES, categories);
values.put(Cols.LAST_UPDATED, Utils.DATE_FORMAT.format(lastUpdated));
AppProviderTest.insertApp(contentResolver, context, id, name, values, repoId);
}
public List<String> categories() {
final ContentResolver resolver = context.getContentResolver();
final Uri uri = CategoryProvider.getAllCategories();
final String[] projection = {Schema.CategoryTable.Cols.NAME};
final Cursor cursor = resolver.query(uri, projection, null, null, null);
List<String> categories = new ArrayList<>(30);
if (cursor != null) {
if (cursor.getCount() > 0) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
final String name = cursor.getString(0);
categories.add(name);
cursor.moveToNext();
}
}
cursor.close();
}
Collections.sort(categories);
return categories;
}
}

View File

@@ -5,11 +5,14 @@ import android.text.TextUtils;
import android.util.Log;
import org.apache.commons.io.IOUtils;
import org.fdroid.database.FDroidDatabase;
import org.fdroid.database.InitialRepository;
import org.fdroid.database.RepositoryDao;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.TestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.robolectric.RobolectricTestRunner;
import org.xmlpull.v1.XmlPullParserException;
@@ -25,10 +28,17 @@ import androidx.test.core.app.ApplicationProvider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class DBHelperTest {
static final String TAG = "DBHelperTest";
private static final String TAG = "DBHelperTest";
private final Context context = ApplicationProvider.getApplicationContext();
private List<String> getReposFromXml(String xml) throws IOException, XmlPullParserException {
File additionalReposXml = File.createTempFile("." + context.getPackageName() + "-DBHelperTest_",
@@ -43,11 +53,26 @@ public class DBHelperTest {
return DBHelper.parseAdditionalReposXml(additionalReposXml);
}
protected Context context;
@Test
public void testDefaultReposAddedToDb() {
FDroidDatabase db = mock(FDroidDatabase.class);
RepositoryDao repoDao = mock(RepositoryDao.class);
when(db.getRepositoryDao()).thenReturn(repoDao);
@Before
public final void setupBase() {
context = ApplicationProvider.getApplicationContext();
// pre-populate the DB
DBHelper.prePopulateDb(context, db);
// verify that all default repos were added to DB
int numRepos = getDefaultRepoCount();
verify(repoDao, times(numRepos)).insert(ArgumentMatchers.any(InitialRepository.class));
}
/**
* Returns the number of repos in app/src/main/res/default_repo.xml
*/
private int getDefaultRepoCount() {
int itemCount = context.getResources().getStringArray(R.array.default_repos).length;
return itemCount / DBHelper.REPO_XML_ITEM_COUNT;
}
@Test

View File

@@ -1,178 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ContentProviderController;
import org.robolectric.annotation.Config;
import androidx.test.core.app.ApplicationProvider;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class DatabaseMigration {
protected ContentResolver contentResolver;
protected ContextWrapper context;
protected ContentProviderController contentProviderController;
@Before
public final void setupBase() {
contentResolver = ApplicationProvider.getApplicationContext().getContentResolver();
context = TestUtils.createContextWithContentResolver(contentResolver);
contentProviderController = TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
}
@After
public void teardown() {
contentProviderController.shutdown();
}
@Test
public void migrationsFromDbVersion42Onward() {
Preferences.setupForTests(context);
SQLiteOpenHelper opener = new MigrationRunningOpenHelper(context);
opener.getReadableDatabase();
opener.close();
}
/**
* The database created by this in {@link MigrationRunningOpenHelper#onCreate(SQLiteDatabase)}
* should be identical to the one which was created by F-Droid circa git tag "db-version/42".
* After creating the database, this will then ask the base
* {@link DBHelper#onUpgrade(SQLiteDatabase, int, int)} method to run up until the current
* {@link DBHelper#DB_VERSION}.
*/
class MigrationRunningOpenHelper extends DBHelper {
public static final String TABLE_REPO = "fdroid_repo";
MigrationRunningOpenHelper(Context context) {
super(context);
}
@Override
public void onCreate(SQLiteDatabase db) {
createAppTable(db);
createApkTable(db);
createRepoTable(db);
insertRepos(db);
onUpgrade(db, 42, DBHelper.DB_VERSION);
}
private void createAppTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE fdroid_app ("
+ "id text not null, "
+ "name text not null, "
+ "summary text not null, "
+ "icon text, "
+ "description text not null, "
+ "license text not null, "
+ "webURL text, "
+ "trackerURL text, "
+ "sourceURL text, "
+ "suggestedVercode text,"
+ "upstreamVersion text,"
+ "upstreamVercode integer,"
+ "antiFeatures string,"
+ "donateURL string,"
+ "bitcoinAddr string,"
+ "litecoinAddr string,"
+ "dogecoinAddr string,"
+ "flattrID string,"
+ "requirements string,"
+ "categories string,"
+ "added string,"
+ "lastUpdated string,"
+ "compatible int not null,"
+ "ignoreAllUpdates int not null,"
+ "ignoreThisUpdate int not null,"
+ "iconUrl text, "
+ "primary key(id));");
db.execSQL("create index app_id on fdroid_app (id);");
}
private void createApkTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE fdroid_apk ( "
+ "id text not null, "
+ "version text not null, "
+ "repo integer not null, "
+ "hash text not null, "
+ "vercode int not null,"
+ "apkName text not null, "
+ "size int not null, "
+ "sig string, "
+ "srcname string, "
+ "minSdkVersion integer, "
+ "maxSdkVersion integer, "
+ "permissions string, "
+ "features string, "
+ "nativecode string, "
+ "hashType string, "
+ "added string, "
+ "compatible int not null, "
+ "incompatibleReasons text, "
+ "primary key(id, vercode)"
+ ");");
db.execSQL("create index apk_vercode on fdroid_apk (vercode);");
db.execSQL("create index apk_id on fdroid_apk (id);");
}
private void createRepoTable(SQLiteDatabase db) {
db.execSQL("create table " + TABLE_REPO + " ("
+ "_id integer primary key, "
+ "address text not null, "
+ "name text, description text, inuse integer not null, "
+ "priority integer not null, pubkey text, fingerprint text, "
+ "maxage integer not null default 0, "
+ "version integer not null default 0, "
+ "lastetag text, lastUpdated string);");
}
private void insertRepos(SQLiteDatabase db) {
String pubKey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
String fingerprint = Utils.calcFingerprint(pubKey);
ContentValues fdroidValues = new ContentValues();
fdroidValues.put("address", "https://f-droid.org/repo");
fdroidValues.put("name", "F-Droid");
fdroidValues.put("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."); // NOCHECKSTYLE LineLength
fdroidValues.put("pubkey", pubKey);
fdroidValues.put("fingerprint", fingerprint);
fdroidValues.put("maxage", 0);
fdroidValues.put("inuse", 1);
fdroidValues.put("priority", 10);
fdroidValues.put("lastetag", (String) null);
db.insert(TABLE_REPO, null, fdroidValues);
ContentValues archiveValues = new ContentValues();
archiveValues.put("address", "https://f-droid.org/archive");
archiveValues.put("name", "F-Droid Archive");
archiveValues.put("description", "The archive repository of the F-Droid client. This contains older versions of applications from the main repository."); // NOCHECKSTYLE LineLength
archiveValues.put("pubkey", pubKey);
archiveValues.put("fingerprint", fingerprint);
archiveValues.put("maxage", 0);
archiveValues.put("inuse", 0);
archiveValues.put("priority", 20);
archiveValues.put("lastetag", (String) null);
db.insert(TABLE_REPO, null, archiveValues);
}
}
}

View File

@@ -1,40 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.ContextWrapper;
import org.fdroid.fdroid.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.robolectric.android.controller.ContentProviderController;
import androidx.test.core.app.ApplicationProvider;
public abstract class FDroidProviderTest { // NOPMD This abstract class does not have any abstract methods
protected ContentResolver contentResolver;
protected ContentProviderController<AppProvider> contentProviderController;
protected ContextWrapper context;
@Before
public final void setupBase() {
contentResolver = ApplicationProvider.getApplicationContext().getContentResolver();
context = TestUtils.createContextWithContentResolver(contentResolver);
contentProviderController = TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
}
@After
public final void tearDownBase() {
contentProviderController.shutdown();
CategoryProvider.Helper.clearCategoryIdCache();
DBHelper.clearDbHelperSingleton();
}
protected Repo setEnabled(Repo repo, boolean enabled) {
ContentValues enable = new ContentValues(1);
enable.put(Schema.RepoTable.Cols.IN_USE, enabled);
RepoProvider.Helper.update(context, repo, enable);
return RepoProvider.Helper.findByAddress(context, repo.address);
}
}

View File

@@ -1,233 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Map;
import androidx.test.core.app.ApplicationProvider;
import static org.fdroid.fdroid.Assert.assertIsInstalledVersionInDb;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class InstalledAppProviderTest extends FDroidProviderTest {
@Before
public void setup() {
TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class);
Preferences.setupForTests(context);
}
@Test
public void insertSingleApp() {
Map<String, Long> foundBefore = InstalledAppProvider.Helper.lastUpdateTimes(
ApplicationProvider.getApplicationContext());
assertEquals(foundBefore.size(), 0);
ContentValues values = new ContentValues();
values.put(Cols.Package.NAME, "org.example.test-app");
values.put(Cols.APPLICATION_LABEL, "Test App");
values.put(Cols.VERSION_CODE, 1021);
values.put(Cols.VERSION_NAME, "Longhorn");
values.put(Cols.HASH, "has of test app");
values.put(Cols.HASH_TYPE, "fake hash type");
values.put(Cols.LAST_UPDATE_TIME, 100000000L);
values.put(Cols.SIGNATURE, "000111222333444555666777888999aaabbbcccdddeeefff");
contentResolver.insert(InstalledAppProvider.getContentUri(), values);
Map<String, Long> foundAfter = InstalledAppProvider.Helper.lastUpdateTimes(
ApplicationProvider.getApplicationContext());
assertEquals(1, foundAfter.size());
assertEquals(100000000L, foundAfter.get("org.example.test-app").longValue());
Cursor cursor = contentResolver.query(InstalledAppProvider.getAppUri("org.example.test-app"), Cols.ALL,
null, null, null);
assertEquals(cursor.getCount(), 1);
cursor.moveToFirst();
assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
assertEquals("Test App", cursor.getString(cursor.getColumnIndex(Cols.APPLICATION_LABEL)));
assertEquals(1021, cursor.getInt(cursor.getColumnIndex(Cols.VERSION_CODE)));
assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(Cols.VERSION_NAME)));
assertEquals("has of test app", cursor.getString(cursor.getColumnIndex(Cols.HASH)));
assertEquals("fake hash type", cursor.getString(cursor.getColumnIndex(Cols.HASH_TYPE)));
assertEquals(100000000L, cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME)));
assertEquals("000111222333444555666777888999aaabbbcccdddeeefff",
cursor.getString(cursor.getColumnIndex(Cols.SIGNATURE)));
cursor.close();
}
@Test
public void testHelperAll() {
final String packageName0 = "com.0";
final String packageName1 = "com.1";
final String packageName2 = "com.2";
App[] apps = InstalledAppProvider.Helper.all(context);
assertEquals(0, apps.length);
insertInstalledApp(packageName0, 0, "v0");
insertInstalledApp(packageName1, 1, "v1");
insertInstalledApp(packageName2, 2, "v2");
assertResultCount(contentResolver, 3, InstalledAppProvider.getContentUri());
assertResultCount(contentResolver, 3, InstalledAppProvider.getAllAppsUri());
assertIsInstalledVersionInDb(contentResolver, packageName0, 0, "v0");
assertIsInstalledVersionInDb(contentResolver, packageName1, 1, "v1");
assertIsInstalledVersionInDb(contentResolver, packageName2, 2, "v2");
apps = InstalledAppProvider.Helper.all(context);
assertEquals(3, apps.length);
assertEquals(packageName0, apps[0].packageName);
assertEquals("v0", apps[0].suggestedVersionName);
assertEquals(0, apps[0].suggestedVersionCode);
assertEquals(packageName1, apps[1].packageName);
assertEquals("v1", apps[1].suggestedVersionName);
assertEquals(1, apps[1].suggestedVersionCode);
assertEquals(packageName2, apps[2].packageName);
assertEquals("v2", apps[2].suggestedVersionName);
assertEquals(2, apps[2].suggestedVersionCode);
assertNotEquals(packageName0, apps[2].packageName);
}
@Test
public void testInsert() {
assertResultCount(contentResolver, 0, InstalledAppProvider.getContentUri());
insertInstalledApp("com.example.com1", 1, "v1");
insertInstalledApp("com.example.com2", 2, "v2");
insertInstalledApp("com.example.com3", 3, "v3");
assertResultCount(contentResolver, 3, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(contentResolver, "com.example.com1", 1, "v1");
assertIsInstalledVersionInDb(contentResolver, "com.example.com2", 2, "v2");
assertIsInstalledVersionInDb(contentResolver, "com.example.com3", 3, "v3");
App[] apps = InstalledAppProvider.Helper.all(context);
assertEquals(3, apps.length);
}
@Test
public void testUpdate() {
insertInstalledApp("com.example.app1", 10, "1.0");
insertInstalledApp("com.example.app2", 10, "1.0");
assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0");
contentResolver.insert(
InstalledAppProvider.getContentUri(),
createContentValues("com.example.app2", 11, "1.1")
);
assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 11, "1.1");
}
/**
* We expect this to happen, because we should be using insert() instead as it will
* do an insert/replace query.
*/
@Test(expected = UnsupportedOperationException.class)
public void testUpdateFails() {
contentResolver.update(
InstalledAppProvider.getAppUri("com.example.app2"),
createContentValues(11, "1.1"),
null, null
);
}
@Test
public void testLastUpdateTime() {
String packageName = "com.example.app";
insertInstalledApp(packageName, 10, "1.0");
assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(contentResolver, packageName, 10, "1.0");
Uri uri = InstalledAppProvider.getAppUri(packageName);
String[] projection = {
Cols.Package.NAME,
Cols.LAST_UPDATE_TIME,
};
Cursor cursor = contentResolver.query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME));
assertTrue(lastUpdateTime > 0);
assertTrue(lastUpdateTime < System.currentTimeMillis());
cursor.close();
insertInstalledApp(packageName, 11, "1.1");
cursor = contentResolver.query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME)));
cursor.close();
}
@Test
public void testDelete() {
insertInstalledApp("com.example.app1", 10, "1.0");
insertInstalledApp("com.example.app2", 10, "1.0");
assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
contentResolver.delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null);
assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0");
}
private ContentValues createContentValues(int versionCode, String versionNumber) {
return createContentValues(null, versionCode, versionNumber);
}
private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
ContentValues values = new ContentValues(3);
if (appId != null) {
values.put(Cols.Package.NAME, appId);
}
values.put(Cols.APPLICATION_LABEL, "Mock app: " + appId);
values.put(Cols.VERSION_CODE, versionCode);
values.put(Cols.VERSION_NAME, versionNumber);
values.put(Cols.SIGNATURE, "");
values.put(Cols.LAST_UPDATE_TIME, System.currentTimeMillis());
values.put(Cols.HASH_TYPE, "sha256");
values.put(Cols.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe");
return values;
}
private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
ContentValues values = createContentValues(appId, versionCode, versionNumber);
contentResolver.insert(InstalledAppProvider.getContentUri(), values);
}
}
// https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide

View File

@@ -1,52 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import androidx.annotation.Nullable;
public class InstalledAppTestUtils {
/**
* Will tell {@code pm} that we are installing {@code packageName}, and then update the
* "installed apps" table in the database.
*/
public static void install(Context context,
String packageName,
int versionCode, String versionName) {
install(context, packageName, versionCode, versionName, null);
}
public static void install(Context context,
String packageName,
int versionCode, String versionName,
@Nullable String signingCert) {
install(context, packageName, versionCode, versionName, signingCert, null);
}
public static void install(Context context,
String packageName,
int versionCode, String versionName,
@Nullable String signingCert,
@Nullable String hash) {
PackageInfo info = new PackageInfo();
info.packageName = packageName;
info.versionCode = versionCode;
info.versionName = versionName;
info.applicationInfo = new ApplicationInfo();
info.applicationInfo.publicSourceDir = "/tmp/mock-location";
if (signingCert != null) {
info.signatures = new Signature[]{new Signature(signingCert)};
}
String hashType = "sha256";
if (hash == null) {
hash = "00112233445566778899aabbccddeeff";
}
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
}
}

View File

@@ -1,283 +0,0 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.Context;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class PreferredSignatureTest extends FDroidProviderTest {
private static final String PACKAGE_NAME = "app.example.com";
@Before
public void setup() {
Preferences.setupForTests(context);
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@Override
public void onPreferenceChange() {
AppProvider.Helper.calcSuggestedApks(context);
}
});
}
private Repo createFDroidRepo() {
return RepoProviderTest.insertRepo(context, "https://f-droid.org/fdroid/repo", "", "", "");
}
private App populateFDroidRepo(Repo repo) {
App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 3100, repo, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, app, 1100, TestUtils.FDROID_SIG); // 1.0
TestUtils.insertApk(context, app, 2100, TestUtils.FDROID_SIG); // 2.0
TestUtils.insertApk(context, app, 3100, TestUtils.FDROID_SIG); // 3.0
TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); // 2.0
TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); // 3.0
TestUtils.updateDbAfterInserting(context);
return app;
}
private Repo createDevRepo() {
return RepoProviderTest.insertRepo(context, "https://dev.upstream.com/fdroid/repo", "", "", "");
}
private App populateDevRepo(Repo repo) {
App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo, TestUtils.THIRD_PARTY_SIG);
TestUtils.insertApk(context, app, 1001, TestUtils.THIRD_PARTY_SIG); // 1.0-rc2
TestUtils.insertApk(context, app, 1100, TestUtils.THIRD_PARTY_SIG); // 1.0
TestUtils.insertApk(context, app, 2001, TestUtils.THIRD_PARTY_SIG); // 2.0-rc1
TestUtils.insertApk(context, app, 2002, TestUtils.THIRD_PARTY_SIG); // 2.0-rc2
TestUtils.insertApk(context, app, 2100, TestUtils.THIRD_PARTY_SIG); // 2.0
TestUtils.insertApk(context, app, 3001, TestUtils.THIRD_PARTY_SIG); // 3.0-rc1
TestUtils.insertApk(context, app, 3100, TestUtils.THIRD_PARTY_SIG); // 3.0
TestUtils.insertApk(context, app, 4001, TestUtils.THIRD_PARTY_SIG); // 4.0-rc1
TestUtils.insertApk(context, app, 4002, TestUtils.THIRD_PARTY_SIG); // 4.0-rc2
TestUtils.insertApk(context, app, 4100, TestUtils.THIRD_PARTY_SIG); // 4.0
TestUtils.insertApk(context, app, 5001, TestUtils.THIRD_PARTY_SIG); // 5.0-rc1
TestUtils.insertApk(context, app, 5002, TestUtils.THIRD_PARTY_SIG); // 5.0-rc2
TestUtils.insertApk(context, app, 5003, TestUtils.THIRD_PARTY_SIG); // 5.0-rc3
TestUtils.updateDbAfterInserting(context);
return app;
}
private Repo createUpstreamRepo() {
return RepoProviderTest.insertRepo(context, "https://upstream.com/fdroid/repo", "", "", "");
}
private App populateUpstreamRepo(Repo repo) {
App app = TestUtils.insertApp(context, PACKAGE_NAME, "App", 4100, repo, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, app, 4100, TestUtils.UPSTREAM_SIG);
TestUtils.updateDbAfterInserting(context);
return app;
}
@Test
public void onlyFDroid() {
populateFDroidRepo(createFDroidRepo());
assertSuggested(context, 3100, TestUtils.UPSTREAM_SIG);
}
/**
* @see #assertFdroidThenDev()
*/
@Test
public void fdroidThenDev1() {
Repo fdroid = createFDroidRepo();
Repo dev = createDevRepo();
populateFDroidRepo(fdroid);
populateDevRepo(dev);
assertFdroidThenDev();
}
/**
* @see #assertFdroidThenDev()
*/
@Test
public void fdroidThenDev2() {
Repo fdroid = createFDroidRepo();
Repo dev = createDevRepo();
populateDevRepo(dev);
populateFDroidRepo(fdroid);
assertFdroidThenDev();
}
/**
* Both {@link #fdroidThenDev1()} and {@link #fdroidThenDev2()} add the same repos, with the same priorities and
* the same apps/apks. The only difference is in the order with which they get added to the database. They both
* then delegate here and assert that everything works as expected. The reason for testing like this is to ensure
* that the order of rows in the database has no bearing on the correct suggestions of signatures.
*
* @see #fdroidThenDev1()
* @see #fdroidThenDev2()
*/
private void assertFdroidThenDev() {
assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG);
Preferences.get().setUnstableUpdates(true);
assertSuggested(context, 5003, TestUtils.THIRD_PARTY_SIG);
Preferences.get().setUnstableUpdates(false);
assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG);
}
/**
* @see #assertFdroidThenUpstream()
*/
@Test
public void fdroidThenUpstream1() {
Repo fdroid = createFDroidRepo();
Repo upstream = createUpstreamRepo();
populateUpstreamRepo(upstream);
populateFDroidRepo(fdroid);
assertFdroidThenUpstream();
}
/**
* @see #assertFdroidThenUpstream()
*/
@Test
public void fdroidThenUpstream2() {
Repo fdroid = createFDroidRepo();
Repo upstream = createUpstreamRepo();
populateFDroidRepo(fdroid);
populateUpstreamRepo(upstream);
assertFdroidThenUpstream();
}
/**
* @see #fdroidThenUpstream1()
* @see #fdroidThenUpstream2()
* @see #assertFdroidThenDev()
*/
private void assertFdroidThenUpstream() {
assertSuggested(context, 4100, TestUtils.UPSTREAM_SIG);
}
/**
* @see #assertFdroidThenUpstreamThenDev()
*/
@Test
public void fdroidThenUpstreamThenDev1() {
Repo fdroid = createFDroidRepo();
Repo upstream = createUpstreamRepo();
Repo dev = createDevRepo();
populateFDroidRepo(fdroid);
populateUpstreamRepo(upstream);
populateDevRepo(dev);
assertFdroidThenUpstreamThenDev();
}
/**
* @see #assertFdroidThenUpstreamThenDev()
*/
@Test
public void fdroidThenUpstreamThenDev2() {
Repo fdroid = createFDroidRepo();
Repo upstream = createUpstreamRepo();
Repo dev = createDevRepo();
populateDevRepo(dev);
populateUpstreamRepo(upstream);
populateFDroidRepo(fdroid);
assertFdroidThenUpstreamThenDev();
}
/**
* @see #fdroidThenUpstreamThenDev1()
* @see #fdroidThenUpstreamThenDev2()
* @see #assertFdroidThenDev()
*/
private void assertFdroidThenUpstreamThenDev() {
assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG);
Preferences.get().setUnstableUpdates(true);
assertSuggested(context, 5003, TestUtils.THIRD_PARTY_SIG);
Preferences.get().setUnstableUpdates(false);
assertSuggested(context, 4100, TestUtils.THIRD_PARTY_SIG);
}
/**
* @see #assertFdroidThenDevThenUpstream()
*/
@Test
public void fdroidThenDevThenUpstream1() {
Repo fdroid = createFDroidRepo();
Repo dev = createDevRepo();
Repo upstream = createUpstreamRepo();
populateFDroidRepo(fdroid);
populateDevRepo(dev);
populateUpstreamRepo(upstream);
assertFdroidThenDevThenUpstream();
}
/**
* @see #assertFdroidThenDevThenUpstream()
*/
@Test
public void fdroidThenDevThenUpstream2() {
Repo fdroid = createFDroidRepo();
Repo dev = createDevRepo();
Repo upstream = createUpstreamRepo();
populateFDroidRepo(fdroid);
populateDevRepo(dev);
populateUpstreamRepo(upstream);
assertFdroidThenDevThenUpstream();
}
/**
* @see #fdroidThenDevThenUpstream1()
* @see #fdroidThenDevThenUpstream2()
* @see #assertFdroidThenDev()
*/
private void assertFdroidThenDevThenUpstream() {
assertSuggested(context, 4100, TestUtils.UPSTREAM_SIG);
}
private void assertSuggested(Context context, int suggestedVersion, String suggestedSig) {
App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), PACKAGE_NAME);
assertEquals("Suggested version on App", suggestedVersion, suggestedApp.autoInstallVersionCode);
Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(context, suggestedApp);
assertEquals("Version on suggested Apk", suggestedVersion, suggestedApk.versionCode);
TestUtils.assertSignaturesMatch("Signature on suggested Apk", suggestedSig, suggestedApk.sig);
}
}

View File

@@ -1,156 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.ContentResolver;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import org.fdroid.fdroid.mock.MockApk;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
import androidx.test.core.app.ApplicationProvider;
import static org.fdroid.fdroid.Assert.assertInvalidUri;
import static org.fdroid.fdroid.Assert.assertValidUri;
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class ProviderUriTests {
private static final String CONTENT_URI_BASE = "content://" + FDroidProvider.AUTHORITY;
private static final String APK_PROVIDER_URI_BASE = CONTENT_URI_BASE + ".ApkProvider";
private static final String APP_PROVIDER_URI_BASE = CONTENT_URI_BASE + ".AppProvider";
private static final String TEMP_APP_PROVIDER_URI_BASE = CONTENT_URI_BASE + ".TempAppProvider";
private ContentResolver resolver;
@Before
public void setup() {
resolver = ApplicationProvider.getApplicationContext().getContentResolver();
}
@After
public void teardown() {
DBHelper.clearDbHelperSingleton();
}
@Test
public void invalidInstalledAppProviderUris() {
TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class);
assertInvalidUri(resolver, InstalledAppProvider.getAuthority());
assertInvalidUri(resolver, "blah");
}
@Test
public void validInstalledAppProviderUris() {
TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class);
String[] projection = new String[]{InstalledAppTable.Cols._ID};
assertValidUri(resolver, InstalledAppProvider.getContentUri(), projection);
assertValidUri(resolver, InstalledAppProvider.getAppUri("org.example.app"), projection);
assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah"), projection);
assertValidUri(resolver, InstalledAppProvider.getSearchUri("\"blah\""), projection);
assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah & sneh"), projection);
assertValidUri(resolver, InstalledAppProvider.getSearchUri("http://blah.example.com?sneh=\"sneh\""), projection);
}
@Test
public void invalidRepoProviderUris() {
TestUtils.registerContentProvider(RepoProvider.getAuthority(), RepoProvider.class);
assertInvalidUri(resolver, RepoProvider.getAuthority());
assertInvalidUri(resolver, "blah");
}
@Test
public void validRepoProviderUris() {
TestUtils.registerContentProvider(RepoProvider.getAuthority(), RepoProvider.class);
String[] projection = new String[]{Schema.RepoTable.Cols._ID};
assertValidUri(resolver, RepoProvider.getContentUri(), projection);
assertValidUri(resolver, RepoProvider.getContentUri(10000L), projection);
assertValidUri(resolver, RepoProvider.allExceptSwapUri(), projection);
}
@Test
public void invalidAppProviderUris() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
assertInvalidUri(resolver, AppProvider.getAuthority());
assertInvalidUri(resolver, "blah");
}
@Test
public void validAppProviderUris() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
String[] projection = new String[]{Schema.AppMetadataTable.Cols._ID};
assertValidUri(resolver, AppProvider.getContentUri(), APP_PROVIDER_URI_BASE, projection);
assertValidUri(resolver, AppProvider.getSearchUri("'searching!'", null), APP_PROVIDER_URI_BASE + "/search/'searching!'", projection);
assertValidUri(resolver, AppProvider.getSearchUri("'searching!'", "Games"), APP_PROVIDER_URI_BASE + "/search/'searching!'/Games", projection);
assertValidUri(resolver, AppProvider.getSearchUri("/", null), APP_PROVIDER_URI_BASE + "/search/%2F", projection);
assertValidUri(resolver, AppProvider.getSearchUri("/", "Games"), APP_PROVIDER_URI_BASE + "/search/%2F/Games", projection);
assertValidUri(resolver, AppProvider.getSearchUri("", null), APP_PROVIDER_URI_BASE, projection);
assertValidUri(resolver, AppProvider.getSearchUri("", "Games"), APP_PROVIDER_URI_BASE + "/category/Games", projection);
assertValidUri(resolver, AppProvider.getCategoryUri("Games"), APP_PROVIDER_URI_BASE + "/category/Games", projection);
assertValidUri(resolver, AppProvider.getSearchUri((String) null, null), APP_PROVIDER_URI_BASE, projection);
assertValidUri(resolver, AppProvider.getSearchUri((String) null, "Games"), APP_PROVIDER_URI_BASE + "/category/Games", projection);
assertValidUri(resolver, AppProvider.getInstalledUri(), APP_PROVIDER_URI_BASE + "/installed", projection);
assertValidUri(resolver, AppProvider.getCanUpdateUri(), APP_PROVIDER_URI_BASE + "/canUpdate", projection);
App app = new App();
app.repoId = 1;
app.packageName = "org.fdroid.fdroid";
assertValidUri(resolver, AppProvider.getSpecificAppUri(app.packageName, app.repoId),
APP_PROVIDER_URI_BASE + "/app/1/org.fdroid.fdroid", projection);
}
@Test
public void validTempAppProviderUris() {
TestUtils.registerContentProvider(TempAppProvider.getAuthority(), TempAppProvider.class);
String[] projection = new String[]{Schema.AppMetadataTable.Cols._ID};
// Required so that the `assertValidUri` calls below will indeed have a real temp_fdroid_app
// table to query.
TempAppProvider.Helper.init(TestUtils.createContextWithContentResolver(resolver), 123);
List<String> packageNames = new ArrayList<>(2);
packageNames.add("org.fdroid.fdroid");
packageNames.add("com.example.com");
assertValidUri(resolver, TempAppProvider.getAppsUri(packageNames, 1),
TEMP_APP_PROVIDER_URI_BASE + "/apps/1/org.fdroid.fdroid%2Ccom.example.com", projection);
assertValidUri(resolver, TempAppProvider.getContentUri(), TEMP_APP_PROVIDER_URI_BASE, projection);
}
@Test
public void invalidApkProviderUris() {
TestUtils.registerContentProvider(ApkProvider.getAuthority(), ApkProvider.class);
assertInvalidUri(resolver, ApkProvider.getAuthority());
assertInvalidUri(resolver, "blah");
}
@Test
public void validApkProviderUris() {
TestUtils.registerContentProvider(ApkProvider.getAuthority(), ApkProvider.class);
String[] projection = new String[]{Schema.ApkTable.Cols._ID};
List<Apk> apks = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
apks.add(new MockApk("com.example." + i, i));
}
assertValidUri(resolver, ApkProvider.getContentUri(),
APK_PROVIDER_URI_BASE, projection);
assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"),
APK_PROVIDER_URI_BASE + "/app/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri(new MockApk("org.fdroid.fdroid", 100)),
APK_PROVIDER_URI_BASE + "/apk-any-repo/100/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100, null),
APK_PROVIDER_URI_BASE + "/apk-any-repo/100/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getRepoUri(1000),
APK_PROVIDER_URI_BASE + "/repo/1000", projection);
}
}

View File

@@ -1,291 +0,0 @@
/*
* Copyright (C) 2016 Blue Jay Wireless
* Copyright (C) 2014-2016 Hans-Christoph Steiner <hans@eds.org>
* Copyright (C) 2014-2016 Peter Serwylo <peter@serwylo.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import androidx.annotation.Nullable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@Config(application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class RepoProviderTest extends FDroidProviderTest {
private static final String[] COLS = RepoTable.Cols.ALL;
/**
* Returns the number of repos in app/src/main/res/default_repo.xml
*/
public static int getDefaultRepoCount(Context context) {
int itemCount = context.getResources().getStringArray(R.array.default_repos).length;
return itemCount / DBHelper.REPO_XML_ITEM_COUNT;
}
/**
* Set to random time zone to make sure that the dates are properly parsed.
*/
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void countEnabledRepos() {
// By default, f-droid is enabled.
assertEquals(1, RepoProvider.Helper.countEnabledRepos(context));
Repo gpRepo = RepoProvider.Helper.findByAddress(context, "https://guardianproject.info/fdroid/repo");
gpRepo = setEnabled(gpRepo, true);
assertEquals(2, RepoProvider.Helper.countEnabledRepos(context));
Repo fdroidRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/repo");
setEnabled(fdroidRepo, false);
setEnabled(gpRepo, false);
assertEquals(0, RepoProvider.Helper.countEnabledRepos(context));
}
@Test
public void lastUpdated() {
assertNull(RepoProvider.Helper.lastUpdate(context));
Repo gpRepo = RepoProvider.Helper.findByAddress(context, "https://guardianproject.info/fdroid/repo");
// Set date to 2017-04-05 11:56:38
setLastUpdate(gpRepo, new Date(1491357408643L));
// GP is not yet enabled, so it is not counted.
assertNull(RepoProvider.Helper.lastUpdate(context));
// Set date to 2017-04-04 11:56:38
Repo fdroidRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/repo");
setLastUpdate(fdroidRepo, new Date(1491357408643L - (1000 * 60 * 60 * 24)));
assertEquals("2017-04-04", Utils.formatDate(RepoProvider.Helper.lastUpdate(context), null));
setEnabled(gpRepo, true);
assertEquals("2017-04-05", Utils.formatDate(RepoProvider.Helper.lastUpdate(context), null));
}
private Repo setLastUpdate(Repo repo, Date date) {
ContentValues values = new ContentValues(1);
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(date, null));
RepoProvider.Helper.update(context, repo, values);
return RepoProvider.Helper.findByAddress(context, repo.address);
}
@Test
public void findByUrl() {
Repo fdroidRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/repo");
Repo fdroidArchiveRepo = RepoProvider.Helper.findByAddress(context, "https://f-droid.org/archive");
String[] noRepos = {
"https://not-a-repo.example.com",
"https://f-droid.org",
"https://f-droid.org/",
};
for (String url : noRepos) {
assertNull(RepoProvider.Helper.findByUrl(context, Uri.parse(url), COLS));
}
String[] fdroidRepoUrls = {
"https://f-droid.org/repo/index.jar",
"https://f-droid.org/repo/index.jar?random-junk-in-query=yes",
"https://f-droid.org/repo/index.jar?random-junk-in-query=yes&more-junk",
"https://f-droid.org/repo/icons/org.fdroid.fdroid.100.png",
"https://f-droid.org/repo/icons-640/org.fdroid.fdroid.100.png",
};
assertUrlsBelongToRepo(fdroidRepoUrls, fdroidRepo);
String[] fdroidArchiveUrls = {
"https://f-droid.org/archive/index.jar",
"https://f-droid.org/archive/index.jar?random-junk-in-query=yes",
"https://f-droid.org/archive/index.jar?random-junk-in-query=yes&more-junk",
"https://f-droid.org/archive/icons/org.fdroid.fdroid.100.png",
"https://f-droid.org/archive/icons-640/org.fdroid.fdroid.100.png",
};
assertUrlsBelongToRepo(fdroidArchiveUrls, fdroidArchiveRepo);
}
private void assertUrlsBelongToRepo(String[] urls, Repo expectedRepo) {
for (String url : urls) {
Repo actualRepo = RepoProvider.Helper.findByUrl(context, Uri.parse(url), COLS);
assertNotNull("No repo matching URL " + url, actualRepo);
assertEquals("Invalid repo for URL [" + url + "]. Expected [" + expectedRepo.address + "] but got ["
+ actualRepo.address + "]", expectedRepo.id, actualRepo.id);
}
}
/**
* The {@link DBHelper} class populates the default repos when it first creates a database.
* The names/URLs/signing certificates for these repos are all hard coded in the source/res.
*/
@Test
public void defaultRepos() {
List<Repo> defaultRepos = RepoProvider.Helper.all(context);
assertEquals(defaultRepos.size(), getDefaultRepoCount(context));
String[] reposFromXml = context.getResources().getStringArray(R.array.default_repos);
if (reposFromXml.length % DBHelper.REPO_XML_ITEM_COUNT != 0) {
throw new IllegalArgumentException(
"default_repo.xml array does not have the right number of elements");
}
for (int i = 0; i < reposFromXml.length / DBHelper.REPO_XML_ITEM_COUNT; i++) {
int offset = i * DBHelper.REPO_XML_ITEM_COUNT;
assertRepo(
defaultRepos.get(i),
reposFromXml[offset + 1], // address
reposFromXml[offset + 2].replaceAll("\\s+", " "), // description
Utils.calcFingerprint(reposFromXml[offset + 7]), // pubkey
reposFromXml[offset] // name
);
}
}
@Test
public void canAddRepo() {
int defaultRepoCount = getDefaultRepoCount(context);
assertEquals(defaultRepoCount, RepoProvider.Helper.all(context).size());
Repo mock1 = insertRepo(
context,
"https://mock-repo-1.example.com/fdroid/repo",
"Just a made up repo",
"ABCDEF1234567890",
"Mock Repo 1"
);
Repo mock2 = insertRepo(
context,
"http://mock-repo-2.example.com/fdroid/repo",
"Mock repo without a name",
"0123456789ABCDEF"
);
assertEquals(defaultRepoCount + 2, RepoProvider.Helper.all(context).size());
assertRepo(
mock1,
"https://mock-repo-1.example.com/fdroid/repo",
"Just a made up repo",
"ABCDEF1234567890",
"Mock Repo 1"
);
assertRepo(
mock2,
"http://mock-repo-2.example.com/fdroid/repo",
"Mock repo without a name",
"0123456789ABCDEF",
"mock-repo-2.example.com/fdroid/repo"
);
}
private static void assertRepo(Repo actualRepo, String expectedAddress, String expectedDescription,
String expectedFingerprint, String expectedName) {
assertEquals(expectedAddress, actualRepo.address);
assertEquals(expectedDescription, actualRepo.description);
assertEquals(expectedFingerprint, actualRepo.fingerprint);
assertEquals(expectedName, actualRepo.name);
}
@Test
public void canDeleteRepo() {
Repo mock1 = insertRepo(
context,
"https://mock-repo-1.example.com/fdroid/repo",
"Just a made up repo",
"ABCDEF1234567890",
"Mock Repo 1"
);
Repo mock2 = insertRepo(
context,
"http://mock-repo-2.example.com/fdroid/repo",
"Mock repo without a name",
"0123456789ABCDEF"
);
int defaultRepoCount = getDefaultRepoCount(context);
List<Repo> beforeDelete = RepoProvider.Helper.all(context);
assertEquals(defaultRepoCount + 2, beforeDelete.size());
assertEquals(mock1.id, beforeDelete.get(defaultRepoCount).id);
assertEquals(mock2.id, beforeDelete.get(defaultRepoCount + 1).id);
RepoProvider.Helper.remove(context, mock1.getId());
List<Repo> afterDelete = RepoProvider.Helper.all(context);
assertEquals(defaultRepoCount + 1, afterDelete.size());
assertEquals(mock2.id, afterDelete.get(defaultRepoCount).id);
}
public Repo insertRepo(Context context, String address, String description, String fingerprint) {
return insertRepo(context, address, description, fingerprint, null);
}
public static Repo insertRepo(Context context, String address, String description,
String fingerprint, @Nullable String name) {
return insertRepo(context, address, description, fingerprint, name, false);
}
public static Repo insertRepo(Context context, String address, String description,
String fingerprint, @Nullable String name, boolean isSwap) {
ContentValues values = new ContentValues();
values.put(RepoTable.Cols.ADDRESS, address);
values.put(RepoTable.Cols.DESCRIPTION, description);
values.put(RepoTable.Cols.FINGERPRINT, fingerprint);
values.put(RepoTable.Cols.NAME, name);
values.put(RepoTable.Cols.IS_SWAP, isSwap);
RepoProvider.Helper.insert(context, values);
return RepoProvider.Helper.findByAddress(context, address);
}
}

View File

@@ -1,953 +0,0 @@
/*
* Copyright (C) 2016 Blue Jay Wireless
* Copyright (C) 2015 Daniel Martí <mvdan@mvdan.cc>
* Copyright (C) 2014-2016 Hans-Christoph Steiner <hans@eds.org>
* Copyright (C) 2014-2016 Peter Serwylo <peter@serwylo.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.data;
import android.text.TextUtils;
import android.util.Log;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.mock.MockRepo;
import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import androidx.annotation.NonNull;
import static org.junit.Assert.assertEquals;
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.fail;
@RunWith(RobolectricTestRunner.class)
public class RepoXMLHandlerTest {
private static final String TAG = "RepoXMLHandlerTest";
private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; // NOCHECKSTYLE LineLength
/**
* Set to random time zone to make sure that the dates are properly parsed.
*/
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void testExtendedPerms() throws IOException {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is just a test of the extended permissions attributes.";
expectedRepo.timestamp = 1467169032;
RepoDetails actualDetails = getFromFile("extendedPerms.xml");
handlerTestSuite(expectedRepo, actualDetails, 2, 6, 14, 16);
}
@Test
public void testObbIndex() throws IOException {
writeResourceToObbDir("main.1101613.obb.main.twoversions.obb");
writeResourceToObbDir("main.1101615.obb.main.twoversions.obb");
writeResourceToObbDir("main.1434483388.obb.main.oldversion.obb");
writeResourceToObbDir("main.1619.obb.mainpatch.current.obb");
writeResourceToObbDir("patch.1619.obb.mainpatch.current.obb");
RepoDetails actualDetails = getFromFile("obbIndex.xml");
for (Apk indexApk : actualDetails.apks) {
Apk localApk = new Apk();
localApk.packageName = indexApk.packageName;
localApk.versionCode = indexApk.versionCode;
localApk.hashType = indexApk.hashType;
App.initInstalledObbFiles(localApk);
assertEquals(indexApk.obbMainFile, localApk.obbMainFile);
assertEquals(indexApk.obbMainFileSha256, localApk.obbMainFileSha256);
assertEquals(indexApk.obbPatchFile, localApk.obbPatchFile);
assertEquals(indexApk.obbPatchFileSha256, localApk.obbPatchFileSha256);
}
}
@Test
public void testSimpleIndex() {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; // NOCHECKSTYLE LineLength
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."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1398733213;
RepoDetails actualDetails = getFromFile("simpleIndex.xml");
handlerTestSuite(expectedRepo, actualDetails, 0, 0, -1, 12);
}
@Test
public void testSmallRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "Android-Nexus-7-20139453 on UNSET";
expectedRepo.signingCertificate = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c"; // NOCHECKSTYLE LineLength
expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453";
expectedRepo.timestamp = 1412696461;
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",
"org.adaway",
"info.guardianproject.gilga",
"com.google.zxing.client.android",
"info.guardianproject.lildebi",
"de.danoeh.antennapod",
"info.guardianproject.otr.app.im",
"org.torproject.android",
"org.gege.caldavsyncadapter",
"info.guardianproject.checkey",
});
for (App app : actualDetails.apps) {
if ("org.mozilla.firefox".equals(app.packageName)) {
assertEquals(1411776000000L, app.added.getTime());
assertEquals(1411862400000L, app.lastUpdated.getTime());
}
}
}
@Test(expected = IllegalArgumentException.class)
public void testSimpleIndexWithCorruptedPackageName() throws Throwable {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; // NOCHECKSTYLE LineLength
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."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1398733213;
InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("simpleIndexWithCorruptedPackageName.xml");
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
RepoDetails repoDetails = new RepoDetails();
MockRepo mockRepo = new MockRepo(100, Repo.PUSH_REQUEST_IGNORE);
RepoXMLHandler handler = new RepoXMLHandler(mockRepo, repoDetails);
reader.setContentHandler(handler);
InputSource is = new InputSource(new BufferedInputStream(inputStream));
try {
reader.parse(is);
} catch (org.xml.sax.SAXException e) {
throw e.getCause();
}
fail();
}
@Test
public void testPushRequestsRepoIgnore() {
Repo expectedRepo = new Repo();
expectedRepo.name = "non-public test repo";
expectedRepo.signingCertificate = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is a repository of apps to be used with F-Droid. 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://gitlab.com/u/fdroid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1472071347;
RepoDetails actualDetails = getFromFile("pushRequestsIndex.xml", Repo.PUSH_REQUEST_IGNORE);
handlerTestSuite(expectedRepo, actualDetails, 2, 14, -1, 17);
checkPushRequests(actualDetails);
List<RepoPushRequest> repoPushRequests = actualDetails.repoPushRequestList;
assertNotNull(repoPushRequests);
assertEquals(0, repoPushRequests.size());
}
@Test
public void testPushRequestsRepoAlways() {
Repo expectedRepo = new Repo();
expectedRepo.name = "non-public test repo";
expectedRepo.signingCertificate = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is a repository of apps to be used with F-Droid. 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://gitlab.com/u/fdroid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1472071347;
RepoDetails actualDetails = getFromFile("pushRequestsIndex.xml", Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
handlerTestSuite(expectedRepo, actualDetails, 2, 14, -1, 17);
checkPushRequests(actualDetails);
List<RepoPushRequest> repoPushRequests = actualDetails.repoPushRequestList;
assertNotNull(repoPushRequests);
assertEquals(6, repoPushRequests.size());
}
@Test
public void testPushRequestsRepoCorruption() {
RepoPushRequest repoPushRequest;
repoPushRequest = new RepoPushRequest(null, null, null); // request with no data
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid", "999999999999");
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid",
String.valueOf(((long) Integer.MAX_VALUE) + 1));
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid",
String.valueOf(((long) Integer.MIN_VALUE) - 1));
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("Robert'); DROP TABLE Students; --", "org.fdroid.fdroid", null);
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, "org.fdroid.fdroid");
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "Robert'); DROP TABLE Students; --", "123.1.1");
assertEquals(repoPushRequest.request, "install");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "--", "123");
assertEquals(repoPushRequest.request, "install");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
repoPushRequest = new RepoPushRequest("uninstall", "Robert'); DROP TABLE Students; --", "123");
assertEquals(repoPushRequest.request, "uninstall");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
repoPushRequest = new RepoPushRequest("badrquest", "asdfasdfasdf", "123");
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, "asdfasdfasdf");
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
}
@Test
public void testMediumRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "Guardian Project Official Releases";
expectedRepo.signingCertificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f"; // NOCHECKSTYLE LineLength
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."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1411427879;
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",
"info.guardianproject.checkey",
"info.guardianproject.courier",
"org.fdroid.fdroid",
"info.guardianproject.gpg",
"info.guardianproject.lildebi",
"info.guardianproject.notepadbot",
"org.witness.sscphase1",
"org.torproject.android",
"info.guardianproject.browser",
"info.guardianproject.pixelknot",
"info.guardianproject.chatsecure.emoji.core",
"info.guardianproject.mrapp",
});
}
@Test
public void testLargeRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
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."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1412746769;
RepoDetails actualDetails = getFromFile("largeRepo.xml");
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 // NOCHECKSTYLE LineLength
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
* | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p'
*/
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",
"com.zoffcc.applications.aagtl", "aarddict.android", "com.kai1973i",
"net.georgewhiteside.android.abstractart", "com.morphoss.acal",
"org.billthefarmer.accordion", "com.achep.acdisplay", "anupam.acrylic",
"net.androidcomics.acv", "org.adaway", "com.matoski.adbm",
"org.adblockplus.android", "siir.es.adbWireless", "org.dgtale.icsimport",
"com.addi", "org.hystudio.android.dosbox", "hu.vsza.adsdroid", "org.adw.launcher",
"dev.ukanth.ufirewall", "com.madgag.agit", "jp.sblo.pandora.aGrep",
"net.gorry.aicia", "com.brosmike.airpushdetector", "org.ligi.ajsha",
"org.akvo.rsr.up", "com.angrydoughnuts.android.alarmclock", "org.jtb.alogcat",
"rs.pedjaapps.alogcatroot.app", "org.ametro", "com.orphan.amplayer",
"eu.domob.anacam", "com.as.anagramsolver", "com.nephoapp.anarxiv",
"net.bible.android.activity", "li.klass.fhem", "org.xapek.andiodine", "net.avs234",
"com.github.andlyticsproject", "org.quovadit.apps.andof",
"com.gpl.rpg.AndorsTrail", "net.progval.android.andquote", "net.rocrail.androc",
"de.hechler.andfish", "com.android.inputmethod.latin", "aws.apps.androidDrawables",
"org.yuttadhammo.tipitaka", "uk.co.bitethebullet.android.token",
"jp.ksksue.app.terminal", "com.templaro.opsiz.aka", "fr.asterope",
"android.androidVNC", "com.tritop.androsense2", "net.tedstein.AndroSS",
"com.androzic", "org.andstatus.app", "net.sourceforge.andsys",
"com.miqote.angelplayerwp", "eu.domob.angulo", "com.ichi2.anki",
"net.haltcondition.anode", "An.stop", "de.danoeh.antennapod",
"com.fivasim.antikythera", "de.antonwolf.agendawidget", "com.example.anycut",
"org.liberty.android.fantastischmemo", "com.menny.android.anysoftkeyboard",
"com.anysoftkeyboard.languagepack.catalan", "com.anysoftkeyboard.theme.classic_pc",
"com.anysoftkeyboard.languagepack.danish",
"com.anysoftkeyboard.languagepack.esperanto",
"com.anysoftkeyboard.languagepack.french_xlarge",
"com.anysoftkeyboard.languagepack.georgian.fdroid",
"com.anysoftkeyboard.languagepack.greek",
"com.anysoftkeyboard.languagepack.hebrew_large",
"org.herrlado.ask.languagepack.lithuanian",
"com.anysoftkeyboard.languagepack.hungarian",
"com.anysoftkeyboard.languagepack.malayalam",
"com.anysoftkeyboard.languagepack.pali",
"com.anysoftkeyboard.languagepack.persian",
"com.anysoftkeyboard.languagepack.spain", "com.anysoftkeyboard.languagepack.SSH",
"com.anysoftkeyboard.languagepack.ukrainian", "com.scar45.aokp.co.webviewer",
"org.thialfihar.android.apg", "ch.blinkenlights.android.apnswitch",
"com.andrew.apollo", "com.nolanlawson.apptracker",
"com.episode6.android.appalarm.pro", "org.moparisthebest.appbak",
"org.microg.nlp.backend.apple", "com.gueei.applocker",
"com.google.code.appsorganizer", "com.google.code.apps2org",
"cx.hell.android.pdfview", "org.androidfromfrankfurt.archnews", "org.ardour",
"com.primavera.arduino.listener", "arity.calculator",
"com.commonsware.android.arXiv", "com.dozingcatsoftware.asciicam",
"com.alfray.asqare", "dk.andsen.asqlitemanager", "net.somethingdreadful.MAL",
"org.tamanegi.atmosphere", "indrora.atomic",
"com.google.android.apps.authenticator2", "com.teamdc.stephendiniz.autoaway",
"com.everysoft.autoanswer", "com.elsdoerfer.android.autostarts", "com.ds.avare",
"apps.babycaretimer", "com.tkjelectronics.balanduino", "com.liato.bankdroid",
"uk.ac.cam.cl.dtg.android.barcodebox", "com.google.zxing.client.android",
"net.szym.barnacle", "com.dougkeen.bart", "ch.blinkenlights.battery",
"net.sf.andbatdog.batterydog", "ch.rrelmy.android.batterymanager",
"org.droidparts.battery_widget", "org.androidappdev.batterywidget",
"com.darshancomputing.BatteryIndicatorPro", "com.tobykurien.batteryfu",
"com.mohammadag.beamfile", "com.corner23.android.beautyclocklivewallpaper",
"com.knirirr.beecount", "com.beem.project.beem", "com.glanznig.beepme",
"headrevision.BehatReporter", "com.asksven.betterwifionoff",
"com.asksven.betterbatterystats", "net.imatruck.betterweather",
"org.segin.bfinterpreter", "com.ihunda.android.binauralbeat",
"org.birthdayadapter", "com.rigid.birthdroid", "com.saibotd.bitbeaker",
"de.schildbach.wallet", "com.veken0m.bitcoinium", "com.miracleas.bitcoin_spinner",
"caldwell.ben.bites", "eu.domob.bjtrainer",
"de.Cherubin7th.blackscreenpresentationremote", "com.miqote.brswp",
"com.blippex.app", "de.grobox.blitzmail", "org.blockinger.game",
"org.scoutant.blokish", "org.broeuschmeul.android.gps.bluetooth.provider",
"com.hermit.btreprap", "ru.sash0k.bluetooth_terminal", "net.bluetoothviewer",
"com.hexad.bluezime", "com.hexad.bluezime.hidenabler", "cxa.lineswallpaper",
"com.zola.bmi", "com.boardgamegeek", "byrne.utilities.converter",
"org.yuttadhammo.BodhiTimer", "mobi.boilr.boilr", "org.beide.bomber",
"org.bombusmod", "com.eleybourn.bookcatalogue", "com.totsp.bookworm",
"com.botbrew.basil", "com.github.grimpy.botifier",
"priv.twoerner.brightnesswidget", "nl.frankkie.bronylivewallpaper",
"com.intrications.android.sharebrowser", "fr.strasweb.browserquest",
"net.androgames.level", "com.notriddle.budget", "budo.budoist",
"org.nathan.jf.build.prop.editor", "com.sandeel.bushidoblocks", "no.rkkc.bysykkel",
"info.guardianproject.cacert", "com.frozendevs.cache.cleaner",
"at.bitfire.cadroid", "org.iilab.pb", "home.jmstudios.calc",
"com.android2.calculator3", "org.gege.caldavsyncadapter",
"de.k3b.android.calendar.ics.adapter", "com.plusonelabs.calendar",
"de.ub0r.android.callmeter", "com.call.recorder",
"com.wordpress.sarfraznawaz.callerdetails", "com.integralblue.callerid",
"com.android.camera2", "campyre.android", "com.dozingcatsoftware.cameratimer",
"com.jadn.cc", "me.kuehle.carreport", "org.systemcall.scores",
"com.ridgelineapps.resdicegame", "com.nolanlawson.logcat", "com.github.cetoolbox",
"fr.strasweb.asso", "com.linkomnia.android.Changjie", "org.atai.TessUI",
"com.kkinder.charmap", "com.googlecode.chartdroid",
"info.guardianproject.otr.app.im", "org.zephyrsoft.checknetwork",
"jwtc.android.chess", "cz.hejl.chesswalk", "org.hekmatof.chesswatch",
"org.scoutant.cc", "com.nilhcem.frcndict", "com.nolanlawson.chordreader",
"net.pmarks.chromadoze", "me.bpear.chromeapkpackager",
"us.lindanrandy.cidrcalculator", "name.starnberger.guenther.android.cbw",
"com.sapos_aplastados.game.clash_of_balls", "de.qspool.clementineremote",
"de.ub0r.android.clipboardbeam", "com.ssaurel.clocklw", "org.floens.chan",
"dk.mide.fas.cmnightlies", "de.fmaul.android.cmis", "com.banasiak.coinflip",
"com.banasiak.coinflipext.example", "com.coinbase.android",
"com.brianco.colorclock", "com.color.colornamer", "com.nauj27.android.colorpicker",
"org.androidsoft.coloring", "org.gringene.colourclock", "net.kervala.comicsreader",
"com.sgr_b2.compass", "net.micode.compass", "org.dyndns.fules.ck",
"org.gringene.concentricclock", "org.connectbot", "de.measite.contactmerger",
"com.appengine.paranoid_android.lost", "com.boombuler.widgets.contacts",
"com.github.nutomic.controldlna", "eu.siacs.conversations", "org.coolreader",
"com.dconstructing.cooper", "se.johanhil.clipboard", "ru.o2genum.coregame",
"net.vivekiyer.GAL", "com.example.CosyDVR", "de.mreiter.countit",
"com.cr5315.cfdc", "ch.fixme.cowsay", "com.qubling.sidekick",
"com.bvalosek.cpuspy", "com.github.alijc.cricketsalarm", "groomiac.crocodilenote",
"org.eehouse.android.xw4", "org.nick.cryptfs.passwdmanager", "com.csipsimple",
"com.hykwok.CurrencyConverter", "com.elsewhat.android.currentwallpaper",
"com.manor.currentwidget", "net.cyclestreets", "org.mult.daap",
"com.bottleworks.dailymoney", "org.jessies.dalvikexplorer", "com.darknessmap",
"net.nurik.roman.dashclock",
"it.gmariotti.android.apps.dashclock.extensions.battery", "com.mridang.cellinfo",
"net.logomancy.dashquotes.civ5", "me.malladi.dashcricket", "com.dwak.lastcall",
"de.bashtian.dashclocksunrise", "com.mridang.wifiinfo", "dasher.android",
"com.umang.dashnotifier", "at.bitfire.davdroid", "net.czlee.debatekeeper",
"org.dyndns.sven_ola.debian_kit", "net.debian.debiandroid", "com.wentam.defcol",
"com.serone.desktoplabel", "com.f2prateek.dfg", "org.dnaq.dialer2",
"jpf.android.diary", "com.voidcode.diasporawebclient",
"com.edwardoyarzun.diccionario", "de.kugihan.dictionaryformids.hmi_android",
"si.modrajagoda.didi", "net.logomancy.diedroid",
"kaljurand_at_gmail_dot_com.diktofon", "in.shick.diode",
"com.google.android.diskusage", "to.networld.android.divedroid",
"org.diygenomics.pg", "org.sufficientlysecure.viewer",
"org.sufficientlysecure.viewer.fontpack", "com.dozingcatsoftware.dodge",
"org.katsarov.dofcalc", "org.dolphinemu.dolphinemu", "us.bravender.android.dongsa",
"net.iowaline.dotdash", "de.stefan_oltmann.kaesekaestchen", "steele.gerry.dotty",
"fr.xtof54.dragonGoApp", "com.drismo", "com.xatik.app.droiddraw.client",
"jackpal.droidexaminer", "edu.rit.poe.atomix", "org.petero.droidfish",
"org.beide.droidgain", "org.jtb.droidlife", "com.mkf.droidsat", "org.droidseries",
"org.droidupnp", "com.googlecode.droidwall", "de.delusions.measure",
"com.shurik.droidzebra", "de.onyxbits.drudgery", "ch.dissem.android.drupal",
"github.daneren2005.dsub", "se.johanhil.duckduckgo",
"com.duckduckgo.mobile.android", "it.ecosw.dudo", "org.dynalogin.android",
"org.uaraven.e", "com.seb.SLWP", "com.seb.SLWPmaps", "net.pejici.easydice",
"de.audioattack.openlink", "app.easytoken", "com.f0x.eddymalou",
"org.congresointeractivo.elegilegi", "com.ultramegatech.ey",
"com.blntsoft.emailpopup", "com.kibab.android.EncPassChanger",
"org.epstudios.epmobile", "org.jamienicol.episodes",
"com.mirasmithy.epochlauncher", "it.angrydroids.epub3reader", "it.iiizio.epubator",
"com.googlecode.awsms", "com.mehmetakiftutuncu.eshotroid",
"com.googlecode.eyesfree.espeak", "com.sweetiepiggy.everylocale",
"de.pinyto.exalteddicer", "org.kost.externalip", "ch.hsr.eyecam",
"com.google.marvin.shell", "org.fdroid.fdroid",
"com.easwareapps.f2lflap2lock_adfree", "faenza.adw.theme", "org.balau.fakedawn",
"de.stefan_oltmann.falling_blocks", "com.codebutler.farebot", "org.ligi.fast",
"com.mod.android.widget.fbcw", "org.fastergps",
"org.geometerplus.zlibrary.ui.android",
"org.geometerplus.fbreader.plugin.local_opds_scanner",
"org.geometerplus.fbreader.plugin.tts", "com.hyperionics.fbreader.plugin.tts_plus",
"net.micode.fileexplorer", "com.cyanogenmod.filemanager.ics",
"com.michaldabski.filemanager", "com.github.wdkapps.fillup",
"se.erikofsweden.findmyphone", "org.mozilla.firefox", "com.ten15.diyfish",
"org.mysociety.FixMyStreet", "uk.co.danieljarvis.android.flashback",
"com.nightshadelabs.anotherbrowser", "com.studio332.flickit",
"com.gmail.charleszq", "fi.harism.wallpaper.flier", "org.aja.flightmode",
"dk.nindroid.rss", "genius.mohammad.floating.stickies", "net.fred.feedex",
"fr.xplod.focal", "com.oakley.fon", "com.sputnik.wispr", "ro.ieval.fonbot",
"net.phunehehe.foocam", "com.iazasoft.footguy", "org.jsharkey.sky",
"it.andreascarpino.forvodroid", "be.digitalia.fosdem",
"de.b0nk.fp1_epo_autoupdate", "pt.isec.tp.am",
"com.blogspot.tonyatkins.freespeech", "org.fedorahosted.freeotp",
"de.cwde.freeshisen", "nf.frex.android", "de.wikilab.android.friendica01",
"de.serverfrog.pw.android", "org.froscon.schedule", "com.frostwire.android",
"org.jfedor.frozenbubble", "be.ppareit.swiftp_free", "com.easwareapps.g2l",
"com.nesswit.galbijjimsearcher", "com.traffar.game_of_life", "com.androidemu.gba",
"com.tobykurien.google_news", "com.androidemu.gbc", "com.gcstar.scanner",
"com.gcstar.viewer", "com.jeffboody.GearsES2eclair",
"com.namsor.api.samples.gendre", "de.onyxbits.geobookmark", "pl.nkg.geokrety",
"se.danielj.geometridestroyer", "eu.hydrologis.geopaparazzi",
"org.herrlado.geofonts", "com.github.ruleant.getback_gps", "at.bitfire.gfxtablet",
"com.ghostsq.commander", "com.ghostsq.commander.samba",
"com.ghostsq.commander.sftp", "net.gaast.giggity", "info.guardianproject.gilga",
"com.github.mobile", "com.timvdalen.gizmooi", "com.glTron",
"zame.GloomyDungeons.opensource.game", "com.kaeruct.glxy", "de.duenndns.gmdice",
"org.gmote.client.android", "org.gnucash.android", "info.guardianproject.gpg",
"org.ligi.gobandroid_hd", "com.googlecode.gogodroid",
"org.wroot.android.goldeneye", "com.traffar.gomoku", "net.sf.crypt.gort",
"com.mendhak.gpslogger", "com.gpstether", "com.Bisha.TI89EmuDonation",
"org.cyanogenmod.great.freedom", "it.greenaddress.cordova", "org.gfd.gsmlocation",
"de.srlabs.gsmmap", "com.googlecode.gtalksms", "ru.zxalexis.ugaday",
"com.gulshansingh.hackerlivewallpaper", "org.pocketworkstation.pckeyboard",
"net.sf.times", "org.durka.hallmonitor", "com.smerty.ham", "net.tapi.handynotes",
"ca.mimic.apphangar", "com.hobbyone.HashDroid", "com.ginkel.hashit",
"byrne.utilities.hashpass", "com.zaren", "com.jakebasile.android.hearingsaver",
"ca.ddaly.android.heart", "com.vanderbie.heart_rate_monitor",
"com.jwetherell.heart_rate_monitor", "fi.testbed2", "com.borneq.heregpslocation",
"net.damsy.soupeaucaillou.heriswap", "com.sam.hex",
"org.gitorious.jamesjrh.isokeys", "com.manuelmaly.hn", "com.gluegadget.hndroid",
"com.omegavesko.holocounter", "com.tortuca.holoken",
"com.dynamicg.homebuttonlauncher", "com.naholyr.android.horairessncf",
"it.andreascarpino.hostisdown", "com.nilhcem.hostseditor",
"com.smorgasbork.hotdeath", "net.sf.andhsli.hotspotlogin",
"com.bobbyrne01.howfardoyouswim", "hsware.HSTempo", "org.jtb.httpmon",
"eu.woju.android.packages.hud", "de.nico.ha_manager", "com.roguetemple.hydroid",
"com.frankcalise.h2droid", "de.boesling.hydromemo", "com.roguetemple.hyperroid",
"com.ancantus.HYPNOTOAD", "com.kostmo.wallpaper.spiral", "net.i2p.android.router",
"com.germainz.identiconizer", "com.dozuki.ifixit", "com.cradle.iitc_mobile",
"eu.e43.impeller", "am.ed.importcontacts", "org.libreoffice.impressremote",
"com.shahul3d.indiasatelliteweather", "org.smc.inputmethod.indic",
"net.luniks.android.inetify", "com.bri1.soundbored", "com.silentlexx.instead",
"uk.co.ashtonbrsc.android.intentintercept", "org.smblott.intentradio",
"org.safermobile.intheclear", "to.doc.android.ipv6config",
"org.woltage.irssiconnectbot", "org.valos.isolmoa", "com.github.egonw.isotopes",
"de.tui.itlogger", "com.teleca.jamendo", "com.nolanlawson.jnameconverter",
"julianwi.javainstaller", "com.achep.widget.jellyclock", "com.jlyr",
"com.jonglen7.jugglinglab", "jupiter.broadcasting.live.tv",
"uk.co.jarofgreen.JustADamnCompass", "jp.co.kayo.android.localplayer",
"jp.co.kayo.android.localplayer.ds.ampache",
"jp.co.kayo.android.localplayer.ds.podcast", "com.brocktice.JustSit",
"com.fsck.k9", "de.cketti.dashclock.k9", "vnd.blueararat.kaleidoscope6",
"com.leafdigital.kanji.android", "com.matteopacini.katana",
"org.kde.kdeconnect_tp", "net.lardcave.keepassnfc", "com.android.keepass",
"com.seawolfsanctuary.keepingtracks", "com.nolanlawson.keepscore",
"de.enaikoon.android.keypadmapper3", "com.concentricsky.android.khan",
"com.leinardi.kitchentimer", "org.nerdcircus.android.klaxon", "at.dasz.KolabDroid",
"org.kontalk", "org.kwaak3", "pro.oneredpixel.l9droid", "eu.prismsw.lampshade",
"com.adstrosoftware.launchappops", "com.android.launcher3",
"com.example.android.maxpapers", "de.danielweisser.android.ldapsync",
"net.fercanet.LNM", "org.pulpdust.lesserpad", "net.healeys.lexic",
"com.mykola.lexinproject", "com.martinborjesson.o2xtouchlednotifications",
"de.grobox.liberario", "fm.libre.droid", "acr.browser.barebones",
"acr.browser.lightning", "info.guardianproject.lildebi",
"com.willhauck.linconnectclient", "org.peterbaldwin.client.android.tinyurl",
"org.linphone", "it.mn.salvi.linuxDayOSM", "com.xenris.liquidwarsos",
"de.onyxbits.listmyapps", "name.juodumas.ext_kbd_lithuanian", "com.lligainterm",
"ch.rrelmy.android.locationcachemap", "in.shick.lockpatterngenerator",
"it.reyboz.screenlock", "sk.madzik.android.logcatudp",
"in.shubhamchaudhary.logmein", "com.powerpoint45.lucidbrowser",
"org.lumicall.android", "jpf.android.magiadni", "com.anoshenko.android.mahjongg",
"info.kesavan.malartoon", "uk.ac.ed.inf.mandelbrotmaps", "com.zapta.apps.maniana",
"be.quentinloos.manille", "com.chmod0.manpages", "net.pierrox.mcompass",
"org.dsandler.apps.markers", "org.evilsoft.pathfinder.reference",
"de.ph1b.audiobook", "net.cactii.mathdoku", "org.jessies.mathdroid",
"org.aminb.mathtools.app", "jp.yhonda", "org.projectmaxs.main",
"org.projectmaxs.module.alarmset", "org.projectmaxs.module.bluetooth",
"org.projectmaxs.module.bluetoothadmin", "org.projectmaxs.module.clipboard",
"org.projectmaxs.module.contactsread", "org.projectmaxs.module.fileread",
"org.projectmaxs.module.filewrite", "org.projectmaxs.module.locationfine",
"org.projectmaxs.module.misc", "org.projectmaxs.module.nfc",
"org.projectmaxs.module.notification", "org.projectmaxs.module.phonestateread",
"org.projectmaxs.module.ringermode", "org.projectmaxs.module.shell",
"org.projectmaxs.module.smsnotify", "org.projectmaxs.module.smsread",
"org.projectmaxs.module.smssend", "org.projectmaxs.module.smswrite",
"org.projectmaxs.module.wifiaccess", "org.projectmaxs.module.wifichange",
"org.projectmaxs.transport.xmpp", "com.harleensahni.android.mbr",
"eu.johncasson.meerkatchallenge", "org.billthefarmer.melodeon",
"org.zakky.memopad", "org.androidsoft.games.memory.kids", "net.asceai.meritous",
"com.intervigil.micdroid", "com.midisheetmusic", "de.syss.MifareClassicTool",
"com.rhiannonweb.android.migrainetracker", "com.evancharlton.mileage",
"com.xlythe.minecraftclock", "it.reyboz.minesweeper", "com.example.sshtry",
"jp.gr.java_conf.hatalab.mnv", "nitezh.ministock", "org.kde.necessitas.ministro",
"com.jaygoel.virginminuteschecker", "de.azapps.mirakelandroid",
"de.azapps.mirakel.dashclock", "org.bitbucket.tickytacky.mirrormirror",
"de.homac.Mirrored", "org.mixare", "edu.harvard.android.mmskeeper",
"org.tbrk.mnemododo", "com.matburt.mobileorg", "com.gs.mobileprint",
"org.n52.sosmobileclient", "com.dngames.mobilewebcam", "com.mobiperf",
"dev.drsoran.moloko", "ivl.android.moneybalance",
"com.tobiaskuban.android.monthcalendarwidgetfoss", "org.montrealtransit.android",
"org.montrealtransit.android.schedule.stmbus", "akk.astro.droid.moonphase",
"org.epstudios.morbidmeter", "org.mosspaper", "org.cry.otp", "com.morlunk.mountie",
"com.spazedog.mounts2sd", "com.nutomic.zertman",
"org.logicallycreative.movingpolygons", "org.mozilla.mozstumbler",
"com.uraroji.garage.android.mp3recvoice", "com.namelessdev.mpdroid",
"org.bc_bd.mrwhite", "com.fgrim.msnake", "com.gelakinetic.mtgfam",
"com.hectorone.multismssender", "org.tamanegi.wallpaper.multipicture.dnt",
"kr.softgear.multiping", "com.artifex.mupdfdemo",
"paulscode.android.mupen64plusae", "com.android.music",
"com.danielme.muspyforandroid", "org.mustard.android", "org.mumod.android",
"net.nurik.roman.muzei", "net.ebt.muzei.miyazaki",
"com.projectsexception.myapplist.open", "net.dahanne.banq.notifications",
"org.totschnig.myexpenses", "com.futonredemption.mylocation", "ch.fixme.status",
"i4nc4mp.myLock", "org.aykit.MyOwnNotes", "org.mythdroid",
"tkj.android.homecontrol.mythmote", "org.coolfrood.mytronome",
"name.livitski.games.puzzle.android", "de.laxu.apps.nachtlagerdownloader",
"com.nanoconverter.zlab", "fr.miximum.napply", "org.vono.narau",
"com.example.muzei.muzeiapod", "de.msal.muzei.nationalgeographic",
"org.navitproject.navit", "org.ndeftools.boilerplate", "jp.sfjp.webglmol.NDKmol",
"com.opendoorstudios.ds4droid", "com.kvance.Nectroid", "jp.sawada.np2android",
"com.androidemu.nes", "net.jaqpot.netcounter", "free.yhc.netmbuddy",
"org.ncrmnt.nettts", "de.mangelow.network", "info.lamatricexiste.network",
"com.googlecode.networklog", "org.gc.networktester", "com.newsblur",
"jp.softstudio.DriversLicenseReader", "pl.net.szafraniec.NFCKey",
"se.anyro.nfc_reader", "pl.net.szafraniec.NFCTagmaker", "com.sinpo.xnfc",
"com.digitallizard.nicecompass", "net.gorry.android.input.nicownng",
"ru.glesik.nostrangersms", "it.sineo.android.noFrillsCPUClassic",
"com.netthreads.android.noiz2", "pe.moe.nori", "info.guardianproject.notepadbot",
"bander.notepad", "ru.ttyh.neko259.notey", "org.jmoyer.NotificationPlus",
"apps.droidnotify", "net.thauvin.erik.android.noussd", "org.npr.android.news",
"mobi.cyann.nstools", "org.ntpsync", "com.notriddle.null_launcer",
"com.numix.calculator", "com.numix.icons_circle", "org.jfedor.nxtremotecontrol",
"com.powerje.nyan", "com.palliser.nztides", "dk.jens.backup",
"com.valleytg.oasvn.android", "eu.lighthouselabs.obd.reader",
"nz.gen.geek_central.ObjViewer", "trikita.obsqr", "edu.sfsu.cs.orange.ocr",
"com.gh4a", "org.odk.collect.android", "org.sufficientlysecure.localcalendar",
"com.sli.ohmcalc", "org.openintents.about", "org.openintents.filemanager",
"org.openintents.safe", "edu.nyu.cs.omnidroid.app", "org.hanenoshino.onscripter",
"com.euedge.openaviationmap.android", "pro.dbro.bart",
"net.sourceforge.opencamera", "org.brandroid.openmanager",
"io.github.sanbeg.flashlight", "com.nexes.manager", "de.skubware.opentraining",
"com.dje.openwifinetworkremover", "be.brunoparmentier.openbikesharing.app",
"app.openconnect", "at.tomtasche.reader", "org.opengpx",
"org.sufficientlysecure.keychain", "de.jdsoft.law", "org.openlp.android",
"de.uni_potsdam.hpi.openmensa", "jp.redmine.redmineclient",
"cz.romario.opensudoku", "edu.killerud.kitchentimer", "de.blinkt.openvpn",
"de.schaeuffelhut.android.openvpn", "org.ale.openwatch", "com.vwp.owmap",
"com.vwp.owmini", "jp.co.omronsoft.openwnn", "com.googlecode.openwnn.legacy",
"orbitlivewallpaperfree.puzzleduck.com", "org.torproject.android",
"org.ethack.orwall", "info.guardianproject.browser", "com.eolwral.osmonitor",
"net.oschina.app", "org.billthefarmer.scope",
"ch.nexuscomputing.android.osciprimeics", "net.osmand.srtmPlugin.paid",
"net.osmand.parkingPlugin", "net.osmand.plus", "net.anzix.osm.upload",
"me.guillaumin.android.osmtracker", "de.ub0r.android.otpdroid",
"com.traffar.oware", "com.owncloud.android", "de.luhmer.owncloudnewsreader",
"nu.firetech.android.pactrack", "it.rgp.nyagua.pafcalc",
"org.moparisthebest.pageplus", "net.nightwhistler.pageturner",
"com.cybrosys.palmcalc", "com.niparasc.papanikolis", "ru.valle.btc",
"com.paranoid.ParanoidWallpapers", "org.ligi.passandroid", "com.passcard",
"gg.mw.passera", "com.jefftharris.passwdsafe", "com.uploadedlobster.PwdHash",
"com.zeapo.pwdstore", "org.passwordmaker.android", "byrne.utilities.pasteedroid",
"com.th.XenonWallpapers", "io.github.droidapps.pdfreader",
"name.bagi.levente.pedometer", "org.jf.Penroser", "fi.harism.wallpaper.flowers",
"mobi.omegacentauri.PerApp", "com.brewcrewfoo.performance",
"com.frozendevs.periodictable", "de.arnowelzel.android.periodical",
"org.androidsoft.app.permission", "com.FireFart.Permissions2",
"com.byagowi.persiancalendar", "org.dyndns.ipignoli.petronius",
"org.lf_net.pgpunlocker", "de.onyxbits.photobookmark",
"unisiegen.photographers.activity", "com.ruesga.android.wallpapers.photophase",
"org.esteban.piano", "org.musicbrainz.picard.barcodescanner", "com.pindroid",
"com.boombuler.piraten.map", "com.rj.pixelesque", "info.guardianproject.pixelknot",
"com.morlunk.mumbleclient", "eu.lavarde.pmtd", "de.onyxbits.pocketbandit",
"com.zachrattner.pockettalk", "edu.cmu.pocketsphinx.demo", "com.axelby.podax",
"com.polipoid", "com.politedroid", "com.hlidskialf.android.pomodoro",
"com.kpz.pomodorotasks.activity", "com.tinkerlog.android.pongtime",
"org.sixgun.ponyexpress", "com.xargsgrep.portknocker", "net.tevp.postcode",
"org.ppsspp.ppsspp", "com.proch.practicehub", "android.game.prboom",
"fr.simon.marquis.preferencesmanager", "damo.three.ie",
"com.gracecode.android.presentation", "com.falconware.prestissimo", "org.primftpd",
"org.us.andriod", "org.okfn.pod", "ro.ui.pttdroid", "org.macno.puma",
"com.boztalay.puppyframeuid", "com.purplefoto.pfdock", "org.example.pushupbuddy",
"name.boyle.chris.sgtpuzzles", "com.littlebytesofpi.pylauncher",
"org.pyload.android.client", "com.zagayevskiy.pacman",
"com.lgallardo.qbittorrentclient", "com.android.quake", "com.jeyries.quake2",
"com.iskrembilen.quasseldroid", "com.qsp.player", "com.bwx.bequick",
"com.hughes.android.dictionary", "vu.de.urpool.quickdroid", "be.geecko.QuickLyric",
"net.vreeken.quickmsg", "com.lightbox.android.camera", "com.write.Quill",
"es.cesar.quitesleep", "net.xenotropic.quizznworldcap", "com.radioreddit.android",
"org.openbmap", "com.tmarki.comicmaker", "cc.rainwave.android",
"be.norio.randomapp", "org.recentwidget", "au.com.wallaceit.reddinator",
"com.btmura.android.reddit", "org.quantumbadger.redreader",
"net.dahanne.android.regalandroid", "urbanstew.RehearsalAssistant",
"com.reicast.emulator", "com.harasoft.relaunch", "com.wanghaus.remembeer",
"net.noio.Reminder", "com.lostrealm.lembretes",
"org.peterbaldwin.client.android.vlcremote", "de.onyxbits.remotekeyboard",
"org.damazio.notifier", "com.vsmartcard.remotesmartcardreader.app",
"remuco.client.android", "com.replica.replicaisland", "br.usp.ime.retrobreaker",
"org.retroarch", "buet.rafi.dictionary", "fr.keuse.rightsalert",
"org.hoi_polloi.android.ringcode", "com.ringdroid", "com.dririan.RingyDingyDingy",
"fr.hnit.riverferry", "se.norenh.rkfread", "com.robert.maps", "org.rmll",
"info.staticfree.android.robotfindskitten", "com.abcdjdj.rootverifier",
"de.zieren.rot13", "org.penghuang.tools.rotationlock",
"com.spydiko.rotationmanager_foss", "mohammad.adib.roundr", "com.ath0.rpn",
"ru0xdc.rtkgps", "de.steinpfeffer.rdt", "net.aangle.rvclock", "org.sagemath.droid",
"com.cepmuvakkit.times", "monakhv.android.samlib",
"com.maxfierke.sandwichroulette", "cri.sanity", "it.sasabz.android.sasabus",
"org.ligi.satoshiproof", "com.vonglasow.michael.satstat",
"org.chorem.android.saymytexts", "org.crocodile.sbautologin",
"org.ale.scanner.zotero", "org.scid.android", "com.lukekorth.screennotifications",
"com.jotabout.screeninfo", "com.gmail.altakey.effy",
"net.jjc1138.android.scrobbler", "org.scummvm.scummvm", "gr.ndre.scuttloid",
"com.gmail.jerickson314.sdscanner", "com.seafile.seadroid",
"com.seafile.seadroid2", "com.ideasfrombrain.search_based_launcher_v2",
"com.scottmain.android.searchlight", "com.shadcat.secdroid",
"com.doplgangr.secrecy", "fr.simon.marquis.secretcodes", "fr.seeks",
"com.ariwilson.seismowallpaper", "mobi.omegacentauri.SendReduced",
"com.ivanvolosyuk.sharetobrowser", "ru.gelin.android.sendtosd",
"org.totschnig.sendwithftp", "de.onyxbits.sensorreadout", "at.univie.sensorium",
"com.monead.games.android.sequence", "org.servalproject", "org.servDroid.web",
"net.sourceforge.servestream", "me.sheimi.sgit", "org.emergent.android.weave",
"net.sylvek.sharemyposition", "com.MarcosDiez.shareviahttp", "com.android.shellms",
"com.boombuler.games.shift", "name.soulayrol.rhaa.sholi",
"com.github.nicolassmith.urlevaluator", "com.totsp.crossword.shortyz",
"com.showmehills", "be.ppareit.shutdown", "org.sickstache", "kr.hybdms.sidepanel",
"org.billthefarmer.siggen", "ru.neverdark.silentnight", "com.better.alarm",
"com.brentpanther.bitcoinwidget", "nl.ttys0.simplec25k", "com.chessclock.android",
"com.casimirlab.simpleDeadlines", "com.mareksebera.simpledilbert",
"com.dnielfe.manager", "com.adam.aslfms", "ee.smkv.calc.loan",
"com.poloure.simplerss", "nl.mpcjanssen.simpletask", "kdk.android.simplydo",
"eu.siebeck.sipswitch", "org.sipdroid.sipua", "com.sismics.reader",
"com.google.android.stardroid", "eu.flatworld.android.slider",
"de.shandschuh.slightbackup", "org.androidsoft.games.slowit",
"tritop.androidSLWCpuWidget", "tritop.android.SLWTrafficMeterWidget",
"wb.receiptspro", "com.unleashyouradventure.swaccess", "com.java.SmokeReducer",
"com.zegoggles.smssync", "bughunter2.smsfilter", "net.everythingandroid.smspopup",
"de.ub0r.android.smsdroid", "org.addhen.smssync", "com.mobilepearls.sokoban",
"com.kmagic.solitaire", "org.andglkmod.hunkypunk", "sonoroxadc.garethmurfin.co.uk",
"com.htruong.inputmethod.latin", "com.roozen.SoundManagerv2",
"net.micode.soundrecorder", "com.akop.bach", "org.sparkleshare.android",
"de.shandschuh.sparserss", "mixedbit.speechtrainer", "net.codechunk.speedofsound",
"fly.speedmeter.grub", "isn.fly.speedmeter", "SpeedoMeterApp.main",
"net.majorkernelpanic.spydroid", "csci567.squeez", "uk.org.ngo.squeezer",
"org.sufficientlysecure.standalonecalendar", "fr.bellev.stdatmosphere",
"com.piwi.stickeroid", "net.stkaddons.viewer", "com.nma.util.sdcardtrac",
"com.gokhanmoral.stweaks.app", "net.sourceforge.subsonic.androidapp",
"org.subsurface", "in.ac.dtu.subtlenews", "com.app2go.sudokufree", "de.sudoq",
"org.sudowars", "net.pejici.summation", "info.staticfree.SuperGenPass",
"com.koushikdutta.superuser", "com.omegavesko.sutransplus",
"biz.codefuture.svgviewer", "com.nutomic.syncthingandroid",
"org.ciasaboark.tacere", "com.kyakujin.android.tagnotepad",
"com.weicheng.taipeiyoubikeoffline", "com.google.android.marvin.talkback",
"com.ciarang.tallyphant", "org.tof", "com.acvarium.tasclock", "org.dmfs.tasks",
"org.tasks", "goo.TeaTimer", "net.chilon.matt.teacup", "fr.xgouchet.texteditor",
"org.telegram.messenger", "com.jmartin.temaki", "jackpal.androidterm",
"de.schildbach.wallet_test", "org.paulmach.textedit", "de.onyxbits.textfiction",
"com.myopicmobile.textwarrior.android", "pl.narfsoftware.thermometer",
"com.threedlite.livePolys", "org.ironrabbit.bhoboard", "org.ironrabbit",
"de.smasi.tickmate", "com.gladis.tictactoe", "org.tigase.messenger.phone.pro",
"com.lecz.android.tiltmazes", "org.dpadgett.timer", "com.alfray.timeriffic",
"com.tastycactus.timesheet", "org.poirsouille.tinc_gui",
"com.danvelazco.fbwrapper", "org.tint", "org.tint.adblock",
"com.xmission.trevin.android.todo", "org.chrisbailey.todo",
"com.earthblood.tictactoe", "com.dwalkes.android.toggleheadset2", "org.tomdroid",
"ch.hgdev.toposuite", "com.colinmcdonough.android.torch", "com.piratebayfree",
"com.amphoras.tpthelper", "org.traccar.client", "org.zephyrsoft.trackworktime",
"org.softcatala.traductor", "com.aselalee.trainschedule",
"com.andybotting.tramhunter", "org.transdroid.full", "org.transdroid",
"org.transdroid.search", "fr.ybo.transportsbordeaux", "fr.ybo.transportsrennes",
"com.lonepulse.travisjr", "de.koelle.christian.trickytripper",
"org.hermit.tricorder", "caldwell.ben.trolly", "fr.strasweb.campus",
"com.unwrappedapps.android.wallpaper.creative", "org.tryton.client",
"org.segin.ttleditor", "org.ttrssreader", "com.seavenois.tetris",
"com.dalthed.tucan", "tuioDroid.impl", "de.tum.in.tumcampus",
"org.billthefarmer.tuner", "org.tunesremote", "com.tunes.viewer",
"com.maskyn.fileeditorpro", "com.drodin.tuxrider", "org.me.tvhguide",
"org.mariotaku.twidere", "org.mariotaku.twidere.extension.twitlonger",
"com.reddyetwo.hashmypass.app", "com.twsitedapps.homemanager",
"szelok.app.twister", "com.googamaphone.typeandspeak", "org.zeitgeist.movement",
"ro.weednet.contactssync", "org.madore.android.unicodeMap",
"info.staticfree.android.units", "net.ralphbroenink.muzei.unsplash",
"com.u17od.upm", "cc.co.eurdev.urecorder", "com.threedlite.urforms",
"com.bretternst.URLazy", "me.hda.urlhda", "aws.apps.usbDeviceEnumerator",
"com.threedlite.userhash.location", "ch.blinkenlights.android.vanilla",
"org.kreed.vanilla", "com.dozingcatsoftware.bouncy", "de.blau.android",
"net.momodalo.app.vimtouch", "com.code.android.vibevault",
"com.pheelicks.visualizer", "org.videolan.vlc", "com.vlille.checker",
"com.pilot51.voicenotify", "de.jurihock.voicesmith",
"mancioboxblog.altervista.it.volumecontrol",
"org.projectvoodoo.simplecarrieriqdetector", "org.projectvoodoo.otarootkeeper",
"org.projectvoodoo.screentestpatterns", "com.poinsart.votar", "org.vudroid",
"sk.vx.connectbot", "net.mafro.android.wakeonlan", "fr.gaulupeau.apps.InThePoche",
"de.markusfisch.android.wavelines", "ru.gelin.android.weather.notification",
"ru.gelin.android.weather.notification.skin.blacktext",
"ru.gelin.android.weather.notification.skin.whitetext",
"de.geeksfactory.opacclient", "net.solutinno.websearch", "de.ub0r.android.websms",
"de.ub0r.android.websms.connector.gmx",
"de.ub0r.android.websms.connector.smspilotru", "com.ubergeek42.WeechatAndroid",
"jp.co.qsdn.android.jinbei3d", "fr.ludo1520.whatexp",
"org.wheelmap.android.online", "org.edunivers.whereami", "seanfoy.wherering",
"de.freewarepoint.whohasmystuff", "org.cprados.wificellmanager",
"ru.glesik.wifireminders", "org.androidappdev.wifiwidget",
"org.marcus905.wifi.ace", "de.j4velin.wifiAutoOff", "teaonly.droideye",
"org.wahtod.wififixer", "net.sourceforge.wifiremoteplay", "com.volosyukivan",
"net.wigle.wigleandroid", "org.wikilovesmonuments",
"de.mbutscher.wikiandpad.alphabeta", "org.wikimedia.commons",
"org.wikimedia.commons.muzei", "org.wikipedia", "fr.renzo.wikipoff",
"org.github.OxygenGuide", "org.wiktionary", "uk.org.cardboardbox.wonderdroid",
"com.developfreedom.wordpowermadeeasy", "org.wordpress.android",
"eu.vranckaert.worktime", "com.irahul.worldclock", "com.sigseg.android.worldmap",
"mazechazer.android.wottankquiz", "org.nick.wwwjdic", "au.com.darkside.XServer",
"com.xabber.androiddev", "org.xbmc.android.remote", "org.xcsoar",
"net.bytten.xkcdviewer", "org.helllabs.android.xmp", "biz.gyrus.yaab", "de.yaacc",
"org.yaaic", "org.yabause.android", "uobikiemukot.yaft", "com.tum.yahtzee",
"org.yaxim.androidclient", "fi.harism.wallpaper.yinyang",
"dentex.youtube.downloader", "com.yubico.yubiclip", "com.yubico.yubioath",
"com.yubico.yubitotp", "de.antonfluegge.android.yubnubwidgetadfree",
"com.gimranov.zandy.app", "org.zirco", "org.smerty.zooborns", "org.qii.weiciyuan",
"com.googlecode.tcime",
});
}
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.isEmpty()) {
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) {
assertNotNull(actualApps);
assertNotNull(expctedAppIds);
assertEquals(actualApps.size(), expctedAppIds.length);
for (String id : expctedAppIds) {
boolean thisAppMissing = true;
for (App app : actualApps) {
if (TextUtils.equals(app.packageName, id)) {
thisAppMissing = false;
break;
}
}
assertFalse(thisAppMissing);
}
}
private void checkPushRequests(RepoDetails actualDetails) {
final Object[] expectedPushRequestsIndex = new Object[]{
"install", "org.fdroid.fdroid", 101002,
"install", "org.fdroid.fdroid.privileged", null,
"uninstall", "com.android.vending", null,
"uninstall", "com.facebook.orca", -12345,
"uninstall", null, null, // request with no data
"install", "asdfasdfasdf", null, // non-existent app
};
checkIncludedApps(actualDetails.apps, new String[]{
"org.fdroid.fdroid", "org.fdroid.fdroid.privileged",
});
List<RepoPushRequest> repoPushRequestList = actualDetails.repoPushRequestList;
int i = 0;
for (RepoPushRequest repoPushRequest : repoPushRequestList) {
assertEquals(repoPushRequest.request, expectedPushRequestsIndex[i]);
assertEquals(repoPushRequest.packageName, expectedPushRequestsIndex[i + 1]);
assertEquals(repoPushRequest.versionCode, expectedPushRequestsIndex[i + 2]);
i += 3;
}
}
private void handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails,
int appCount, int apkCount, int maxAge, int version) {
assertNotNull(actualDetails);
assertFalse(TextUtils.isEmpty(actualDetails.signingCert));
assertEquals(expectedRepo.signingCertificate.length(), actualDetails.signingCert.length());
assertEquals(expectedRepo.signingCertificate, actualDetails.signingCert);
assertFalse(FAKE_SIGNING_CERT.equals(actualDetails.signingCert));
assertFalse(TextUtils.isEmpty(actualDetails.name));
assertEquals(expectedRepo.name.length(), actualDetails.name.length());
assertEquals(expectedRepo.name, actualDetails.name);
assertFalse(TextUtils.isEmpty(actualDetails.description));
assertEquals(expectedRepo.description.length(), actualDetails.description.length());
assertEquals(expectedRepo.description, actualDetails.description);
assertEquals(actualDetails.maxAge, maxAge);
assertEquals(actualDetails.version, version);
assertEquals(expectedRepo.timestamp, actualDetails.timestamp);
List<App> apps = actualDetails.apps;
assertNotNull(apps);
assertEquals(apps.size(), appCount);
for (App app : apps) {
assertTrue("Added should have been set", app.added.getTime() > 0);
assertTrue("Last Updated should have been set", app.lastUpdated.getTime() > 0);
}
List<Apk> apks = actualDetails.apks;
assertNotNull(apks);
assertEquals(apks.size(), apkCount);
}
@NonNull
private RepoDetails getFromFile(String indexFilename) {
return getFromFile(indexFilename, Repo.PUSH_REQUEST_IGNORE);
}
@NonNull
private RepoDetails getFromFile(String indexFilename, int pushRequests) {
return getFromFile(getClass().getClassLoader(), indexFilename, pushRequests);
}
@NonNull
public static RepoDetails getFromFile(ClassLoader classLoader, String indexFilename, int pushRequests) {
Log.i(TAG, "test file: " + classLoader.getResource(indexFilename));
InputStream inputStream = classLoader.getResourceAsStream(indexFilename);
return RepoDetails.getFromFile(inputStream, pushRequests);
}
private void writeResourceToObbDir(String assetName) throws IOException {
InputStream input = getClass().getClassLoader().getResourceAsStream(assetName);
String packageName = assetName.substring(assetName.indexOf("obb"),
assetName.lastIndexOf('.'));
File f = new File(App.getObbDir(packageName), assetName);
FileUtils.copyToFile(input, f);
input.close();
}
}

View File

@@ -1,15 +1,9 @@
package org.fdroid.fdroid.mock;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
public class MockApk extends Apk {
public MockApk(String id, int versionCode) {
this.packageName = id;
this.versionCode = versionCode;
}
public MockApk(String id, int versionCode, String repoAddress, String apkName) {
this.packageName = id;
this.versionCode = versionCode;
@@ -17,14 +11,4 @@ public class MockApk extends Apk {
this.apkName = apkName;
}
public MockApk(App app, int versionCode) {
this.appId = app.getId();
this.versionCode = versionCode;
}
public MockApk(long appId, int versionCode) {
this.appId = appId;
this.versionCode = versionCode;
}
}

View File

@@ -1,150 +0,0 @@
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.util.Log;
import org.fdroid.fdroid.IndexUpdater.UpdateException;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
import androidx.annotation.NonNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(RobolectricTestRunner.class)
public class AcceptableMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
private static final String TAG = "AcceptableMultiRepoTest";
private void assertSomewhatAcceptable() {
Log.i(TAG, "Asserting at least one versions of each .apk is in index.");
List<Repo> repos = RepoProvider.Helper.all(context);
assertEquals("Repos", 3, repos.size());
assertApp2048();
assertAppAdaway();
assertAppAdbWireless();
assertAppIcsImport();
}
@Test
public void testAcceptableConflictingThenMainThenArchive() throws UpdateException {
assertEmpty();
updateConflicting();
updateMain();
updateArchive();
assertSomewhatAcceptable();
}
@Test
public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException {
assertEmpty();
updateConflicting();
updateArchive();
updateMain();
assertSomewhatAcceptable();
}
@Test
public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException {
assertEmpty();
updateArchive();
updateMain();
updateConflicting();
assertSomewhatAcceptable();
}
@Test
public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException {
assertEmpty();
updateArchive();
updateConflicting();
updateMain();
assertSomewhatAcceptable();
}
@Test
public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException {
assertEmpty();
updateMain();
updateArchive();
updateConflicting();
assertSomewhatAcceptable();
}
@Test
public void testAcceptableMainThenConflictingThenArchive() throws UpdateException {
assertEmpty();
updateMain();
updateConflicting();
updateArchive();
assertSomewhatAcceptable();
}
@NonNull
private Repo getMainRepo() {
Repo repo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI);
assertNotNull(repo);
return repo;
}
@NonNull
private Repo getArchiveRepo() {
Repo repo = RepoProvider.Helper.findByAddress(context, REPO_ARCHIVE_URI);
assertNotNull(repo);
return repo;
}
@NonNull
private Repo getConflictingRepo() {
Repo repo = RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI);
assertNotNull(repo);
return repo;
}
@Test
public void testOrphanedApps() throws UpdateException {
assertEmpty();
updateArchive();
updateMain();
updateConflicting();
assertSomewhatAcceptable();
disableRepo(getArchiveRepo());
disableRepo(getMainRepo());
disableRepo(getConflictingRepo());
RepoProvider.Helper.purgeApps(context, getArchiveRepo());
RepoProvider.Helper.purgeApps(context, getMainRepo());
RepoProvider.Helper.purgeApps(context, getConflictingRepo());
assertEmpty();
}
private void disableRepo(Repo repo) {
ContentValues values = new ContentValues(1);
values.put(Cols.IN_USE, 0);
RepoProvider.Helper.update(context, repo, values);
}
}

View File

@@ -1,48 +0,0 @@
package org.fdroid.fdroid.updater;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.Utils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Tests two versions of the official main F-Droid metadata, from 10 days apart. This is here
* because there is so much metadata to parse in the main repo, covering many different aspects
* of the available metadata. Some apps will be added, others updated, and it should all just work.
*/
@RunWith(RobolectricTestRunner.class)
public class FDroidRepoUpdateTest extends MultiIndexUpdaterTest {
private static final String TAG = "FDroidRepoUpdateTest";
private static final String REPO_FDROID = "F-Droid";
private static final String REPO_FDROID_URI = "https://f-droid.org/repo";
private static final String REPO_FDROID_PUB_KEY = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
@Test
public void doesntCrash() throws IndexUpdater.UpdateException {
assertEmpty();
updateEarlier();
updateLater();
updateV1Later();
}
protected void updateEarlier() throws IndexUpdater.UpdateException {
Utils.debugLog(TAG, "Updating earlier version of F-Droid repo");
updateRepo(createIndexUpdater(REPO_FDROID, REPO_FDROID_URI, context, REPO_FDROID_PUB_KEY),
"index.fdroid.2016-10-30.jar");
}
protected void updateLater() throws IndexUpdater.UpdateException {
Utils.debugLog(TAG, "Updating later version of F-Droid repo");
updateRepo(createIndexUpdater(REPO_FDROID, REPO_FDROID_URI, context, REPO_FDROID_PUB_KEY),
"index.fdroid.2016-11-10.jar");
}
protected void updateV1Later() throws IndexUpdater.UpdateException {
Utils.debugLog(TAG, "Updating later version of F-Droid index-v1");
updateRepo(createIndexV1Updater(REPO_FDROID, REPO_FDROID_URI, context, REPO_FDROID_PUB_KEY),
"index-v1.fdroid.2017-07-07.jar");
}
}

View File

@@ -1,129 +0,0 @@
package org.fdroid.fdroid.updater;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class Issue763MultiRepo extends MultiIndexUpdaterTest {
private Repo microGRepo;
private Repo antoxRepo;
@Before
public void setup() {
String microGCert = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37";
microGRepo = createRepo("MicroG", "https://microg.org/fdroid/repo", context, microGCert);
String antoxCert = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6";
antoxRepo = createRepo("Tox", "https://pkg.tox.chat/fdroid/repo", context, antoxCert);
}
@Test
public void testAntoxRepo() throws IndexUpdater.UpdateException {
assertAntoxEmpty();
setEnabled(microGRepo, true);
updateAntox();
assertAntoxExists();
}
private void updateAntox() throws IndexUpdater.UpdateException {
updateRepo(new IndexUpdater(context, antoxRepo), "index.antox.jar");
}
@Test
public void testMicroGRepo() throws IndexUpdater.UpdateException {
assertMicroGEmpty();
setEnabled(microGRepo, true);
updateMicroG();
assertMicroGExists();
}
private void updateMicroG() throws IndexUpdater.UpdateException {
updateRepo(new IndexUpdater(context, microGRepo), "index.microg.jar");
}
@Test
public void antoxAndMicroG() throws IndexUpdater.UpdateException {
assertMicroGEmpty();
assertAntoxEmpty();
setEnabled(microGRepo, true);
setEnabled(antoxRepo, true);
updateMicroG();
updateAntox();
assertMicroGExists();
assertAntoxExists();
setEnabled(microGRepo, false);
RepoProvider.Helper.purgeApps(context, microGRepo);
assertMicroGEmpty();
assertAntoxExists();
setEnabled(microGRepo, true);
updateMicroG();
assertMicroGExists();
assertAntoxExists();
setEnabled(antoxRepo, false);
RepoProvider.Helper.purgeApps(context, antoxRepo);
assertMicroGExists();
assertAntoxEmpty();
setEnabled(antoxRepo, true);
updateAntox();
assertMicroGExists();
assertAntoxExists();
}
private void assertAntoxEmpty() {
List<Apk> actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL);
assertEquals(0, actualApksBeforeUpdate.size());
}
private void assertMicroGEmpty() {
List<Apk> actualApksBeforeUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL);
assertEquals(0, actualApksBeforeUpdate.size());
}
private void assertAntoxExists() {
String packageName = "chat.tox.antox";
List<Apk> actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL);
int[] expectedVersions = new int[]{15421};
assertApp(packageName, expectedVersions);
assertApksExist(actualApksAfterUpdate, packageName, expectedVersions);
}
private void assertMicroGExists() {
List<Apk> actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL);
String vendingPackage = "com.android.vending";
int[] expectedVendingVersions = new int[]{1};
assertApp(vendingPackage, expectedVendingVersions);
assertApksExist(actualApksAfterUpdate, vendingPackage, expectedVendingVersions);
String gmsPackage = "com.google.android.gms";
int[] expectedGmsVersions = new int[]{11059462, 10545451, 10545440, 10087438, 10087435, 9258259, 8492252};
assertApp(gmsPackage, expectedGmsVersions);
assertApksExist(actualApksAfterUpdate, gmsPackage, expectedGmsVersions);
String gsfPackage = "com.google.android.gsf";
int[] expectedGsfVersions = new int[]{8};
assertApp(gsfPackage, expectedGsfVersions);
assertApksExist(actualApksAfterUpdate, gsfPackage, expectedGsfVersions);
}
}

View File

@@ -1,219 +0,0 @@
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.content.Context;
import android.text.TextUtils;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.IndexUpdater.UpdateException;
import org.fdroid.fdroid.IndexV1Updater;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.RepoProviderTest;
import org.fdroid.fdroid.data.Schema;
import org.junit.Before;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import androidx.annotation.NonNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class MultiIndexUpdaterTest extends FDroidProviderTest {
@SuppressWarnings("unused")
private static final String TAG = "AcceptableMultiIndexUpdaterTest"; // NOPMD
protected static final String REPO_MAIN = "Test F-Droid repo";
protected static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)";
protected static final String REPO_CONFLICTING = "Test F-Droid repo with different apps";
protected static final String REPO_MAIN_URI = "https://f-droid.org/repo";
protected static final String REPO_ARCHIVE_URI = "https://f-droid.org/archive";
protected static final String REPO_CONFLICTING_URI = "https://example.com/conflicting/fdroid/repo";
private static final String PUB_KEY =
"3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" +
"55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" +
"6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" +
"300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" +
"657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" +
"00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" +
"0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" +
"92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" +
"97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" +
"cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" +
"be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" +
"de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" +
"a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" +
"b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" +
"0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" +
"73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" +
"bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" +
"3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" +
"e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" +
"ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" +
"67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" +
"df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" +
"259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" +
"1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" +
"58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" +
"5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" +
"d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" +
"ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" +
"495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" +
"98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" +
"e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b";
@Before
public final void setupMultiRepo() throws Exception {
// Remove default repos.
for (int i = 1; i <= RepoProviderTest.getDefaultRepoCount(context); ++i) {
RepoProvider.Helper.remove(context, i);
}
Preferences.setupForTests(context);
}
protected void assertApp(String packageName, int[] versionCodes) {
List<Apk> apks = ApkProvider.Helper.findByPackageName(context, packageName);
assertApksExist(apks, packageName, versionCodes);
}
protected void assertApp2048() {
assertApp("com.uberspot.a2048", new int[]{19, 18});
}
protected void assertAppAdaway() {
assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35});
}
protected void assertAppAdbWireless() {
assertApp("siir.es.adbWireless", new int[]{12});
}
protected void assertAppIcsImport() {
assertApp("org.dgtale.icsimport", new int[]{3, 2});
}
@NonNull
protected Repo findRepo(@NonNull String name, List<Repo> allRepos) {
Repo repo = null;
for (Repo r : allRepos) {
if (TextUtils.equals(name, r.getName())) {
repo = r;
break;
}
}
assertNotNull("Repo " + allRepos, repo);
return repo;
}
/**
* Checks that each version of appId as specified in versionCodes is present in apksToCheck.
*/
protected void assertApksExist(List<Apk> apksToCheck, String appId, int[] versionCodes) {
for (int versionCode : versionCodes) {
boolean found = false;
for (Apk apk : apksToCheck) {
if (apk.versionCode == versionCode && apk.packageName.equals(appId)) {
found = true;
break;
}
}
assertTrue("Couldn't find app " + appId + ", v" + versionCode, found);
}
}
protected void assertEmpty() {
assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size());
String[] packages = {
"com.uberspot.a2048",
"org.adaway",
"siir.es.adbWireless",
};
for (String id : packages) {
assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size());
}
}
protected Repo createRepo(String name, String uri, Context context) {
return createRepo(name, uri, context, PUB_KEY);
}
/**
* Creates a real instance of {@code Repo} by loading it from the database,
* that ensures it includes the primary key from the database.
*/
static Repo createRepo(String name, String uri, Context context, String signingCert) {
ContentValues values = new ContentValues(3);
values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
values.put(Schema.RepoTable.Cols.ADDRESS, uri);
values.put(Schema.RepoTable.Cols.NAME, name);
RepoProvider.Helper.insert(context, values);
return RepoProvider.Helper.findByAddress(context, uri);
}
protected IndexUpdater createIndexUpdater(String name, String uri, Context context) {
return new IndexUpdater(context, createRepo(name, uri, context));
}
protected IndexUpdater createIndexUpdater(String name, String uri, Context context, String signingCert) {
return new IndexUpdater(context, createRepo(name, uri, context, signingCert));
}
protected IndexV1Updater createIndexV1Updater(String name, String uri, Context context, String signingCert) {
return new IndexV1Updater(context, createRepo(name, uri, context, signingCert));
}
protected void updateConflicting() throws UpdateException {
updateRepo(createIndexUpdater(REPO_CONFLICTING, REPO_CONFLICTING_URI, context), "multiRepo.conflicting.jar");
}
protected void updateMain() throws UpdateException {
updateRepo(createIndexUpdater(REPO_MAIN, REPO_MAIN_URI, context), "multiRepo.normal.jar");
}
protected void updateArchive() throws UpdateException {
updateRepo(createIndexUpdater(REPO_ARCHIVE, REPO_ARCHIVE_URI, context), "multiRepo.archive.jar");
}
protected void updateRepo(IndexUpdater updater, String indexJarPath) throws UpdateException {
File indexJar = TestUtils.copyResourceToTempFile(indexJarPath);
try {
if (updater instanceof IndexV1Updater) {
JarFile jarFile = new JarFile(indexJar);
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
InputStream indexInputStream = jarFile.getInputStream(indexEntry);
((IndexV1Updater) updater).processIndexV1(indexInputStream, indexEntry, null);
} else {
updater.processDownloadedFile(indexJar);
}
} catch (IOException e) {
e.printStackTrace();
fail();
} finally {
if (indexJar != null && indexJar.exists()) {
indexJar.delete();
}
}
}
}

View File

@@ -1,459 +0,0 @@
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.util.Log;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppTestUtils;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSystemProperties;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.StringDef;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Config(shadows = ProperMultiIndexUpdaterTest.ArmSystemProperties.class)
@RunWith(RobolectricTestRunner.class)
public class ProperMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
private static final String TAG = "ProperMultiRepoSupport";
@Retention(RetentionPolicy.SOURCE)
@StringDef({"Conflicting", "Normal"})
public @interface RepoIdentifier {
}
@Test
public void appsRemovedFromRepo() throws IndexUpdater.UpdateException {
assertEquals(0, AppProvider.Helper.all(context.getContentResolver()).size());
updateMain();
Repo repo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI);
assertEquals(3, AppProvider.Helper.all(context.getContentResolver()).size());
assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size());
assertEquals(3, ApkProvider.Helper.findByPackageName(context, "org.adaway").size());
assertEquals(2, ApkProvider.Helper.findByPackageName(context, "com.uberspot.a2048").size());
assertEquals(1, ApkProvider.Helper.findByPackageName(context, "siir.es.adbWireless").size());
IndexUpdater updater = new IndexUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
updateRepo(updater, "multiRepo.conflicting.jar");
assertEquals(2, AppProvider.Helper.all(context.getContentResolver()).size());
assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size());
assertEquals(4, ApkProvider.Helper.findByPackageName(context, "org.adaway").size());
assertEquals(2, ApkProvider.Helper.findByPackageName(context, "org.dgtale.icsimport").size());
}
@Test
public void mainRepo() throws IndexUpdater.UpdateException {
assertEmpty();
updateMain();
assertMainRepo();
// Even though there is a version 54 in the repo, version 53 is marked as the current version.
assertCanUpdate("org.adaway", 49, 53);
}
@Test
public void archiveRepo() throws IndexUpdater.UpdateException {
assertEmpty();
updateArchive();
assertMainArchiveRepoMetadata();
assertCanUpdate("org.adaway", 49, 51);
}
@Test
public void conflictingRepo() throws IndexUpdater.UpdateException {
assertEmpty();
updateConflicting();
assertConflictingRepo();
assertCanUpdate("org.adaway", 49, 53);
}
private Map<String, App> allApps() {
List<App> apps = AppProvider.Helper.all(context.getContentResolver());
Map<String, App> appsIndexedByPackageName = new HashMap<>(apps.size());
for (App app : apps) {
appsIndexedByPackageName.put(app.packageName, app);
}
return appsIndexedByPackageName;
}
@Test
public void metadataWithRepoPriority() throws IndexUpdater.UpdateException {
updateMain();
updateArchive();
updateConflicting();
Repo mainRepo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI);
assertEquals(1, mainRepo.priority);
assertEquals(2, RepoProvider.Helper.findByAddress(context, REPO_ARCHIVE_URI).priority);
assertEquals(3, RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI).priority);
assertMainRepo();
assertMainArchiveRepoMetadata();
assertConflictingRepo();
assertRepoTakesPriority("Conflicting");
// Make the conflicting repo less important than the main repo.
ContentValues values = new ContentValues(1);
values.put(Cols.PRIORITY, 5);
RepoProvider.Helper.update(context, mainRepo, values);
Repo updatedMainRepo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI);
assertEquals(5, updatedMainRepo.priority);
assertRepoTakesPriority("Normal");
}
private void assertRepoTakesPriority(@RepoIdentifier String higherPriority) {
Map<String, App> allApps = allApps();
// Provided by both the "Main" and "Conflicting" repo, so need to fetch metdata from the
// repo with the higher "Conflicting" repo has a higher priority.
App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"org.adaway");
assertAdAwayMetadata(adAway, higherPriority);
assertAdAwayMetadata(allApps.get("org.adaway"), higherPriority);
// This is only provided by the "Main" or "Archive" repo. Both the main and archive repo both
// pull their metadata from the same build recipe in fdroidserver. The only difference is that
// the archive repository contains .apks from further back, but their metadata is the same.
App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"com.uberspot.a2048");
assert2048Metadata(a2048, "Normal");
assert2048Metadata(allApps.get("com.uberspot.a2048"), "Normal");
// This is only provided by the "Conflicting" repo.
App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"org.dgtale.icsimport");
assertCalendarMetadata(calendar, "Conflicting");
assertCalendarMetadata(allApps.get("org.dgtale.icsimport"), "Conflicting");
// This is only provided by the "Main" repo.
App adb = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"siir.es.adbWireless");
assertAdbMetadata(adb, "Normal");
assertAdbMetadata(allApps.get("siir.es.adbWireless"), "Normal");
}
@Test
public void testCorrectConflictingThenMainThenArchive() throws IndexUpdater.UpdateException {
assertEmpty();
updateConflicting();
updateMain();
updateArchive();
assertExpected();
}
@Test
public void testCorrectConflictingThenArchiveThenMain() throws IndexUpdater.UpdateException {
assertEmpty();
updateConflicting();
updateArchive();
updateMain();
assertExpected();
}
@Test
public void testCorrectArchiveThenMainThenConflicting() throws IndexUpdater.UpdateException {
assertEmpty();
updateArchive();
updateMain();
updateConflicting();
assertExpected();
}
@Test
public void testCorrectArchiveThenConflictingThenMain() throws IndexUpdater.UpdateException {
assertEmpty();
updateArchive();
updateConflicting();
updateMain();
assertExpected();
}
@Test
public void testCorrectMainThenArchiveThenConflicting() throws IndexUpdater.UpdateException {
assertEmpty();
updateMain();
updateArchive();
updateConflicting();
assertExpected();
}
@Test
public void testCorrectMainThenConflictingThenArchive() throws IndexUpdater.UpdateException {
assertEmpty();
updateMain();
updateConflicting();
updateArchive();
assertExpected();
}
/**
* Check that all of the expected apps and apk versions are available in the database. This
* check will take into account the repository the apks came from, to ensure that each
* repository indeed contains the apks that it said it would provide.
*/
private void assertExpected() {
Log.i(TAG, "Asserting all versions of each .apk are in index.");
List<Repo> repos = RepoProvider.Helper.all(context);
assertEquals("Repos", 3, repos.size());
assertMainRepo(repos);
assertMainArchiveRepoMetadata(repos);
assertConflictingRepo(repos);
// Even though there is a version 54 in the repo, version 53 is marked as the current version.
assertCanUpdate("org.adaway", 49, 53);
}
private void assertCanUpdate(String packageName, int installedVersion, int expectedUpdateVersion) {
InstalledAppTestUtils.install(context, packageName, installedVersion,
"v" + installedVersion, TestUtils.FDROID_CERT);
List<App> appsToUpdate = AppProvider.Helper.findCanUpdate(context, AppMetadataTable.Cols.ALL);
assertEquals(1, appsToUpdate.size());
assertEquals(installedVersion, appsToUpdate.get(0).installedVersionCode);
assertEquals(expectedUpdateVersion, appsToUpdate.get(0).autoInstallVersionCode);
}
private void assertMainRepo() {
assertMainRepo(RepoProvider.Helper.all(context));
}
/**
* + 2048 (com.uberspot.a2048)
* - Version 1.96 (19)
* - Version 1.95 (18)
* + AdAway (org.adaway)
* - Version 3.0.2 (54)
* - Version 3.0.1 (53)
* - Version 3.0 (52)
* + adbWireless (siir.es.adbWireless)
* - Version 1.5.4 (12)
*/
private void assertMainRepo(List<Repo> allRepos) {
Repo repo = findRepo(REPO_MAIN, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals("Apks for main repo", apks.size(), 6);
assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19});
assertApksExist(apks, "org.adaway", new int[]{52, 53, 54});
assertApksExist(apks, "siir.es.adbWireless", new int[]{12});
assert2048Metadata(repo, "Normal");
assertAdAwayMetadata(repo, "Normal");
assertAdbMetadata(repo, "Normal");
}
private void assert2048Metadata(Repo repo, @RepoIdentifier String id) {
App a2048 = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.uberspot.a2048",
repo.getId(), AppMetadataTable.Cols.ALL);
assert2048Metadata(a2048, id);
}
/**
* @param id An identifier that we've put in the metadata for each repo to ensure that
* we can identify the metadata is coming from the correct repo.
*/
private void assert2048Metadata(App a2048, @RepoIdentifier String id) {
assertNotNull(a2048);
assertEquals("2048", a2048.name);
assertEquals(String.format("<p>2048 from %s repo.</p>", id), a2048.description);
assertEquals(String.format("Puzzle game (%s)", id), a2048.summary);
assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webSite);
assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceCode);
assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.issueTracker);
}
private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) {
App adaway = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.adaway",
repo.getId(), AppMetadataTable.Cols.ALL);
assertAdAwayMetadata(adaway, id);
}
/**
* @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
*/
private void assertAdAwayMetadata(App adaway, @RepoIdentifier String id) {
assertNotNull(adaway);
assertEquals(String.format("AdAway", id),
adaway.name);
assertEquals(String.format("<p>AdAway from %s repo.</p>", id),
adaway.description);
assertEquals(String.format("Block advertisements (%s)", id),
adaway.summary);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id),
adaway.webSite);
assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id),
adaway.sourceCode);
assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id),
adaway.issueTracker);
assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id),
adaway.changelog);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id),
adaway.donate);
assertEquals(String.format("369138", id), adaway.flattrID);
}
private void assertAdbMetadata(Repo repo, @RepoIdentifier String id) {
App adb = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "siir.es.adbWireless",
repo.getId(), AppMetadataTable.Cols.ALL);
assertAdbMetadata(adb, id);
}
/**
* @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
*/
private void assertAdbMetadata(App adb, @RepoIdentifier String id) {
assertNotNull(adb);
assertEquals("adbWireless", adb.name);
assertEquals(String.format("<p>adbWireless from %s repo.</p>", id), adb.description);
assertEquals(String.format("Wireless adb (%s)", id), adb.summary);
assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webSite);
assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceCode);
assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.issueTracker);
}
private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) {
App calendar = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.dgtale.icsimport",
repo.getId(), AppMetadataTable.Cols.ALL);
assertCalendarMetadata(calendar, id);
}
/**
* @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
*/
private void assertCalendarMetadata(App calendar, @RepoIdentifier String id) {
assertNotNull(calendar);
assertEquals("Add to calendar",
calendar.name);
assertEquals(String.format("<p>Add to calendar from %s repo.</p>", id),
calendar.description);
assertEquals(String.format("Import .ics files into calendar (%s)", id),
calendar.summary);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id),
calendar.webSite);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id),
calendar.sourceCode);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id),
calendar.issueTracker);
assertEquals("2225390",
calendar.flattrID);
}
private void assertMainArchiveRepoMetadata() {
assertMainArchiveRepoMetadata(RepoProvider.Helper.all(context));
}
/**
* + AdAway (org.adaway)
* - Version 2.9.2 (51)
* - Version 2.9.1 (50)
* - Version 2.9 (49)
* - Version 2.8.1 (48)
* - Version 2.8 (47)
* - Version 2.7 (46)
* - Version 2.6 (45)
* - Version 2.3 (42)
* - Version 2.1 (40)
* - Version 1.37 (38)
* - Version 1.36 (37)
* - Version 1.35 (36)
* - Version 1.34 (35)
*/
private void assertMainArchiveRepoMetadata(List<Repo> allRepos) {
Repo repo = findRepo(REPO_ARCHIVE, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals("Apks for main archive repo", 13, apks.size());
assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51});
assertAdAwayMetadata(repo, "Normal");
}
private void assertConflictingRepo() {
assertConflictingRepo(RepoProvider.Helper.all(context));
}
/**
* + AdAway (org.adaway)
* - Version 3.0.1 (53) *
* - Version 3.0 (52) *
* - Version 2.9.2 (51) *
* - Version 2.2.1 (50) *
* + Add to calendar (org.dgtale.icsimport)
* - Version 1.2 (3)
* - Version 1.1 (2)
*/
private void assertConflictingRepo(List<Repo> allRepos) {
Repo repo = findRepo(REPO_CONFLICTING, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals("Apks for conflicting repo", 6, apks.size());
assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53});
assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3});
assertAdAwayMetadata(repo, "Conflicting");
assertCalendarMetadata(repo, "Conflicting");
}
/**
* Allows us to customize the result of Build.SUPPORTED_ABIS.
* In these tests, we want to "install" and check for updates of Adaway, but that depends
* on the armeabi, x86, or mips architectures, whereas the {@link ShadowSystemProperties}
* only returns armeabi-v7a by default.
* Based on https://groups.google.com/d/msg/robolectric/l_W2EbOek6s/O-GTce8jBQAJ.
*/
@Implements(className = "android.os.SystemProperties")
public static class ArmSystemProperties extends ShadowSystemProperties {
@Implementation
@SuppressWarnings("unused")
public static String get(String key) {
if ("ro.product.cpu.abilist".equals(key)) {
return "armeabi";
}
return ShadowSystemProperties.native_get(key);
}
}
}

View File

@@ -1,50 +0,0 @@
# Multiple Repos Test
This covers the three indexes:
* multiRepo.normal.jar
* multiRepo.archive.jar
* multiRepo.conflicting.jar
The goal is that F-Droid client should be able to:
* Update all three repos successfully
* Show all included versions for download in the UI
* Somehow deal nicely with the fact that two repos provide versions 50-53 of AdAway
## multiRepo.normal.jar
* 2048 (com.uberspot.a2048)
- Version 1.96 (19)
- Version 1.95 (18)
* AdAway (org.adaway)
- Version 3.0.2 (54)
- Version 3.0.1 (53)
- Version 3.0 (52)
* adbWireless (siir.es.adbWireless)
- Version 1.5.4 (12)
## multiRepo.archive.jar
* AdAway (org.adaway)
- Version 2.9.2 (51)
- Version 2.9.1 (50)
- Version 2.9 (49)
- Version 2.8.1 (48)
- Version 2.7 (46)
- Version 2.6 (45)
- Version 2.3 (42)
- Version 2.1 (40)
- Version 1.37 (37)
- Version 1.35 (36)
- Version 1.34 (35)
## multiRepo.conflicting.jar
* AdAway (org.adaway)
- Version 3.0.1 (53)
- Version 3.0 (52)
- Version 2.9.2 (51)
- Version 2.2.1 (50)
* Add to calendar (org.dgtale.icsimport)
- Version 1.2 (3)
- Version 1.1 (2)

View File

@@ -1,129 +0,0 @@
{
"$schema": "http://json-schema.org/draft/2019-09/schema#",
"$id": "https://cleaninsights.org/schemas/cimp.schema.json",
"title": "CleanInsights Matomo Proxy API",
"description": "The scheme defining the JSON API of the CleanInsights Matomo Proxy.",
"type": "object",
"properties": {
"idsite": {
"title": "Matomo Site ID",
"description": "The site ID used in the Matomo server which will collect and analyze the gathered data.",
"examples": [1, 2, 3, 345345],
"type": "integer",
"minimum": 1
},
"lang": {
"title": "HTTP Accept-Language Header",
"description": "A HTTP Accept-Language header. Matomo uses this value to detect the visitor's country.",
"examples": ["fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5", "en", "de_AT"],
"type": "string"
},
"ua": {
"title": "HTTP User-Agent Header",
"description": "A HTTP User-Agent. The user agent is used to detect the operating system and browser used.",
"examples": ["Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"],
"type": "string"
},
"visits": {
"title": "Visit Measurements",
"description": "List of aggregated measurements to specific pages/scenes/activities.",
"type": "array",
"items": {
"title": "Visit Measurement",
"description": "A single aggregated measurement of repeated visits to a page/scene/activity.",
"type": "object",
"properties": {
"action_name": {
"title": "Visited Page/Scene/Activity Identifier",
"description": "Main identifier to track page/scene/activity visits in Matomo.",
"examples": ["For example, Help / Feedback will create the Action Feedback in the category Help."],
"type": "string",
"minLength": 1
},
"period_start": {
"title": "Start UNIX Epoch Timestamp",
"description": "Beginning of the aggregation period in seconds since 1970-01-01 00:00:00 UTC",
"examples": [1602499451],
"type": "integer"
},
"period_end": {
"title": "End UNIX Epoch Timestamp",
"description": "End of the aggregation period in seconds since 1970-01-01 00:00:00 UTC",
"examples": [1602499451],
"type": "integer"
},
"times": {
"title": "Number of Times Occurred",
"description": "The number of times the visit to this page/scene/activity happened during the specified period.",
"examples": [1, 2, 3, 26745],
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false,
"required": ["action_name", "period_start", "period_end", "times"]
}
},
"events": {
"title": "Event Measurement",
"description": "List of aggregated measurements of a specific event. (e.g. like a press of a button, picture taken etc.)",
"type": "array",
"items": {
"title": "Event Measurement",
"description": "A single aggregated measurement of a specific event.",
"type": "object",
"properties": {
"category": {
"title": "Event Category Identifier",
"description": "A category identifier for the Matomo event tracking: https://matomo.org/docs/event-tracking/",
"examples": ["Videos", "Music", "Games"],
"type": "string",
"minLength": 1
},
"action": {
"title": "Event Action Identifier",
"description": "An action identifier for the Matomo event tracking: https://matomo.org/docs/event-tracking/",
"examples": ["Play", "Pause", "Duration", "Add Playlist", "Downloaded", "Clicked"],
"type": "string",
"minLength": 1
},
"name": {
"title": "Event Name",
"description": "An action name for the Matomo event tracking: https://matomo.org/docs/event-tracking/",
"examples": ["Office Space", "Jonathan Coulton - Code Monkey", "kraftwerk-autobahn.mp3"],
"type": "string"
},
"value": {
"title": "Event Value",
"description": "A value for the Matomo event tracking: https://matomo.org/docs/event-tracking/",
"examples": [0, 1, 1.5, 100, 56.44332],
"type": "number"
},
"period_start": {
"title": "Start UNIX Epoch Timestamp",
"description": "Beginning of the aggregation period in seconds since 1970-01-01 00:00:00 UTC",
"examples": [1602499451],
"type": "integer"
},
"period_end": {
"title": "End UNIX Epoch Timestamp",
"description": "End of the aggregation period in seconds since 1970-01-01 00:00:00 UTC",
"examples": [1602499451],
"type": "integer"
},
"times": {
"title": "Number of Times Occurred",
"description": "The number of times the visit to this page/scene/activity happened during the specified period.",
"examples": [1, 2, 3, 26745],
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false,
"required": ["category", "action","period_start", "period_end", "times"]
}
}
},
"additionalProperties": false,
"required": ["idsite"]
}

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,790 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<fdroid>
<repo icon="guardianproject.png" name="Guardian Project Official Releases" pubkey="308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6" timestamp="1488828510" url="https://guardianproject.info/fdroid/repo" version="18">
<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. </description>
<mirror>http://bdf2wcxujkg6qqff.onion/fdroid/repo</mirror>
<mirror>https://guardianproject.info/fdroid/repo</mirror>
<mirror>https://s3.amazonaws.com/guardianproject/fdroid/repo</mirror>
</repo>
<install packageName="org.torproject.android"/>
<install packageName="info.guardianproject.orfox"/>
<application id="info.guardianproject.cacert">
<id>info.guardianproject.cacert</id>
<added>2013-08-19</added>
<lastupdated>2013-10-31</lastupdated>
<name>CACertMan</name>
<summary>Disable untrusted certificates</summary>
<icon>info.guardianproject.cacert.4.png</icon>
<desc>&lt;p&gt;Android 4+ allows you to disable certificates from the system Settings and root isn't required, so try that first if you want to manually mess with the certificates. The app won't work with Android 4+ anyway.&lt;/p&gt;&lt;p&gt;An app to manage security certificates on your phone also containing a version of the Android CACert keystore derived from Mozilla. If a certificate has recently become untrusted you can either install an update to this app or you can backup and remove certificates by yourself.&lt;/p&gt;&lt;p&gt;Requires root: Yes, it writes to the system partition. You will need a device that has the grep command on it (via busybox: present on most custom ROMs). If the save doesnt work, then you will need to make your /system partition read-write by using a file explorer like &lt;a href="fdroid.app:com.ghostsq.commander"&gt;Ghost Commander&lt;/a&gt; or via a command in &lt;a href="fdroid.app:jackpal.androidterm"&gt;Terminal Emulator&lt;/a&gt;.&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Security,GuardianProject</categories>
<category>Security</category>
<web>https://guardianproject.info/2011/09/05/cacertman-app-to-address-diginotar-other-bad-cas</web>
<source>https://github.com/guardianproject/cacert</source>
<tracker>https://github.com/guardianproject/cacert/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<requirements>root</requirements>
<package>
<version>0.0.2.20111012</version>
<versioncode>4</versioncode>
<apkname>CACertMan-0.0.2-alpha-20111011.apk</apkname>
<hash type="sha256">251ebd40ce4a281a2292692707fb1e9c91428994cbad80a416a297db51069eb8</hash>
<size>172263</size>
<sdkver>7</sdkver>
<targetSdkVersion>7</targetSdkVersion>
<added>2013-08-19</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
</package>
<package>
<version>0.0.2-20110906</version>
<versioncode>3</versioncode>
<apkname>CACertMan-0.0.2-20110906.apk</apkname>
<hash type="sha256">c217c49abe5134007ceb2623a6189a73fa02af9d2b2bbcc5cbc4cb5da7b36a5d</hash>
<size>170305</size>
<sdkver>8</sdkver>
<targetSdkVersion>8</targetSdkVersion>
<added>2013-10-31</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
</package>
</application>
<application id="org.witness.informacam.app">
<id>org.witness.informacam.app</id>
<added>2013-12-11</added>
<lastupdated>2015-11-03</lastupdated>
<name>CameraV</name>
<summary>An InformaCam app to generate verifiable media</summary>
<icon>org.witness.informacam.app.206.png</icon>
<desc>&lt;p&gt;An InformaCam app to generate verifiable media.&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Development,GuardianProject</categories>
<category>Development</category>
<web>https://guardianproject.info/apps/camerav/</web>
<source>https://github.com/guardianproject/CameraV</source>
<tracker>https://dev.guardianproject.info/projects/informacam/issues</tracker>
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
<marketversion/>
<marketvercode>9999999</marketvercode>
<package>
<version>0.2.6</version>
<versioncode>206</versioncode>
<apkname>CameraVApp-release-0.2.6.apk</apkname>
<hash type="sha256">508f453e26c8c83dba858b53b21d909d549fe5646d01eb198c96c22d8e521e7c</hash>
<size>24123646</size>
<sdkver>16</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-11-03</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,GET_TOP_ACTIVITY_INFO,ACCESS_NETWORK_STATE,BLUETOOTH,WAKE_LOCK</permissions>
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
</package>
<package>
<version>0.2.4</version>
<versioncode>204</versioncode>
<apkname>CameraV-release-0.2.4.apk</apkname>
<hash type="sha256">a10eefaed5a12c353525b07e655f6959fe1eb06cd5c549be56afaca6db0c6ce0</hash>
<size>24062229</size>
<sdkver>16</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-10-02</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,ACCESS_NETWORK_STATE,GET_TASKS,BLUETOOTH,WAKE_LOCK</permissions>
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
</package>
<package>
<version>0.2.2</version>
<versioncode>202</versioncode>
<apkname>CameraVApp-release-0.2.2.apk</apkname>
<hash type="sha256">8b17cbe2a5cb777b49f5ef67a390f9d9d68765c90213ac64f3ca0456860dc9b7</hash>
<size>24283932</size>
<sdkver>16</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-09-16</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>CAMERA,READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,USE_CREDENTIALS,RECORD_AUDIO,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION,GET_ACCOUNTS,INTERNET,ACCESS_COARSE_LOCATION,READ_PHONE_STATE,KILL_BACKGROUND_PROCESSES,ACCESS_NETWORK_STATE,GET_TASKS,BLUETOOTH,WAKE_LOCK</permissions>
<nativecode>armeabi,armeabi-v7a,x86</nativecode>
</package>
</application>
<application id="info.guardianproject.otr.app.im">
<id>info.guardianproject.otr.app.im</id>
<added>2013-03-19</added>
<lastupdated>2016-12-06</lastupdated>
<name>ChatSecure</name>
<summary>Instant Messaging client with OTR</summary>
<icon>info.guardianproject.otr.app.im.1423001.png</icon>
<desc>&lt;p&gt;XMPP (Jabber) client that can do end-to-end encryption using the &lt;a href="http://en.wikipedia.org/wiki/Off-the-Record_Messaging"&gt;Off-the-Record Messaging&lt;/a&gt; protocol and can anonymize your chats via the &lt;a href="fdroid.app:org.torproject.android"&gt;Orbot&lt;/a&gt; app (root not required).&lt;/p&gt;&lt;p&gt;The app used to be called GibberBot.&lt;/p&gt;</desc>
<license>Apache2</license>
<categories>Internet,GuardianProject</categories>
<category>Internet</category>
<web>https://dev.guardianproject.info/projects/gibberbot</web>
<source>https://github.com/guardianproject/Gibberbot</source>
<tracker>https://dev.guardianproject.info/projects/gibberbot</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>14.2.3</version>
<versioncode>1423001</versioncode>
<apkname>ChatSecure-v14.2.3a.apk</apkname>
<hash type="sha256">36d7d71c8a2115bdd2bd63bb639af286ee3242cce11cdb5c53378d1a7f35528e</hash>
<size>10502397</size>
<sdkver>9</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2016-02-03</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
<package>
<version>14.2.2</version>
<versioncode>1422001</versioncode>
<apkname>ChatSecure-v14.2.2.apk</apkname>
<hash type="sha256">9d4620fec0c7837ddffccde7918d7a7db0976fbcd361b96659abd93b5cc0d9e3</hash>
<size>10502135</size>
<sdkver>9</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2016-12-06</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
<package>
<version>14.2.1</version>
<versioncode>1421001</versioncode>
<apkname>ChatSecure-v14.2.1.apk</apkname>
<hash type="sha256">f82a3a7a823f5540b335743eb1399d0fd1f61bc68958750b5ef6aa0d95ad9a54</hash>
<size>10463010</size>
<sdkver>9</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-08-24</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>info.guardianproject.otr.app.providers.imps.permission.READ_ONLY,RECEIVE_BOOT_COMPLETED,CHANGE_WIFI_MULTICAST_STATE,READ_EXTERNAL_STORAGE,com.google.android.googleapps.permission.GOOGLE_AUTH,USE_CREDENTIALS,VIBRATE,WRITE_EXTERNAL_STORAGE,GET_ACCOUNTS,ACCESS_WIFI_STATE,UPDATE_APP_OPS_STATS,INTERNET,info.guardianproject.otr.app.im.permission.IM_SERVICE,ACCESS_NETWORK_STATE,MANAGE_ACCOUNTS,info.guardianproject.otr.app.providers.imps.permission.WRITE_ONLY,WAKE_LOCK</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
</application>
<application id="info.guardianproject.soundrecorder">
<id>info.guardianproject.soundrecorder</id>
<added>2013-12-13</added>
<lastupdated>2013-12-13</lastupdated>
<name>ChatSecureVoicePlugin</name>
<summary>ChatSecure Voice Messaging</summary>
<icon>info.guardianproject.soundrecorder.2.png</icon>
<desc>&lt;p&gt;This is a plugin for &lt;a href="fdroid.app:info.guardianproject.otr.app.im"&gt;ChatSecure&lt;/a&gt;. It does not have any function on its own. For Your Ears Only... completely private, end-to-end encryption voice message recording, sending, receiving and playback.&lt;/p&gt;&lt;p&gt; * For use with &lt;a href="fdroid.app:info.guardianproject.otr.app.im"&gt;ChatSecure&lt;/a&gt;'s encrypted "Off-the-record" data stream * Works over Tor - the ONLY Onion-routed voice messaging system, for total anonymity&lt;/p&gt;</desc>
<license>SIL Open Font License, MIT License and the CC 3.0 License [CC-By with attribution requirement waived]</license>
<categories>Multimedia,Security,GuardianProject</categories>
<category>Multimedia</category>
<web>https://guardianproject.info/apps/chatsecure</web>
<source>https://github.com/guardianproject/ChatSecureVoicePlugin</source>
<tracker>https://dev.guardianproject.info/projects/chatsecure/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>0.2</version>
<versioncode>2</versioncode>
<apkname>ChatSecureVoiceMessaging-0.2.apk</apkname>
<hash type="sha256">abae18cc9cfa62fca5dce072c4c50d41b4fece506967ce9a3e2711cd1031dbee</hash>
<size>394212</size>
<sdkver>10</sdkver>
<targetSdkVersion>10</targetSdkVersion>
<added>2013-12-13</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_PHONE_STATE,READ_EXTERNAL_STORAGE,RECORD_AUDIO,WRITE_EXTERNAL_STORAGE,WAKE_LOCK</permissions>
</package>
</application>
<application id="info.guardianproject.checkey">
<id>info.guardianproject.checkey</id>
<added>2014-07-12</added>
<lastupdated>2015-03-09</lastupdated>
<name>Checkey</name>
<summary>Info on local apps</summary>
<icon>info.guardianproject.checkey.102.png</icon>
<desc>&lt;p&gt;Checkey is a utility for getting information about the APKs that are installed on your device. Starting with a list of all of the apps that you have installed on your device, it will show you the APK signature with a single touch, and provides links to virustotal.com and androidobservatory.org to easily access the profiles of that APK. It will also let you export the signing certificate and generate ApkSignaturePin pin files for use with the TrustedIntents library.&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Development,GuardianProject</categories>
<category>Development</category>
<web>https://dev.guardianproject.info/projects/checkey</web>
<source>https://github.com/guardianproject/checkey</source>
<tracker>https://dev.guardianproject.info/projects/checkey/issues</tracker>
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
<marketversion/>
<marketvercode>9999999</marketvercode>
<package>
<version>0.1.2</version>
<versioncode>102</versioncode>
<apkname>Checkey-0.1.2.apk</apkname>
<hash type="sha256">754701dbac52de5ca3930c2393970c03ef9aa07d1456911e9bf254d6014e0645</hash>
<size>842881</size>
<sdkver>8</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-03-09</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
<package>
<version>0.1.1</version>
<versioncode>101</versioncode>
<apkname>Checkey-0.1.1.apk</apkname>
<hash type="sha256">2d81f339bb69626af42e8868dc6928c9072ebcbae76e1ff5ac8172e78ebe9cdd</hash>
<size>967083</size>
<sdkver>8</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-01-28</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
<package>
<version>0.1</version>
<versioncode>1</versioncode>
<apkname>Checkey-0.1.apk</apkname>
<hash type="sha256">a8e3c102d5279a3029d0eebdeda2ffdbe1f8a3493ea7dbdc31a11affc708ee57</hash>
<size>878679</size>
<sdkver>8</sdkver>
<targetSdkVersion>19</targetSdkVersion>
<added>2014-07-12</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
</application>
<application id="info.guardianproject.courier">
<id>info.guardianproject.courier</id>
<added>2014-05-14</added>
<lastupdated>2014-06-26</lastupdated>
<name>Courier</name>
<summary>Privacy-aware RSS feed reader</summary>
<icon>info.guardianproject.courier.15.png</icon>
<desc>&lt;p&gt;No description available&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Reading,GuardianProject</categories>
<category>Reading</category>
<web/>
<source>https://github.com/guardianproject/securereader</source>
<tracker/>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>0.1.9</version>
<versioncode>15</versioncode>
<apkname>Courier-0.1.9.apk</apkname>
<hash type="sha256">bf6566da1f90831887f5bf5605f8d816b1f7f694969459dec599b8bc01a827d3</hash>
<size>16484753</size>
<sdkver>9</sdkver>
<targetSdkVersion>15</targetSdkVersion>
<added>2014-06-26</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,VIBRATE,ACCESS_WIFI_STATE,INTERNET,ACCESS_NETWORK_STATE,BLUETOOTH,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
<package>
<version>0.1.8</version>
<versioncode>14</versioncode>
<apkname>Courier-0.1.8.apk</apkname>
<hash type="sha256">e013db095e8da843fae5ac44be6152e51377ee717e5c8a7b6d913d7720566b5a</hash>
<size>16536125</size>
<sdkver>9</sdkver>
<targetSdkVersion>15</targetSdkVersion>
<added>2014-05-14</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>READ_EXTERNAL_STORAGE,BLUETOOTH_ADMIN,VIBRATE,ACCESS_WIFI_STATE,INTERNET,ACCESS_NETWORK_STATE,BLUETOOTH,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
</application>
<application id="info.guardianproject.lildebi">
<id>info.guardianproject.lildebi</id>
<added>2013-02-06</added>
<lastupdated>2015-01-26</lastupdated>
<name>Lil' Debi</name>
<summary>Run Debian on your phone</summary>
<icon>info.guardianproject.lildebi.5400.png</icon>
<desc>&lt;p&gt;Lil' Debi builds up a whole Debian chroot on your phone entirely using debootstrap. You choose the release, mirror, and size of the disk image, and away it goes. It could take up to an hour on a slow device.&lt;/p&gt;&lt;p&gt;Then it has a simple chroot manager that fscks your disk, mounts/unmounts things, starts/stops sshd if you have it installed, etc. You can also then use apt-get to install any package that is released for ARM processors. This includes things like a complete real shell, Tor, TraceRouteTCP, iwconfig/ipconfig, and other security and crypto tools. Works well with &lt;a href="fdroid.app:jackpal.androidterm"&gt;Terminal Emulator&lt;/a&gt;—just run `/debian/shell` to get a Debian shell.&lt;/p&gt;&lt;p&gt;The aim of Lil Debi is to provide a transparent and tightly integrated Debian install on your Android device. It mounts all of your Android partitions in Debian space, so you see a fusion of both systems. It's even possible to have Lil Debi launch the normal Debian init start-up scripts when it starts, so that all you need to do is apt-get install and any servers you install will just work.&lt;/p&gt;&lt;p&gt;Lil' Debi works with as few modifications to the Android system as possible. Currently, it only adds a /bin symlink, and a /debian mount directory. It does not touch /system at all.&lt;/p&gt;&lt;p&gt;Requires root: Yes, because it needs to run debootstrap, create dirs in /, mount/umount, etc.&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Development,GuardianProject</categories>
<category>Development</category>
<web>https://github.com/guardianproject/lildebi/wiki</web>
<source>https://github.com/guardianproject/lildebi</source>
<tracker>https://github.com/guardianproject/lildebi/issues</tracker>
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
<marketversion/>
<marketvercode>999999999</marketvercode>
<requirements>root</requirements>
<package>
<version>0.5.4</version>
<versioncode>5400</versioncode>
<apkname>LilDebi-0.5.4-release.apk</apkname>
<hash type="sha256">2c490376d8853fae04e79541f5d61e66a42ed0e890208945a11036c4a7b111da</hash>
<size>1876705</size>
<sdkver>8</sdkver>
<targetSdkVersion>20</targetSdkVersion>
<maxsdkver>20</maxsdkver>
<added>2015-01-26</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
</package>
<package>
<version>0.5.3</version>
<versioncode>5300</versioncode>
<apkname>LilDebi-0.5.3-release.apk</apkname>
<hash type="sha256">01c5a8e1fd778c141e70633d14f1b69228d6f492961098616e0446c116cf9e44</hash>
<size>1879560</size>
<sdkver>8</sdkver>
<targetSdkVersion>19</targetSdkVersion>
<added>2015-01-26</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
</package>
<package>
<version>0.5.2</version>
<versioncode>5200</versioncode>
<apkname>LilDebi-0.5.2-release.apk</apkname>
<hash type="sha256">07fa3dfb690e44eb540942ba2a51718c72351c91a253a56a0c90649f6d8903dd</hash>
<size>1861790</size>
<sdkver>8</sdkver>
<targetSdkVersion>19</targetSdkVersion>
<added>2014-10-22</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>RECEIVE_BOOT_COMPLETED,READ_EXTERNAL_STORAGE,ACCESS_SUPERUSER,WRITE_EXTERNAL_STORAGE,INTERNET,ACCESS_NETWORK_STATE,jackpal.androidterm.permission.RUN_SCRIPT,WAKE_LOCK</permissions>
</package>
</application>
<application id="info.guardianproject.locationprivacy">
<id>info.guardianproject.locationprivacy</id>
<added>2015-01-29</added>
<lastupdated>2016-12-06</lastupdated>
<name>LocationPrivacy</name>
<summary>privacy filters for when you are sharing your location</summary>
<icon>info.guardianproject.locationprivacy.30.png</icon>
<desc>&lt;p&gt;LocationPrivacy is not really app but rather a set of "Intent Filters" for all of the various ways of sharing location. When you share location from one app, LocationPrivacy offers itself as an option. It then recognizes insecure methods of sharing location, and then converts them to more secure methods. This mostly means that it rewrites URLs to use https, and even to use `geo:` URIs, which can work on fully offline setups. LocationPrivacy mostly works by reading the location information from the URL itself. For many URLs, LocationPrivacy must actually load some of the webpage in order to get the location.&lt;/p&gt;&lt;p&gt;LocationPrivacy can also serve as a way to redirect all location links to your favorite mapping app. All map apps in Android can view `geo:` URIs, and LocationPrivacy converts many kinds of links to `geo:` URIs, including: Google Maps, OpenStreetMap, Amap, Baidu Map, QQ Map, Nokia HERE, Yandex Maps.&lt;/p&gt;&lt;p&gt;This was started as part of the T2 Panic work, since sharing location is so often a part of panic apps. Follow our progress here: https://guardianproject.info/tag/panic&lt;/p&gt;&lt;p&gt;Dont see your language? Join us and help translate the app: https://www.transifex.com/projects/p/locationprivacy&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Navigation,Security,GuardianProject</categories>
<category>Navigation</category>
<web>https://dev.guardianproject.info/projects/panic</web>
<source>https://github.com/guardianproject/LocationPrivacy</source>
<tracker>https://dev.guardianproject.info/projects/panic/issues</tracker>
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
<marketversion/>
<marketvercode>9999999</marketvercode>
<package>
<version>0.3</version>
<versioncode>30</versioncode>
<apkname>LocationPrivacy-0.3.apk</apkname>
<hash type="sha256">ec2b2c6e3a99422fbe8229711dfc7b741961c2ba7bc171c745818d8b76fc4d63</hash>
<size>1130602</size>
<sdkver>9</sdkver>
<targetSdkVersion>22</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
<package>
<version>0.2</version>
<versioncode>20</versioncode>
<apkname>LocationPrivacy-0.2.apk</apkname>
<hash type="sha256">3cad63152ef9b04e1c2b880c286a80c65c083880612aaa36c0c4480b96adfea8</hash>
<size>1129409</size>
<sdkver>9</sdkver>
<targetSdkVersion>22</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
<package>
<version>0.1</version>
<versioncode>10</versioncode>
<apkname>LocationPrivacy-0.1.apk</apkname>
<hash type="sha256">130cfcc8b916682d974aa4e13385b47bdc23d07b0de852640563b880aeb61d1f</hash>
<size>818384</size>
<sdkver>8</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-01-29</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>INTERNET</permissions>
</package>
</application>
<application id="info.guardianproject.notepadbot">
<id>info.guardianproject.notepadbot</id>
<added>2013-01-16</added>
<lastupdated>2014-03-10</lastupdated>
<name>NoteCipher</name>
<summary>Notepad with lock</summary>
<icon>info.guardianproject.notepadbot.12.png</icon>
<desc>&lt;p&gt;Simple app for taking notes that encrypts everything behind a password.&lt;/p&gt;&lt;p&gt;Status: Beta.&lt;/p&gt;</desc>
<license>Apache2</license>
<categories>Office,GuardianProject</categories>
<category>Office</category>
<web>https://guardianproject.info</web>
<source>https://github.com/guardianproject/notecipher</source>
<tracker>https://github.com/guardianproject/notecipher/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>0.1</version>
<versioncode>12</versioncode>
<apkname>NoteCipher-beta-0.1.apk</apkname>
<hash type="sha256">b560a3d6364c32990ea7505f53b019f64fde597d67513f41a50e7d034af48caa</hash>
<size>7321123</size>
<sdkver>10</sdkver>
<targetSdkVersion>19</targetSdkVersion>
<added>2014-03-10</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
<package>
<version>0.0.7.1</version>
<versioncode>11</versioncode>
<apkname>NoteCipher-0.0.7.1.apk</apkname>
<hash type="sha256">da518f13206d2218234bfcc83205b7b2b81ec67a4cc448f818c617332235e700</hash>
<size>3729342</size>
<sdkver>11</sdkver>
<targetSdkVersion>17</targetSdkVersion>
<added>2013-10-31</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
<package>
<version>0.0.7</version>
<versioncode>10</versioncode>
<apkname>NoteCipher-0.0.7.apk</apkname>
<hash type="sha256">8fa7536a87634c6b3441053c4f16315e4fd5aa6ef672a0026a594c107308d7bf</hash>
<size>3731119</size>
<sdkver>7</sdkver>
<targetSdkVersion>17</targetSdkVersion>
<added>2013-01-16</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
</application>
<application id="org.witness.sscphase1">
<id>org.witness.sscphase1</id>
<added>2013-08-19</added>
<lastupdated>2013-10-31</lastupdated>
<name>ObscuraCam</name>
<summary>A camera app that keeps certain information private</summary>
<icon>org.witness.sscphase1.34.png</icon>
<desc>&lt;p&gt;Ever capture someone in a photo or video, then realize they may not want to be in it? Not comfortable posting a friend, family member or childs face on the internet? Worried about the geolocation data in the picture giving away private hideaway? Tired of Facebook, Google and other sites “auto detecting” faces in your photos? Then this is for you, giving you the power to better protect the identity of those captures in your photos, before you post them online.&lt;/p&gt;&lt;p&gt;Take a picture or load a photo or video from the Gallery, and ObscuraCam will automatically detect faces that you can pixelate, redact (blackout) or protect with funny nose and glasses. You can also invert pixelate, so that only the person you select is visible, and no one in the background can be recognized.&lt;/p&gt;&lt;p&gt;This app will also remove all identifying data stored in photos including GPS location data and phone make &amp;amp; model. You can save the protected photo back to the Gallery, or share it directly to Facebook, Twitter or any other “Share” enabled app.&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Multimedia,Security,GuardianProject</categories>
<category>Multimedia</category>
<web>https://guardianproject.info/apps/obscuracam</web>
<source>https://github.com/guardianproject/obscuracam</source>
<tracker>https://github.com/guardianproject/obscuracam/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>2.0-RC2b</version>
<versioncode>34</versioncode>
<apkname>ObscuraCam-2.0-RC2b.apk</apkname>
<hash type="sha256">eeea54985c96769524ec82fb1d3599b193a2d20d1f57f3afc4c97b11bd48df8f</hash>
<size>8240221</size>
<sdkver>10</sdkver>
<targetSdkVersion>11</targetSdkVersion>
<added>2013-10-31</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,READ_MEDIA_STORAGE,WRITE_MEDIA_STORAGE,VIBRATE,WAKE_LOCK,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
<package>
<version>1.2-FINAL</version>
<versioncode>25</versioncode>
<apkname>ObscuraCam-1.2-FINAL.apk</apkname>
<hash type="sha256">fc4b1e26b09ab79b1ab174e8985b89985a0110f9d97d2b0472e529c85e3a1d89</hash>
<size>1728825</size>
<sdkver>8</sdkver>
<targetSdkVersion>8</targetSdkVersion>
<added>2013-08-19</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
</application>
<application id="org.torproject.android">
<id>org.torproject.android</id>
<added>2013-07-22</added>
<lastupdated>2016-12-06</lastupdated>
<name>Orbot</name>
<summary>Tor (anonymity) client</summary>
<icon>org.torproject.android.15208000.png</icon>
<desc>&lt;p&gt;Tor is both software and an open network that helps you defend against network surveillance that threatens personal freedom and privacy, confidential business activities and relationships.&lt;/p&gt;&lt;p&gt;Orbot allows access to Tor by accessing a local SOCKS or HTTP proxy. On a rooted device, the proxying can be completely transparent i.e. the app that accesses the network need not be aware of the proxy's existence; you can choose which apps go via the proxy in the settings.&lt;/p&gt;&lt;p&gt;If you don't have root access, there are some apps that are designed to work closely with tor or allow proxied connections: &lt;a href="fdroid.app:info.guardianproject.otr.app.im"&gt;ChatSecure&lt;/a&gt;, &lt;a href="fdroid.app:info.guardianproject.browser"&gt;Orweb&lt;/a&gt; and &lt;a href="fdroid.app:org.mariotaku.twidere"&gt;Twidere&lt;/a&gt;. There is also a proxy configurator addon for &lt;a href="fdroid.app:org.mozilla.firefox"&gt;org.mozilla.firefox&lt;/a&gt; called &lt;a href="https://github.com/guardianproject/ProxyMob/downloads"&gt;ProxyMob&lt;/a&gt; (not yet available from the Mozilla addon site).&lt;/p&gt;&lt;p&gt;Requires root: No, but you will need to use apps that allow proxies if root is not granted.&lt;/p&gt;</desc>
<license>NewBSD</license>
<categories>Security,Internet,GuardianProject</categories>
<category>Security</category>
<web>http://www.torproject.org/docs/android.html.en</web>
<source>https://gitweb.torproject.org/orbot.git</source>
<tracker>https://dev.guardianproject.info/projects/orbot/issues</tracker>
<donate>https://www.torproject.org/donate/donate.html.en</donate>
<flattr>5649</flattr>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>15.2.0-RC-8-multi</version>
<versioncode>15208000</versioncode>
<apkname>Orbot-v15.2.0-RC-8-multi.apk</apkname>
<hash type="sha256">3758e1b6e6b9a3b7848b253d08d6c0b1b1b3223184da4bd2ba1aaff8cf676357</hash>
<size>12296544</size>
<sdkver>16</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-11-07</added>
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
<nativecode>armeabi,x86</nativecode>
</package>
<package>
<version>15.2.0-RC-7-multi</version>
<versioncode>15207000</versioncode>
<apkname>Orbot-v15.2.0-RC-7-multi.apk</apkname>
<hash type="sha256">8dc3edf0a9799eb23b5e478e15547e38831b28cc3e88b049aa5f41b7b72e7bf9</hash>
<size>12457510</size>
<sdkver>16</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-11-04</added>
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
<nativecode>arm64-v8a,armeabi,armeabi-v7a,x86</nativecode>
</package>
<package>
<version>15.2.0-RC-5</version>
<versioncode>15205000</versioncode>
<apkname>Orbot-v15.2.0-RC-5-arm.apk</apkname>
<hash type="sha256">51c7e2b6a6de542e0d44f82d89ddf1d3216ec7a28297381ef15b12da2f3246f7</hash>
<size>7600548</size>
<sdkver>16</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-11-03</added>
<sig>8bd7e51b479aeba908ff46ada3305a29</sig>
<permissions>ACCESS_NETWORK_STATE,ACCESS_SUPERUSER,INTERNET,RECEIVE_BOOT_COMPLETED</permissions>
<nativecode>arm64-v8a,armeabi,armeabi-v7a</nativecode>
</package>
</application>
<application id="info.guardianproject.orfox">
<id>info.guardianproject.orfox</id>
<added>2016-09-24</added>
<lastupdated>2016-12-06</lastupdated>
<name>Orfox</name>
<summary>Orfox: Tor Browser for Android</summary>
<icon>info.guardianproject.orfox.4.png</icon>
<desc>&lt;p&gt;Orfox is the most privacy-enhancing web browser on Android, for visiting any website, even if its normally censored, monitored, or on the hidden web. It is a port of the desktop Tor Browser to the Android version of Firefox.&lt;/p&gt;&lt;p&gt;Orfox is a companion browser to &lt;a href="fdroid.app:org.torproject.android"&gt;Orbot&lt;/a&gt;, the port of Tor to Android. Orbot anonymizes internet traffic by routing it through many different stages and you must have that enabled first, though root isn't needed. Orfox disables certain other browser features that could be used to identify you.&lt;/p&gt;&lt;p&gt;Orfox replaces &lt;a href="fdroid.app:info.guardianproject.browser"&gt;Orweb&lt;/a&gt; as your private browser.&lt;/p&gt;</desc>
<license>MPL</license>
<categories>Internet,Security,GuardianProject</categories>
<category>Internet</category>
<web/>
<source>https://github.com/guardianproject/orfox</source>
<tracker>https://dev.guardianproject.info/projects/orfox/issues?set_filter=1</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>Fennec-45.5.1esr/TorBrowser-6.5-1/Orfox-1.2.1</version>
<versioncode>4</versioncode>
<apkname>Orfox-1.2.1-TorBrowser-6.5-Fennec45.5.1-build2.apk</apkname>
<hash type="sha256">d43032e79c7c31cabb194b8c1c4b14fbf73dd2cfda958ba415879ddf2f38ace2</hash>
<size>35273126</size>
<sdkver>9</sdkver>
<targetSdkVersion>22</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>info.guardianproject.orfox.permissions.FORMHISTORY_PROVIDER,info.guardianproject.orfox.permissions.PASSWORD_PROVIDER,READ_EXTERNAL_STORAGE,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,DOWNLOAD_WITHOUT_NOTIFICATION,INTERNET,com.android.launcher.permission.UNINSTALL_SHORTCUT,info.guardianproject.orfox.permissions.BROWSER_PROVIDER,com.android.browser.permission.READ_HISTORY_BOOKMARKS,com.android.launcher.permission.INSTALL_SHORTCUT,ACCESS_NETWORK_STATE,WAKE_LOCK</permissions>
<nativecode>armeabi-v7a</nativecode>
</package>
<package>
<version>Fennec-45.4.0esr/TorBrowser-6.5-1/Orfox-1.2</version>
<versioncode>3</versioncode>
<apkname>Orfox-1.2-TorBrowser-6.5-Fennec45.4.0.apk</apkname>
<hash type="sha256">9b5f6614b94a47ae561e8c974d42056ba6cb6da520766deda09aec3699aeff94</hash>
<size>35242066</size>
<sdkver>9</sdkver>
<targetSdkVersion>22</targetSdkVersion>
<added>2016-09-24</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
<permissions>info.guardianproject.orfox.permissions.FORMHISTORY_PROVIDER,info.guardianproject.orfox.permissions.PASSWORD_PROVIDER,READ_EXTERNAL_STORAGE,VIBRATE,WRITE_EXTERNAL_STORAGE,CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,DOWNLOAD_WITHOUT_NOTIFICATION,INTERNET,com.android.launcher.permission.UNINSTALL_SHORTCUT,info.guardianproject.orfox.permissions.BROWSER_PROVIDER,com.android.browser.permission.READ_HISTORY_BOOKMARKS,com.android.launcher.permission.INSTALL_SHORTCUT,ACCESS_NETWORK_STATE,WAKE_LOCK</permissions>
<nativecode>armeabi-v7a</nativecode>
</package>
</application>
<application id="info.guardianproject.browser">
<id>info.guardianproject.browser</id>
<added>2012-10-22</added>
<lastupdated>2015-11-26</lastupdated>
<name>Orweb</name>
<summary>Privacy-enhanced browser</summary>
<icon>info.guardianproject.browser.7010.png</icon>
<desc>&lt;p&gt;Orweb is a companion browser to &lt;a href="fdroid.app:org.torproject.android"&gt;Orbot&lt;/a&gt;, the port of Tor to Android.&lt;/p&gt;&lt;p&gt;Orbot anonymizes internet traffic by routing it through many different stages and you must have that enabled first, though root isn't needed. Orweb disables certain other browser features that could be used to identify you.&lt;/p&gt;</desc>
<license>GPL</license>
<categories>Internet,Security,GuardianProject</categories>
<category>Internet</category>
<web>https://guardianproject.info/apps/orweb</web>
<source>https://github.com/guardianproject/orweb</source>
<tracker>https://dev.guardianproject.info/projects/orweb/issues?set_filter=1</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>0.7.1</version>
<versioncode>7010</versioncode>
<apkname>Orweb-0.7.1.apk</apkname>
<hash type="sha256">949d65d6e8a1eadd0aa626bdc7c5a3e2b0dbe5a38dea1d725cce2a34ec84f0d4</hash>
<size>2424394</size>
<sdkver>9</sdkver>
<targetSdkVersion>21</targetSdkVersion>
<added>2015-11-26</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
</package>
<package>
<version>0.7</version>
<versioncode>28</versioncode>
<apkname>Orweb-release-0.7.apk</apkname>
<hash type="sha256">763541f43f5dc136744b4361fe67d36f25cc036526d6c3e934287d72d1b411ab</hash>
<size>1244875</size>
<sdkver>9</sdkver>
<targetSdkVersion>18</targetSdkVersion>
<added>2014-11-13</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
</package>
<package>
<version>0.6.1</version>
<versioncode>27</versioncode>
<apkname>Orweb-release-0.6.1.apk</apkname>
<hash type="sha256">103f4a98fa282923c07e445b2a383e946b6c15e10ed08005af3d0743249a0359</hash>
<size>931433</size>
<sdkver>9</sdkver>
<targetSdkVersion>19</targetSdkVersion>
<added>2014-06-30</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>READ_EXTERNAL_STORAGE,INTERNET,WRITE_EXTERNAL_STORAGE</permissions>
</package>
</application>
<application id="info.guardianproject.pixelknot">
<id>info.guardianproject.pixelknot</id>
<added>2013-02-26</added>
<lastupdated>2016-12-06</lastupdated>
<name>PixelKnot</name>
<summary>Hide messages inside files</summary>
<icon>info.guardianproject.pixelknot.100.png</icon>
<desc>&lt;p&gt;Image steganography app with old school F5 steganography&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Office,GuardianProject</categories>
<category>Office</category>
<web>https://guardianproject.info</web>
<source>https://github.com/guardianproject/PixelKnot</source>
<tracker>https://github.com/guardianproject/PixelKnot/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>1.0.0</version>
<versioncode>100</versioncode>
<apkname>PixelKnot-release-1.0.0.apk</apkname>
<hash type="sha256">f97557cf7ec81ade50c308c5552dc6dc827d0e02ce90f84b1df6b7477d9f5a39</hash>
<size>1983586</size>
<sdkver>17</sdkver>
<targetSdkVersion>25</targetSdkVersion>
<added>2016-12-06</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>VIBRATE,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<uses-permission maxSdkVersion="18" name="android.permission.VIBRATE"/>
<uses-permission maxSdkVersion="18" name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<nativecode>arm64-v8a,armeabi,armeabi-v7a,mips,mips64,x86,x86_64</nativecode>
</package>
<package>
<version>0.3.3</version>
<versioncode>6</versioncode>
<apkname>PixelKnot-release-0.3.3.apk</apkname>
<hash type="sha256">6beede8519a9e87ba8edaa5a76f203cfefd5f39eb911e789031cc6e911714b89</hash>
<size>4751233</size>
<sdkver>14</sdkver>
<targetSdkVersion>17</targetSdkVersion>
<added>2015-06-26</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>CAMERA,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
<package>
<version>0.3.1</version>
<versioncode>4</versioncode>
<apkname>PixelKnot-release-0.3.1.apk</apkname>
<hash type="sha256">a3101fe8a2d47ab205cb00459fa62c639a6fac4538f6cd9d06eb48d2965c4d21</hash>
<size>3976822</size>
<sdkver>9</sdkver>
<targetSdkVersion>17</targetSdkVersion>
<added>2013-07-22</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
<permissions>CAMERA,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE</permissions>
<nativecode>armeabi</nativecode>
</package>
</application>
<application id="info.guardianproject.ripple">
<id>info.guardianproject.ripple</id>
<added>2016-12-06</added>
<lastupdated>2016-12-06</lastupdated>
<name>Ripple</name>
<summary>Trigger apps to protect your privacy when in anxious or panic situations</summary>
<icon>info.guardianproject.ripple.75.png</icon>
<desc>&lt;p&gt;Ripple is a "panic button" that can send it's trigger message to any app that is a "panic responder". Such apps can do things like lock, disguise themselves, delete private data, send an emergency message, and more. It is meant for situations where there is time to react, but where users need to be sure it is not mistakenly set off.&lt;/p&gt;&lt;p&gt;This is a BETA version of this app! We are working now to add support to as many other apps as possible. ChatSecure, Orweb, Umbrella, and Zom already have support, these apps are coming soon: Courier, PanicButton, OpenKeychain, Orfox, SMSSecure, and StoryMaker.&lt;/p&gt;&lt;p&gt;Here are two example scenarios:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; An organization gets regularly raided by the security forces, who search all of the computers and mobile devices on the premises. The organization usually has at least a minute or two of warning before a raid starts. They need a very reliable way to trigger wiping all of the data from the sensitive apps.&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt; An aid worker has lots of sensitive data about people on their device. They regularly sync that data up with a secure, central database. Occasionally, the aid worker has to leave the country on very short notice. The border guards regularly download the entire contents of mobile devices of people crossing through. While waiting in line at the border, the aid worker sees the border guards seizing people's devices, and then remembers all the data on the device, so she unlocks her phone and hits the wipe trigger, which wipes all sensitive apps from the device. When the aid worker returns to the central office, the device is again synced up with the central database.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This was started as part of the T2 Panic work, since sharing location is so often a part of panic apps. Follow our progress here:&lt;/p&gt;&lt;p&gt;&lt;a href="https://guardianproject.info/tag/panic"&gt;https://guardianproject.info/tag/panic&lt;/a&gt;&lt;a href="https://dev.guardianproject.info/projects/panic"&gt;https://dev.guardianproject.info/projects/panic&lt;/a&gt; Dont see your language? Join us and help translate the app: &lt;a href="https://www.transifex.com/projects/p/rippleapp"&gt;https://www.transifex.com/projects/p/rippleapp&lt;/a&gt;&lt;/p&gt;&lt;p&gt;==Learn More==&lt;/p&gt;&lt;p&gt;★ ABOUT US: Guardian Project is a group of developers that make secure mobile apps and open-source code for a better tomorrow ★ OUR WEBSITE: &lt;a href="https://GuardianProject.info"&gt;https://GuardianProject.info&lt;/a&gt; ★ ON TWITTER: &lt;a href="https://twitter.com/guardianproject"&gt;https://twitter.com/guardianproject&lt;/a&gt; ★ FREE SOFTWARE: Ripple is free software. You can take a look at our source code, or contribute to help make Ripple even better: &lt;a href="https://github.com/guardianproject/Ripple"&gt;https://github.com/guardianproject/Ripple&lt;/a&gt; ★ MESSAGE US: Are we missing your favorite feature? Found an annoying bug? Please tell us! Wed love to hear from you. Send us an email: support@guardianproject.info or find us in our chat room &lt;a href="https://guardianproject.info/contact"&gt;https://guardianproject.info/contact&lt;/a&gt;&lt;/p&gt;</desc>
<license>GPLv3</license>
<categories>Navigation,Security,GuardianProject</categories>
<category>Navigation</category>
<web>https://dev.guardianproject.info/projects/panic</web>
<source>https://github.com/guardianproject/ripple</source>
<tracker>https://dev.guardianproject.info/projects/panic/issues</tracker>
<bitcoin>1Fi5xUHiAPRKxHvyUGVFGt9extBe8Srdbk</bitcoin>
<marketversion/>
<marketvercode>9999999</marketvercode>
<package>
<version>0.2</version>
<versioncode>75</versioncode>
<apkname>Ripple-0.2-release.apk</apkname>
<hash type="sha256">4b14b1b402f0197e1e6ffe2c11e052432fc8a52749f5f02d9cc67799658df239</hash>
<size>1669315</size>
<sdkver>10</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
</package>
<package>
<version>0.1</version>
<versioncode>2</versioncode>
<apkname>Ripple-0.1.apk</apkname>
<hash type="sha256">9fd24cbb3552123e6ee119f912f1646dd21cd7a683734a8d502d8b44854a284b</hash>
<size>1670285</size>
<sdkver>10</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
</package>
<package>
<version>0.0</version>
<versioncode>1</versioncode>
<apkname>Ripple-0.0.apk</apkname>
<hash type="sha256">025894a5f3a39a288ee60bb6c9cc2c559d395f22fed020d1086308ba12df85a3</hash>
<size>1664407</size>
<sdkver>10</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-12-06</added>
<sig>d70ac6a02b53ebdd1354ea7af7b9ceee</sig>
</package>
</application>
<application id="info.guardianproject.chatsecure.emoji.core">
<id>info.guardianproject.chatsecure.emoji.core</id>
<added>2013-12-02</added>
<lastupdated>2013-12-02</lastupdated>
<name>StickerPack</name>
<summary>ChatSecure Open Emoji Plugin</summary>
<icon>info.guardianproject.chatsecure.emoji.core.1.png</icon>
<desc>&lt;p&gt;Plugin for &lt;a href="fdroid.app:info.guardianproject.otr.app.im"&gt;ChatSecure&lt;/a&gt; to support for core emoji input and display. Based on "Phantom Open Emoji" project.&lt;/p&gt;</desc>
<license>SIL Open Font License, MIT License and the CC 3.0 License [CC-By with attribution requirement waived]</license>
<categories>Multimedia,Security,GuardianProject</categories>
<category>Multimedia</category>
<web>https://guardianproject.info/apps/chatsecure</web>
<source>https://github.com/guardianproject/ChatSecureVoicePlugin</source>
<tracker>https://dev.guardianproject.info/projects/chatsecure/issues</tracker>
<marketversion/>
<marketvercode>999999999</marketvercode>
<package>
<version>1.0</version>
<versioncode>1</versioncode>
<apkname>ChatSecurePluginOpenEmoji-release-v1.apk</apkname>
<hash type="sha256">131c1ebaf795c3f053701285699f0b7e517de1c7fdba56e247b1ec31766b2808</hash>
<size>1814271</size>
<sdkver>8</sdkver>
<targetSdkVersion>17</targetSdkVersion>
<added>2013-12-02</added>
<sig>a0eeebb161f946e3516945fae8a92a3e</sig>
</package>
</application>
</fdroid>

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
dummy

View File

@@ -1 +0,0 @@
dummy

View File

@@ -1 +0,0 @@
dummy

View File

@@ -1 +0,0 @@
dummy

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
dummy

View File

File diff suppressed because one or more lines are too long

View File

Binary file not shown.

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" ?><fdroid><repo icon="fdroid-icon.png" name="F-Droid" pubkey="308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b" timestamp="1398733213" url="https://f-droid.org/repo" version="12"><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.
</description></repo></fdroid>

View File

File diff suppressed because one or more lines are too long

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,7 +1,13 @@
package org.fdroid.fdroid.nearby;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.junit.Ignore;
@@ -13,12 +19,6 @@ import org.robolectric.shadows.ShadowLog;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import androidx.test.core.app.ApplicationProvider;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Test that this can start and stop the webserver.

View File

@@ -168,23 +168,13 @@ public class LocalHTTPDTest {
assertTrue(string.indexOf("test.html") > 0);
connection.disconnect();
IOUtils.copy(classLoader.getResourceAsStream("index.microg.jar"),
new FileOutputStream(new File(webRoot, "index.microg.jar")));
url = new URL(baseUrl + "/index.microg.jar");
connection = (HttpURLConnection) url.openConnection();
assertEquals(200, connection.getResponseCode());
byte[] actual = IOUtils.toByteArray(connection.getInputStream());
byte[] expected = IOUtils.toByteArray(classLoader.getResourceAsStream("index.microg.jar"));
Assert.assertArrayEquals(expected, actual);
connection.disconnect();
IOUtils.copy(classLoader.getResourceAsStream("extendedPerms.xml"),
new FileOutputStream(new File(webRoot, "extendedPerms.xml")));
url = new URL(baseUrl + "/extendedPerms.xml");
connection = (HttpURLConnection) url.openConnection();
assertEquals(200, connection.getResponseCode());
actual = IOUtils.toByteArray(connection.getInputStream());
expected = IOUtils.toByteArray(classLoader.getResourceAsStream("extendedPerms.xml"));
byte[] actual = IOUtils.toByteArray(connection.getInputStream());
byte[] expected = IOUtils.toByteArray(classLoader.getResourceAsStream("extendedPerms.xml"));
Assert.assertArrayEquals(expected, actual);
connection.disconnect();
}

View File

@@ -38,7 +38,8 @@ public class LocalRepoKeyStoreTest {
JarOutputStream jo = new JarOutputStream(bo);
JarEntry je = new JarEntry(IndexUpdater.DATA_FILE_NAME);
jo.putNextEntry(je);
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("smallRepo.xml");
InputStream inputStream =
getClass().getClassLoader().getResourceAsStream("all_fields_index-v1.json");
IOUtils.copy(inputStream, jo);
jo.close();
bo.close();
@@ -53,7 +54,7 @@ public class LocalRepoKeyStoreTest {
JarFile jarFile = new JarFile(xmlIndexJar, true);
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexUpdater.DATA_FILE_NAME);
byte[] data = IOUtils.toByteArray(jarFile.getInputStream(indexEntry));
assertEquals(17187, data.length);
assertEquals(6431, data.length);
assertNotNull(IndexUpdater.getSigningCertFromJar(indexEntry));
}
}

View File

@@ -1,55 +0,0 @@
package org.fdroid.fdroid.panic;
import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.RepoProviderTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
public class PanicResponderActivityTest extends FDroidProviderTest {
/**
* The {@link DBHelper} class populates the default repos when it first creates a database.
* The names/URLs/signing certificates for these repos are all hard coded in the source/res.
*/
@Test
public void defaultRepos() {
int defaultRepoCount = RepoProviderTest.getDefaultRepoCount(context);
List<Repo> defaultRepos = RepoProvider.Helper.all(context);
assertEquals(defaultRepos.size(), defaultRepoCount);
Repo gpRepo = RepoProvider.Helper.findByAddress(context, "https://guardianproject.info/fdroid/repo");
setEnabled(gpRepo, true);
assertEquals(2, RepoProvider.Helper.countEnabledRepos(context));
PanicResponderActivity.resetRepos(context);
assertEquals(1, RepoProvider.Helper.countEnabledRepos(context));
defaultRepos = RepoProvider.Helper.all(context);
assertEquals(defaultRepoCount, defaultRepos.size());
RepoProviderTest.insertRepo(
context,
"https://mock-repo-1.example.com/fdroid/repo",
"Just a made up repo",
"ABCDEF1234567890",
"Mock Repo 1"
);
defaultRepos = RepoProvider.Helper.all(context);
assertEquals(defaultRepoCount + 1, defaultRepos.size());
assertEquals(2, RepoProvider.Helper.countEnabledRepos(context));
PanicResponderActivity.resetRepos(context);
defaultRepos = RepoProvider.Helper.all(context);
assertEquals(defaultRepoCount, defaultRepos.size());
assertEquals(1, RepoProvider.Helper.countEnabledRepos(context));
}
}

View File

@@ -1,6 +1,8 @@
package org.fdroid.fdroid.updater;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -80,11 +82,6 @@ public class SwapRepoTest {
}
};
TestUtils.registerContentProvider(ApkProvider.getAuthority(), ApkProvider.class);
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
TestUtils.registerContentProvider(RepoProvider.getAuthority(), RepoProvider.class);
TestUtils.registerContentProvider(TempAppProvider.getAuthority(), TempAppProvider.class);
Preferences.setupForTests(context);
}
@@ -147,8 +144,7 @@ public class SwapRepoTest {
assertFalse(TextUtils.isEmpty(signingCert));
assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert)));
Repo repo = MultiIndexUpdaterTest.createRepo("", FDroidApp.repo.getAddress(),
context, signingCert);
Repo repo = createRepo("", FDroidApp.repo.getAddress(), context, signingCert);
IndexUpdater updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
@@ -164,11 +160,11 @@ public class SwapRepoTest {
assertTrue(foundRepo);
assertNotEquals(-1, repo.getId());
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals(1, apks.size());
for (Apk apk : apks) {
System.out.println(apk);
}
// List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
// assertEquals(1, apks.size());
// for (Apk apk : apks) {
// System.out.println(apk);
// }
//MultiIndexUpdaterTest.assertApksExist(apks, context.getPackageName(), new int[]{BuildConfig.VERSION_CODE});
Thread.sleep(10000);
} finally {
@@ -177,4 +173,17 @@ public class SwapRepoTest {
}
}
}
/**
* Creates a real instance of {@code Repo} by loading it from the database,
* that ensures it includes the primary key from the database.
*/
static Repo createRepo(String name, String uri, Context context, String signingCert) {
ContentValues values = new ContentValues(3);
values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
values.put(Schema.RepoTable.Cols.ADDRESS, uri);
values.put(Schema.RepoTable.Cols.NAME, name);
RepoProvider.Helper.insert(context, values);
return RepoProvider.Helper.findByAddress(context, uri);
}
}