mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-02-04 20:21:57 -05:00
move main project files into standard gradle/Android Studio layout
This makes it a lot easier to setup all the testing stuff. Mostly, I'm tired of fighting Android Studio's fragility, so I want to remove as much non-standardness as possible in the hopes of improving that situation. closes #534 https://gitlab.com/fdroid/fdroidclient/issues/534
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.test;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Resources;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.os.Build;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.test.mock.MockContext;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This test case class provides a framework for testing a single
|
||||
* {@link ContentProvider} and for testing your app code with an
|
||||
* isolated content provider. Instead of using the system map of
|
||||
* providers that is based on the manifests of other applications, the test
|
||||
* case creates its own internal map. It then uses this map to resolve providers
|
||||
* given an authority. This allows you to inject test providers and to null out
|
||||
* providers that you do not want to use.
|
||||
* <p>
|
||||
* This test case also sets up the following mock objects:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>
|
||||
* An {@link android.test.IsolatedContext} that stubs out Context methods that might
|
||||
* affect the rest of the running system, while allowing tests to do real file and
|
||||
* database work.
|
||||
* </li>
|
||||
* <li>
|
||||
* A {@link android.test.mock.MockContentResolver} that provides the functionality of a
|
||||
* regular content resolver, but uses {@link IsolatedContext}. It stubs out
|
||||
* {@link ContentResolver#notifyChange(Uri, ContentObserver, boolean)} to
|
||||
* prevent the test from affecting the running system.
|
||||
* </li>
|
||||
* <li>
|
||||
* An instance of the provider under test, running in an {@link IsolatedContext}.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This framework is set up automatically by the base class' {@link #setUp()} method. If you
|
||||
* override this method, you must call the super method as the first statement in
|
||||
* your override.
|
||||
* </p>
|
||||
* <p>
|
||||
* In order for their tests to be run, concrete subclasses must provide their own
|
||||
* constructor with no arguments. This constructor must call
|
||||
* {@link #ProviderTestCase2MockContext(Class, String)} as its first operation.
|
||||
* </p>
|
||||
* For more information on content provider testing, please see
|
||||
* <a href="{@docRoot}tools/testing/contentprovider_testing.html">Content Provider Testing</a>.
|
||||
*/
|
||||
public abstract class ProviderTestCase2MockContext<T extends ContentProvider> extends AndroidTestCase {
|
||||
|
||||
Class<T> mProviderClass;
|
||||
String mProviderAuthority;
|
||||
|
||||
private IsolatedContext mProviderContext;
|
||||
private MockContentResolver mResolver;
|
||||
|
||||
private class MockContext2 extends MockContext {
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return getContext().getResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDir(String name, int mode) {
|
||||
// name the directory so the directory will be separated from
|
||||
// one created through the regular Context
|
||||
return getContext().getDir("mockcontext2_" + name, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param providerClass The class name of the provider under test
|
||||
* @param providerAuthority The provider's authority string
|
||||
*/
|
||||
public ProviderTestCase2MockContext(Class<T> providerClass, String providerAuthority) {
|
||||
mProviderClass = providerClass;
|
||||
mProviderAuthority = providerAuthority;
|
||||
}
|
||||
|
||||
private T mProvider;
|
||||
|
||||
/**
|
||||
* Returns the content provider created by this class in the {@link #setUp()} method.
|
||||
* @return T An instance of the provider class given as a parameter to the test case class.
|
||||
*/
|
||||
public T getProvider() {
|
||||
return mProvider;
|
||||
}
|
||||
|
||||
protected abstract Context createMockContext(Context delegate);
|
||||
|
||||
/**
|
||||
* Sets up the environment for the test fixture.
|
||||
* <p>
|
||||
* Creates a new
|
||||
* {@link android.test.mock.MockContentResolver}, a new IsolatedContext
|
||||
* that isolates the provider's file operations, and a new instance of
|
||||
* the provider under test within the isolated environment.
|
||||
* </p>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mResolver = new MockContentResolver();
|
||||
final String filenamePrefix = "test.";
|
||||
final RenamingDelegatingContext targetContextWrapper = new
|
||||
RenamingDelegatingContext(
|
||||
createMockContext(new MockContext2()), // The context that most methods are delegated to
|
||||
getContext(), // The context that file methods are delegated to
|
||||
filenamePrefix);
|
||||
|
||||
mProviderContext = new IsolatedContext(mResolver, new ContextWrapper(targetContextWrapper) {
|
||||
// The FDroidProvider class needs access to an application context in order to initialize
|
||||
// the singleton DBHelper instance.
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return targetContextWrapper;
|
||||
}
|
||||
});
|
||||
|
||||
mProvider = mProviderClass.newInstance();
|
||||
mProvider.attachInfo(mProviderContext, null);
|
||||
assertNotNull(mProvider);
|
||||
mResolver.addProvider(mProviderAuthority, getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tears down the environment for the test fixture.
|
||||
* <p>
|
||||
* Calls {@link android.content.ContentProvider#shutdown()} on the
|
||||
* {@link android.content.ContentProvider} represented by mProvider.
|
||||
*/
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
shutdownProvider();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private void shutdownProvider() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mProvider.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link MockContentResolver} created by this class during initialization. You
|
||||
* must use the methods of this resolver to access the provider under test.
|
||||
*
|
||||
* @return A {@link MockContentResolver} instance.
|
||||
*/
|
||||
public MockContentResolver getMockContentResolver() {
|
||||
return mResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link IsolatedContext} created by this class during initialization.
|
||||
* @return The {@link IsolatedContext} instance
|
||||
*/
|
||||
public IsolatedContext getMockContext() {
|
||||
return mProviderContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a new content provider of the same type as that passed to the test case class,
|
||||
* with an authority name set to the authority parameter, and using an SQLite database as
|
||||
* the underlying data source. The SQL statement parameter is used to create the database.
|
||||
* This method also creates a new {@link MockContentResolver} and adds the provider to it.
|
||||
* </p>
|
||||
* <p>
|
||||
* Both the new provider and the new resolver are put into an {@link IsolatedContext}
|
||||
* that uses the targetContext parameter for file operations and a {@link MockContext}
|
||||
* for everything else. The IsolatedContext prepends the filenamePrefix parameter to
|
||||
* file, database, and directory names.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is a convenience method for creating a "mock" provider that can contain test data.
|
||||
* </p>
|
||||
*
|
||||
* @param targetContext The context to use as the basis of the IsolatedContext
|
||||
* @param filenamePrefix A string that is prepended to file, database, and directory names
|
||||
* @param providerClass The type of the provider being tested
|
||||
* @param authority The authority string to associated with the test provider
|
||||
* @param databaseName The name assigned to the database
|
||||
* @param databaseVersion The version assigned to the database
|
||||
* @param sql A string containing the SQL statements that are needed to create the desired
|
||||
* database and its tables. The format is the same as that generated by the
|
||||
* <a href="http://www.sqlite.org/sqlite.html">sqlite3</a> tool's <code>.dump</code> command.
|
||||
* @return ContentResolver A new {@link MockContentResolver} linked to the provider
|
||||
*
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public static <T extends ContentProvider> ContentResolver newResolverWithContentProviderFromSql(
|
||||
Context targetContext, String filenamePrefix, Class<T> providerClass, String authority,
|
||||
String databaseName, int databaseVersion, String sql)
|
||||
throws IllegalAccessException, InstantiationException {
|
||||
MockContentResolver resolver = new MockContentResolver();
|
||||
RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
|
||||
new MockContext(), // The context that most methods are delegated to
|
||||
targetContext, // The context that file methods are delegated to
|
||||
filenamePrefix);
|
||||
Context context = new IsolatedContext(resolver, targetContextWrapper);
|
||||
DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql);
|
||||
|
||||
T provider = providerClass.newInstance();
|
||||
provider.attachInfo(context, null);
|
||||
resolver.addProvider(authority, provider);
|
||||
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
19
app/src/androidTest/java/mock/MockApplicationInfo.java
Normal file
19
app/src/androidTest/java/mock/MockApplicationInfo.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package mock;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
public class MockApplicationInfo extends ApplicationInfo {
|
||||
|
||||
private final PackageInfo info;
|
||||
|
||||
public MockApplicationInfo(PackageInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence loadLabel(PackageManager pm) {
|
||||
return "Mock app: " + info.packageName;
|
||||
}
|
||||
}
|
||||
27
app/src/androidTest/java/mock/MockCategoryResources.java
Normal file
27
app/src/androidTest/java/mock/MockCategoryResources.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package mock;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class MockCategoryResources extends MockFDroidResources {
|
||||
|
||||
public MockCategoryResources(Context getStringDelegatingContext) {
|
||||
super(getStringDelegatingContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
switch (id) {
|
||||
case R.string.category_All:
|
||||
return "All";
|
||||
case R.string.category_Recently_Updated:
|
||||
return "Recently Updated";
|
||||
case R.string.category_Whats_New:
|
||||
return "Whats New";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package mock;
|
||||
|
||||
/**
|
||||
* As more components are required to test different parts of F-Droid, we can
|
||||
* create them and add them here (and accessors to the parent class).
|
||||
*/
|
||||
public class MockContextEmptyComponents extends MockContextSwappableComponents {
|
||||
|
||||
public MockContextEmptyComponents() {
|
||||
setPackageManager(new MockEmptyPackageManager());
|
||||
setResources(new MockEmptyResources());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mock;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.test.mock.MockContext;
|
||||
|
||||
public class MockContextSwappableComponents extends MockContext {
|
||||
|
||||
private PackageManager packageManager;
|
||||
private Resources resources;
|
||||
private MockContentResolver contentResolver;
|
||||
|
||||
public MockContextSwappableComponents setPackageManager(PackageManager pm) {
|
||||
packageManager = pm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MockContextSwappableComponents setResources(Resources resources) {
|
||||
this.resources = resources;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MockContextSwappableComponents setContentResolver(MockContentResolver contentResolver) {
|
||||
this.contentResolver = contentResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageManager getPackageManager() {
|
||||
return packageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockContentResolver getContentResolver() {
|
||||
return contentResolver;
|
||||
}
|
||||
}
|
||||
16
app/src/androidTest/java/mock/MockEmptyPackageManager.java
Normal file
16
app/src/androidTest/java/mock/MockEmptyPackageManager.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package mock;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.test.mock.MockPackageManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MockEmptyPackageManager extends MockPackageManager {
|
||||
|
||||
@Override
|
||||
public List<PackageInfo> getInstalledPackages(int flags) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
12
app/src/androidTest/java/mock/MockEmptyResources.java
Normal file
12
app/src/androidTest/java/mock/MockEmptyResources.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package mock;
|
||||
|
||||
import android.test.mock.MockResources;
|
||||
|
||||
public class MockEmptyResources extends MockResources {
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
37
app/src/androidTest/java/mock/MockFDroidResources.java
Normal file
37
app/src/androidTest/java/mock/MockFDroidResources.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package mock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.mock.MockResources;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class MockFDroidResources extends MockResources {
|
||||
|
||||
private Context getStringDelegatingContext;
|
||||
|
||||
public MockFDroidResources(Context getStringDelegatingContext) {
|
||||
this.getStringDelegatingContext = getStringDelegatingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int id) {
|
||||
return getStringDelegatingContext.getString(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInteger(int id) {
|
||||
switch (id) {
|
||||
case R.integer.fdroid_repo_inuse:
|
||||
return 1;
|
||||
case R.integer.fdroid_archive_inuse:
|
||||
return 0;
|
||||
case R.integer.fdroid_repo_priority:
|
||||
return 10;
|
||||
case R.integer.fdroid_archive_priority:
|
||||
return 20;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mock;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.test.mock.MockPackageManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class MockInstallablePackageManager extends MockPackageManager {
|
||||
|
||||
private List<PackageInfo> info = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public List<PackageInfo> getInstalledPackages(int flags) {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageInfo getPackageInfo(String id, int flags) {
|
||||
for (PackageInfo i : info) {
|
||||
if (i.packageName.equals(id)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void install(String id, int version, String versionName) {
|
||||
PackageInfo existing = getPackageInfo(id, 0);
|
||||
if (existing != null) {
|
||||
existing.versionCode = version;
|
||||
existing.versionName = versionName;
|
||||
} else {
|
||||
PackageInfo p = new PackageInfo();
|
||||
p.packageName = id;
|
||||
p.versionCode = version;
|
||||
p.versionName = versionName;
|
||||
info.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {
|
||||
return new MockApplicationInfo(getPackageInfo(packageName, 0));
|
||||
}
|
||||
|
||||
public void remove(String id) {
|
||||
for (Iterator<PackageInfo> it = info.iterator(); it.hasNext();) {
|
||||
PackageInfo info = it.next();
|
||||
if (info.packageName.equals(id)) {
|
||||
it.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.mock.MockApk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class ApkProviderHelperTest extends BaseApkProviderTest {
|
||||
|
||||
public void testKnownApks() {
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
TestUtils.insertApk(this, "org.example", i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
TestUtils.insertApk(this, "com.example", i);
|
||||
}
|
||||
|
||||
TestUtils.insertApk(this, "com.apk.thingo", 1);
|
||||
|
||||
Apk[] known = {
|
||||
new MockApk("org.fdroid.fdroid", 1),
|
||||
new MockApk("org.fdroid.fdroid", 3),
|
||||
new MockApk("org.fdroid.fdroid", 5),
|
||||
|
||||
new MockApk("com.example", 1),
|
||||
new MockApk("com.example", 2),
|
||||
};
|
||||
|
||||
Apk[] unknown = {
|
||||
new MockApk("org.fdroid.fdroid", 7),
|
||||
new MockApk("org.fdroid.fdroid", 9),
|
||||
new MockApk("org.fdroid.fdroid", 11),
|
||||
new MockApk("org.fdroid.fdroid", 13),
|
||||
|
||||
new MockApk("com.example", 3),
|
||||
new MockApk("com.example", 4),
|
||||
new MockApk("com.example", 5),
|
||||
|
||||
new MockApk("info.example", 1),
|
||||
new MockApk("info.example", 2),
|
||||
};
|
||||
|
||||
List<Apk> apksToCheck = new ArrayList<>(known.length + unknown.length);
|
||||
Collections.addAll(apksToCheck, known);
|
||||
Collections.addAll(apksToCheck, unknown);
|
||||
|
||||
String[] projection = {
|
||||
ApkProvider.DataColumns.PACKAGE_NAME,
|
||||
ApkProvider.DataColumns.VERSION_CODE,
|
||||
};
|
||||
|
||||
List<Apk> knownApks = ApkProvider.Helper.knownApks(getMockContext(), apksToCheck, projection);
|
||||
|
||||
assertResultCount(known.length, knownApks);
|
||||
|
||||
for (Apk knownApk : knownApks) {
|
||||
assertContains(knownApks, knownApk);
|
||||
}
|
||||
}
|
||||
|
||||
public void testFindByApp() {
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
TestUtils.insertApk(this, "org.example", i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
TestUtils.insertApk(this, "com.example", i);
|
||||
}
|
||||
|
||||
TestUtils.insertApk(this, "com.apk.thingo", 1);
|
||||
|
||||
assertTotalApkCount(7 + 9 + 3 + 1);
|
||||
|
||||
List<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid");
|
||||
assertResultCount(7, fdroidApks);
|
||||
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
|
||||
|
||||
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example");
|
||||
assertResultCount(9, exampleApks);
|
||||
assertBelongsToApp(exampleApks, "org.example");
|
||||
|
||||
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example");
|
||||
assertResultCount(3, exampleApks2);
|
||||
assertBelongsToApp(exampleApks2, "com.example");
|
||||
|
||||
List<Apk> thingoApks = ApkProvider.Helper.findByPackageName(getMockContext(), "com.apk.thingo");
|
||||
assertResultCount(1, thingoApks);
|
||||
assertBelongsToApp(thingoApks, "com.apk.thingo");
|
||||
}
|
||||
|
||||
public void testUpdate() {
|
||||
|
||||
Uri apkUri = TestUtils.insertApk(this, "com.example", 10);
|
||||
|
||||
String[] allFields = ApkProvider.DataColumns.ALL;
|
||||
Cursor cursor = getMockContentResolver().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.vercode);
|
||||
|
||||
assertNull(apk.features);
|
||||
assertNull(apk.added);
|
||||
assertNull(apk.hashType);
|
||||
|
||||
apk.features = Utils.CommaSeparatedList.make("one,two,three");
|
||||
long dateTimestamp = System.currentTimeMillis();
|
||||
apk.added = new Date(dateTimestamp);
|
||||
apk.hashType = "i'm a hash type";
|
||||
|
||||
ApkProvider.Helper.update(getMockContext(), apk);
|
||||
|
||||
// Should not have inserted anything else, just updated the already existing apk.
|
||||
Cursor allCursor = getMockContentResolver().query(ApkProvider.getContentUri(), allFields, null, null, null);
|
||||
assertResultCount(1, allCursor);
|
||||
allCursor.close();
|
||||
|
||||
Cursor updatedCursor = getMockContentResolver().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.vercode);
|
||||
|
||||
assertNotNull(updatedApk.features);
|
||||
assertNotNull(updatedApk.added);
|
||||
assertNotNull(updatedApk.hashType);
|
||||
|
||||
assertEquals("one,two,three", updatedApk.features.toString());
|
||||
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
|
||||
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
|
||||
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
|
||||
assertEquals("i'm a hash type", updatedApk.hashType);
|
||||
}
|
||||
|
||||
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++) {
|
||||
TestUtils.insertApk(this, "org.fdroid.apk." + i, i);
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ApkProvider.DataColumns.VERSION, "v1.1");
|
||||
values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
|
||||
values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
|
||||
TestUtils.insertApk(this, "com.example", 11, values);
|
||||
|
||||
// ...and a few more for good measure...
|
||||
for (int i = 15; i < 20; i++) {
|
||||
TestUtils.insertApk(this, "com.other.thing." + i, i);
|
||||
}
|
||||
|
||||
Apk apk = ApkProvider.Helper.find(getMockContext(), "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.vercode);
|
||||
assertEquals("v1.1", apk.version);
|
||||
assertEquals("xxxxyyyy", apk.hash);
|
||||
assertEquals("a hash type", apk.hashType);
|
||||
|
||||
String[] projection = {
|
||||
ApkProvider.DataColumns.PACKAGE_NAME,
|
||||
ApkProvider.DataColumns.HASH,
|
||||
};
|
||||
|
||||
Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection);
|
||||
|
||||
assertNotNull(apkLessFields);
|
||||
|
||||
assertEquals("com.example", apkLessFields.packageName);
|
||||
assertEquals("xxxxyyyy", apkLessFields.hash);
|
||||
|
||||
// Didn't ask for these fields, so should be their default values...
|
||||
assertNull(apkLessFields.hashType);
|
||||
assertNull(apkLessFields.version);
|
||||
assertEquals(0, apkLessFields.vercode);
|
||||
|
||||
Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000);
|
||||
assertNull(notFound);
|
||||
}
|
||||
|
||||
}
|
||||
336
app/src/androidTest/java/org/fdroid/fdroid/ApkProviderTest.java
Normal file
336
app/src/androidTest/java/org/fdroid/fdroid/ApkProviderTest.java
Normal file
@@ -0,0 +1,336 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.mock.MockApk;
|
||||
import org.fdroid.fdroid.mock.MockApp;
|
||||
import org.fdroid.fdroid.mock.MockRepo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ApkProviderTest extends BaseApkProviderTest {
|
||||
|
||||
/**
|
||||
* I want to test the protected {@link org.fdroid.fdroid.data.ApkProvider#getContentUri(java.util.List)}
|
||||
* method, but don't want to make it public. This exposes it.
|
||||
*/
|
||||
private static class PublicApkProvider extends ApkProvider {
|
||||
|
||||
public static final int MAX_APKS_TO_QUERY = ApkProvider.MAX_APKS_TO_QUERY;
|
||||
|
||||
public static Uri getContentUri(List<Apk> apks) {
|
||||
return ApkProvider.getContentUri(apks);
|
||||
}
|
||||
}
|
||||
|
||||
public void testUris() {
|
||||
assertInvalidUri(ApkProvider.getAuthority());
|
||||
assertInvalidUri(RepoProvider.getContentUri());
|
||||
|
||||
List<Apk> apks = new ArrayList<>(3);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
apks.add(new MockApk("com.example." + i, i));
|
||||
}
|
||||
|
||||
assertValidUri(ApkProvider.getContentUri());
|
||||
assertValidUri(ApkProvider.getAppUri("org.fdroid.fdroid"));
|
||||
assertValidUri(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)));
|
||||
assertValidUri(ApkProvider.getContentUri());
|
||||
assertValidUri(PublicApkProvider.getContentUri(apks));
|
||||
assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100));
|
||||
assertValidUri(ApkProvider.getRepoUri(1000));
|
||||
|
||||
List<Apk> manyApks = new ArrayList<>(PublicApkProvider.MAX_APKS_TO_QUERY - 5);
|
||||
for (int i = 0; i < PublicApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
|
||||
manyApks.add(new MockApk("com.example." + i, i));
|
||||
}
|
||||
assertValidUri(PublicApkProvider.getContentUri(manyApks));
|
||||
|
||||
manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
|
||||
manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
|
||||
try {
|
||||
// Technically, it is a valid URI, because it doesn't
|
||||
// throw an UnsupportedOperationException. However it
|
||||
// is still not okay (we run out of bindable parameters
|
||||
// in the sqlite query.
|
||||
assertValidUri(PublicApkProvider.getContentUri(manyApks));
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This is the expected error behaviour.
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAppApks() {
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
|
||||
TestUtils.insertApk(this, "com.example", i);
|
||||
}
|
||||
|
||||
assertTotalApkCount(20);
|
||||
|
||||
Cursor fdroidApks = getMockContentResolver().query(
|
||||
ApkProvider.getAppUri("org.fdroid.fdroid"),
|
||||
getMinimalProjection(),
|
||||
null, null, null);
|
||||
assertResultCount(10, fdroidApks);
|
||||
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
|
||||
fdroidApks.close();
|
||||
|
||||
Cursor exampleApks = getMockContentResolver().query(
|
||||
ApkProvider.getAppUri("com.example"),
|
||||
getMinimalProjection(),
|
||||
null, null, null);
|
||||
assertResultCount(10, exampleApks);
|
||||
assertBelongsToApp(exampleApks, "com.example");
|
||||
exampleApks.close();
|
||||
|
||||
ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example"));
|
||||
|
||||
Cursor all = queryAllApks();
|
||||
assertResultCount(10, all);
|
||||
assertBelongsToApp(all, "org.fdroid.fdroid");
|
||||
all.close();
|
||||
}
|
||||
|
||||
public void testInvalidUpdateUris() {
|
||||
Apk apk = new MockApk("org.fdroid.fdroid", 10);
|
||||
|
||||
List<Apk> apks = new ArrayList<>();
|
||||
apks.add(apk);
|
||||
|
||||
assertCantUpdate(ApkProvider.getContentUri());
|
||||
assertCantUpdate(ApkProvider.getAppUri("org.fdroid.fdroid"));
|
||||
assertCantUpdate(ApkProvider.getRepoUri(1));
|
||||
assertCantUpdate(PublicApkProvider.getContentUri(apks));
|
||||
assertCantUpdate(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
|
||||
|
||||
// The only valid ones are:
|
||||
// ApkProvider.getContentUri(apk)
|
||||
// ApkProvider.getContentUri(id, version)
|
||||
// which are tested elsewhere.
|
||||
}
|
||||
|
||||
public void testDeleteArbitraryApks() {
|
||||
Apk one = insertApkForRepo("com.example.one", 1, 10);
|
||||
Apk two = insertApkForRepo("com.example.two", 1, 10);
|
||||
Apk three = insertApkForRepo("com.example.three", 1, 10);
|
||||
Apk four = insertApkForRepo("com.example.four", 1, 10);
|
||||
Apk five = insertApkForRepo("com.example.five", 1, 10);
|
||||
|
||||
assertTotalApkCount(5);
|
||||
|
||||
assertEquals("com.example.one", one.packageName);
|
||||
assertEquals("com.example.two", two.packageName);
|
||||
assertEquals("com.example.five", five.packageName);
|
||||
|
||||
String[] expectedIds = {
|
||||
"com.example.one",
|
||||
"com.example.two",
|
||||
"com.example.three",
|
||||
"com.example.four",
|
||||
"com.example.five",
|
||||
};
|
||||
|
||||
List<Apk> all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
|
||||
List<String> actualIds = new ArrayList<>();
|
||||
for (Apk apk : all) {
|
||||
actualIds.add(apk.packageName);
|
||||
}
|
||||
|
||||
TestUtils.assertContainsOnly(actualIds, expectedIds);
|
||||
|
||||
List<Apk> toDelete = new ArrayList<>(3);
|
||||
toDelete.add(two);
|
||||
toDelete.add(three);
|
||||
toDelete.add(four);
|
||||
ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete);
|
||||
|
||||
assertTotalApkCount(2);
|
||||
|
||||
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
|
||||
List<String> actualRemainingIds = new ArrayList<>();
|
||||
for (Apk apk : allRemaining) {
|
||||
actualRemainingIds.add(apk.packageName);
|
||||
}
|
||||
|
||||
String[] expectedRemainingIds = {
|
||||
"com.example.one",
|
||||
"com.example.five",
|
||||
};
|
||||
|
||||
TestUtils.assertContainsOnly(actualRemainingIds, expectedRemainingIds);
|
||||
}
|
||||
|
||||
public void testInvalidDeleteUris() {
|
||||
Apk apk = new MockApk("org.fdroid.fdroid", 10);
|
||||
|
||||
assertCantDelete(ApkProvider.getContentUri());
|
||||
assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
|
||||
assertCantDelete(ApkProvider.getContentUri(apk));
|
||||
assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
|
||||
}
|
||||
|
||||
private static final long REPO_KEEP = 1;
|
||||
private static final long REPO_DELETE = 2;
|
||||
|
||||
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 = getMockContentResolver().query(
|
||||
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
|
||||
assertResultCount(10, cursor);
|
||||
assertBelongsToRepo(cursor, REPO_DELETE);
|
||||
cursor.close();
|
||||
|
||||
int count = ApkProvider.Helper.deleteApksByRepo(getMockContext(), new MockRepo(REPO_DELETE));
|
||||
assertEquals(10, count);
|
||||
|
||||
assertTotalApkCount(10);
|
||||
cursor = getMockContentResolver().query(
|
||||
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
|
||||
assertResultCount(0, cursor);
|
||||
cursor.close();
|
||||
|
||||
// The only remaining apks should be those from REPO_KEEP.
|
||||
assertBelongsToRepo(queryAllApks(), REPO_KEEP);
|
||||
}
|
||||
|
||||
public void testQuery() {
|
||||
Cursor cursor = queryAllApks();
|
||||
assertNotNull(cursor);
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
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...
|
||||
Uri newUri = TestUtils.insertApk(this, apk.packageName, apk.vercode);
|
||||
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
|
||||
cursor = queryAllApks();
|
||||
assertNotNull(cursor);
|
||||
assertEquals(1, cursor.getCount());
|
||||
|
||||
// We intentionally throw an IllegalArgumentException if you haven't
|
||||
// yet called cursor.move*()...
|
||||
try {
|
||||
new Apk(cursor);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Success!
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
// 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.vercode);
|
||||
}
|
||||
|
||||
public void testCount() {
|
||||
String[] projectionFields = getMinimalProjection();
|
||||
String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
|
||||
|
||||
for (int i = 0; i < 13; i++) {
|
||||
TestUtils.insertApk(this, "com.example", i);
|
||||
}
|
||||
|
||||
Uri all = ApkProvider.getContentUri();
|
||||
Cursor allWithFields = getMockContentResolver().query(all, projectionFields, null, null, null);
|
||||
Cursor allWithCount = getMockContentResolver().query(all, projectionCount, null, null, null);
|
||||
|
||||
assertResultCount(13, allWithFields);
|
||||
allWithFields.close();
|
||||
assertResultCount(1, allWithCount);
|
||||
|
||||
allWithCount.moveToFirst();
|
||||
int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
|
||||
assertEquals(13, allWithCount.getInt(countColumn));
|
||||
allWithCount.close();
|
||||
}
|
||||
|
||||
public void testInsertWithExtraFields() {
|
||||
|
||||
assertResultCount(0, queryAllApks());
|
||||
|
||||
String[] repoFields = new String[] {
|
||||
RepoProvider.DataColumns.DESCRIPTION,
|
||||
RepoProvider.DataColumns.ADDRESS,
|
||||
RepoProvider.DataColumns.FINGERPRINT,
|
||||
RepoProvider.DataColumns.NAME,
|
||||
RepoProvider.DataColumns.PUBLIC_KEY,
|
||||
};
|
||||
|
||||
for (String field : repoFields) {
|
||||
ContentValues invalidRepo = new ContentValues();
|
||||
invalidRepo.put(field, "Test data");
|
||||
try {
|
||||
TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
assertResultCount(0, queryAllApks());
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ApkProvider.DataColumns.REPO_ID, 10);
|
||||
values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com");
|
||||
values.put(ApkProvider.DataColumns.REPO_VERSION, 3);
|
||||
values.put(ApkProvider.DataColumns.FEATURES, "Some features");
|
||||
Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values);
|
||||
|
||||
assertResultCount(1, queryAllApks());
|
||||
|
||||
String[] projections = ApkProvider.DataColumns.ALL;
|
||||
Cursor cursor = getMockContentResolver().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("Some features", apk.features.toString());
|
||||
assertEquals("com.example.com", apk.packageName);
|
||||
assertEquals(1, apk.vercode);
|
||||
assertEquals(10, apk.repo);
|
||||
}
|
||||
|
||||
}
|
||||
386
app/src/androidTest/java/org/fdroid/fdroid/AppProviderTest.java
Normal file
386
app/src/androidTest/java/org/fdroid/fdroid/AppProviderTest.java
Normal file
@@ -0,0 +1,386 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import mock.MockCategoryResources;
|
||||
import mock.MockContextSwappableComponents;
|
||||
import mock.MockInstallablePackageManager;
|
||||
|
||||
public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
||||
|
||||
public AppProviderTest() {
|
||||
super(AppProvider.class, AppProvider.getAuthority());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
getSwappableContext().setResources(new MockCategoryResources(getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resources getMockResources() {
|
||||
return new MockCategoryResources(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getMinimalProjection() {
|
||||
return new String[] {
|
||||
AppProvider.DataColumns.PACKAGE_NAME,
|
||||
AppProvider.DataColumns.NAME,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Although this doesn't directly relate to the AppProvider, it is here because
|
||||
* the AppProvider used to stumble across this bug when asking for installed apps,
|
||||
* and the device had over 1000 apps installed.
|
||||
*/
|
||||
public void testMaxSqliteParams() {
|
||||
|
||||
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
||||
getSwappableContext().setPackageManager(pm);
|
||||
|
||||
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++) {
|
||||
pm.install("com.example.app" + i, 1, "v" + 1);
|
||||
}
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(1, AppProvider.getInstalledUri());
|
||||
|
||||
for (int i = 50; i < 500; i++) {
|
||||
pm.install("com.example.app" + i, 1, "v" + 1);
|
||||
}
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(2, AppProvider.getInstalledUri());
|
||||
|
||||
for (int i = 500; i < 1100; i++) {
|
||||
pm.install("com.example.app" + i, 1, "v" + 1);
|
||||
}
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(3, AppProvider.getInstalledUri());
|
||||
}
|
||||
|
||||
public void testCantFindApp() {
|
||||
assertNull(AppProvider.Helper.findByPackageName(getMockContentResolver(), "com.example.doesnt-exist"));
|
||||
}
|
||||
|
||||
public void testUris() {
|
||||
assertInvalidUri(AppProvider.getAuthority());
|
||||
assertInvalidUri(ApkProvider.getContentUri());
|
||||
|
||||
assertValidUri(AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider");
|
||||
assertValidUri(AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'");
|
||||
assertValidUri(AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F");
|
||||
assertValidUri(AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider");
|
||||
assertValidUri(AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider");
|
||||
assertValidUri(AppProvider.getNoApksUri());
|
||||
assertValidUri(AppProvider.getInstalledUri());
|
||||
assertValidUri(AppProvider.getCanUpdateUri());
|
||||
|
||||
App app = new App();
|
||||
app.packageName = "org.fdroid.fdroid";
|
||||
|
||||
List<App> apps = new ArrayList<>(1);
|
||||
apps.add(app);
|
||||
|
||||
assertValidUri(AppProvider.getContentUri(app));
|
||||
assertValidUri(AppProvider.getContentUri(apps));
|
||||
assertValidUri(AppProvider.getContentUri("org.fdroid.fdroid"));
|
||||
}
|
||||
|
||||
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(
|
||||
MockInstallablePackageManager packageManager,
|
||||
String id, int installedVercode, int suggestedVercode,
|
||||
boolean ignoreAll, int ignoreVercode) {
|
||||
ContentValues values = new ContentValues(3);
|
||||
values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode);
|
||||
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll);
|
||||
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
|
||||
insertApp(id, "App: " + id, values);
|
||||
|
||||
TestUtils.installAndBroadcast(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
|
||||
}
|
||||
|
||||
public void testCanUpdate() {
|
||||
|
||||
MockContextSwappableComponents c = getSwappableContext();
|
||||
|
||||
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
||||
c.setPackageManager(pm);
|
||||
|
||||
insertApp("not installed", "not installed");
|
||||
insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
|
||||
insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
|
||||
|
||||
ContentResolver r = getMockContentResolver();
|
||||
|
||||
// Can't "update", although can "install"...
|
||||
App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed");
|
||||
assertFalse(notInstalled.canAndWantToUpdate());
|
||||
|
||||
App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available");
|
||||
App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore");
|
||||
App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all");
|
||||
App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest");
|
||||
App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old");
|
||||
|
||||
assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate());
|
||||
assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate());
|
||||
assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate());
|
||||
assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate());
|
||||
assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate());
|
||||
|
||||
App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore");
|
||||
App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all");
|
||||
App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest");
|
||||
App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest");
|
||||
|
||||
assertTrue(installedOldNoIgnore.canAndWantToUpdate());
|
||||
assertFalse(installedOldIgnoreAll.canAndWantToUpdate());
|
||||
assertFalse(installedOldIgnoreLatest.canAndWantToUpdate());
|
||||
assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate());
|
||||
|
||||
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
|
||||
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",
|
||||
};
|
||||
|
||||
TestUtils.assertContainsOnly(expectedUpdateableIds, canUpdateIds);
|
||||
}
|
||||
|
||||
public void testIgnored() {
|
||||
|
||||
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
||||
getSwappableContext().setPackageManager(pm);
|
||||
|
||||
insertApp("not installed", "not installed");
|
||||
insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
|
||||
insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
|
||||
insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
|
||||
insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
|
||||
|
||||
assertResultCount(10, AppProvider.getContentUri());
|
||||
|
||||
String[] projection = {AppProvider.DataColumns.PACKAGE_NAME};
|
||||
List<App> ignoredApps = AppProvider.Helper.findIgnored(getMockContext(), projection);
|
||||
|
||||
String[] expectedIgnored = {
|
||||
"installed, already latest, ignore all",
|
||||
"installed, already latest, ignore latest",
|
||||
// NOT "installed, already latest, ignore old" - because it
|
||||
// is should only ignore if "ignored version" is >= suggested
|
||||
|
||||
"installed, old version, ignore all",
|
||||
"installed, old version, ignore latest",
|
||||
// NOT "installed, old version, ignore newer, but not latest"
|
||||
// for the same reason as above.
|
||||
};
|
||||
|
||||
assertContainsOnlyIds(ignoredApps, expectedIgnored);
|
||||
}
|
||||
|
||||
private void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
|
||||
List<String> actualIds = new ArrayList<>(actualApps.size());
|
||||
for (App app : actualApps) {
|
||||
actualIds.add(app.packageName);
|
||||
}
|
||||
TestUtils.assertContainsOnly(actualIds, expectedIds);
|
||||
}
|
||||
|
||||
public void testInstalled() {
|
||||
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
||||
getSwappableContext().setPackageManager(pm);
|
||||
|
||||
insertApps(100);
|
||||
|
||||
assertResultCount(100, AppProvider.getContentUri());
|
||||
assertResultCount(0, AppProvider.getInstalledUri());
|
||||
|
||||
for (int i = 10; i < 20; i++) {
|
||||
TestUtils.installAndBroadcast(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
|
||||
}
|
||||
|
||||
assertResultCount(10, AppProvider.getInstalledUri());
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// We intentionally throw an IllegalArgumentException if you haven't
|
||||
// yet called cursor.move*()...
|
||||
try {
|
||||
new App(cursor);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Success!
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private Cursor queryAllApps() {
|
||||
return getMockContentResolver().query(AppProvider.getContentUri(), getMinimalProjection(), null, null, null);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// "Categories"
|
||||
// (at this point) not an additional table, but we treat them sort of
|
||||
// like they are. That means that if we change the implementation to
|
||||
// use a separate table in the future, these should still pass.
|
||||
// ========================================================================
|
||||
|
||||
public void testCategoriesSingle() {
|
||||
insertAppWithCategory("com.dog", "Dog", "Animal");
|
||||
insertAppWithCategory("com.rock", "Rock", "Mineral");
|
||||
insertAppWithCategory("com.banana", "Banana", "Vegetable");
|
||||
|
||||
List<String> categories = AppProvider.Helper.categories(getMockContext());
|
||||
String[] expected = new String[] {
|
||||
getMockContext().getResources().getString(R.string.category_Whats_New),
|
||||
getMockContext().getResources().getString(R.string.category_Recently_Updated),
|
||||
getMockContext().getResources().getString(R.string.category_All),
|
||||
"Animal",
|
||||
"Mineral",
|
||||
"Vegetable",
|
||||
};
|
||||
TestUtils.assertContainsOnly(categories, expected);
|
||||
}
|
||||
|
||||
public void testCategoriesMultiple() {
|
||||
insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal");
|
||||
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable");
|
||||
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable");
|
||||
|
||||
List<String> categories = AppProvider.Helper.categories(getMockContext());
|
||||
String[] expected = new String[] {
|
||||
getMockContext().getResources().getString(R.string.category_Whats_New),
|
||||
getMockContext().getResources().getString(R.string.category_Recently_Updated),
|
||||
getMockContext().getResources().getString(R.string.category_All),
|
||||
|
||||
"Animal",
|
||||
"Mineral",
|
||||
"Vegetable",
|
||||
};
|
||||
TestUtils.assertContainsOnly(categories, expected);
|
||||
|
||||
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");
|
||||
|
||||
List<String> categoriesLonger = AppProvider.Helper.categories(getMockContext());
|
||||
String[] expectedLonger = new String[] {
|
||||
getMockContext().getResources().getString(R.string.category_Whats_New),
|
||||
getMockContext().getResources().getString(R.string.category_Recently_Updated),
|
||||
getMockContext().getResources().getString(R.string.category_All),
|
||||
|
||||
"Animal",
|
||||
"Mineral",
|
||||
"Vegetable",
|
||||
|
||||
"Running",
|
||||
"Shooting",
|
||||
"Jumping",
|
||||
"Bleh",
|
||||
"Sneh",
|
||||
"Pleh",
|
||||
"Blah",
|
||||
"Test category",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"With apostrophe's",
|
||||
};
|
||||
|
||||
TestUtils.assertContainsOnly(categoriesLonger, expectedLonger);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// Misc helper functions
|
||||
// (to be used by any tests in this suite)
|
||||
// =======================================================================
|
||||
|
||||
private void insertApp(String id, String name) {
|
||||
insertApp(id, name, new ContentValues());
|
||||
}
|
||||
|
||||
private void insertAppWithCategory(String id, String name, String categories) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(AppProvider.DataColumns.CATEGORIES, categories);
|
||||
insertApp(id, name, values);
|
||||
}
|
||||
|
||||
private void insertApp(String id, String name,
|
||||
ContentValues additionalValues) {
|
||||
TestUtils.insertApp(getMockContentResolver(), id, name, additionalValues);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides helper methods that can be used by both Helper and plain old
|
||||
* Provider tests. Allows the test classes to contain only test methods,
|
||||
* hopefully making them easier to understand.
|
||||
*
|
||||
* This should not contain any test methods, or else they get executed
|
||||
* once for every concrete subclass.
|
||||
*/
|
||||
abstract class BaseApkProviderTest extends FDroidProviderTest<ApkProvider> {
|
||||
|
||||
BaseApkProviderTest() {
|
||||
super(ApkProvider.class, ApkProvider.getAuthority());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getMinimalProjection() {
|
||||
return new String[] {
|
||||
ApkProvider.DataColumns.PACKAGE_NAME,
|
||||
ApkProvider.DataColumns.VERSION_CODE,
|
||||
ApkProvider.DataColumns.NAME,
|
||||
ApkProvider.DataColumns.REPO_ID,
|
||||
};
|
||||
}
|
||||
|
||||
protected final Cursor queryAllApks() {
|
||||
return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null);
|
||||
}
|
||||
|
||||
protected void assertContains(List<Apk> apks, Apk apk) {
|
||||
boolean found = false;
|
||||
for (Apk a : apks) {
|
||||
if (a.vercode == apk.vercode && a.packageName.equals(apk.packageName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
fail("Apk [" + apk + "] not found in " + TestUtils.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.repo);
|
||||
}
|
||||
}
|
||||
|
||||
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
|
||||
ContentValues additionalValues = new ContentValues();
|
||||
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
|
||||
Uri uri = TestUtils.insertApk(this, id, versionCode, additionalValues);
|
||||
return ApkProvider.Helper.get(getSwappableContext(), uri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
import android.test.ProviderTestCase2MockContext;
|
||||
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.FDroidProvider;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import mock.MockContextEmptyComponents;
|
||||
import mock.MockContextSwappableComponents;
|
||||
import mock.MockFDroidResources;
|
||||
|
||||
public abstract class FDroidProviderTest<T extends FDroidProvider> extends ProviderTestCase2MockContext<T> {
|
||||
|
||||
private FDroidProvider[] allProviders = {
|
||||
new AppProvider(),
|
||||
new RepoProvider(),
|
||||
new ApkProvider(),
|
||||
new InstalledAppProvider(),
|
||||
};
|
||||
|
||||
private MockContextSwappableComponents swappableContext;
|
||||
|
||||
public FDroidProviderTest(Class<T> providerClass, String providerAuthority) {
|
||||
super(providerClass, providerAuthority);
|
||||
}
|
||||
|
||||
protected Resources getMockResources() {
|
||||
return new MockFDroidResources(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
FDroidProvider.clearDbHelperSingleton();
|
||||
|
||||
// Instantiate all providers other than the one which was already created by the base class.
|
||||
// This is because F-Droid providers tend to perform joins onto tables managed by other
|
||||
// providers, and so we need to be able to insert into those other providers for these
|
||||
// joins to be tested correctly.
|
||||
for (FDroidProvider provider : allProviders) {
|
||||
if (!provider.getName().equals(getProvider().getName())) {
|
||||
provider.attachInfo(getMockContext(), null);
|
||||
getMockContentResolver().addProvider(provider.getName(), provider);
|
||||
}
|
||||
}
|
||||
|
||||
getSwappableContext().setResources(getMockResources());
|
||||
|
||||
// The *Provider.Helper.* functions tend to take a Context as their
|
||||
// first parameter. This context is used to connect to the relevant
|
||||
// content provider. Thus, we need a context that is able to connect
|
||||
// to the mock content resolver, in order to reach the provider
|
||||
// under test.
|
||||
getSwappableContext().setContentResolver(getMockContentResolver());
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ECLAIR)
|
||||
public void testObviouslyInvalidUris() {
|
||||
assertInvalidUri("http://www.google.com");
|
||||
assertInvalidUri(ContactsContract.AUTHORITY_URI);
|
||||
assertInvalidUri("junk");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context createMockContext(Context delegate) {
|
||||
swappableContext = new MockContextEmptyComponents();
|
||||
return swappableContext;
|
||||
}
|
||||
|
||||
public MockContextSwappableComponents getSwappableContext() {
|
||||
return swappableContext;
|
||||
}
|
||||
|
||||
protected void assertCantDelete(Uri uri) {
|
||||
try {
|
||||
getMockContentResolver().delete(uri, null, null);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertCantUpdate(Uri uri) {
|
||||
try {
|
||||
getMockContentResolver().update(uri, new ContentValues(), null, null);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertInvalidUri(String uri) {
|
||||
assertInvalidUri(Uri.parse(uri));
|
||||
}
|
||||
|
||||
protected void assertValidUri(String uri) {
|
||||
assertValidUri(Uri.parse(uri));
|
||||
}
|
||||
|
||||
protected void assertInvalidUri(Uri uri) {
|
||||
try {
|
||||
// Use getProvdider instead of getContentResolver, because the mock
|
||||
// content resolver wont result in the provider we are testing, and
|
||||
// hence we don't get to see how our provider responds to invalid
|
||||
// uris.
|
||||
getProvider().query(uri, getMinimalProjection(), null, null, null);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) { }
|
||||
}
|
||||
|
||||
protected void assertValidUri(Uri uri) {
|
||||
Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
|
||||
assertNotNull(cursor);
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
protected void assertValidUri(Uri actualUri, String expectedUri) {
|
||||
assertValidUri(actualUri);
|
||||
assertEquals(expectedUri, actualUri.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Many queries need at least some sort of projection in order to produce
|
||||
* valid SQL. As such, we also need to know about that, so we can provide
|
||||
* helper functions that revolve around the contnet provider under test.
|
||||
*/
|
||||
protected abstract String[] getMinimalProjection();
|
||||
|
||||
protected void assertResultCount(int expectedCount, Uri uri) {
|
||||
Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
|
||||
assertResultCount(expectedCount, cursor);
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
protected void assertResultCount(int expectedCount, List items) {
|
||||
assertNotNull(items);
|
||||
assertEquals(expectedCount, items.size());
|
||||
}
|
||||
|
||||
protected void assertResultCount(int expectedCount, Cursor result) {
|
||||
assertNotNull(result);
|
||||
assertEquals(expectedCount, result.getCount());
|
||||
}
|
||||
|
||||
protected void assertIsInstalledVersionInDb(String appId, int versionCode, String versionName) {
|
||||
Uri uri = InstalledAppProvider.getAppUri(appId);
|
||||
|
||||
String[] projection = {
|
||||
InstalledAppProvider.DataColumns.PACKAGE_NAME,
|
||||
InstalledAppProvider.DataColumns.VERSION_CODE,
|
||||
InstalledAppProvider.DataColumns.VERSION_NAME,
|
||||
InstalledAppProvider.DataColumns.APPLICATION_LABEL,
|
||||
};
|
||||
|
||||
Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null);
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount());
|
||||
|
||||
cursor.moveToFirst();
|
||||
|
||||
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
|
||||
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
|
||||
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
}
|
||||
14
app/src/androidTest/java/org/fdroid/fdroid/FDroidTest.java
Normal file
14
app/src/androidTest/java/org/fdroid/fdroid/FDroidTest.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.CUPCAKE)
|
||||
public class FDroidTest extends ActivityInstrumentationTestCase2<FDroid> {
|
||||
|
||||
public FDroidTest() {
|
||||
super("org.fdroid.fdroid", FDroid.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.os.Build;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.compat.FileCompatForTest;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FileCompatTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String TAG = "FileCompatTest";
|
||||
|
||||
private File dir;
|
||||
private SanitizedFile sourceFile;
|
||||
private SanitizedFile destFile;
|
||||
|
||||
public void setUp() {
|
||||
dir = TestUtils.getWriteableDir(getInstrumentation());
|
||||
sourceFile = SanitizedFile.knownSanitized(TestUtils.copyAssetToDir(getInstrumentation().getContext(), "simpleIndex.jar", dir));
|
||||
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
|
||||
assertFalse(destFile.exists());
|
||||
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
if (!sourceFile.delete()) {
|
||||
System.out.println("Can't delete " + sourceFile.getAbsolutePath() + ".");
|
||||
}
|
||||
|
||||
if (!destFile.delete()) {
|
||||
System.out.println("Can't delete " + destFile.getAbsolutePath() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
public void testSymlinkRuntime() {
|
||||
FileCompatForTest.symlinkRuntimeTest(sourceFile, destFile);
|
||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||
}
|
||||
|
||||
public void testSymlinkLibcore() {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile);
|
||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||
} else {
|
||||
Log.w(TAG, "Cannot test symlink-libcore on this device. Requires android-19, but this has android-" + Build.VERSION.SDK_INT);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSymlinkOs() {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
FileCompatForTest.symlinkOsTest(sourceFile, destFile);
|
||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||
} else {
|
||||
Log.w(TAG, "Cannot test symlink-os on this device. Requires android-21, but only has android-" + Build.VERSION.SDK_INT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
|
||||
import mock.MockInstallablePackageManager;
|
||||
|
||||
/**
|
||||
* Tests the ability of the {@link org.fdroid.fdroid.data.InstalledAppCacheUpdater} to stay in sync with
|
||||
* the {@link android.content.pm.PackageManager}.
|
||||
* For practical reasons, it extends FDroidProviderTest<InstalledAppProvider>, although there is also a
|
||||
* separate test for the InstalledAppProvider which tests the CRUD operations in more detail.
|
||||
*/
|
||||
public class InstalledAppCacheTest extends FDroidProviderTest<InstalledAppProvider> {
|
||||
|
||||
private MockInstallablePackageManager packageManager;
|
||||
|
||||
public InstalledAppCacheTest() {
|
||||
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
packageManager = new MockInstallablePackageManager();
|
||||
getSwappableContext().setPackageManager(packageManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getMinimalProjection() {
|
||||
return new String[] {
|
||||
InstalledAppProvider.DataColumns.PACKAGE_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
public void install(String appId, int versionCode, String versionName) {
|
||||
packageManager.install(appId, versionCode, versionName);
|
||||
}
|
||||
|
||||
public void remove(String appId) {
|
||||
packageManager.remove(appId);
|
||||
}
|
||||
|
||||
/* TODO fix me
|
||||
public void testFromEmptyCache() {
|
||||
assertResultCount(0, InstalledAppProvider.getContentUri());
|
||||
for (int i = 1; i <= 15; i ++) {
|
||||
install("com.example.app" + i, 200, "2.0");
|
||||
}
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
String[] expectedInstalledIds = {
|
||||
"com.example.app1",
|
||||
"com.example.app2",
|
||||
"com.example.app3",
|
||||
"com.example.app4",
|
||||
"com.example.app5",
|
||||
"com.example.app6",
|
||||
"com.example.app7",
|
||||
"com.example.app8",
|
||||
"com.example.app9",
|
||||
"com.example.app10",
|
||||
"com.example.app11",
|
||||
"com.example.app12",
|
||||
"com.example.app13",
|
||||
"com.example.app14",
|
||||
"com.example.app15",
|
||||
};
|
||||
|
||||
TestUtils.assertContainsOnly(getInstalledAppIdsFromProvider(), expectedInstalledIds);
|
||||
}
|
||||
|
||||
private String[] getInstalledAppIdsFromProvider() {
|
||||
Uri uri = InstalledAppProvider.getContentUri();
|
||||
String[] projection = { InstalledAppProvider.DataColumns.PACKAGE_NAME };
|
||||
Cursor result = getMockContext().getContentResolver().query(uri, projection, null, null, null);
|
||||
if (result == null) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
String[] installedAppIds = new String[result.getCount()];
|
||||
result.moveToFirst();
|
||||
int i = 0;
|
||||
while (!result.isAfterLast()) {
|
||||
installedAppIds[i] = result.getString(result.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME));
|
||||
result.moveToNext();
|
||||
i ++;
|
||||
}
|
||||
result.close();
|
||||
return installedAppIds;
|
||||
}
|
||||
|
||||
public void testAppsAdded() {
|
||||
assertResultCount(0, InstalledAppProvider.getContentUri());
|
||||
|
||||
install("com.example.app1", 1, "v1");
|
||||
install("com.example.app2", 1, "v1");
|
||||
install("com.example.app3", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(3, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
|
||||
|
||||
install("com.example.app10", 1, "v1");
|
||||
install("com.example.app11", 1, "v1");
|
||||
install("com.example.app12", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(6, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app11", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app12", 1, "v1");
|
||||
}
|
||||
|
||||
public void testAppsRemoved() {
|
||||
install("com.example.app1", 1, "v1");
|
||||
install("com.example.app2", 1, "v1");
|
||||
install("com.example.app3", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(3, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
|
||||
|
||||
remove("com.example.app2");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
|
||||
}
|
||||
|
||||
public void testAppsUpdated() {
|
||||
install("com.example.app1", 1, "v1");
|
||||
install("com.example.app2", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
|
||||
|
||||
install("com.example.app2", 20, "v2.0");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app2", 20, "v2.0");
|
||||
}
|
||||
|
||||
public void testAppsAddedRemovedAndUpdated() {
|
||||
install("com.example.app1", 1, "v1");
|
||||
install("com.example.app2", 1, "v1");
|
||||
install("com.example.app3", 1, "v1");
|
||||
install("com.example.app4", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(4, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
|
||||
|
||||
install("com.example.app1", 13, "v1.3");
|
||||
remove("com.example.app2");
|
||||
remove("com.example.app3");
|
||||
install("com.example.app10", 1, "v1");
|
||||
InstalledAppCacheUpdater.updateInForeground(getMockContext());
|
||||
|
||||
assertResultCount(3, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app1", 13, "v1.3");
|
||||
assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
import mock.MockInstallablePackageManager;
|
||||
|
||||
public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppProvider> {
|
||||
|
||||
private MockInstallablePackageManager packageManager;
|
||||
|
||||
public InstalledAppProviderTest() {
|
||||
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
packageManager = new MockInstallablePackageManager();
|
||||
getSwappableContext().setPackageManager(packageManager);
|
||||
}
|
||||
|
||||
protected MockInstallablePackageManager getPackageManager() {
|
||||
return packageManager;
|
||||
}
|
||||
|
||||
public void testUris() {
|
||||
assertInvalidUri(InstalledAppProvider.getAuthority());
|
||||
assertInvalidUri(RepoProvider.getContentUri());
|
||||
assertInvalidUri(AppProvider.getContentUri());
|
||||
assertInvalidUri(ApkProvider.getContentUri());
|
||||
assertInvalidUri("blah");
|
||||
|
||||
assertValidUri(InstalledAppProvider.getContentUri());
|
||||
assertValidUri(InstalledAppProvider.getAppUri("com.example.com"));
|
||||
assertValidUri(InstalledAppProvider.getAppUri("blah"));
|
||||
}
|
||||
|
||||
public void testInsert() {
|
||||
|
||||
assertResultCount(0, InstalledAppProvider.getContentUri());
|
||||
|
||||
insertInstalledApp("com.example.com1", 1, "v1");
|
||||
insertInstalledApp("com.example.com2", 2, "v2");
|
||||
insertInstalledApp("com.example.com3", 3, "v3");
|
||||
|
||||
assertResultCount(3, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.com1", 1, "v1");
|
||||
assertIsInstalledVersionInDb("com.example.com2", 2, "v2");
|
||||
assertIsInstalledVersionInDb("com.example.com3", 3, "v3");
|
||||
}
|
||||
|
||||
public void testUpdate() {
|
||||
|
||||
insertInstalledApp("com.example.app1", 10, "1.0");
|
||||
insertInstalledApp("com.example.app2", 10, "1.0");
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
|
||||
|
||||
try {
|
||||
getMockContentResolver().update(
|
||||
InstalledAppProvider.getAppUri("com.example.app2"),
|
||||
createContentValues(11, "1.1"),
|
||||
null, null
|
||||
);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// We expect this to happen, because we should be using insert() instead.
|
||||
}
|
||||
|
||||
getMockContentResolver().insert(
|
||||
InstalledAppProvider.getContentUri(),
|
||||
createContentValues("com.example.app2", 11, "1.1")
|
||||
);
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app2", 11, "1.1");
|
||||
|
||||
}
|
||||
|
||||
public void testDelete() {
|
||||
|
||||
insertInstalledApp("com.example.app1", 10, "1.0");
|
||||
insertInstalledApp("com.example.app2", 10, "1.0");
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
|
||||
getMockContentResolver().delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null);
|
||||
|
||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
|
||||
|
||||
}
|
||||
|
||||
public void testInsertWithBroadcast() {
|
||||
|
||||
installAndBroadcast("com.example.broadcasted1", 10, "v1.0");
|
||||
installAndBroadcast("com.example.broadcasted2", 105, "v1.05");
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.broadcasted1", 10, "v1.0");
|
||||
assertIsInstalledVersionInDb("com.example.broadcasted2", 105, "v1.05");
|
||||
}
|
||||
|
||||
public void testUpdateWithBroadcast() {
|
||||
|
||||
installAndBroadcast("com.example.toUpgrade", 1, "v0.1");
|
||||
|
||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.toUpgrade", 1, "v0.1");
|
||||
|
||||
upgradeAndBroadcast("com.example.toUpgrade", 2, "v0.2");
|
||||
|
||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.toUpgrade", 2, "v0.2");
|
||||
|
||||
}
|
||||
|
||||
public void testDeleteWithBroadcast() {
|
||||
|
||||
installAndBroadcast("com.example.toKeep", 1, "v0.1");
|
||||
installAndBroadcast("com.example.toDelete", 1, "v0.1");
|
||||
|
||||
assertResultCount(2, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
|
||||
assertIsInstalledVersionInDb("com.example.toDelete", 1, "v0.1");
|
||||
|
||||
removeAndBroadcast("com.example.toDelete");
|
||||
|
||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
||||
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getMinimalProjection() {
|
||||
return new String[] {
|
||||
InstalledAppProvider.DataColumns.PACKAGE_NAME,
|
||||
InstalledAppProvider.DataColumns.VERSION_CODE,
|
||||
InstalledAppProvider.DataColumns.VERSION_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
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(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId);
|
||||
}
|
||||
values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId);
|
||||
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
|
||||
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
|
||||
values.put(InstalledAppProvider.DataColumns.SIGNATURE, "");
|
||||
return values;
|
||||
}
|
||||
|
||||
private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
|
||||
ContentValues values = createContentValues(appId, versionCode, versionNumber);
|
||||
getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
|
||||
}
|
||||
|
||||
private void removeAndBroadcast(String appId) {
|
||||
TestUtils.removeAndBroadcast(getSwappableContext(), getPackageManager(), appId);
|
||||
}
|
||||
|
||||
private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
|
||||
TestUtils.upgradeAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
|
||||
}
|
||||
|
||||
private void installAndBroadcast(String appId, int versionCode, String versionName) {
|
||||
TestUtils.installAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.RenamingDelegatingContext;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.FDroidProvider;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.TempApkProvider;
|
||||
import org.fdroid.fdroid.data.TempAppProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MultiRepoUpdaterTest extends InstrumentationTestCase {
|
||||
private static final String TAG = "RepoUpdaterTest";
|
||||
|
||||
private static final String REPO_MAIN = "Test F-Droid repo";
|
||||
private static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)";
|
||||
private static final String REPO_CONFLICTING = "Test F-Droid repo with different apps";
|
||||
|
||||
private Context context;
|
||||
private RepoUpdater conflictingRepoUpdater;
|
||||
private RepoUpdater mainRepoUpdater;
|
||||
private RepoUpdater archiveRepoUpdater;
|
||||
private File testFilesDir;
|
||||
|
||||
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";
|
||||
|
||||
public class TestContext extends RenamingDelegatingContext {
|
||||
|
||||
private MockContentResolver resolver;
|
||||
|
||||
public TestContext() {
|
||||
super(getInstrumentation().getTargetContext(), "test.");
|
||||
|
||||
resolver = new MockContentResolver();
|
||||
resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider()));
|
||||
resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider()));
|
||||
resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider()));
|
||||
resolver.addProvider(TempAppProvider.getAuthority(), prepareProvider(new TempAppProvider()));
|
||||
resolver.addProvider(TempApkProvider.getAuthority(), prepareProvider(new TempApkProvider()));
|
||||
}
|
||||
|
||||
private ContentProvider prepareProvider(ContentProvider provider) {
|
||||
provider.attachInfo(this, null);
|
||||
provider.onCreate();
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFilesDir() {
|
||||
return getInstrumentation().getTargetContext().getFilesDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* String resources used during testing (e.g. when bootstraping the database) are from
|
||||
* the real org.fdroid.fdroid app, not the test org.fdroid.fdroid.test app.
|
||||
*/
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return getInstrumentation().getTargetContext().getResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentResolver getContentResolver() {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetManager getAssets() {
|
||||
return getInstrumentation().getContext().getAssets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabasePath(String name) {
|
||||
return new File(getInstrumentation().getContext().getFilesDir(), "fdroid_test.db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
// Used by the DBHelper singleton instance.
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
FDroidProvider.clearDbHelperSingleton();
|
||||
|
||||
context = new TestContext();
|
||||
|
||||
testFilesDir = TestUtils.getWriteableDir(getInstrumentation());
|
||||
|
||||
// On a fresh database install, there will be F-Droid + GP repos, including their Archive
|
||||
// repos that we are not interested in.
|
||||
RepoProvider.Helper.remove(context, 1);
|
||||
RepoProvider.Helper.remove(context, 2);
|
||||
RepoProvider.Helper.remove(context, 3);
|
||||
RepoProvider.Helper.remove(context, 4);
|
||||
|
||||
conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context);
|
||||
mainRepoUpdater = createUpdater(REPO_MAIN, context);
|
||||
archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.d(TAG, "Asserting all versions of each .apk are in index.");
|
||||
List<Repo> repos = RepoProvider.Helper.all(context);
|
||||
assertEquals("Repos", 3, repos.size());
|
||||
|
||||
assertMainRepo(repos);
|
||||
assertMainArchiveRepo(repos);
|
||||
assertConflictingRepo(repos);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void assertSomewhatAcceptable() {
|
||||
Log.d(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();
|
||||
}
|
||||
|
||||
private void assertApp(String packageName, int[] versionCodes) {
|
||||
List<Apk> apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL);
|
||||
assertApksExist(apks, packageName, versionCodes);
|
||||
}
|
||||
|
||||
private void assertApp2048() {
|
||||
assertApp("com.uberspot.a2048", new int[]{19, 18});
|
||||
}
|
||||
|
||||
private void assertAppAdaway() {
|
||||
assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35});
|
||||
}
|
||||
|
||||
private void assertAppAdbWireless() {
|
||||
assertApp("siir.es.adbWireless", new int[]{12});
|
||||
}
|
||||
|
||||
private void assertAppIcsImport() {
|
||||
assertApp("org.dgtale.icsimport", new int[]{3, 2});
|
||||
}
|
||||
|
||||
/**
|
||||
* + 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, ApkProvider.DataColumns.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});
|
||||
}
|
||||
|
||||
/**
|
||||
* + 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 assertMainArchiveRepo(List<Repo> allRepos) {
|
||||
Repo repo = findRepo(REPO_ARCHIVE, allRepos);
|
||||
|
||||
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.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});
|
||||
}
|
||||
|
||||
/**
|
||||
* + 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, ApkProvider.DataColumns.ALL);
|
||||
assertEquals("Apks for main repo", 6, apks.size());
|
||||
assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53});
|
||||
assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private 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.
|
||||
*/
|
||||
private void assertApksExist(List<Apk> apksToCheck, String appId, int[] versionCodes) {
|
||||
for (int versionCode : versionCodes) {
|
||||
boolean found = false;
|
||||
for (Apk apk : apksToCheck) {
|
||||
if (apk.vercode == versionCode && apk.packageName.equals(appId)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("Couldn't find app " + appId + ", v" + versionCode, found);
|
||||
}
|
||||
}
|
||||
|
||||
private 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());
|
||||
}
|
||||
}
|
||||
|
||||
/* At time fo writing, the following tests did not pass. This is because the multi-repo support
|
||||
in F-Droid was not sufficient. When working on proper multi repo support than this should be
|
||||
ucommented and all these tests should pass:
|
||||
|
||||
public void testCorrectConflictingThenMainThenArchive() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateConflicting() && updateMain() && updateArchive()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCorrectConflictingThenArchiveThenMain() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateConflicting() && updateArchive() && updateMain()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCorrectArchiveThenMainThenConflicting() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateArchive() && updateMain() && updateConflicting()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCorrectArchiveThenConflictingThenMain() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateArchive() && updateConflicting() && updateMain()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCorrectMainThenArchiveThenConflicting() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateMain() && updateArchive() && updateConflicting()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCorrectMainThenConflictingThenArchive() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateMain() && updateConflicting() && updateArchive()) {
|
||||
assertExpected();
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public void testAcceptableConflictingThenMainThenArchive() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateConflicting() && updateMain() && updateArchive()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateConflicting() && updateArchive() && updateMain()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateArchive() && updateMain() && updateConflicting()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateArchive() && updateConflicting() && updateMain()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateMain() && updateArchive() && updateConflicting()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
public void testAcceptableMainThenConflictingThenArchive() throws UpdateException {
|
||||
assertEmpty();
|
||||
if (updateMain() && updateConflicting() && updateArchive()) {
|
||||
assertSomewhatAcceptable();
|
||||
}
|
||||
}
|
||||
|
||||
private RepoUpdater createUpdater(String name, Context context) {
|
||||
Repo repo = new Repo();
|
||||
repo.pubkey = PUB_KEY;
|
||||
repo.address = UUID.randomUUID().toString();
|
||||
repo.name = name;
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(RepoProvider.DataColumns.PUBLIC_KEY, repo.pubkey);
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, repo.address);
|
||||
values.put(RepoProvider.DataColumns.NAME, repo.name);
|
||||
|
||||
RepoProvider.Helper.insert(context, values);
|
||||
|
||||
// Need to reload the repo based on address so that it includes the primary key from
|
||||
// the database.
|
||||
return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
|
||||
}
|
||||
|
||||
private boolean updateConflicting() throws UpdateException {
|
||||
return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar");
|
||||
}
|
||||
|
||||
private boolean updateMain() throws UpdateException {
|
||||
return updateRepo(mainRepoUpdater, "multiRepo.normal.jar");
|
||||
}
|
||||
|
||||
private boolean updateArchive() throws UpdateException {
|
||||
return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar");
|
||||
}
|
||||
|
||||
private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File indexJar = TestUtils.copyAssetToDir(context, indexJarPath, testFilesDir);
|
||||
updater.processDownloadedFile(indexJar);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
140
app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java
Normal file
140
app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java
Normal file
@@ -0,0 +1,140 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class RepoUpdaterTest extends InstrumentationTestCase {
|
||||
private static final String TAG = "RepoUpdaterTest";
|
||||
|
||||
private Context context;
|
||||
private RepoUpdater repoUpdater;
|
||||
private File testFilesDir;
|
||||
|
||||
String simpleIndexPubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
|
||||
|
||||
@Override
|
||||
protected void setUp() {
|
||||
context = getInstrumentation().getContext();
|
||||
testFilesDir = TestUtils.getWriteableDir(getInstrumentation());
|
||||
Repo repo = new Repo();
|
||||
repo.pubkey = this.simpleIndexPubkey;
|
||||
repoUpdater = new RepoUpdater(context, repo);
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJar() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir);
|
||||
|
||||
// these are supposed to succeed
|
||||
try {
|
||||
repoUpdater.processDownloadedFile(simpleIndexJar);
|
||||
} catch (UpdateException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJarWithoutSignatureJar() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithoutSignature.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJarWithCorruptedManifestJar() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedManifest.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
} catch (SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJarWithCorruptedSignature() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedSignature.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
} catch (SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJarWithCorruptedCertificate() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedCertificate.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
} catch (SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromJarWithCorruptedEverything() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedEverything.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
} catch (SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
|
||||
public void testExtractIndexFromMasterKeyIndexJar() {
|
||||
if (!testFilesDir.canWrite()) {
|
||||
return;
|
||||
}
|
||||
// this is supposed to fail
|
||||
try {
|
||||
File jarFile = TestUtils.copyAssetToDir(context, "masterKeyIndex.jar", testFilesDir);
|
||||
repoUpdater.processDownloadedFile(jarFile);
|
||||
fail();
|
||||
} catch (UpdateException | SecurityException e) {
|
||||
// success!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.mock.MockRepo;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
public class RepoXMLHandlerTest extends AndroidTestCase {
|
||||
private static final String TAG = "RepoXMLHandlerTest";
|
||||
|
||||
private static final String FAKE_PUBKEY = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
|
||||
|
||||
public RepoXMLHandlerTest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
public void testSimpleIndex() {
|
||||
Repo expectedRepo = new Repo();
|
||||
expectedRepo.name = "F-Droid";
|
||||
expectedRepo.pubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
|
||||
expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid.";
|
||||
RepoDetails actualDetails = getFromFile("simpleIndex.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 0, 0, -1, 12);
|
||||
}
|
||||
|
||||
public void testSmallRepo() {
|
||||
Repo expectedRepo = new Repo();
|
||||
expectedRepo.name = "Android-Nexus-7-20139453 on UNSET";
|
||||
expectedRepo.pubkey = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c";
|
||||
expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453";
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
public void testMediumRepo() {
|
||||
Repo expectedRepo = new Repo();
|
||||
expectedRepo.name = "Guardian Project Official Releases";
|
||||
expectedRepo.pubkey = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f";
|
||||
expectedRepo.description = "The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store.";
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
public void testLargeRepo() {
|
||||
Repo expectedRepo = new Repo();
|
||||
expectedRepo.name = "F-Droid";
|
||||
expectedRepo.pubkey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
|
||||
expectedRepo.description = "The official FDroid repository. Applications in this repository are mostly built directory from the source code. Some are official binaries built by the original application developers - these will be replaced by source-built versions over time.";
|
||||
RepoDetails actualDetails = getFromFile("largeRepo.xml");
|
||||
handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12);
|
||||
/*
|
||||
* generated using: sed 's,<application,\n<application,g' largeRepo.xml
|
||||
* | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p'
|
||||
*/
|
||||
checkIncludedApps(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 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 handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails, int appCount, int apkCount, int maxAge, int version) {
|
||||
assertNotNull(actualDetails);
|
||||
assertFalse(TextUtils.isEmpty(actualDetails.signingCert));
|
||||
assertEquals(expectedRepo.pubkey.length(), actualDetails.signingCert.length());
|
||||
assertEquals(expectedRepo.pubkey, actualDetails.signingCert);
|
||||
assertFalse(FAKE_PUBKEY.equals(actualDetails.signingCert));
|
||||
|
||||
assertFalse(TextUtils.isEmpty(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);
|
||||
|
||||
List<App> apps = actualDetails.apps;
|
||||
assertNotNull(apps);
|
||||
assertEquals(apps.size(), appCount);
|
||||
|
||||
List<Apk> apks = actualDetails.apks;
|
||||
assertNotNull(apks);
|
||||
assertEquals(apks.size(), apkCount);
|
||||
}
|
||||
|
||||
private static class RepoDetails implements RepoXMLHandler.IndexReceiver {
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
public String signingCert;
|
||||
public int maxAge;
|
||||
public int version;
|
||||
|
||||
public List<Apk> apks = new ArrayList<>();
|
||||
public List<App> apps = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void receiveRepo(String name, String description, String signingCert, int maxage, int version) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.signingCert = signingCert;
|
||||
this.maxAge = maxage;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveApp(App app, List<Apk> packages) {
|
||||
apks.addAll(packages);
|
||||
apps.add(app);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private RepoDetails getFromFile(String indexFilename) {
|
||||
SAXParser parser;
|
||||
try {
|
||||
parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
XMLReader reader = parser.getXMLReader();
|
||||
RepoDetails repoDetails = new RepoDetails();
|
||||
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails);
|
||||
reader.setContentHandler(handler);
|
||||
String resName = "assets/" + indexFilename;
|
||||
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));
|
||||
InputStream input = getClass().getClassLoader().getResourceAsStream(resName);
|
||||
InputSource is = new InputSource(new BufferedInputStream(input));
|
||||
reader.parse(is);
|
||||
return repoDetails;
|
||||
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
|
||||
// Satisfies the compiler, but fail() will always throw a runtime exception so we never
|
||||
// reach this return statement.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SanitizedFileTest extends AndroidTestCase {
|
||||
|
||||
public void testSanitizedFile() {
|
||||
|
||||
File directory = new File("/tmp/blah");
|
||||
|
||||
String safeFile = "safe";
|
||||
String nonEvilFile = "$%^safe-and_bleh.boo*@~";
|
||||
String evilFile = ";rm /etc/shadow;";
|
||||
|
||||
File safeNotSanitized = new File(directory, safeFile);
|
||||
File nonEvilNotSanitized = new File(directory, nonEvilFile);
|
||||
File evilNotSanitized = new File(directory, evilFile);
|
||||
|
||||
assertEquals("/tmp/blah/safe", safeNotSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/$%^safe-and_bleh.boo*@~", nonEvilNotSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/;rm /etc/shadow;", evilNotSanitized.getAbsolutePath());
|
||||
|
||||
assertEquals("safe", safeNotSanitized.getName());
|
||||
assertEquals("$%^safe-and_bleh.boo*@~", nonEvilNotSanitized.getName());
|
||||
assertEquals("shadow;", evilNotSanitized.getName()); // Should be ;rm /etc/shadow; but the forward slashes are naughty.
|
||||
|
||||
SanitizedFile safeSanitized = new SanitizedFile(directory, safeFile);
|
||||
SanitizedFile nonEvilSanitized = new SanitizedFile(directory, nonEvilFile);
|
||||
SanitizedFile evilSanitized = new SanitizedFile(directory, evilFile);
|
||||
|
||||
assertEquals("/tmp/blah/safe", safeSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/safe-and_bleh.boo", nonEvilSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/rmetcshadow", evilSanitized.getAbsolutePath());
|
||||
|
||||
assertEquals("safe", safeSanitized.getName());
|
||||
assertEquals("safe-and_bleh.boo", nonEvilSanitized.getName());
|
||||
assertEquals("rmetcshadow", evilSanitized.getName());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
250
app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java
Normal file
250
app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java
Normal file
@@ -0,0 +1,250 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.receiver.PackageAddedReceiver;
|
||||
import org.fdroid.fdroid.receiver.PackageRemovedReceiver;
|
||||
import org.fdroid.fdroid.receiver.PackageUpgradedReceiver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import mock.MockContextSwappableComponents;
|
||||
import mock.MockInstallablePackageManager;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private static final String TAG = "TestUtils";
|
||||
|
||||
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 insertApp(ContentResolver resolver, String appId, String name) {
|
||||
insertApp(resolver, appId, name, new ContentValues());
|
||||
}
|
||||
|
||||
public static void insertApp(ContentResolver resolver, String id, String name, ContentValues additionalValues) {
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
|
||||
values.put(AppProvider.DataColumns.NAME, name);
|
||||
|
||||
// Required fields (NOT NULL in the database).
|
||||
values.put(AppProvider.DataColumns.SUMMARY, "test summary");
|
||||
values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
|
||||
values.put(AppProvider.DataColumns.LICENSE, "GPL?");
|
||||
values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
|
||||
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
|
||||
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
|
||||
|
||||
values.putAll(additionalValues);
|
||||
|
||||
Uri uri = AppProvider.getContentUri();
|
||||
|
||||
resolver.insert(uri, values);
|
||||
}
|
||||
|
||||
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode) {
|
||||
return insertApk(providerTest, id, versionCode, new ContentValues());
|
||||
}
|
||||
|
||||
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode, ContentValues additionalValues) {
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(ApkProvider.DataColumns.PACKAGE_NAME, id);
|
||||
values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode);
|
||||
|
||||
// Required fields (NOT NULL in the database).
|
||||
values.put(ApkProvider.DataColumns.REPO_ID, 1);
|
||||
values.put(ApkProvider.DataColumns.VERSION, "The good one");
|
||||
values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa");
|
||||
values.put(ApkProvider.DataColumns.NAME, "Test Apk");
|
||||
values.put(ApkProvider.DataColumns.SIZE, 10000);
|
||||
values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1);
|
||||
|
||||
values.putAll(additionalValues);
|
||||
|
||||
Uri uri = ApkProvider.getContentUri();
|
||||
|
||||
return providerTest.getMockContentResolver().insert(uri, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will tell {@code pm} that we are installing {@code appId}, and then alert the
|
||||
* {@link org.fdroid.fdroid.receiver.PackageAddedReceiver}. This will in turn update the
|
||||
* "installed apps" table in the database.
|
||||
*/
|
||||
public static void installAndBroadcast(MockContextSwappableComponents context,
|
||||
MockInstallablePackageManager pm, String appId,
|
||||
int versionCode, String versionName) {
|
||||
|
||||
context.setPackageManager(pm);
|
||||
pm.install(appId, versionCode, versionName);
|
||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
|
||||
installIntent.setData(Uri.parse("package:" + appId));
|
||||
new PackageAddedReceiver().onReceive(context, installIntent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
|
||||
*/
|
||||
public static void upgradeAndBroadcast(MockContextSwappableComponents context,
|
||||
MockInstallablePackageManager pm, String appId,
|
||||
int versionCode, String versionName) {
|
||||
/*
|
||||
removeAndBroadcast(context, pm, appId);
|
||||
installAndBroadcast(context, pm, appId, versionCode, versionName);
|
||||
*/
|
||||
context.setPackageManager(pm);
|
||||
pm.install(appId, versionCode, versionName);
|
||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
|
||||
installIntent.setData(Uri.parse("package:" + appId));
|
||||
new PackageUpgradedReceiver().onReceive(context, installIntent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
|
||||
*/
|
||||
public static void removeAndBroadcast(MockContextSwappableComponents context, MockInstallablePackageManager pm, String appId) {
|
||||
|
||||
context.setPackageManager(pm);
|
||||
pm.remove(appId);
|
||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
|
||||
installIntent.setData(Uri.parse("package:" + appId));
|
||||
new PackageRemovedReceiver().onReceive(context, installIntent);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File copyAssetToDir(Context context, String assetName, File directory) {
|
||||
File tempFile;
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
try {
|
||||
tempFile = File.createTempFile(assetName + "-", ".testasset", directory);
|
||||
Log.d(TAG, "Copying asset file " + assetName + " to directory " + directory);
|
||||
input = context.getAssets().open(assetName);
|
||||
output = new FileOutputStream(tempFile);
|
||||
Utils.copy(input, output);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
Utils.closeQuietly(output);
|
||||
Utils.closeQuietly(input);
|
||||
}
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer internal over external storage, because external tends to be FAT filesystems,
|
||||
* which don't support symlinks (which we test using this method).
|
||||
*/
|
||||
public static File getWriteableDir(Instrumentation instrumentation) {
|
||||
Context context = instrumentation.getContext();
|
||||
Context targetContext = instrumentation.getTargetContext();
|
||||
|
||||
File[] dirsToTry = new File[]{
|
||||
context.getCacheDir(),
|
||||
context.getFilesDir(),
|
||||
targetContext.getCacheDir(),
|
||||
targetContext.getFilesDir(),
|
||||
context.getExternalCacheDir(),
|
||||
context.getExternalFilesDir(null),
|
||||
targetContext.getExternalCacheDir(),
|
||||
targetContext.getExternalFilesDir(null),
|
||||
Environment.getExternalStorageDirectory(),
|
||||
};
|
||||
|
||||
return getWriteableDir(dirsToTry);
|
||||
}
|
||||
|
||||
private static File getWriteableDir(File[] dirsToTry) {
|
||||
|
||||
for (File dir : dirsToTry) {
|
||||
if (dir != null && dir.canWrite()) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
129
app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
Normal file
129
app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class UtilsTest extends AndroidTestCase {
|
||||
|
||||
String fdroidFingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB";
|
||||
String fdroidPubkey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
|
||||
|
||||
String gpRepoFingerprint = "59050C8155DCA377F23D5A15B77D3713400CDBD8B42FBFBE0E3F38096E68CECE";
|
||||
String gpRepoPubkey = "308203c5308202ada00302010202047b7cf549300d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85";
|
||||
|
||||
String gpTest0Fingerprint = "C4DC0B2AB5AB58F0CDBF97FF903CF12415F468D90B11877803BC172D31012B2E";
|
||||
String gpTest0Pubkey = "308204f3308202dba003020102020436aac0dc300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3133313130353232353534325a170d3133313130363232353534325a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30820222300d06092a864886f70d01010105000382020f003082020a0282020100b1f3cd3db9207f80e9d854159d40a15344bfcc377fba61983d1ac843e52e2fc1a81d96325174328f77dbe382b2b239567d50ad2e1fea13f1272b0370693acd03b9aef3e5a908118065f21193735552c123a9f59f99c2822b7bba7082c72649e17666ac70d332f1c7cf20830373c86f11d2f80a2aa0307c3b526b8769b69371555540f246ca892db4b51226788bb3b869284254266f3ccb1d7b5b08a2cf398f53877b09da0f1cc922ecc928c477660979d07998b29678feaea9b5c93d3a12f89f695eeda766280df22b688e1da15d979845a81c81f9d1252e2e5fd415df2eb0f28cb339a9d9bc13ec1a059333ca766a0982464f8d9a67397f066b3926aa5ac6f2216962da5705d2b9723353ac3b670f5ab4d365cde4e5d0375ca52e7e8c151dd90eda0025be09feae4c94c59608243b45f0527ad8d46e0a0bc97ac27870af53c0550706502ecfa56a30d7442012e6115ada79243481b759319def848199df423c9664574d8d8a7f8949e9f3549e8695fa0b02eab1dc8e30c6432d159076ceb91b709bd848f4e5d74a0880c1ead0534b1f8a354edd05a6d7b44f9a566f9e419bab6834ff2f2d2a54c797b407ccb2d4648247e69b2b85186f9ebd087879a580be281b73f46975e5c94b5a935adf019d6d56992742ebb23865f94a14ed17fc7fb0fbea43eb686760298ae98b197ac8da2ec0b61be240b6f2a574208da9e0fd9e14d90203010001a321301f301d0603551d0e04160414282e3362786f92645dd7809905166e473bbfc722300d06092a864886f70d01010b05000382020100295efaa7d0e985b408a7c6f2891cae1fa7b6338774eee624edd838c0fbaadc755d140ed6007b91e662434010659a4a5597709e23828a1a5e9846b4369ee8fcef10b85fc64db7726aee8c8d93753d4828250323ebdb768ed9958f4c2c61eb48d2329a0196a47898662ed9418e5ba223c4c1e285e94bfe0f5d5b4813b9d0b6b49d304a79879698d320e1ff5e36be441f1dcda5715d4644825d669b15de2765d285253231fbe052360426fe976af404381909043cfe8e7a537275dc75f367235eb0fc357884ea36f00cdb21fbc75ca2ac9c53adc202456e40d0e950af09c4f5de3d876f43fda7880be4800ff2635f681c19a5b8f1cd68319e78f5ff8e29f5225db849f03d473926aa2d492df970cbcba266211003e7c84f5852ea089b62679acd6243e56b18384596443c379effa1419027345bb929a46193c5c1f6274c83f14a8720189ab178945ef56a99fb16ac8aedb6d8b9ec10bd1f6935b189aa9470947d909bf8d0630e8b189696a79e9560631fa79cc22cddc17594c2c86e03fa7102811154d10aa2ff631732c491969b8a356632eabcf22596db4eb011cfaf426ec972967e2c5f426fa1f3e9d8c1d90fbb7665660d02456d9b7c1fa6bb68b0d53c29c6ef4e7c81f91c1819f754a53a03124a36b539cde945287c5be8817244c1548c17ff671f729545dc9155c94f01ceb620333f10000acbeba866cb242155daa76a5169";
|
||||
|
||||
String gpTest1Fingerprint = "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50";
|
||||
String gpTest1Pubkey = "3082039a30820282020900aa6887be1ec84bde300d06092a864886f70d010105050030818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f301e170d3134303332383230343132365a170d3431303831323230343132365a30818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a02820101009f4895a4a160d14e9de49dd61ac9434715c2aea25a9de75f0361e3f9bd77306cff7a8f508f9a9edc31dfb5b3aa2571e22b1711c08f0616892fa4efdf94321ec93211486b314bcf27385f670492683a0e50f5a022ede2bfc00c69b14e8c8678f313d6d280feb9c53445f087fa9d12a31392ca63d75351587e3cd2337fbf95fd7c2a9322883d74f18680165a697d4a1a4fa3bd835bd45f00561447350af4ec6b6740c0ae7950ff53c386a2efc43a280e4270912d20eb464761799fdbbae50dd0df01f9b25673499029a2e869203e7d63e7ca98826dabf856c965f472de691ddc77f6ed8db468684baf76f7f1cdf7fc3a07109ad8aea8e332a807bedbb8143bbe230203010001300d06092a864886f70d010105050003820101005284015baba5eb092a3c681634b46b9f59a0dbb651c89ca65af730bfeb22726e048194cbd54fb4242f5ec8e514e26dd8887cbcb431f3f2eb224780b6a2204e614d705aed4bd66e153c216d35e1dc1e38e226566af74bb229a2416ea6ffb388d6f64a68386332f34f50d48b630541e2871030bd27d90a1688f46bff4e9707059cd22e56820a4a3d01f9a91b442f6adf0776d9f73533a2dcd7214305491414dbc7c734166cd833e227f9bd8a82b3d464c662c71a07703fb14de0564cad1d3851e35cc9a04ce36fde2abf8d8d9dec07752e535f35aabc3632d6d2106086477e346efebb0d4bec7afc461d7ab7f96200c2dadb2da41d09342aa2fa9ab94ab92d2053";
|
||||
|
||||
// this pair has one digit missing from the pubkey
|
||||
String pubkeyShortByOneFingerprint = "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50";
|
||||
String pubkeyShortByOnePubkey = "3082039a30820282020900aa6887be1ec84bde300d06092a86488f70d010105050030818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f301e170d3134303332383230343132365a170d3431303831323230343132365a30818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a02820101009f4895a4a160d14e9de49dd61ac9434715c2aea25a9de75f0361e3f9bd77306cff7a8f508f9a9edc31dfb5b3aa2571e22b1711c08f0616892fa4efdf94321ec93211486b314bcf27385f670492683a0e50f5a022ede2bfc00c69b14e8c8678f313d6d280feb9c53445f087fa9d12a31392ca63d75351587e3cd2337fbf95fd7c2a9322883d74f18680165a697d4a1a4fa3bd835bd45f00561447350af4ec6b6740c0ae7950ff53c386a2efc43a280e4270912d20eb464761799fdbbae50dd0df01f9b25673499029a2e869203e7d63e7ca98826dabf856c965f472de691ddc77f6ed8db468684baf76f7f1cdf7fc3a07109ad8aea8e332a807bedbb8143bbe230203010001300d06092a864886f70d010105050003820101005284015baba5eb092a3c681634b46b9f59a0dbb651c89ca65af730bfeb22726e048194cbd54fb4242f5ec8e514e26dd8887cbcb431f3f2eb224780b6a2204e614d705aed4bd66e153c216d35e1dc1e38e226566af74bb229a2416ea6ffb388d6f64a68386332f34f50d48b630541e2871030bd27d90a1688f46bff4e9707059cd22e56820a4a3d01f9a91b442f6adf0776d9f73533a2dcd7214305491414dbc7c734166cd833e227f9bd8a82b3d464c662c71a07703fb14de0564cad1d3851e35cc9a04ce36fde2abf8d8d9dec07752e535f35aabc3632d6d2106086477e346efebb0d4bec7afc461d7ab7f96200c2dadb2da41d09342aa2fa9ab94ab92d2053";
|
||||
|
||||
// this pair has one digit missing from the fingerprint
|
||||
String fingerprintShortByOneFingerprint = "C63AED1AC79D37C7B047442AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50";
|
||||
String fingerprintShortByOnePubkey = "3082039a30820282020900aa6887be1ec84bde300d06092a864886f70d010105050030818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f301e170d3134303332383230343132365a170d3431303831323230343132365a30818e310b30090603550406130255533111300f06035504080c084e657720596f726b311e301c060355040a0c15477561726469616e2050726f6a65637420546573743122302006035504030c19746573742e677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d01090116197465737440677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a02820101009f4895a4a160d14e9de49dd61ac9434715c2aea25a9de75f0361e3f9bd77306cff7a8f508f9a9edc31dfb5b3aa2571e22b1711c08f0616892fa4efdf94321ec93211486b314bcf27385f670492683a0e50f5a022ede2bfc00c69b14e8c8678f313d6d280feb9c53445f087fa9d12a31392ca63d75351587e3cd2337fbf95fd7c2a9322883d74f18680165a697d4a1a4fa3bd835bd45f00561447350af4ec6b6740c0ae7950ff53c386a2efc43a280e4270912d20eb464761799fdbbae50dd0df01f9b25673499029a2e869203e7d63e7ca98826dabf856c965f472de691ddc77f6ed8db468684baf76f7f1cdf7fc3a07109ad8aea8e332a807bedbb8143bbe230203010001300d06092a864886f70d010105050003820101005284015baba5eb092a3c681634b46b9f59a0dbb651c89ca65af730bfeb22726e048194cbd54fb4242f5ec8e514e26dd8887cbcb431f3f2eb224780b6a2204e614d705aed4bd66e153c216d35e1dc1e38e226566af74bb229a2416ea6ffb388d6f64a68386332f34f50d48b630541e2871030bd27d90a1688f46bff4e9707059cd22e56820a4a3d01f9a91b442f6adf0776d9f73533a2dcd7214305491414dbc7c734166cd833e227f9bd8a82b3d464c662c71a07703fb14de0564cad1d3851e35cc9a04ce36fde2abf8d8d9dec07752e535f35aabc3632d6d2106086477e346efebb0d4bec7afc461d7ab7f96200c2dadb2da41d09342aa2fa9ab94ab92d2053";
|
||||
|
||||
// this pair has one digit added to the pubkey
|
||||
String pubkeyLongByOneFingerprint = "59050C8155DCA377F23D5A15B77D3713400CDBD8B42FBFBE0E3F38096E68CECE";
|
||||
String pubkeyLongByOnePubkey = "308203c5308202ada00302010202047b7cf5493000d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85";
|
||||
|
||||
// this pair has one digit added to the fingerprint
|
||||
String fingerprintLongByOneFingerprint = "59050C8155DCA377F23D5A15B77D37134000CDBD8B42FBFBE0E3F38096E68CECE";
|
||||
String fingerprintLongByOnePubkey = "308203c5308202ada00302010202047b7cf549300d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85";
|
||||
|
||||
public void testFormatFingerprint() {
|
||||
String badResult = Utils.formatFingerprint(getContext(), "");
|
||||
// real fingerprints
|
||||
String formatted;
|
||||
formatted = Utils.formatFingerprint(getContext(), fdroidFingerprint);
|
||||
assertFalse(formatted.equals(badResult));
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
formatted = Utils.formatFingerprint(getContext(), gpRepoFingerprint);
|
||||
assertFalse(formatted.equals(badResult));
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
formatted = Utils.formatFingerprint(getContext(), gpTest1Fingerprint);
|
||||
assertFalse(formatted.equals(badResult));
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
// random garbage
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "234k2lk3jljwlk4j2lk3jlkmqwekljrlkj34lk2jlk2j34lkjl2k3j4lk2j34lja"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "g000000000000000000000000000000000000000000000000000000000000000"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "98273498723948728934789237489273p1928731982731982739182739817238"));
|
||||
// too short
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C5"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "C63AED1"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "f"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), ""));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), null));
|
||||
// real digits but too long
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50F"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(getContext(), "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"));
|
||||
}
|
||||
|
||||
public void testCalcFingerprintString() {
|
||||
// these should pass
|
||||
assertEquals(fdroidFingerprint, Utils.calcFingerprint(fdroidPubkey));
|
||||
assertEquals(gpRepoFingerprint, Utils.calcFingerprint(gpRepoPubkey));
|
||||
assertEquals(gpTest0Fingerprint, Utils.calcFingerprint(gpTest0Pubkey));
|
||||
assertEquals(gpTest1Fingerprint, Utils.calcFingerprint(gpTest1Pubkey));
|
||||
|
||||
// these should fail
|
||||
assertFalse(gpRepoFingerprint.equals(
|
||||
Utils.calcFingerprint(fdroidPubkey)));
|
||||
assertFalse(gpTest0Fingerprint.equals(
|
||||
Utils.calcFingerprint(fdroidPubkey)));
|
||||
assertFalse(gpTest1Fingerprint.equals(
|
||||
Utils.calcFingerprint(fdroidPubkey)));
|
||||
assertFalse(fdroidFingerprint.equals(
|
||||
Utils.calcFingerprint(gpRepoPubkey)));
|
||||
assertFalse(gpTest0Fingerprint.equals(
|
||||
Utils.calcFingerprint(gpRepoPubkey)));
|
||||
assertFalse(gpTest1Fingerprint.equals(
|
||||
Utils.calcFingerprint(gpRepoPubkey)));
|
||||
|
||||
assertFalse(fingerprintShortByOneFingerprint.equals(
|
||||
Utils.calcFingerprint(fingerprintShortByOnePubkey)));
|
||||
assertFalse(fingerprintLongByOneFingerprint.equals(
|
||||
Utils.calcFingerprint(fingerprintLongByOnePubkey)));
|
||||
try {
|
||||
assertFalse(pubkeyShortByOneFingerprint.equals(
|
||||
Utils.calcFingerprint(pubkeyShortByOnePubkey)));
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
assertTrue(true); // we should get this Exception!
|
||||
}
|
||||
try {
|
||||
assertFalse(pubkeyLongByOneFingerprint.equals(
|
||||
Utils.calcFingerprint(pubkeyLongByOnePubkey)));
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
assertTrue(true); // we should get this Exception!
|
||||
}
|
||||
}
|
||||
|
||||
public void testCalcFingerprintCertificate() {
|
||||
// TODO write tests that work with a Certificate
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
|
||||
/**
|
||||
* Used to expose the protected methods from FileCompat in a public manner so
|
||||
* that they can be called from a test harness.
|
||||
*/
|
||||
public class FileCompatForTest extends FileCompat {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void symlinkOsTest(SanitizedFile source, SanitizedFile dest) {
|
||||
symlinkOs(source, dest);
|
||||
}
|
||||
|
||||
public static void symlinkRuntimeTest(SanitizedFile source, SanitizedFile dest) {
|
||||
symlinkRuntime(source, dest);
|
||||
}
|
||||
|
||||
public static void symlinkLibcoreTest(SanitizedFile source, SanitizedFile dest) {
|
||||
symlinkLibcore(source, dest);
|
||||
}
|
||||
|
||||
}
|
||||
12
app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java
Normal file
12
app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package org.fdroid.fdroid.mock;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
public class MockApk extends Apk {
|
||||
|
||||
public MockApk(String id, int versionCode) {
|
||||
this.packageName = id;
|
||||
this.vercode = versionCode;
|
||||
}
|
||||
|
||||
}
|
||||
16
app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java
Normal file
16
app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.fdroid.fdroid.mock;
|
||||
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
||||
public class MockApp extends App {
|
||||
|
||||
public MockApp(String id) {
|
||||
this(id, "App " + id);
|
||||
}
|
||||
|
||||
public MockApp(String id, String name) {
|
||||
this.packageName = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.fdroid.fdroid.mock;
|
||||
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
public class MockRepo extends Repo {
|
||||
|
||||
public MockRepo(long repoId) {
|
||||
id = repoId;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user