Merge branch 'issue-1059--preferred-sig' into 'master'
Support for " preferred sig" See merge request !552
This commit is contained in:
commit
d37e25db21
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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, "", "", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -263,6 +263,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
||||
"installedSig",
|
||||
"installedVersionCode",
|
||||
"installedVersionName",
|
||||
"preferredSigner",
|
||||
"prefs",
|
||||
"TAG",
|
||||
};
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user