Merge branch 'multi-sig-tests' into 'master'

Tests and slight improvements for multi-sig support when calculating suggested versions.

See merge request !534
This commit is contained in:
Hans-Christoph Steiner 2017-06-15 17:43:47 +00:00
commit d54788aafa
21 changed files with 418 additions and 84 deletions

View File

@ -6,6 +6,7 @@ import android.os.Build;
import android.os.Environment;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.fdroid.fdroid.AssetUtils;
import org.fdroid.fdroid.data.SanitizedFile;
@ -30,6 +31,8 @@ import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class FileCompatTest {
private static final String TAG = "FileCompatTest";
private SanitizedFile sourceFile;
private SanitizedFile destFile;
@ -47,11 +50,11 @@ public class FileCompatTest {
@After
public void tearDown() {
if (!sourceFile.delete()) {
System.out.println("Can't delete " + sourceFile.getAbsolutePath() + ".");
Log.w(TAG, "Can't delete " + sourceFile.getAbsolutePath() + ".");
}
if (!destFile.delete()) {
System.out.println("Can't delete " + destFile.getAbsolutePath() + ".");
Log.w(TAG, "Can't delete " + destFile.getAbsolutePath() + ".");
}
}

View File

@ -46,16 +46,12 @@ public class HttpDownloaderTest {
final CountDownLatch latch = new CountDownLatch(1);
String urlString = "https://f-droid.org/repo/index.jar";
receivedProgress = false;
System.out.println("downloadUninterruptedTestWithProgress: " + urlString);
receivedProgress = false;
URL url = new URL(urlString);
File destFile = File.createTempFile("dl-", "");
final HttpDownloader httpDownloader = new HttpDownloader(url, destFile);
httpDownloader.setListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
System.out.println("DownloaderProgressListener.sendProgress "
+ sourceUrl + " " + bytesRead + " / " + totalBytes);
receivedProgress = true;
}
});
@ -118,7 +114,6 @@ public class HttpDownloaderTest {
httpDownloader.setListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
System.out.println("DownloaderProgressListener.sendProgress " + bytesRead + " / " + totalBytes);
receivedProgress = true;
latch.countDown();
}

View File

@ -195,6 +195,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES);
}
public void setUnstableUpdates(boolean value) {
preferences.edit().putBoolean(PREF_UNSTABLE_UPDATES, value).apply();
}
public boolean isKeepingInstallHistory() {
return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, DEFAULT_KEEP_INSTALL_HISTORY);
}

View File

@ -379,9 +379,6 @@ public class RepoXMLHandler extends DefaultHandler {
} else {
removeRequestedPermission(attributes.getValue("name"));
}
} else if ("uses-feature".equals(localName) && curapk != null) {
System.out.println("TODO startElement " + uri + " " + localName + " " + qName);
// TODO
}
curchars.setLength(0);
}

View File

@ -250,7 +250,7 @@ public class AppProvider extends FDroidProvider {
join(
InstalledAppTable.NAME,
"installed",
"installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
"installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID);
requiresInstalledTable = true;
}
}
@ -270,7 +270,7 @@ public class AppProvider extends FDroidProvider {
leftJoin(
InstalledAppTable.NAME,
"installed",
"installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
"installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID);
requiresInstalledTable = true;
}
}
@ -953,50 +953,49 @@ public class AppProvider extends FDroidProvider {
* with the closest version code to that, without going over.
* If the app is not compatible at all (i.e. no versions were compatible)
* then we take the highest, otherwise we take the highest compatible version.
* If the app is installed, then all apks signed by a different certificate are
* ignored for the purpose of this calculation.
*
* @see #updateSuggestedFromLatest()
*/
private void updateSuggestedFromUpstream() {
Utils.debugLog(TAG, "Calculating suggested versions for all apps which specify an upstream version code.");
Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code.");
final String apk = getApkTableName();
final String app = getTableName();
final String installed = InstalledAppTable.NAME;
final boolean unstableUpdates = Preferences.get().getUnstableUpdates();
String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND ");
// The join onto `appForThisApk` is to ensure that the MAX(apk.versionCode) is chosen from
// all apps regardless of repo. If we joined directly onto the outer `app` table we are
// in the process of updating, then it would be limited to only apks from the same repo.
// By adding the extra join, and then joining based on the packageId of this inner app table
// and the app table we are updating, we take into account all apks for this app.
// The check apk.sig = COALESCE(installed.sig, apk.sig) would ideally be better written as:
// `installedSig IS NULL OR installedSig = apk.sig`
// however that would require a separate sub query for each `installedSig` which is more
// expensive. Using a COALESCE is a less expressive way to write the same thing with only
// a single subquery.
// Also note that the `installedSig IS NULL` is not because there is a `NULL` entry in the
// installed table (this is impossible), but rather because the subselect above returned
// zero rows.
String updateSql =
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
" FROM " + apk +
" JOIN " + app + " AS appForThisApk ON (appForThisApk." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID + ") " +
" LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " +
" WHERE " +
joinToApksRegardlessOfRepo() + " AND " +
app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " +
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
restrictToStable +
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " +
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 ";
db().execSQL(updateSql);
}
/**
* Ensure that when we select a list of {@link ApkTable} rows for which to calculate the
* {@link Cols#SUGGESTED_VERSION_CODE}, that we select all apks belonging to the same package,
* regardless of which repo they come from. We can't just join {@link ApkTable} onto the
* {@link AppMetadataTable}, because the {@link AppMetadataTable} table is specific to a repo.
*
* This is required so that apps always have the highest possible
* {@link Cols#SUGGESTED_VERSION_CODE}, regardless of the repository priorities. Without this,
* then each {@link AppMetadataTable} row will have a different {@link Cols#SUGGESTED_VERSION_CODE}
* depending on which repo it came from. With this, each {@link AppMetadataTable} row has the
* same {@link Cols#SUGGESTED_VERSION_CODE}, even if that version is from a different repo.
*/
private String joinToApksRegardlessOfRepo() {
final String apk = getApkTableName();
final String app = getTableName();
return app + "." + Cols.PACKAGE_ID + " = (" +
" SELECT innerAppName." + Cols.PACKAGE_ID +
" FROM " + app + " as innerAppName " +
" WHERE innerAppName." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID +
") ";
LoggingQuery.execSQL(db(), updateSql);
}
/**
@ -1006,23 +1005,29 @@ public class AppProvider extends FDroidProvider {
* If the suggested version is null, it means that we could not figure it
* out from the upstream vercode. In such a case, fall back to the simpler
* algorithm as if upstreamVercode was 0.
*
* @see #updateSuggestedFromUpstream()
*/
private void updateSuggestedFromLatest() {
Utils.debugLog(TAG, "Calculating suggested versions for all apps which don't specify an upstream version code.");
final String apk = getApkTableName();
final String app = getTableName();
final String installed = InstalledAppTable.NAME;
String updateSql =
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
" FROM " + apk +
" JOIN " + app + " AS appForThisApk ON (appForThisApk." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID + ") " +
" LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " +
" WHERE " +
joinToApksRegardlessOfRepo() + " AND " +
app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " +
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + ApkTable.Cols.IS_COMPATIBLE + " = 1 ) ) " +
" WHERE COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL ";
db().execSQL(updateSql);
LoggingQuery.execSQL(db(), updateSql);
}
private void updateIconUrls() {

View File

@ -108,8 +108,7 @@ class DBHelper extends SQLiteOpenHelper {
+ ApkTable.Cols.ADDED_DATE + " string, "
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
+ ApkTable.Cols.ANTI_FEATURES + " string, "
+ "PRIMARY KEY (" + ApkTable.Cols.APP_ID + ", " + ApkTable.Cols.VERSION_CODE + ", " + ApkTable.Cols.REPO_ID + ")"
+ ApkTable.Cols.ANTI_FEATURES + " string"
+ ");";
static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
@ -183,7 +182,7 @@ class DBHelper extends SQLiteOpenHelper {
private static final String CREATE_TABLE_INSTALLED_APP = "CREATE TABLE " + InstalledAppTable.NAME
+ " ( "
+ InstalledAppTable.Cols.PACKAGE_NAME + " TEXT NOT NULL PRIMARY KEY, "
+ InstalledAppTable.Cols.PACKAGE_ID + " INT NOT NULL UNIQUE, "
+ InstalledAppTable.Cols.VERSION_CODE + " INT NOT NULL, "
+ InstalledAppTable.Cols.VERSION_NAME + " TEXT NOT NULL, "
+ InstalledAppTable.Cols.APPLICATION_LABEL + " TEXT NOT NULL, "
@ -193,7 +192,7 @@ class DBHelper extends SQLiteOpenHelper {
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
+ " );";
protected static final int DB_VERSION = 69;
protected static final int DB_VERSION = 71;
private final Context context;
@ -277,6 +276,40 @@ class DBHelper extends SQLiteOpenHelper {
addIndexV1AppFields(db, oldVersion);
recalculatePreferredMetadata(db, oldVersion);
addWhatsNewAndVideo(db, oldVersion);
dropApkPrimaryKey(db, oldVersion);
addIntegerPrimaryKeyToInstalledApps(db, oldVersion);
}
private void addIntegerPrimaryKeyToInstalledApps(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 71) {
return;
}
Log.i(TAG, "Replacing primary key on installed app table with integer for performance.");
db.beginTransaction();
try {
if (tableExists(db, Schema.InstalledAppTable.NAME)) {
db.execSQL("DROP TABLE " + Schema.InstalledAppTable.NAME);
}
db.execSQL(CREATE_TABLE_INSTALLED_APP);
ensureIndexes(db);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void dropApkPrimaryKey(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 70) {
return;
}
// versionCode + repo is no longer a valid primary key given a repo can have multiple apks
// with the same versionCode, signed by different certificates.
Log.i(TAG, "Dropping composite primary key on apk table in favour of sqlite's rowid");
resetTransient(db);
}
private void addWhatsNewAndVideo(SQLiteDatabase db, int oldVersion) {
@ -1050,9 +1083,11 @@ class DBHelper extends SQLiteOpenHelper {
AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ");");
}
Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME);
db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_appId_vercode on " + InstalledAppTable.NAME + " (" +
InstalledAppTable.Cols.PACKAGE_NAME + ", " + InstalledAppTable.Cols.VERSION_CODE + ");");
if (columnExists(db, InstalledAppTable.NAME, InstalledAppTable.Cols.PACKAGE_ID)) {
Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME);
db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_packageId_vercode on " + InstalledAppTable.NAME + " (" +
InstalledAppTable.Cols.PACKAGE_ID + ", " + InstalledAppTable.Cols.VERSION_CODE + ");");
}
Utils.debugLog(TAG, "Ensuring indexes exist for " + RepoTable.NAME);
db.execSQL("CREATE INDEX IF NOT EXISTS repo_id_isSwap on " + RepoTable.NAME + " (" +

View File

@ -24,7 +24,7 @@ public class InstalledApp extends ValueObject {
case Schema.InstalledAppTable.Cols._ID:
id = cursor.getLong(i);
break;
case Schema.InstalledAppTable.Cols.PACKAGE_NAME:
case Schema.InstalledAppTable.Cols.Package.NAME:
packageName = cursor.getString(i);
break;
case Schema.InstalledAppTable.Cols.VERSION_CODE:

View File

@ -10,6 +10,7 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
@ -41,7 +42,7 @@ public class InstalledAppProvider extends FDroidProvider {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
cachedInfo.put(
cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)),
cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)),
cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME))
);
cursor.moveToNext();
@ -136,7 +137,17 @@ public class InstalledAppProvider extends FDroidProvider {
}
private QuerySelection queryApp(String packageName) {
return new QuerySelection(Cols.PACKAGE_NAME + " = ?", new String[]{packageName});
return new QuerySelection(Cols.Package.NAME + " = ?", new String[]{packageName});
}
private QuerySelection queryAppSubQuery(String packageName) {
String pkg = Schema.PackageTable.NAME;
String subQuery = "(" +
" SELECT " + pkg + "." + Schema.PackageTable.Cols.ROW_ID +
" FROM " + pkg +
" WHERE " + pkg + "." + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?)";
String query = Cols.PACKAGE_ID + " = " + subQuery;
return new QuerySelection(query, new String[]{packageName});
}
private QuerySelection querySearch(String query) {
@ -144,6 +155,26 @@ public class InstalledAppProvider extends FDroidProvider {
new String[]{"%" + query + "%"});
}
private static class QueryBuilder extends org.fdroid.fdroid.data.QueryBuilder {
@Override
protected String getRequiredTables() {
String pkg = Schema.PackageTable.NAME;
String installed = InstalledAppTable.NAME;
return installed + " JOIN " + pkg +
" ON (" + pkg + "." + Schema.PackageTable.Cols.ROW_ID + " = " +
installed + "." + Cols.PACKAGE_ID + ")";
}
@Override
public void addField(String field) {
if (TextUtils.equals(field, Cols.Package.NAME)) {
appendField(Schema.PackageTable.Cols.PACKAGE_NAME, Schema.PackageTable.NAME, field);
} else {
appendField(field, InstalledAppTable.NAME);
}
}
}
@Override
public Cursor query(Uri uri, String[] projection,
String customSelection, String[] selectionArgs, String sortOrder) {
@ -170,8 +201,15 @@ public class InstalledAppProvider extends FDroidProvider {
throw new UnsupportedOperationException(message);
}
Cursor cursor = db().query(getTableName(), projection,
selection.getSelection(), selection.getArgs(), null, null, sortOrder);
QueryBuilder query = new QueryBuilder();
query.addFields(projection);
if (projection.length == 0) {
query.addField(Cols._ID);
}
query.addSelection(selection);
query.addOrderBy(sortOrder);
Cursor cursor = db().rawQuery(query.toString(), selection.getArgs());
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@ -184,7 +222,7 @@ public class InstalledAppProvider extends FDroidProvider {
}
QuerySelection query = new QuerySelection(where, whereArgs);
query = query.add(queryApp(uri.getLastPathSegment()));
query = query.add(queryAppSubQuery(uri.getLastPathSegment()));
return db().delete(getTableName(), query.getSelection(), query.getArgs());
}
@ -196,9 +234,16 @@ public class InstalledAppProvider extends FDroidProvider {
throw new UnsupportedOperationException("Insert not supported for " + uri + ".");
}
if (values.containsKey(Cols.Package.NAME)) {
String packageName = values.getAsString(Cols.Package.NAME);
long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName);
values.remove(Cols.Package.NAME);
values.put(Cols.PACKAGE_ID, packageId);
}
verifyVersionNameNotNull(values);
db().replaceOrThrow(getTableName(), null, values);
return getAppUri(values.getAsString(Cols.PACKAGE_NAME));
return getAppUri(values.getAsString(Cols.Package.NAME));
}
/**

View File

@ -288,7 +288,7 @@ public class InstalledAppProviderService extends IntentService {
static void insertAppIntoDb(Context context, PackageInfo packageInfo, String hashType, String hash) {
Uri uri = InstalledAppProvider.getContentUri();
ContentValues contentValues = new ContentValues();
contentValues.put(InstalledAppTable.Cols.PACKAGE_NAME, packageInfo.packageName);
contentValues.put(InstalledAppTable.Cols.Package.NAME, packageInfo.packageName);
contentValues.put(InstalledAppTable.Cols.VERSION_CODE, packageInfo.versionCode);
contentValues.put(InstalledAppTable.Cols.VERSION_NAME, packageInfo.versionName);
contentValues.put(InstalledAppTable.Cols.APPLICATION_LABEL,

View File

@ -66,6 +66,20 @@ final class LoggingQuery {
return db.rawQuery(query, queryArgs);
}
private void execSQLInternal() {
if (BuildConfig.DEBUG) {
long startTime = System.currentTimeMillis();
db.execSQL(query);
long queryDuration = System.currentTimeMillis() - startTime;
if (queryDuration >= SLOW_QUERY_DURATION) {
logSlowQuery(queryDuration);
}
} else {
db.execSQL(query);
}
}
/**
* Log the query and its duration to the console. In addition, execute an "EXPLAIN QUERY PLAN"
* for the query in question so that the query can be diagnosed (https://sqlite.org/eqp.html)
@ -116,4 +130,8 @@ final class LoggingQuery {
public static Cursor query(SQLiteDatabase db, String query, String[] queryBuilderArgs) {
return new LoggingQuery(db, query, queryBuilderArgs).rawQuery();
}
public static void execSQL(SQLiteDatabase db, String sql) {
new LoggingQuery(db, sql, null).execSQLInternal();
}
}

View File

@ -329,7 +329,7 @@ public interface Schema {
interface Cols {
String _ID = "rowid as _id"; // Required for CursorLoaders
String PACKAGE_NAME = "appId";
String PACKAGE_ID = "packageId";
String VERSION_CODE = "versionCode";
String VERSION_NAME = "versionName";
String APPLICATION_LABEL = "applicationLabel";
@ -338,8 +338,12 @@ public interface Schema {
String HASH_TYPE = "hashType";
String HASH = "hash";
interface Package {
String NAME = "packageName";
}
String[] ALL = {
_ID, PACKAGE_NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
_ID, PACKAGE_ID, Package.NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
SIGNATURE, LAST_UPDATE_TIME, HASH_TYPE, HASH,
};
}

View File

@ -129,7 +129,7 @@ public class TempApkProvider extends ApkProvider {
final String memoryDbName = TempAppProvider.DB;
db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
db.execSQL(TempAppProvider.copyData(Schema.ApkTable.Cols.ALL_COLS, Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_vercode on " + getTableName() + " (" + ApkTable.Cols.VERSION_CODE + ");");
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_appId on " + getTableName() + " (" + ApkTable.Cols.APP_ID + ");");
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + ApkTable.Cols.IS_COMPATIBLE + ");");
}

View File

@ -209,11 +209,14 @@ public class TempAppProvider extends AppProvider {
private void ensureTempTableDetached(SQLiteDatabase db) {
try {
// Ideally we'd ask SQLite if the temp table is attached, but that is not possible.
// Instead, we resort to hackery:
// If the first statement does not throw an exception, then the temp db is attached and the second
// statement will detach the database.
db.rawQuery("SELECT * FROM " + DB + "." + getTableName() + " WHERE 0", null);
db.execSQL("DETACH DATABASE " + DB);
} catch (SQLiteException e) {
// We expect that most of the time the database will not exist unless an error occurred
// midway through the last update, The resulting exception is:
// android.database.sqlite.SQLiteException: no such database: temp_update_db (code 1)
} catch (SQLiteException ignored) {
}
}

View File

@ -278,7 +278,7 @@ public class SwapService extends Service {
if (!TextUtils.isEmpty(fingerprint)) {
values.put(Schema.RepoTable.Cols.FINGERPRINT, peer.getFingerprint());
}
values.put(Schema.RepoTable.Cols.IN_USE, true);
values.put(Schema.RepoTable.Cols.IN_USE, 1);
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
Uri uri = RepoProvider.Helper.insert(this, values);
repo = RepoProvider.Helper.get(this, uri);

View File

@ -142,7 +142,7 @@ public class SelectAppsView extends ListView implements
private void toggleAppSelected(int position) {
Cursor c = (Cursor) adapter.getItem(position);
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
if (getState().hasSelectedPackage(packageName)) {
getState().deselectPackage(packageName);
adapter.updateCheckedIndicatorView(position, false);
@ -176,7 +176,7 @@ public class SelectAppsView extends ListView implements
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItemAtPosition(i);
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
getState().ensureFDroidSelected();
for (String selected : getState().getAppsToSwap()) {
if (TextUtils.equals(packageName, selected)) {
@ -257,7 +257,7 @@ public class SelectAppsView extends ListView implements
TextView labelView = (TextView) view.findViewById(R.id.application_label);
ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.APPLICATION_LABEL));
Drawable icon;

View File

@ -158,7 +158,7 @@ public class Assert {
Uri uri = InstalledAppProvider.getAppUri(appId);
String[] projection = {
InstalledAppTable.Cols.PACKAGE_NAME,
InstalledAppTable.Cols.Package.NAME,
InstalledAppTable.Cols.VERSION_CODE,
InstalledAppTable.Cols.VERSION_NAME,
InstalledAppTable.Cols.APPLICATION_LABEL,
@ -171,7 +171,7 @@ public class Assert {
cursor.moveToFirst();
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME)));
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME)));
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_CODE)));
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_NAME)));
cursor.close();
@ -196,11 +196,16 @@ public class Assert {
values.putAll(additionalValues);
// Don't hard code to 1, let consumers override it in additionalValues then ask for it back.
int repoId = values.getAsInteger(AppMetadataTable.Cols.REPO_ID);
Uri uri = AppProvider.getContentUri();
context.getContentResolver().insert(uri, values);
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1,
AppMetadataTable.Cols.ALL);
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName,
repoId, AppMetadataTable.Cols.ALL);
assertNotNull(app);
return app;
}
public static App ensureApp(Context context, String packageName) {

View File

@ -37,7 +37,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
assertEquals(foundBefore.size(), 0);
ContentValues values = new ContentValues();
values.put(Cols.PACKAGE_NAME, "org.example.test-app");
values.put(Cols.Package.NAME, "org.example.test-app");
values.put(Cols.APPLICATION_LABEL, "Test App");
values.put(Cols.VERSION_CODE, 1021);
values.put(Cols.VERSION_NAME, "Longhorn");
@ -56,7 +56,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
assertEquals(cursor.getCount(), 1);
cursor.moveToFirst();
assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)));
assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
assertEquals("Test App", cursor.getString(cursor.getColumnIndex(Cols.APPLICATION_LABEL)));
assertEquals(1021, cursor.getInt(cursor.getColumnIndex(Cols.VERSION_CODE)));
assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(Cols.VERSION_NAME)));
@ -125,7 +125,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
Uri uri = InstalledAppProvider.getAppUri(packageName);
String[] projection = {
Cols.PACKAGE_NAME,
Cols.Package.NAME,
Cols.LAST_UPDATE_TIME,
};
@ -133,7 +133,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
assertNotNull(cursor);
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)));
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME));
assertTrue(lastUpdateTime > 0);
assertTrue(lastUpdateTime < System.currentTimeMillis());
@ -170,7 +170,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
ContentValues values = new ContentValues(3);
if (appId != null) {
values.put(Cols.PACKAGE_NAME, appId);
values.put(Cols.Package.NAME, appId);
}
values.put(Cols.APPLICATION_LABEL, "Mock app: " + appId);
values.put(Cols.VERSION_CODE, versionCode);

View File

@ -3,6 +3,8 @@ package org.fdroid.fdroid.data;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.support.annotation.Nullable;
public class InstalledAppTestUtils {
@ -13,12 +15,22 @@ public class InstalledAppTestUtils {
public static void install(Context context,
String packageName,
int versionCode, String versionName) {
install(context, packageName, versionCode, versionName, null);
}
public static void install(Context context,
String packageName,
int versionCode, String versionName,
@Nullable String signature) {
PackageInfo info = new PackageInfo();
info.packageName = packageName;
info.versionCode = versionCode;
info.versionName = versionName;
info.applicationInfo = new ApplicationInfo();
info.applicationInfo.publicSourceDir = "/tmp/mock-location";
if (signature != null) {
info.signatures = new Signature[]{new Signature(signature)};
}
String hashType = "sha256";
String hash = "00112233445566778899aabbccddeeff";
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);

View File

@ -0,0 +1,212 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentValues;
import android.content.Context;
import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.security.NoSuchAlgorithmException;
import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@RunWith(RobolectricTestRunner.class)
public class SuggestedVersionTest extends FDroidProviderTest {
private static final String FDROID_CERT = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; // NOCHECKSTYLE LineLength
private static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
private static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
private static final String FDROID_SIG;
private static final String UPSTREAM_SIG;
private static final String THIRD_PARTY_SIG;
static {
// Some code requires the full certificate (e.g. when we mock PackageInfo to give to the
// installed app provider), while others requires the hashed certificate (e.g. inserting
// into the apk provider directly, without the need to mock anything).
try {
FDROID_SIG = new Hasher("MD5", FDROID_CERT.getBytes()).getHash();
UPSTREAM_SIG = new Hasher("MD5", UPSTREAM_CERT.getBytes()).getHash();
THIRD_PARTY_SIG = new Hasher("MD5", THIRD_PARTY_CERT.getBytes()).getHash();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
@Before
public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
Preferences.setup(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
@Test
public void singleRepoSingleSig() {
App singleApp = insertApp(context, "single.app", "Single App (with beta)", 2, "https://beta.simple.repo");
insertApk(context, singleApp, 1, FDROID_SIG);
insertApk(context, singleApp, 2, FDROID_SIG);
insertApk(context, singleApp, 3, FDROID_SIG);
AppProvider.Helper.calcSuggestedApks(context);
App found2 = findApp(singleApp);
assertEquals(2, found2.suggestedVersionCode);
// By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should
// suggest the latest version (3).
Preferences.get().setUnstableUpdates(true);
AppProvider.Helper.calcSuggestedApks(context);
App found3 = findApp(singleApp);
assertEquals(3, found3.suggestedVersionCode);
}
private App findApp(App app) {
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), app.packageName, app.repoId);
}
@Test
public void singleRepoMultiSig() {
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
insertApk(context, unrelatedApp, 3, FDROID_SIG);
App singleApp = insertApp(context, "single.app", "Single App", 4, "https://simple.repo");
insertApk(context, singleApp, 1, FDROID_SIG);
insertApk(context, singleApp, 2, FDROID_SIG);
insertApk(context, singleApp, 3, FDROID_SIG);
insertApk(context, singleApp, 4, UPSTREAM_SIG);
insertApk(context, singleApp, 5, UPSTREAM_SIG);
AppProvider.Helper.calcSuggestedApks(context);
// Given we aren't installed yet, we don't care which signature.
// Just get as close to upstreamVersionCode as possible.
App suggestUpstream4 = findApp(singleApp);
assertEquals(4, suggestUpstream4.suggestedVersionCode);
// Now install v1 with the f-droid signature. In response, we should only suggest
// apps with that sig in the future. That is, version 4 from upstream is not considered.
InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT);
AppProvider.Helper.calcSuggestedApks(context);
App suggestFDroid3 = findApp(singleApp);
assertEquals(3, suggestFDroid3.suggestedVersionCode);
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
insertApk(context, singleApp, 4, FDROID_SIG);
insertApk(context, singleApp, 5, FDROID_SIG);
AppProvider.Helper.calcSuggestedApks(context);
App suggestFDroid4 = findApp(singleApp);
assertEquals(4, suggestFDroid4.suggestedVersionCode);
// Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should
// still become the suggested version now.
Preferences.get().setUnstableUpdates(true);
AppProvider.Helper.calcSuggestedApks(context);
App suggestFDroid5 = findApp(singleApp);
assertEquals(5, suggestFDroid5.suggestedVersionCode);
}
private void recalculateMetadata() {
AppProvider.Helper.calcSuggestedApks(context);
AppProvider.Helper.recalculatePreferredMetadata(context);
}
private App highestPriorityApp(String packageName) {
return AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName);
}
@Test
public void multiRepoMultiSig() {
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
insertApk(context, unrelatedApp, 3, FDROID_SIG);
App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 4, "https://main.repo");
App thirdPartyApp = insertApp(context, "single.app", "Single App (3rd party)", 4, "https://3rd-party.repo");
insertApk(context, mainApp, 1, FDROID_SIG);
insertApk(context, mainApp, 2, FDROID_SIG);
insertApk(context, mainApp, 3, FDROID_SIG);
insertApk(context, mainApp, 4, UPSTREAM_SIG);
insertApk(context, mainApp, 5, UPSTREAM_SIG);
insertApk(context, thirdPartyApp, 3, THIRD_PARTY_SIG);
insertApk(context, thirdPartyApp, 4, THIRD_PARTY_SIG);
insertApk(context, thirdPartyApp, 5, THIRD_PARTY_SIG);
insertApk(context, thirdPartyApp, 6, THIRD_PARTY_SIG);
recalculateMetadata();
// Given we aren't installed yet, we don't care which signature or even which repo.
// Just get as close to upstreamVersionCode as possible.
App suggestAnyVersion4 = highestPriorityApp("single.app");
assertEquals(4, suggestAnyVersion4.suggestedVersionCode);
// Now install v1 with the f-droid signature. In response, we should only suggest
// apps with that sig in the future. That is, version 4 from upstream is not considered.
InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT);
recalculateMetadata();
App suggestFDroid3 = highestPriorityApp("single.app");
assertEquals(3, suggestFDroid3.suggestedVersionCode);
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
insertApk(context, mainApp, 4, FDROID_SIG);
insertApk(context, mainApp, 5, FDROID_SIG);
recalculateMetadata();
App suggestFDroid4 = highestPriorityApp("single.app");
assertEquals(4, suggestFDroid4.suggestedVersionCode);
// Uninstalling the F-Droid build and installing v3 of the third party means we can now go
// back to suggesting version 4.
InstalledAppProviderService.deleteAppFromDb(context, "single.app");
InstalledAppTestUtils.install(context, "single.app", 3, "v3", THIRD_PARTY_CERT);
recalculateMetadata();
suggestAnyVersion4 = highestPriorityApp("single.app");
assertEquals(4, suggestAnyVersion4.suggestedVersionCode);
// Version 6 from the 3rd party repo is not the "upstreamVersionCode", but with beta updates
// it should still become the suggested version now.
Preferences.get().setUnstableUpdates(true);
recalculateMetadata();
App suggest3rdParty6 = highestPriorityApp("single.app");
assertEquals(6, suggest3rdParty6.suggestedVersionCode);
}
private void insertApk(Context context, App app, int versionCode, String signature) {
ContentValues values = new ContentValues();
values.put(Schema.ApkTable.Cols.SIGNATURE, signature);
Assert.insertApk(context, app, versionCode, values);
}
private App insertApp(Context context, String packageName, String appName, int upstreamVersionCode,
String repoUrl) {
Repo repo = ensureRepo(context, repoUrl);
ContentValues values = new ContentValues();
values.put(Cols.REPO_ID, repo.getId());
values.put(Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode);
return Assert.insertApp(context, packageName, appName, values);
}
private Repo ensureRepo(Context context, String repoUrl) {
Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl);
if (existing != null) {
return existing;
}
return RepoProviderTest.insertRepo(context, repoUrl, "", "", "");
}
}

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.updater;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
@ -177,7 +178,6 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
indexV0Details.apps.size();
System.out.println("total apps: " + apps.length + " " + indexV0Details.apps.size());
assertEquals(indexV0Details.apps.size(), apps.length);
assertEquals(apps.length, packages.size());
@ -336,13 +336,10 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
fields.remove(field);
}
if (fields.size() > 0) {
System.out.print(instance.getClass() + " has fields not setup for Jackson: ");
for (String field : fields) {
System.out.print("\"" + field + "\", ");
}
System.out.println("\nRead class javadoc for more info.");
String sb = String.valueOf(instance.getClass()) + " has fields not setup for Jackson: " +
TextUtils.join(", ", fields) + "\nRead class javadoc for more info.";
fail(sb);
}
assertEquals(0, fields.size());
}
@Test
@ -408,7 +405,6 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
}
private Repo parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {
System.out.println("parseRepo ");
parser.nextToken();
parser.nextToken();
ObjectReader repoReader = mapper.readerFor(Repo.class);

View File

@ -38,7 +38,7 @@ public class Issue763MultiRepo extends MultiRepoUpdaterTest {
public void setEnabled(Repo repo, boolean enabled) {
ContentValues values = new ContentValues(1);
values.put(Schema.RepoTable.Cols.IN_USE, enabled);
values.put(Schema.RepoTable.Cols.IN_USE, enabled ? 1 : 0);
RepoProvider.Helper.update(context, repo, values);
}