Merge branch 'issue-1059--preferred-sig' into 'master'

Support for " preferred sig"

See merge request !552
This commit is contained in:
Peter Serwylo 2017-07-05 23:58:18 +00:00
commit d37e25db21
17 changed files with 647 additions and 149 deletions

View File

@ -217,9 +217,14 @@ public class IndexV1Updater extends RepoUpdater {
if (packages != null) {
apks = packages.get(app.packageName);
}
if (apks == null) {
Log.i(TAG, "processIndexV1 empty packages");
apks = new ArrayList<Apk>(0);
apks = new ArrayList<>(0);
}
if (apks.size() > 0) {
app.preferredSigner = apks.get(0).sig;
}
if (appCount % 50 == 0) {

View File

@ -75,8 +75,15 @@ public class ApkProvider extends FDroidProvider {
return resolver.delete(uri, null, null);
}
/**
* Find an app which is closest to the version code suggested by the server, with some caveates:
* <ul>
* <li>If installed, limit to apks signed by the same signer as the installed apk.</li>
* <li>Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).</li>
* </ul>
*/
public static Apk findSuggestedApk(Context context, App app) {
return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.installedSig);
return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.getMostAppropriateSignature());
}
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) {

View File

@ -98,6 +98,8 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
private long id;
@JsonIgnore
private AppPrefs prefs;
@JsonIgnore
public String preferredSigner;
@JacksonInject("repoId")
public long repoId;
@ -286,6 +288,9 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
case Cols.SuggestedApk.VERSION_NAME:
suggestedVersionName = cursor.getString(i);
break;
case Cols.PREFERRED_SIGNER:
preferredSigner = cursor.getString(i);
break;
case Cols.SUGGESTED_VERSION_CODE:
suggestedVersionCode = cursor.getInt(i);
break;
@ -828,6 +833,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
values.put(Cols.FLATTR_ID, flattrID);
values.put(Cols.ADDED, Utils.formatDate(added, ""));
values.put(Cols.LAST_UPDATED, Utils.formatDate(lastUpdated, ""));
values.put(Cols.PREFERRED_SIGNER, preferredSigner);
values.put(Cols.SUGGESTED_VERSION_CODE, suggestedVersionCode);
values.put(Cols.UPSTREAM_VERSION_NAME, upstreamVersionName);
values.put(Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode);
@ -1003,6 +1009,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
dest.writeString(this.bitcoin);
dest.writeString(this.litecoin);
dest.writeString(this.flattrID);
dest.writeString(this.preferredSigner);
dest.writeString(this.upstreamVersionName);
dest.writeInt(this.upstreamVersionCode);
dest.writeString(this.suggestedVersionName);
@ -1050,6 +1057,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
this.bitcoin = in.readString();
this.litecoin = in.readString();
this.flattrID = in.readString();
this.preferredSigner = in.readString();
this.upstreamVersionName = in.readString();
this.upstreamVersionCode = in.readInt();
this.suggestedVersionName = in.readString();
@ -1090,4 +1098,24 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
return new App[size];
}
};
/**
* Choose the signature which we should encourage the user to install.
* Usually, we want the {@link #preferredSigner} rather than any random signature.
* However, if the app is installed, then we override this and instead want to only encourage
* the user to try and install versions with that signature (because thats all the OS will let
* them do).
* TODO: I don't think preferredSigner should ever be null, because if an app has apks then
* we should have chosen the first and used that. If so, then we should change to @NonNull and
* throw an exception if it is null.
*/
@Nullable
public String getMostAppropriateSignature() {
if (!TextUtils.isEmpty(installedSig)) {
return installedSig;
} else if (!TextUtils.isEmpty(preferredSigner)) {
return preferredSigner;
}
return null;
}
}

View File

@ -117,6 +117,11 @@ public class AppProvider extends FDroidProvider {
return app;
}
public static void calcSuggestedApk(Context context, String packageName) {
Uri uri = Uri.withAppendedPath(calcSuggestedApksUri(), packageName);
context.getContentResolver().update(uri, null, null, null);
}
public static void calcSuggestedApks(Context context) {
context.getContentResolver().update(calcSuggestedApksUri(), null, null, null);
}
@ -385,6 +390,7 @@ public class AppProvider extends FDroidProvider {
static {
MATCHER.addURI(getAuthority(), null, CODE_LIST);
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS, CALC_SUGGESTED_APKS);
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS + "/*", CALC_SUGGESTED_APKS);
MATCHER.addURI(getAuthority(), PATH_RECENTLY_UPDATED, RECENTLY_UPDATED);
MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES);
@ -879,7 +885,13 @@ public class AppProvider extends FDroidProvider {
throw new UnsupportedOperationException("Update not supported for " + uri + ".");
}
updateSuggestedApks();
List<String> segments = uri.getPathSegments();
if (segments.size() > 1) {
String packageName = segments.get(1);
updateSuggestedApk(packageName);
} else {
updateSuggestedApks();
}
getContext().getContentResolver().notifyChange(getCanUpdateUri(), null);
return 0;
}
@ -887,8 +899,8 @@ public class AppProvider extends FDroidProvider {
protected void updateAllAppDetails() {
updatePreferredMetadata();
updateCompatibleFlags();
updateSuggestedFromUpstream();
updateSuggestedFromLatest();
updateSuggestedFromUpstream(null);
updateSuggestedFromLatest(null);
updateIconUrls();
}
@ -909,8 +921,13 @@ public class AppProvider extends FDroidProvider {
* {@link android.app.IntentService} as described in https://gitlab.com/fdroid/fdroidclient/issues/520.
*/
protected void updateSuggestedApks() {
updateSuggestedFromUpstream();
updateSuggestedFromLatest();
updateSuggestedFromUpstream(null);
updateSuggestedFromLatest(null);
}
protected void updateSuggestedApk(String packageName) {
updateSuggestedFromUpstream(packageName);
updateSuggestedFromLatest(packageName);
}
private void updatePreferredMetadata() {
@ -964,9 +981,9 @@ public class AppProvider extends FDroidProvider {
* If the app is installed, then all apks signed by a different certificate are
* ignored for the purpose of this calculation.
*
* @see #updateSuggestedFromLatest()
* @see #updateSuggestedFromLatest(String)
*/
private void updateSuggestedFromUpstream() {
private void updateSuggestedFromUpstream(@Nullable String packageName) {
Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code.");
final String apk = getApkTableName();
@ -976,6 +993,14 @@ public class AppProvider extends FDroidProvider {
final boolean unstableUpdates = Preferences.get().getUnstableUpdates();
String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND ");
String restrictToApp = "";
String[] args = null;
if (packageName != null) {
restrictToApp = " AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") ";
args = new String[]{packageName};
}
// 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.
@ -1001,9 +1026,9 @@ public class AppProvider extends FDroidProvider {
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 ";
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 " + restrictToApp;
LoggingQuery.execSQL(db(), updateSql);
LoggingQuery.execSQL(db(), updateSql, args);
}
/**
@ -1014,15 +1039,28 @@ public class AppProvider extends FDroidProvider {
* out from the upstream vercode. In such a case, fall back to the simpler
* algorithm as if upstreamVercode was 0.
*
* @see #updateSuggestedFromUpstream()
* @see #updateSuggestedFromUpstream(String)
*/
private void updateSuggestedFromLatest() {
private void updateSuggestedFromLatest(@Nullable String packageName) {
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;
final String restrictToApps;
final String[] args;
if (packageName == null) {
restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL ";
args = null;
} else {
// Don't update an app with an upstream version code, because that would have been updated
// by updateSuggestedFromUpdate(packageName).
restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") ";
args = new String[]{packageName};
}
String updateSql =
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
@ -1033,9 +1071,9 @@ public class AppProvider extends FDroidProvider {
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 ";
" WHERE " + restrictToApps;
LoggingQuery.execSQL(db(), updateSql);
LoggingQuery.execSQL(db(), updateSql, args);
}
private void updateIconUrls() {

View File

@ -128,6 +128,7 @@ class DBHelper extends SQLiteOpenHelper {
+ AppMetadataTable.Cols.SOURCE_CODE + " text, "
+ AppMetadataTable.Cols.VIDEO + " string, "
+ AppMetadataTable.Cols.CHANGELOG + " text, "
+ AppMetadataTable.Cols.PREFERRED_SIGNER + " text,"
+ AppMetadataTable.Cols.SUGGESTED_VERSION_CODE + " text,"
+ AppMetadataTable.Cols.UPSTREAM_VERSION_NAME + " text,"
+ AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + " integer,"
@ -192,7 +193,7 @@ class DBHelper extends SQLiteOpenHelper {
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
+ " );";
protected static final int DB_VERSION = 71;
protected static final int DB_VERSION = 72;
private final Context context;
@ -278,6 +279,18 @@ class DBHelper extends SQLiteOpenHelper {
addWhatsNewAndVideo(db, oldVersion);
dropApkPrimaryKey(db, oldVersion);
addIntegerPrimaryKeyToInstalledApps(db, oldVersion);
addPreferredSignerToApp(db, oldVersion);
}
private void addPreferredSignerToApp(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 72) {
return;
}
if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.PREFERRED_SIGNER)) {
Log.i(TAG, "Adding preferred signer to app table.");
db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.PREFERRED_SIGNER + " text;");
}
}
private void addIntegerPrimaryKeyToInstalledApps(SQLiteDatabase db, int oldVersion) {

View File

@ -221,10 +221,17 @@ public class InstalledAppProvider extends FDroidProvider {
throw new UnsupportedOperationException("Delete not supported for " + uri + ".");
}
String packageName = uri.getLastPathSegment();
QuerySelection query = new QuerySelection(where, whereArgs);
query = query.add(queryAppSubQuery(uri.getLastPathSegment()));
query = query.add(queryAppSubQuery(packageName));
return db().delete(getTableName(), query.getSelection(), query.getArgs());
Utils.debugLog(TAG, "Deleting " + packageName);
int count = db().delete(getTableName(), query.getSelection(), query.getArgs());
Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName);
AppProvider.Helper.calcSuggestedApk(getContext(), packageName);
return count;
}
@Override
@ -234,15 +241,23 @@ 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);
if (!values.containsKey(Cols.Package.NAME)) {
throw new IllegalStateException("Package name not provided to InstalledAppProvider");
}
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);
Utils.debugLog(TAG, "Inserting/updating " + packageName);
db().replaceOrThrow(getTableName(), null, values);
Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName);
AppProvider.Helper.calcSuggestedApk(getContext(), packageName);
return getAppUri(values.getAsString(Cols.Package.NAME));
}

View File

@ -69,14 +69,21 @@ final class LoggingQuery {
private void execSQLInternal() {
if (BuildConfig.DEBUG) {
long startTime = System.currentTimeMillis();
db.execSQL(query);
long queryDuration = System.currentTimeMillis() - startTime;
executeSQLInternal();
if (queryDuration >= SLOW_QUERY_DURATION) {
logSlowQuery(queryDuration);
}
} else {
executeSQLInternal();
}
}
private void executeSQLInternal() {
if (queryArgs == null || queryArgs.length == 0) {
db.execSQL(query);
} else {
db.execSQL(query, queryArgs);
}
}
@ -131,7 +138,7 @@ final class LoggingQuery {
return new LoggingQuery(db, query, queryBuilderArgs).rawQuery();
}
public static void execSQL(SQLiteDatabase db, String sql) {
new LoggingQuery(db, sql, null).execSQLInternal();
public static void execSQL(SQLiteDatabase db, String sql, String[] queryArgs) {
new LoggingQuery(db, sql, queryArgs).execSQLInternal();
}
}

View File

@ -136,6 +136,7 @@ public interface Schema {
String BITCOIN = "bitcoinAddr";
String LITECOIN = "litecoinAddr";
String FLATTR_ID = "flattrID";
String PREFERRED_SIGNER = "preferredSigner";
String SUGGESTED_VERSION_CODE = "suggestedVercode";
String UPSTREAM_VERSION_NAME = "upstreamVersion";
String UPSTREAM_VERSION_CODE = "upstreamVercode";
@ -192,7 +193,7 @@ public interface Schema {
ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
FEATURE_GRAPHIC, PROMO_GRAPHIC, TV_BANNER, PHONE_SCREENSHOTS,
SEVEN_INCH_SCREENSHOTS, TEN_INCH_SCREENSHOTS, TV_SCREENSHOTS, WEAR_SCREENSHOTS,
SUGGESTED_VERSION_CODE,
PREFERRED_SIGNER, SUGGESTED_VERSION_CODE,
};
/**
@ -208,7 +209,7 @@ public interface Schema {
ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
FEATURE_GRAPHIC, PROMO_GRAPHIC, TV_BANNER, PHONE_SCREENSHOTS,
SEVEN_INCH_SCREENSHOTS, TEN_INCH_SCREENSHOTS, TV_SCREENSHOTS, WEAR_SCREENSHOTS,
SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME,
PREFERRED_SIGNER, SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME,
InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME,
InstalledApp.SIGNATURE, Package.PACKAGE_NAME,
};

View File

@ -178,9 +178,10 @@ public class AppDetailsRecyclerViewAdapter
private Apk getSuggestedApk() {
Apk curApk = null;
String appropriateSig = app.getMostAppropriateSignature();
for (int i = 0; i < versions.size(); i++) {
final Apk apk = versions.get(i);
if (apk.versionCode == app.suggestedVersionCode) {
if (apk.versionCode == app.suggestedVersionCode && TextUtils.equals(apk.sig, appropriateSig)) {
curApk = apk;
break;
}
@ -477,7 +478,7 @@ public class AppDetailsRecyclerViewAdapter
if (callbacks.isAppDownloading()) {
buttonPrimaryView.setText(R.string.downloading);
buttonPrimaryView.setEnabled(false);
} else if (!app.isInstalled() && app.suggestedVersionCode > 0 && versions.size() > 0) {
} else if (!app.isInstalled() && suggestedApk != null) {
// Check count > 0 due to incompatible apps resulting in an empty list.
callbacks.disableAndroidBeam();
// Set Install button and hide second button
@ -486,7 +487,7 @@ public class AppDetailsRecyclerViewAdapter
buttonPrimaryView.setEnabled(true);
} else if (app.isInstalled()) {
callbacks.enableAndroidBeam();
if (app.canAndWantToUpdate(context)) {
if (app.canAndWantToUpdate(context) && suggestedApk != null) {
buttonPrimaryView.setText(R.string.menu_upgrade);
buttonPrimaryView.setOnClickListener(onUpgradeClickListener);
} else {
@ -818,9 +819,12 @@ public class AppDetailsRecyclerViewAdapter
public void bindModel(final Apk apk) {
java.text.DateFormat df = DateFormat.getDateFormat(context);
boolean isSuggested = apk.versionCode == app.suggestedVersionCode &&
TextUtils.equals(apk.sig, app.getMostAppropriateSignature());
version.setText(context.getString(R.string.version)
+ " " + apk.versionName
+ (apk.versionCode == app.suggestedVersionCode ? "" : ""));
+ (isSuggested ? "" : ""));
status.setText(getInstalledStatus(apk));

View File

@ -2,9 +2,17 @@ package org.fdroid.fdroid;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ProviderInfo;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.RepoProviderTest;
import org.fdroid.fdroid.data.Schema;
import org.mockito.AdditionalAnswers;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
@ -15,7 +23,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
@ -25,6 +35,85 @@ public class TestUtils {
@SuppressWarnings("unused")
private static final String TAG = "TestUtils"; // NOPMD
/**
* This is the F-Droid signature used to sign the AdAway binaries used for the multiRepo.*.jar
* repos used by some tests.
*/
public static final String FDROID_CERT = "3082033c30820224a00302010202044e9c4ba6300d06092a864886f70d01010505003060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f6964301e170d3131313031373135333731305a170d3339303330343135333731305a3060310b300906035504061302554b310c300a060355040813034f5247310c300a060355040713034f524731133011060355040a130a6664726f69642e6f7267310f300d060355040b13064644726f6964310f300d060355040313064644726f696430820122300d06092a864886f70d01010105000382010f003082010a0282010100981b0aac96f1c66be3c21e773327ee8c4d3b18c75c548243f4cfedbe8ef0d3c6cc1b3b7b094ddd39cdf71d034ef2cd2d1e7bdca458801b04a531cbe7106a3575151375cb32177b017f81cc508f981a1809d0a417c6f3d59ddfa876c3d91874b1d59e08eaf757da13fb82f7e6f7340abc56f0ab672f02e957d446585931388b1affb6f43a16efc7f060df9c8da17c86899b19495114cc5939decd521e172b48e68c6ec03bc58776acd6a52fd61fd839d2a404df25ae79c2ccec2d9a07c9a1751c341e5e9b706b8e713bec2149e16f5ca15a1d6fe67d52ebb210995ee03d9416118fa9434f65ffe6d43dddfe3e2b0c54b94ea8e5a1031ed41856cd369da41dc6790203010001300d06092a864886f70d0101050500038201010080951aa68b5a2c7ac464b66078afd4826df96e2c10b612a441036e43aa923bfa55f26c61b5d94c2132877a3801c2394328f70b322f6308dbea6ed4f0f4897d73d13af9498277f60685239acd8922275544334d295b07245ef0ec924e1c35e8004d8d268d97c957078149cc5635f8977ce432a56278a03664a45a6be51319b0b5f3e27b2372ae859215e3f3d0f5c8b86d1a42f742abe4d224870d419600966e46d83ce41df04e315353f334378f0f994732a6c05d351b1bea66efc62471762d0f752d379966e8293fc5fe4150665427b0f3fb3a1b64c3b75128abadc02c3efa44c06e2d22ba8f1c3f4b782ac2da0d56307173093fde31215d26ab05714a12d696"; // NOCHECKSTYLE LineLength
public static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
public static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
public static final String FDROID_SIG;
public static final String UPSTREAM_SIG;
public static final String THIRD_PARTY_SIG;
static {
// Some code requires the full certificate (e.g. when we mock PackageInfo to give to the
// 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);
}
}
private static String formatSigForDebugging(String sig) {
String suffix;
// Can't use a switch statement here because *_SIG is not a constant, despite beign static final.
if (sig.equals(FDROID_SIG)) {
suffix = "F-Droid";
} else if (sig.equals(UPSTREAM_SIG)) {
suffix = "Upstream";
} else if (sig.equals(THIRD_PARTY_SIG)) {
suffix = "3rd Party";
} else {
suffix = "Unknown";
}
return sig + " [" + suffix + "]";
}
public static void assertSignaturesMatch(String message, String expected, String actual) {
assertEquals(message, formatSigForDebugging(expected), formatSigForDebugging(actual));
}
public static 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);
}
public static App insertApp(Context context, String packageName, String appName, int upstreamVersionCode,
String repoUrl) {
Repo repo = ensureRepo(context, repoUrl);
ContentValues values = new ContentValues();
values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId());
values.put(Schema.AppMetadataTable.Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode);
return Assert.insertApp(context, packageName, appName, values);
}
public static App insertApp(Context context, String packageName, String appName, int upstreamVersionCode,
Repo repo, String preferredSigner) {
ContentValues values = new ContentValues();
values.put(Schema.AppMetadataTable.Cols.REPO_ID, repo.getId());
values.put(Schema.AppMetadataTable.Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode);
values.put(Schema.AppMetadataTable.Cols.PREFERRED_SIGNER, preferredSigner);
return Assert.insertApp(context, packageName, appName, values);
}
public static Repo ensureRepo(Context context, String repoUrl) {
Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl);
if (existing != null) {
return existing;
}
return RepoProviderTest.insertRepo(context, repoUrl, "", "", "");
}
public static <T extends ContentProvider> void registerContentProvider(String authority, Class<T> providerClass) {
ProviderInfo info = new ProviderInfo();
info.authority = authority;
@ -73,4 +162,15 @@ public class TestUtils {
}
};
}
/**
* Normally apps/apks are only added to the database in response to a repo update.
* At the end of a repo update, the {@link AppProvider} updates the suggested apks and
* recalculates the preferred metadata for each app. Because we are adding apps/apks
* directly to the database, we need to simulate this update after inserting stuff.
*/
public static void updateDbAfterInserting(Context context) {
AppProvider.Helper.calcSuggestedApks(context);
AppProvider.Helper.recalculatePreferredMetadata(context);
}
}

View File

@ -8,8 +8,10 @@ import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.BuildConfig;
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;
@ -22,6 +24,7 @@ import java.util.List;
import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.fdroid.fdroid.Assert.insertApk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -38,6 +41,12 @@ public class AppProviderTest extends FDroidProviderTest {
@Before
public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
Preferences.setup(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
/**
@ -88,12 +97,20 @@ public class AppProviderTest extends FDroidProviderTest {
private void insertAndInstallApp(
String packageName, int installedVercode, int suggestedVercode,
boolean ignoreAll, int ignoreVercode) {
ContentValues values = new ContentValues(3);
values.put(Cols.SUGGESTED_VERSION_CODE, suggestedVercode);
App app = insertApp(contentResolver, context, packageName, "App: " + packageName, values);
App app = insertApp(contentResolver, context, packageName, "App: " + packageName, new ContentValues());
AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll));
InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode);
ContentValues certValue = new ContentValues(1);
certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG);
// Make sure that the relevant apks are also in the DB, or else the `install` method below will
// not be able to correctly calculate the suggested version o the apk.
insertApk(context, packageName, installedVercode, certValue);
if (installedVercode != suggestedVercode) {
insertApk(context, packageName, suggestedVercode, certValue);
}
InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode, TestUtils.FDROID_CERT);
}
@Test

View File

@ -5,8 +5,10 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -29,6 +31,12 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
@Before
public void setup() {
TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class);
Preferences.setup(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
@Test

View File

@ -21,15 +21,15 @@ public class InstalledAppTestUtils {
public static void install(Context context,
String packageName,
int versionCode, String versionName,
@Nullable String signature) {
@Nullable String signingCert) {
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)};
if (signingCert != null) {
info.signatures = new Signature[]{new Signature(signingCert)};
}
String hashType = "sha256";
String hash = "00112233445566778899aabbccddeeff";

View File

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

View File

@ -1,12 +1,8 @@
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;
@ -17,7 +13,6 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
@ -27,31 +22,18 @@ import static org.junit.Assert.assertEquals;
@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);
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@Override
public void onPreferenceChange() {
AppProvider.Helper.calcSuggestedApks(context);
}
});
}
@After
@ -61,10 +43,12 @@ public class SuggestedVersionTest extends FDroidProviderTest {
@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);
App singleApp = TestUtils.insertApp(
context, "single.app", "Single App (with beta)", 2, "https://beta.simple.repo");
TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG);
TestUtils.updateDbAfterInserting(context);
assertSuggested("single.app", 2);
// By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should
@ -75,15 +59,16 @@ public class SuggestedVersionTest extends FDroidProviderTest {
@Test
public void singleRepoMultiSig() {
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
insertApk(context, unrelatedApp, 3, FDROID_SIG);
App unrelatedApp = TestUtils.insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
TestUtils.insertApk(context, unrelatedApp, 3, TestUtils.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);
App singleApp = TestUtils.insertApp(context, "single.app", "Single App", 4, "https://simple.repo");
TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 4, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, singleApp, 5, TestUtils.UPSTREAM_SIG);
TestUtils.updateDbAfterInserting(context);
// Given we aren't installed yet, we don't care which signature.
// Just get as close to upstreamVersionCode as possible.
@ -91,38 +76,41 @@ public class SuggestedVersionTest extends FDroidProviderTest {
// 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);
assertSuggested("single.app", 3, FDROID_SIG, 1);
InstalledAppTestUtils.install(context, "single.app", 1, "v1", TestUtils.FDROID_CERT);
assertSuggested("single.app", 3, TestUtils.FDROID_SIG, 1);
// 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);
assertSuggested("single.app", 4, FDROID_SIG, 1);
TestUtils.insertApk(context, singleApp, 4, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, singleApp, 5, TestUtils.FDROID_SIG);
TestUtils.updateDbAfterInserting(context);
assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1);
// 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);
assertSuggested("single.app", 5, FDROID_SIG, 1);
assertSuggested("single.app", 5, TestUtils.FDROID_SIG, 1);
}
@Test
public void multiRepoMultiSig() {
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
insertApk(context, unrelatedApp, 3, FDROID_SIG);
App unrelatedApp = TestUtils.insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
TestUtils.insertApk(context, unrelatedApp, 3, TestUtils.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");
App mainApp = TestUtils.insertApp(context, "single.app", "Single App (Main repo)", 4, "https://main.repo");
App thirdPartyApp = TestUtils.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);
TestUtils.insertApk(context, mainApp, 1, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 2, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 3, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 4, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, mainApp, 5, TestUtils.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);
TestUtils.insertApk(context, thirdPartyApp, 3, TestUtils.THIRD_PARTY_SIG);
TestUtils.insertApk(context, thirdPartyApp, 4, TestUtils.THIRD_PARTY_SIG);
TestUtils.insertApk(context, thirdPartyApp, 5, TestUtils.THIRD_PARTY_SIG);
TestUtils.insertApk(context, thirdPartyApp, 6, TestUtils.THIRD_PARTY_SIG);
TestUtils.updateDbAfterInserting(context);
// Given we aren't installed yet, we don't care which signature or even which repo.
// Just get as close to upstreamVersionCode as possible.
@ -130,46 +118,48 @@ public class SuggestedVersionTest extends FDroidProviderTest {
// 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);
assertSuggested("single.app", 3, FDROID_SIG, 1);
InstalledAppTestUtils.install(context, "single.app", 1, "v1", TestUtils.FDROID_CERT);
assertSuggested("single.app", 3, TestUtils.FDROID_SIG, 1);
// 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);
assertSuggested("single.app", 4, FDROID_SIG, 1);
TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG);
TestUtils.updateDbAfterInserting(context);
assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1);
// 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);
assertSuggested("single.app", 4, THIRD_PARTY_SIG, 3);
InstalledAppTestUtils.install(context, "single.app", 3, "v3", TestUtils.THIRD_PARTY_CERT);
assertSuggested("single.app", 4, TestUtils.THIRD_PARTY_SIG, 3);
// 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);
assertSuggested("single.app", 6, THIRD_PARTY_SIG, 3);
assertSuggested("single.app", 6, TestUtils.THIRD_PARTY_SIG, 3);
}
/**
* This is specifically for the {@link AppProvider.Helper#findCanUpdate(Context, String[])} method used by
* the {@link org.fdroid.fdroid.UpdateService#showAppUpdatesNotification(List)} method. We need to ensure
* that we don't prompt people to update to the wrong sig after an update.
* This is specifically for the {@link AppProvider.Helper#findCanUpdate(android.content.Context, String[])}
* method used by the {@link org.fdroid.fdroid.UpdateService#showAppUpdatesNotification(List)} method.
* We need to ensure that we don't prompt people to update to the wrong sig after an update.
*/
@Test
public void dontSuggestUpstreamVersions() {
// By setting the "upstreamVersionCode" to 0, we are letting F-Droid choose the highest compatible version.
App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 0, "https://main.repo");
App mainApp = TestUtils.insertApp(context, "single.app", "Single App (Main repo)", 0, "https://main.repo");
insertApk(context, mainApp, 1, FDROID_SIG);
insertApk(context, mainApp, 2, FDROID_SIG);
insertApk(context, mainApp, 3, FDROID_SIG);
insertApk(context, mainApp, 4, FDROID_SIG);
insertApk(context, mainApp, 5, FDROID_SIG);
TestUtils.insertApk(context, mainApp, 1, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 2, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 3, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG);
TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG);
insertApk(context, mainApp, 4, UPSTREAM_SIG);
insertApk(context, mainApp, 5, UPSTREAM_SIG);
insertApk(context, mainApp, 6, UPSTREAM_SIG);
insertApk(context, mainApp, 7, UPSTREAM_SIG);
TestUtils.insertApk(context, mainApp, 4, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, mainApp, 5, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, mainApp, 6, TestUtils.UPSTREAM_SIG);
TestUtils.insertApk(context, mainApp, 7, TestUtils.UPSTREAM_SIG);
TestUtils.updateDbAfterInserting(context);
// If the user was to manually install the app, they should be suggested version 7 from upstream...
assertSuggested("single.app", 7);
@ -178,13 +168,13 @@ public class SuggestedVersionTest extends FDroidProviderTest {
assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL));
// After installing an early F-Droid version, we should then suggest the latest F-Droid version.
InstalledAppTestUtils.install(context, "single.app", 2, "v2", FDROID_CERT);
assertSuggested("single.app", 5, FDROID_SIG, 2);
InstalledAppTestUtils.install(context, "single.app", 2, "v2", TestUtils.FDROID_CERT);
assertSuggested("single.app", 5, TestUtils.FDROID_SIG, 2);
// However once we've reached the maximum F-Droid version, then we should not suggest higher versions
// with different signatures.
InstalledAppProviderService.deleteAppFromDb(context, "single.app");
InstalledAppTestUtils.install(context, "single.app", 5, "v5", FDROID_CERT);
InstalledAppTestUtils.install(context, "single.app", 5, "v5", TestUtils.FDROID_CERT);
assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL));
}
@ -204,9 +194,6 @@ public class SuggestedVersionTest extends FDroidProviderTest {
* apk is not checked.
*/
public void assertSuggested(String packageName, int suggestedVersion, String installedSig, int installedVersion) {
AppProvider.Helper.calcSuggestedApks(context);
AppProvider.Helper.recalculatePreferredMetadata(context);
App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName);
assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode);
assertEquals("Installed signature on App", installedSig, suggestedApp.installedSig);
@ -230,28 +217,4 @@ public class SuggestedVersionTest extends FDroidProviderTest {
}
}
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

@ -263,6 +263,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
"installedSig",
"installedVersionCode",
"installedVersionName",
"preferredSigner",
"prefs",
"TAG",
};

View File

@ -7,6 +7,7 @@ import android.util.Log;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.RepoUpdater;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
@ -246,7 +247,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
}
private void assertCanUpdate(String packageName, int installedVersion, int expectedUpdateVersion) {
InstalledAppTestUtils.install(context, packageName, installedVersion, "v" + installedVersion);
InstalledAppTestUtils.install(context, packageName, installedVersion, "v" + installedVersion, TestUtils.FDROID_CERT);
List<App> appsToUpdate = AppProvider.Helper.findCanUpdate(context, AppMetadataTable.Cols.ALL);
assertEquals(1, appsToUpdate.size());
assertEquals(installedVersion, appsToUpdate.get(0).installedVersionCode);