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:
commit
d54788aafa
@ -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() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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 + " (" +
|
||||
|
@ -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:
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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 + ");");
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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, "", "", "");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user