Merge branch 'multi-sig-tests' into 'master'
Tests and slight improvements for multi-sig support when calculating suggested versions. See merge request !534
This commit is contained in:
commit
d54788aafa
@ -6,6 +6,7 @@ import android.os.Build;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.AssetUtils;
|
import org.fdroid.fdroid.AssetUtils;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
@ -30,6 +31,8 @@ import static org.junit.Assume.assumeTrue;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class FileCompatTest {
|
public class FileCompatTest {
|
||||||
|
|
||||||
|
private static final String TAG = "FileCompatTest";
|
||||||
|
|
||||||
private SanitizedFile sourceFile;
|
private SanitizedFile sourceFile;
|
||||||
private SanitizedFile destFile;
|
private SanitizedFile destFile;
|
||||||
|
|
||||||
@ -47,11 +50,11 @@ public class FileCompatTest {
|
|||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
if (!sourceFile.delete()) {
|
if (!sourceFile.delete()) {
|
||||||
System.out.println("Can't delete " + sourceFile.getAbsolutePath() + ".");
|
Log.w(TAG, "Can't delete " + sourceFile.getAbsolutePath() + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!destFile.delete()) {
|
if (!destFile.delete()) {
|
||||||
System.out.println("Can't delete " + destFile.getAbsolutePath() + ".");
|
Log.w(TAG, "Can't delete " + destFile.getAbsolutePath() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,16 +46,12 @@ public class HttpDownloaderTest {
|
|||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
String urlString = "https://f-droid.org/repo/index.jar";
|
String urlString = "https://f-droid.org/repo/index.jar";
|
||||||
receivedProgress = false;
|
receivedProgress = false;
|
||||||
System.out.println("downloadUninterruptedTestWithProgress: " + urlString);
|
|
||||||
receivedProgress = false;
|
|
||||||
URL url = new URL(urlString);
|
URL url = new URL(urlString);
|
||||||
File destFile = File.createTempFile("dl-", "");
|
File destFile = File.createTempFile("dl-", "");
|
||||||
final HttpDownloader httpDownloader = new HttpDownloader(url, destFile);
|
final HttpDownloader httpDownloader = new HttpDownloader(url, destFile);
|
||||||
httpDownloader.setListener(new ProgressListener() {
|
httpDownloader.setListener(new ProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
||||||
System.out.println("DownloaderProgressListener.sendProgress "
|
|
||||||
+ sourceUrl + " " + bytesRead + " / " + totalBytes);
|
|
||||||
receivedProgress = true;
|
receivedProgress = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -118,7 +114,6 @@ public class HttpDownloaderTest {
|
|||||||
httpDownloader.setListener(new ProgressListener() {
|
httpDownloader.setListener(new ProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
||||||
System.out.println("DownloaderProgressListener.sendProgress " + bytesRead + " / " + totalBytes);
|
|
||||||
receivedProgress = true;
|
receivedProgress = true;
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES);
|
return preferences.getBoolean(PREF_UNSTABLE_UPDATES, DEFAULT_UNSTABLE_UPDATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUnstableUpdates(boolean value) {
|
||||||
|
preferences.edit().putBoolean(PREF_UNSTABLE_UPDATES, value).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isKeepingInstallHistory() {
|
public boolean isKeepingInstallHistory() {
|
||||||
return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, DEFAULT_KEEP_INSTALL_HISTORY);
|
return preferences.getBoolean(PREF_KEEP_INSTALL_HISTORY, DEFAULT_KEEP_INSTALL_HISTORY);
|
||||||
}
|
}
|
||||||
|
@ -379,9 +379,6 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
} else {
|
} else {
|
||||||
removeRequestedPermission(attributes.getValue("name"));
|
removeRequestedPermission(attributes.getValue("name"));
|
||||||
}
|
}
|
||||||
} else if ("uses-feature".equals(localName) && curapk != null) {
|
|
||||||
System.out.println("TODO startElement " + uri + " " + localName + " " + qName);
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
curchars.setLength(0);
|
curchars.setLength(0);
|
||||||
}
|
}
|
||||||
|
@ -250,7 +250,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
join(
|
join(
|
||||||
InstalledAppTable.NAME,
|
InstalledAppTable.NAME,
|
||||||
"installed",
|
"installed",
|
||||||
"installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
|
"installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID);
|
||||||
requiresInstalledTable = true;
|
requiresInstalledTable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +270,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
leftJoin(
|
leftJoin(
|
||||||
InstalledAppTable.NAME,
|
InstalledAppTable.NAME,
|
||||||
"installed",
|
"installed",
|
||||||
"installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
|
"installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID);
|
||||||
requiresInstalledTable = true;
|
requiresInstalledTable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -953,50 +953,49 @@ public class AppProvider extends FDroidProvider {
|
|||||||
* with the closest version code to that, without going over.
|
* with the closest version code to that, without going over.
|
||||||
* If the app is not compatible at all (i.e. no versions were compatible)
|
* If the app is not compatible at all (i.e. no versions were compatible)
|
||||||
* then we take the highest, otherwise we take the highest compatible version.
|
* then we take the highest, otherwise we take the highest compatible version.
|
||||||
|
* If the app is installed, then all apks signed by a different certificate are
|
||||||
|
* ignored for the purpose of this calculation.
|
||||||
|
*
|
||||||
|
* @see #updateSuggestedFromLatest()
|
||||||
*/
|
*/
|
||||||
private void updateSuggestedFromUpstream() {
|
private void updateSuggestedFromUpstream() {
|
||||||
Utils.debugLog(TAG, "Calculating suggested versions for all apps which specify an upstream version code.");
|
Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code.");
|
||||||
|
|
||||||
final String apk = getApkTableName();
|
final String apk = getApkTableName();
|
||||||
final String app = getTableName();
|
final String app = getTableName();
|
||||||
|
final String installed = InstalledAppTable.NAME;
|
||||||
|
|
||||||
final boolean unstableUpdates = Preferences.get().getUnstableUpdates();
|
final boolean unstableUpdates = Preferences.get().getUnstableUpdates();
|
||||||
String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND ");
|
String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND ");
|
||||||
|
|
||||||
|
// The join onto `appForThisApk` is to ensure that the MAX(apk.versionCode) is chosen from
|
||||||
|
// all apps regardless of repo. If we joined directly onto the outer `app` table we are
|
||||||
|
// in the process of updating, then it would be limited to only apks from the same repo.
|
||||||
|
// By adding the extra join, and then joining based on the packageId of this inner app table
|
||||||
|
// and the app table we are updating, we take into account all apks for this app.
|
||||||
|
|
||||||
|
// The check apk.sig = COALESCE(installed.sig, apk.sig) would ideally be better written as:
|
||||||
|
// `installedSig IS NULL OR installedSig = apk.sig`
|
||||||
|
// however that would require a separate sub query for each `installedSig` which is more
|
||||||
|
// expensive. Using a COALESCE is a less expressive way to write the same thing with only
|
||||||
|
// a single subquery.
|
||||||
|
// Also note that the `installedSig IS NULL` is not because there is a `NULL` entry in the
|
||||||
|
// installed table (this is impossible), but rather because the subselect above returned
|
||||||
|
// zero rows.
|
||||||
String updateSql =
|
String updateSql =
|
||||||
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
|
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
|
||||||
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
|
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
|
||||||
" FROM " + apk +
|
" FROM " + apk +
|
||||||
|
" JOIN " + app + " AS appForThisApk ON (appForThisApk." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID + ") " +
|
||||||
|
" LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " +
|
||||||
" WHERE " +
|
" WHERE " +
|
||||||
joinToApksRegardlessOfRepo() + " AND " +
|
app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " +
|
||||||
|
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
|
||||||
restrictToStable +
|
restrictToStable +
|
||||||
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " +
|
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " +
|
||||||
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 ";
|
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 ";
|
||||||
|
|
||||||
db().execSQL(updateSql);
|
LoggingQuery.execSQL(db(), updateSql);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that when we select a list of {@link ApkTable} rows for which to calculate the
|
|
||||||
* {@link Cols#SUGGESTED_VERSION_CODE}, that we select all apks belonging to the same package,
|
|
||||||
* regardless of which repo they come from. We can't just join {@link ApkTable} onto the
|
|
||||||
* {@link AppMetadataTable}, because the {@link AppMetadataTable} table is specific to a repo.
|
|
||||||
*
|
|
||||||
* This is required so that apps always have the highest possible
|
|
||||||
* {@link Cols#SUGGESTED_VERSION_CODE}, regardless of the repository priorities. Without this,
|
|
||||||
* then each {@link AppMetadataTable} row will have a different {@link Cols#SUGGESTED_VERSION_CODE}
|
|
||||||
* depending on which repo it came from. With this, each {@link AppMetadataTable} row has the
|
|
||||||
* same {@link Cols#SUGGESTED_VERSION_CODE}, even if that version is from a different repo.
|
|
||||||
*/
|
|
||||||
private String joinToApksRegardlessOfRepo() {
|
|
||||||
final String apk = getApkTableName();
|
|
||||||
final String app = getTableName();
|
|
||||||
|
|
||||||
return app + "." + Cols.PACKAGE_ID + " = (" +
|
|
||||||
" SELECT innerAppName." + Cols.PACKAGE_ID +
|
|
||||||
" FROM " + app + " as innerAppName " +
|
|
||||||
" WHERE innerAppName." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID +
|
|
||||||
") ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1006,23 +1005,29 @@ public class AppProvider extends FDroidProvider {
|
|||||||
* If the suggested version is null, it means that we could not figure it
|
* If the suggested version is null, it means that we could not figure it
|
||||||
* out from the upstream vercode. In such a case, fall back to the simpler
|
* out from the upstream vercode. In such a case, fall back to the simpler
|
||||||
* algorithm as if upstreamVercode was 0.
|
* algorithm as if upstreamVercode was 0.
|
||||||
|
*
|
||||||
|
* @see #updateSuggestedFromUpstream()
|
||||||
*/
|
*/
|
||||||
private void updateSuggestedFromLatest() {
|
private void updateSuggestedFromLatest() {
|
||||||
Utils.debugLog(TAG, "Calculating suggested versions for all apps which don't specify an upstream version code.");
|
Utils.debugLog(TAG, "Calculating suggested versions for all apps which don't specify an upstream version code.");
|
||||||
|
|
||||||
final String apk = getApkTableName();
|
final String apk = getApkTableName();
|
||||||
final String app = getTableName();
|
final String app = getTableName();
|
||||||
|
final String installed = InstalledAppTable.NAME;
|
||||||
|
|
||||||
String updateSql =
|
String updateSql =
|
||||||
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
|
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
|
||||||
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
|
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
|
||||||
" FROM " + apk +
|
" FROM " + apk +
|
||||||
|
" JOIN " + app + " AS appForThisApk ON (appForThisApk." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID + ") " +
|
||||||
|
" LEFT JOIN " + installed + " ON (" + installed + "." + InstalledAppTable.Cols.PACKAGE_ID + " = " + app + "." + Cols.PACKAGE_ID + ") " +
|
||||||
" WHERE " +
|
" WHERE " +
|
||||||
joinToApksRegardlessOfRepo() + " AND " +
|
app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " +
|
||||||
|
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
|
||||||
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + ApkTable.Cols.IS_COMPATIBLE + " = 1 ) ) " +
|
" ( " + 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 COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL ";
|
||||||
|
|
||||||
db().execSQL(updateSql);
|
LoggingQuery.execSQL(db(), updateSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateIconUrls() {
|
private void updateIconUrls() {
|
||||||
|
@ -108,8 +108,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
+ ApkTable.Cols.ADDED_DATE + " string, "
|
+ ApkTable.Cols.ADDED_DATE + " string, "
|
||||||
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
|
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
|
||||||
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
|
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
|
||||||
+ ApkTable.Cols.ANTI_FEATURES + " string, "
|
+ ApkTable.Cols.ANTI_FEATURES + " string"
|
||||||
+ "PRIMARY KEY (" + ApkTable.Cols.APP_ID + ", " + ApkTable.Cols.VERSION_CODE + ", " + ApkTable.Cols.REPO_ID + ")"
|
|
||||||
+ ");";
|
+ ");";
|
||||||
|
|
||||||
static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
|
static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
|
||||||
@ -183,7 +182,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private static final String CREATE_TABLE_INSTALLED_APP = "CREATE TABLE " + InstalledAppTable.NAME
|
private static final String CREATE_TABLE_INSTALLED_APP = "CREATE TABLE " + InstalledAppTable.NAME
|
||||||
+ " ( "
|
+ " ( "
|
||||||
+ InstalledAppTable.Cols.PACKAGE_NAME + " TEXT NOT NULL PRIMARY KEY, "
|
+ InstalledAppTable.Cols.PACKAGE_ID + " INT NOT NULL UNIQUE, "
|
||||||
+ InstalledAppTable.Cols.VERSION_CODE + " INT NOT NULL, "
|
+ InstalledAppTable.Cols.VERSION_CODE + " INT NOT NULL, "
|
||||||
+ InstalledAppTable.Cols.VERSION_NAME + " TEXT NOT NULL, "
|
+ InstalledAppTable.Cols.VERSION_NAME + " TEXT NOT NULL, "
|
||||||
+ InstalledAppTable.Cols.APPLICATION_LABEL + " TEXT NOT NULL, "
|
+ InstalledAppTable.Cols.APPLICATION_LABEL + " TEXT NOT NULL, "
|
||||||
@ -193,7 +192,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
|
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
|
||||||
+ " );";
|
+ " );";
|
||||||
|
|
||||||
protected static final int DB_VERSION = 69;
|
protected static final int DB_VERSION = 71;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@ -277,6 +276,40 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
addIndexV1AppFields(db, oldVersion);
|
addIndexV1AppFields(db, oldVersion);
|
||||||
recalculatePreferredMetadata(db, oldVersion);
|
recalculatePreferredMetadata(db, oldVersion);
|
||||||
addWhatsNewAndVideo(db, oldVersion);
|
addWhatsNewAndVideo(db, oldVersion);
|
||||||
|
dropApkPrimaryKey(db, oldVersion);
|
||||||
|
addIntegerPrimaryKeyToInstalledApps(db, oldVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addIntegerPrimaryKeyToInstalledApps(SQLiteDatabase db, int oldVersion) {
|
||||||
|
if (oldVersion >= 71) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Replacing primary key on installed app table with integer for performance.");
|
||||||
|
|
||||||
|
db.beginTransaction();
|
||||||
|
try {
|
||||||
|
if (tableExists(db, Schema.InstalledAppTable.NAME)) {
|
||||||
|
db.execSQL("DROP TABLE " + Schema.InstalledAppTable.NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execSQL(CREATE_TABLE_INSTALLED_APP);
|
||||||
|
ensureIndexes(db);
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dropApkPrimaryKey(SQLiteDatabase db, int oldVersion) {
|
||||||
|
if (oldVersion >= 70) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionCode + repo is no longer a valid primary key given a repo can have multiple apks
|
||||||
|
// with the same versionCode, signed by different certificates.
|
||||||
|
Log.i(TAG, "Dropping composite primary key on apk table in favour of sqlite's rowid");
|
||||||
|
resetTransient(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWhatsNewAndVideo(SQLiteDatabase db, int oldVersion) {
|
private void addWhatsNewAndVideo(SQLiteDatabase db, int oldVersion) {
|
||||||
@ -1050,9 +1083,11 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ");");
|
AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ");");
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME);
|
if (columnExists(db, InstalledAppTable.NAME, InstalledAppTable.Cols.PACKAGE_ID)) {
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_appId_vercode on " + InstalledAppTable.NAME + " (" +
|
Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME);
|
||||||
InstalledAppTable.Cols.PACKAGE_NAME + ", " + InstalledAppTable.Cols.VERSION_CODE + ");");
|
db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_packageId_vercode on " + InstalledAppTable.NAME + " (" +
|
||||||
|
InstalledAppTable.Cols.PACKAGE_ID + ", " + InstalledAppTable.Cols.VERSION_CODE + ");");
|
||||||
|
}
|
||||||
|
|
||||||
Utils.debugLog(TAG, "Ensuring indexes exist for " + RepoTable.NAME);
|
Utils.debugLog(TAG, "Ensuring indexes exist for " + RepoTable.NAME);
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS repo_id_isSwap on " + RepoTable.NAME + " (" +
|
db.execSQL("CREATE INDEX IF NOT EXISTS repo_id_isSwap on " + RepoTable.NAME + " (" +
|
||||||
|
@ -24,7 +24,7 @@ public class InstalledApp extends ValueObject {
|
|||||||
case Schema.InstalledAppTable.Cols._ID:
|
case Schema.InstalledAppTable.Cols._ID:
|
||||||
id = cursor.getLong(i);
|
id = cursor.getLong(i);
|
||||||
break;
|
break;
|
||||||
case Schema.InstalledAppTable.Cols.PACKAGE_NAME:
|
case Schema.InstalledAppTable.Cols.Package.NAME:
|
||||||
packageName = cursor.getString(i);
|
packageName = cursor.getString(i);
|
||||||
break;
|
break;
|
||||||
case Schema.InstalledAppTable.Cols.VERSION_CODE:
|
case Schema.InstalledAppTable.Cols.VERSION_CODE:
|
||||||
|
@ -10,6 +10,7 @@ import android.content.res.Resources;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
@ -41,7 +42,7 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
while (!cursor.isAfterLast()) {
|
while (!cursor.isAfterLast()) {
|
||||||
cachedInfo.put(
|
cachedInfo.put(
|
||||||
cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)),
|
cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)),
|
||||||
cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME))
|
cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME))
|
||||||
);
|
);
|
||||||
cursor.moveToNext();
|
cursor.moveToNext();
|
||||||
@ -136,7 +137,17 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private QuerySelection queryApp(String packageName) {
|
private QuerySelection queryApp(String packageName) {
|
||||||
return new QuerySelection(Cols.PACKAGE_NAME + " = ?", new String[]{packageName});
|
return new QuerySelection(Cols.Package.NAME + " = ?", new String[]{packageName});
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuerySelection queryAppSubQuery(String packageName) {
|
||||||
|
String pkg = Schema.PackageTable.NAME;
|
||||||
|
String subQuery = "(" +
|
||||||
|
" SELECT " + pkg + "." + Schema.PackageTable.Cols.ROW_ID +
|
||||||
|
" FROM " + pkg +
|
||||||
|
" WHERE " + pkg + "." + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?)";
|
||||||
|
String query = Cols.PACKAGE_ID + " = " + subQuery;
|
||||||
|
return new QuerySelection(query, new String[]{packageName});
|
||||||
}
|
}
|
||||||
|
|
||||||
private QuerySelection querySearch(String query) {
|
private QuerySelection querySearch(String query) {
|
||||||
@ -144,6 +155,26 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
new String[]{"%" + query + "%"});
|
new String[]{"%" + query + "%"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class QueryBuilder extends org.fdroid.fdroid.data.QueryBuilder {
|
||||||
|
@Override
|
||||||
|
protected String getRequiredTables() {
|
||||||
|
String pkg = Schema.PackageTable.NAME;
|
||||||
|
String installed = InstalledAppTable.NAME;
|
||||||
|
return installed + " JOIN " + pkg +
|
||||||
|
" ON (" + pkg + "." + Schema.PackageTable.Cols.ROW_ID + " = " +
|
||||||
|
installed + "." + Cols.PACKAGE_ID + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addField(String field) {
|
||||||
|
if (TextUtils.equals(field, Cols.Package.NAME)) {
|
||||||
|
appendField(Schema.PackageTable.Cols.PACKAGE_NAME, Schema.PackageTable.NAME, field);
|
||||||
|
} else {
|
||||||
|
appendField(field, InstalledAppTable.NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection,
|
public Cursor query(Uri uri, String[] projection,
|
||||||
String customSelection, String[] selectionArgs, String sortOrder) {
|
String customSelection, String[] selectionArgs, String sortOrder) {
|
||||||
@ -170,8 +201,15 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
throw new UnsupportedOperationException(message);
|
throw new UnsupportedOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor cursor = db().query(getTableName(), projection,
|
QueryBuilder query = new QueryBuilder();
|
||||||
selection.getSelection(), selection.getArgs(), null, null, sortOrder);
|
query.addFields(projection);
|
||||||
|
if (projection.length == 0) {
|
||||||
|
query.addField(Cols._ID);
|
||||||
|
}
|
||||||
|
query.addSelection(selection);
|
||||||
|
query.addOrderBy(sortOrder);
|
||||||
|
|
||||||
|
Cursor cursor = db().rawQuery(query.toString(), selection.getArgs());
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@ -184,7 +222,7 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QuerySelection query = new QuerySelection(where, whereArgs);
|
QuerySelection query = new QuerySelection(where, whereArgs);
|
||||||
query = query.add(queryApp(uri.getLastPathSegment()));
|
query = query.add(queryAppSubQuery(uri.getLastPathSegment()));
|
||||||
|
|
||||||
return db().delete(getTableName(), query.getSelection(), query.getArgs());
|
return db().delete(getTableName(), query.getSelection(), query.getArgs());
|
||||||
}
|
}
|
||||||
@ -196,9 +234,16 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
throw new UnsupportedOperationException("Insert not supported for " + uri + ".");
|
throw new UnsupportedOperationException("Insert not supported for " + uri + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.containsKey(Cols.Package.NAME)) {
|
||||||
|
String packageName = values.getAsString(Cols.Package.NAME);
|
||||||
|
long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName);
|
||||||
|
values.remove(Cols.Package.NAME);
|
||||||
|
values.put(Cols.PACKAGE_ID, packageId);
|
||||||
|
}
|
||||||
|
|
||||||
verifyVersionNameNotNull(values);
|
verifyVersionNameNotNull(values);
|
||||||
db().replaceOrThrow(getTableName(), null, values);
|
db().replaceOrThrow(getTableName(), null, values);
|
||||||
return getAppUri(values.getAsString(Cols.PACKAGE_NAME));
|
return getAppUri(values.getAsString(Cols.Package.NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,7 +288,7 @@ public class InstalledAppProviderService extends IntentService {
|
|||||||
static void insertAppIntoDb(Context context, PackageInfo packageInfo, String hashType, String hash) {
|
static void insertAppIntoDb(Context context, PackageInfo packageInfo, String hashType, String hash) {
|
||||||
Uri uri = InstalledAppProvider.getContentUri();
|
Uri uri = InstalledAppProvider.getContentUri();
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(InstalledAppTable.Cols.PACKAGE_NAME, packageInfo.packageName);
|
contentValues.put(InstalledAppTable.Cols.Package.NAME, packageInfo.packageName);
|
||||||
contentValues.put(InstalledAppTable.Cols.VERSION_CODE, packageInfo.versionCode);
|
contentValues.put(InstalledAppTable.Cols.VERSION_CODE, packageInfo.versionCode);
|
||||||
contentValues.put(InstalledAppTable.Cols.VERSION_NAME, packageInfo.versionName);
|
contentValues.put(InstalledAppTable.Cols.VERSION_NAME, packageInfo.versionName);
|
||||||
contentValues.put(InstalledAppTable.Cols.APPLICATION_LABEL,
|
contentValues.put(InstalledAppTable.Cols.APPLICATION_LABEL,
|
||||||
|
@ -66,6 +66,20 @@ final class LoggingQuery {
|
|||||||
return db.rawQuery(query, queryArgs);
|
return db.rawQuery(query, queryArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void execSQLInternal() {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
db.execSQL(query);
|
||||||
|
long queryDuration = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
|
if (queryDuration >= SLOW_QUERY_DURATION) {
|
||||||
|
logSlowQuery(queryDuration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
db.execSQL(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log the query and its duration to the console. In addition, execute an "EXPLAIN QUERY PLAN"
|
* Log the query and its duration to the console. In addition, execute an "EXPLAIN QUERY PLAN"
|
||||||
* for the query in question so that the query can be diagnosed (https://sqlite.org/eqp.html)
|
* for the query in question so that the query can be diagnosed (https://sqlite.org/eqp.html)
|
||||||
@ -116,4 +130,8 @@ final class LoggingQuery {
|
|||||||
public static Cursor query(SQLiteDatabase db, String query, String[] queryBuilderArgs) {
|
public static Cursor query(SQLiteDatabase db, String query, String[] queryBuilderArgs) {
|
||||||
return new LoggingQuery(db, query, queryBuilderArgs).rawQuery();
|
return new LoggingQuery(db, query, queryBuilderArgs).rawQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void execSQL(SQLiteDatabase db, String sql) {
|
||||||
|
new LoggingQuery(db, sql, null).execSQLInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ public interface Schema {
|
|||||||
|
|
||||||
interface Cols {
|
interface Cols {
|
||||||
String _ID = "rowid as _id"; // Required for CursorLoaders
|
String _ID = "rowid as _id"; // Required for CursorLoaders
|
||||||
String PACKAGE_NAME = "appId";
|
String PACKAGE_ID = "packageId";
|
||||||
String VERSION_CODE = "versionCode";
|
String VERSION_CODE = "versionCode";
|
||||||
String VERSION_NAME = "versionName";
|
String VERSION_NAME = "versionName";
|
||||||
String APPLICATION_LABEL = "applicationLabel";
|
String APPLICATION_LABEL = "applicationLabel";
|
||||||
@ -338,8 +338,12 @@ public interface Schema {
|
|||||||
String HASH_TYPE = "hashType";
|
String HASH_TYPE = "hashType";
|
||||||
String HASH = "hash";
|
String HASH = "hash";
|
||||||
|
|
||||||
|
interface Package {
|
||||||
|
String NAME = "packageName";
|
||||||
|
}
|
||||||
|
|
||||||
String[] ALL = {
|
String[] ALL = {
|
||||||
_ID, PACKAGE_NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
|
_ID, PACKAGE_ID, Package.NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
|
||||||
SIGNATURE, LAST_UPDATE_TIME, HASH_TYPE, HASH,
|
SIGNATURE, LAST_UPDATE_TIME, HASH_TYPE, HASH,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ public class TempApkProvider extends ApkProvider {
|
|||||||
final String memoryDbName = TempAppProvider.DB;
|
final String memoryDbName = TempAppProvider.DB;
|
||||||
db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
|
db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
|
||||||
db.execSQL(TempAppProvider.copyData(Schema.ApkTable.Cols.ALL_COLS, Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
|
db.execSQL(TempAppProvider.copyData(Schema.ApkTable.Cols.ALL_COLS, Schema.ApkTable.NAME, memoryDbName + "." + getTableName()));
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_vercode on " + getTableName() + " (" + ApkTable.Cols.VERSION_CODE + ");");
|
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_appId on " + getTableName() + " (" + ApkTable.Cols.APP_ID + ");");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + ApkTable.Cols.IS_COMPATIBLE + ");");
|
db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + ApkTable.Cols.IS_COMPATIBLE + ");");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,11 +209,14 @@ public class TempAppProvider extends AppProvider {
|
|||||||
|
|
||||||
private void ensureTempTableDetached(SQLiteDatabase db) {
|
private void ensureTempTableDetached(SQLiteDatabase db) {
|
||||||
try {
|
try {
|
||||||
|
// Ideally we'd ask SQLite if the temp table is attached, but that is not possible.
|
||||||
|
// Instead, we resort to hackery:
|
||||||
|
// If the first statement does not throw an exception, then the temp db is attached and the second
|
||||||
|
// statement will detach the database.
|
||||||
|
db.rawQuery("SELECT * FROM " + DB + "." + getTableName() + " WHERE 0", null);
|
||||||
db.execSQL("DETACH DATABASE " + DB);
|
db.execSQL("DETACH DATABASE " + DB);
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException ignored) {
|
||||||
// We expect that most of the time the database will not exist unless an error occurred
|
|
||||||
// midway through the last update, The resulting exception is:
|
|
||||||
// android.database.sqlite.SQLiteException: no such database: temp_update_db (code 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ public class SwapService extends Service {
|
|||||||
if (!TextUtils.isEmpty(fingerprint)) {
|
if (!TextUtils.isEmpty(fingerprint)) {
|
||||||
values.put(Schema.RepoTable.Cols.FINGERPRINT, peer.getFingerprint());
|
values.put(Schema.RepoTable.Cols.FINGERPRINT, peer.getFingerprint());
|
||||||
}
|
}
|
||||||
values.put(Schema.RepoTable.Cols.IN_USE, true);
|
values.put(Schema.RepoTable.Cols.IN_USE, 1);
|
||||||
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
|
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
|
||||||
Uri uri = RepoProvider.Helper.insert(this, values);
|
Uri uri = RepoProvider.Helper.insert(this, values);
|
||||||
repo = RepoProvider.Helper.get(this, uri);
|
repo = RepoProvider.Helper.get(this, uri);
|
||||||
|
@ -142,7 +142,7 @@ public class SelectAppsView extends ListView implements
|
|||||||
|
|
||||||
private void toggleAppSelected(int position) {
|
private void toggleAppSelected(int position) {
|
||||||
Cursor c = (Cursor) adapter.getItem(position);
|
Cursor c = (Cursor) adapter.getItem(position);
|
||||||
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
|
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
|
||||||
if (getState().hasSelectedPackage(packageName)) {
|
if (getState().hasSelectedPackage(packageName)) {
|
||||||
getState().deselectPackage(packageName);
|
getState().deselectPackage(packageName);
|
||||||
adapter.updateCheckedIndicatorView(position, false);
|
adapter.updateCheckedIndicatorView(position, false);
|
||||||
@ -176,7 +176,7 @@ public class SelectAppsView extends ListView implements
|
|||||||
|
|
||||||
for (int i = 0; i < getCount(); i++) {
|
for (int i = 0; i < getCount(); i++) {
|
||||||
Cursor c = (Cursor) getItemAtPosition(i);
|
Cursor c = (Cursor) getItemAtPosition(i);
|
||||||
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
|
String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
|
||||||
getState().ensureFDroidSelected();
|
getState().ensureFDroidSelected();
|
||||||
for (String selected : getState().getAppsToSwap()) {
|
for (String selected : getState().getAppsToSwap()) {
|
||||||
if (TextUtils.equals(packageName, selected)) {
|
if (TextUtils.equals(packageName, selected)) {
|
||||||
@ -257,7 +257,7 @@ public class SelectAppsView extends ListView implements
|
|||||||
TextView labelView = (TextView) view.findViewById(R.id.application_label);
|
TextView labelView = (TextView) view.findViewById(R.id.application_label);
|
||||||
ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
|
ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
|
||||||
|
|
||||||
String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME));
|
String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME));
|
||||||
String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.APPLICATION_LABEL));
|
String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.APPLICATION_LABEL));
|
||||||
|
|
||||||
Drawable icon;
|
Drawable icon;
|
||||||
|
@ -158,7 +158,7 @@ public class Assert {
|
|||||||
Uri uri = InstalledAppProvider.getAppUri(appId);
|
Uri uri = InstalledAppProvider.getAppUri(appId);
|
||||||
|
|
||||||
String[] projection = {
|
String[] projection = {
|
||||||
InstalledAppTable.Cols.PACKAGE_NAME,
|
InstalledAppTable.Cols.Package.NAME,
|
||||||
InstalledAppTable.Cols.VERSION_CODE,
|
InstalledAppTable.Cols.VERSION_CODE,
|
||||||
InstalledAppTable.Cols.VERSION_NAME,
|
InstalledAppTable.Cols.VERSION_NAME,
|
||||||
InstalledAppTable.Cols.APPLICATION_LABEL,
|
InstalledAppTable.Cols.APPLICATION_LABEL,
|
||||||
@ -171,7 +171,7 @@ public class Assert {
|
|||||||
|
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
|
|
||||||
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME)));
|
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME)));
|
||||||
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_CODE)));
|
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_CODE)));
|
||||||
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_NAME)));
|
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_NAME)));
|
||||||
cursor.close();
|
cursor.close();
|
||||||
@ -196,11 +196,16 @@ public class Assert {
|
|||||||
|
|
||||||
values.putAll(additionalValues);
|
values.putAll(additionalValues);
|
||||||
|
|
||||||
|
// Don't hard code to 1, let consumers override it in additionalValues then ask for it back.
|
||||||
|
int repoId = values.getAsInteger(AppMetadataTable.Cols.REPO_ID);
|
||||||
|
|
||||||
Uri uri = AppProvider.getContentUri();
|
Uri uri = AppProvider.getContentUri();
|
||||||
|
|
||||||
context.getContentResolver().insert(uri, values);
|
context.getContentResolver().insert(uri, values);
|
||||||
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1,
|
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName,
|
||||||
AppMetadataTable.Cols.ALL);
|
repoId, AppMetadataTable.Cols.ALL);
|
||||||
|
assertNotNull(app);
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static App ensureApp(Context context, String packageName) {
|
public static App ensureApp(Context context, String packageName) {
|
||||||
|
@ -37,7 +37,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
|
|||||||
assertEquals(foundBefore.size(), 0);
|
assertEquals(foundBefore.size(), 0);
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(Cols.PACKAGE_NAME, "org.example.test-app");
|
values.put(Cols.Package.NAME, "org.example.test-app");
|
||||||
values.put(Cols.APPLICATION_LABEL, "Test App");
|
values.put(Cols.APPLICATION_LABEL, "Test App");
|
||||||
values.put(Cols.VERSION_CODE, 1021);
|
values.put(Cols.VERSION_CODE, 1021);
|
||||||
values.put(Cols.VERSION_NAME, "Longhorn");
|
values.put(Cols.VERSION_NAME, "Longhorn");
|
||||||
@ -56,7 +56,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
|
|||||||
assertEquals(cursor.getCount(), 1);
|
assertEquals(cursor.getCount(), 1);
|
||||||
|
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)));
|
assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
|
||||||
assertEquals("Test App", cursor.getString(cursor.getColumnIndex(Cols.APPLICATION_LABEL)));
|
assertEquals("Test App", cursor.getString(cursor.getColumnIndex(Cols.APPLICATION_LABEL)));
|
||||||
assertEquals(1021, cursor.getInt(cursor.getColumnIndex(Cols.VERSION_CODE)));
|
assertEquals(1021, cursor.getInt(cursor.getColumnIndex(Cols.VERSION_CODE)));
|
||||||
assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(Cols.VERSION_NAME)));
|
assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(Cols.VERSION_NAME)));
|
||||||
@ -125,7 +125,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
|
|||||||
Uri uri = InstalledAppProvider.getAppUri(packageName);
|
Uri uri = InstalledAppProvider.getAppUri(packageName);
|
||||||
|
|
||||||
String[] projection = {
|
String[] projection = {
|
||||||
Cols.PACKAGE_NAME,
|
Cols.Package.NAME,
|
||||||
Cols.LAST_UPDATE_TIME,
|
Cols.LAST_UPDATE_TIME,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
|
|||||||
assertNotNull(cursor);
|
assertNotNull(cursor);
|
||||||
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
|
assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)));
|
assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)));
|
||||||
long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME));
|
long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME));
|
||||||
assertTrue(lastUpdateTime > 0);
|
assertTrue(lastUpdateTime > 0);
|
||||||
assertTrue(lastUpdateTime < System.currentTimeMillis());
|
assertTrue(lastUpdateTime < System.currentTimeMillis());
|
||||||
@ -170,7 +170,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
|
|||||||
private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
|
private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
|
||||||
ContentValues values = new ContentValues(3);
|
ContentValues values = new ContentValues(3);
|
||||||
if (appId != null) {
|
if (appId != null) {
|
||||||
values.put(Cols.PACKAGE_NAME, appId);
|
values.put(Cols.Package.NAME, appId);
|
||||||
}
|
}
|
||||||
values.put(Cols.APPLICATION_LABEL, "Mock app: " + appId);
|
values.put(Cols.APPLICATION_LABEL, "Mock app: " + appId);
|
||||||
values.put(Cols.VERSION_CODE, versionCode);
|
values.put(Cols.VERSION_CODE, versionCode);
|
||||||
|
@ -3,6 +3,8 @@ package org.fdroid.fdroid.data;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.Signature;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
public class InstalledAppTestUtils {
|
public class InstalledAppTestUtils {
|
||||||
|
|
||||||
@ -13,12 +15,22 @@ public class InstalledAppTestUtils {
|
|||||||
public static void install(Context context,
|
public static void install(Context context,
|
||||||
String packageName,
|
String packageName,
|
||||||
int versionCode, String versionName) {
|
int versionCode, String versionName) {
|
||||||
|
install(context, packageName, versionCode, versionName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install(Context context,
|
||||||
|
String packageName,
|
||||||
|
int versionCode, String versionName,
|
||||||
|
@Nullable String signature) {
|
||||||
PackageInfo info = new PackageInfo();
|
PackageInfo info = new PackageInfo();
|
||||||
info.packageName = packageName;
|
info.packageName = packageName;
|
||||||
info.versionCode = versionCode;
|
info.versionCode = versionCode;
|
||||||
info.versionName = versionName;
|
info.versionName = versionName;
|
||||||
info.applicationInfo = new ApplicationInfo();
|
info.applicationInfo = new ApplicationInfo();
|
||||||
info.applicationInfo.publicSourceDir = "/tmp/mock-location";
|
info.applicationInfo.publicSourceDir = "/tmp/mock-location";
|
||||||
|
if (signature != null) {
|
||||||
|
info.signatures = new Signature[]{new Signature(signature)};
|
||||||
|
}
|
||||||
String hashType = "sha256";
|
String hashType = "sha256";
|
||||||
String hash = "00112233445566778899aabbccddeeff";
|
String hash = "00112233445566778899aabbccddeeff";
|
||||||
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
|
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
package org.fdroid.fdroid.data;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Assert;
|
||||||
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
|
import org.fdroid.fdroid.Hasher;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.TestUtils;
|
||||||
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class SuggestedVersionTest extends FDroidProviderTest {
|
||||||
|
|
||||||
|
private static final String FDROID_CERT = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37"; // NOCHECKSTYLE LineLength
|
||||||
|
private static final String UPSTREAM_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
|
||||||
|
private static final String THIRD_PARTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b130abcdeabcde012340123400b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
|
||||||
|
|
||||||
|
private static final String FDROID_SIG;
|
||||||
|
private static final String UPSTREAM_SIG;
|
||||||
|
private static final String THIRD_PARTY_SIG;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Some code requires the full certificate (e.g. when we mock PackageInfo to give to the
|
||||||
|
// installed app provider), while others requires the hashed certificate (e.g. inserting
|
||||||
|
// into the apk provider directly, without the need to mock anything).
|
||||||
|
try {
|
||||||
|
FDROID_SIG = new Hasher("MD5", FDROID_CERT.getBytes()).getHash();
|
||||||
|
UPSTREAM_SIG = new Hasher("MD5", UPSTREAM_CERT.getBytes()).getHash();
|
||||||
|
THIRD_PARTY_SIG = new Hasher("MD5", THIRD_PARTY_CERT.getBytes()).getHash();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
|
||||||
|
Preferences.setup(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Preferences.clearSingletonForTesting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleRepoSingleSig() {
|
||||||
|
App singleApp = insertApp(context, "single.app", "Single App (with beta)", 2, "https://beta.simple.repo");
|
||||||
|
insertApk(context, singleApp, 1, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 2, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 3, FDROID_SIG);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
|
||||||
|
App found2 = findApp(singleApp);
|
||||||
|
assertEquals(2, found2.suggestedVersionCode);
|
||||||
|
|
||||||
|
// By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should
|
||||||
|
// suggest the latest version (3).
|
||||||
|
Preferences.get().setUnstableUpdates(true);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
App found3 = findApp(singleApp);
|
||||||
|
assertEquals(3, found3.suggestedVersionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App findApp(App app) {
|
||||||
|
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), app.packageName, app.repoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleRepoMultiSig() {
|
||||||
|
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
|
||||||
|
insertApk(context, unrelatedApp, 3, FDROID_SIG);
|
||||||
|
|
||||||
|
App singleApp = insertApp(context, "single.app", "Single App", 4, "https://simple.repo");
|
||||||
|
insertApk(context, singleApp, 1, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 2, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 3, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 4, UPSTREAM_SIG);
|
||||||
|
insertApk(context, singleApp, 5, UPSTREAM_SIG);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
|
||||||
|
// Given we aren't installed yet, we don't care which signature.
|
||||||
|
// Just get as close to upstreamVersionCode as possible.
|
||||||
|
App suggestUpstream4 = findApp(singleApp);
|
||||||
|
assertEquals(4, suggestUpstream4.suggestedVersionCode);
|
||||||
|
|
||||||
|
// Now install v1 with the f-droid signature. In response, we should only suggest
|
||||||
|
// apps with that sig in the future. That is, version 4 from upstream is not considered.
|
||||||
|
InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
App suggestFDroid3 = findApp(singleApp);
|
||||||
|
assertEquals(3, suggestFDroid3.suggestedVersionCode);
|
||||||
|
|
||||||
|
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
|
||||||
|
insertApk(context, singleApp, 4, FDROID_SIG);
|
||||||
|
insertApk(context, singleApp, 5, FDROID_SIG);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
App suggestFDroid4 = findApp(singleApp);
|
||||||
|
assertEquals(4, suggestFDroid4.suggestedVersionCode);
|
||||||
|
|
||||||
|
// Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should
|
||||||
|
// still become the suggested version now.
|
||||||
|
Preferences.get().setUnstableUpdates(true);
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
App suggestFDroid5 = findApp(singleApp);
|
||||||
|
assertEquals(5, suggestFDroid5.suggestedVersionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculateMetadata() {
|
||||||
|
AppProvider.Helper.calcSuggestedApks(context);
|
||||||
|
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App highestPriorityApp(String packageName) {
|
||||||
|
return AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multiRepoMultiSig() {
|
||||||
|
App unrelatedApp = insertApp(context, "noisy.app", "Noisy App", 3, "https://simple.repo");
|
||||||
|
insertApk(context, unrelatedApp, 3, FDROID_SIG);
|
||||||
|
|
||||||
|
App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 4, "https://main.repo");
|
||||||
|
App thirdPartyApp = insertApp(context, "single.app", "Single App (3rd party)", 4, "https://3rd-party.repo");
|
||||||
|
|
||||||
|
insertApk(context, mainApp, 1, FDROID_SIG);
|
||||||
|
insertApk(context, mainApp, 2, FDROID_SIG);
|
||||||
|
insertApk(context, mainApp, 3, FDROID_SIG);
|
||||||
|
insertApk(context, mainApp, 4, UPSTREAM_SIG);
|
||||||
|
insertApk(context, mainApp, 5, UPSTREAM_SIG);
|
||||||
|
|
||||||
|
insertApk(context, thirdPartyApp, 3, THIRD_PARTY_SIG);
|
||||||
|
insertApk(context, thirdPartyApp, 4, THIRD_PARTY_SIG);
|
||||||
|
insertApk(context, thirdPartyApp, 5, THIRD_PARTY_SIG);
|
||||||
|
insertApk(context, thirdPartyApp, 6, THIRD_PARTY_SIG);
|
||||||
|
|
||||||
|
recalculateMetadata();
|
||||||
|
|
||||||
|
// Given we aren't installed yet, we don't care which signature or even which repo.
|
||||||
|
// Just get as close to upstreamVersionCode as possible.
|
||||||
|
App suggestAnyVersion4 = highestPriorityApp("single.app");
|
||||||
|
assertEquals(4, suggestAnyVersion4.suggestedVersionCode);
|
||||||
|
|
||||||
|
// Now install v1 with the f-droid signature. In response, we should only suggest
|
||||||
|
// apps with that sig in the future. That is, version 4 from upstream is not considered.
|
||||||
|
InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT);
|
||||||
|
recalculateMetadata();
|
||||||
|
App suggestFDroid3 = highestPriorityApp("single.app");
|
||||||
|
assertEquals(3, suggestFDroid3.suggestedVersionCode);
|
||||||
|
|
||||||
|
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
|
||||||
|
insertApk(context, mainApp, 4, FDROID_SIG);
|
||||||
|
insertApk(context, mainApp, 5, FDROID_SIG);
|
||||||
|
recalculateMetadata();
|
||||||
|
App suggestFDroid4 = highestPriorityApp("single.app");
|
||||||
|
assertEquals(4, suggestFDroid4.suggestedVersionCode);
|
||||||
|
|
||||||
|
// Uninstalling the F-Droid build and installing v3 of the third party means we can now go
|
||||||
|
// back to suggesting version 4.
|
||||||
|
InstalledAppProviderService.deleteAppFromDb(context, "single.app");
|
||||||
|
InstalledAppTestUtils.install(context, "single.app", 3, "v3", THIRD_PARTY_CERT);
|
||||||
|
recalculateMetadata();
|
||||||
|
suggestAnyVersion4 = highestPriorityApp("single.app");
|
||||||
|
assertEquals(4, suggestAnyVersion4.suggestedVersionCode);
|
||||||
|
|
||||||
|
// Version 6 from the 3rd party repo is not the "upstreamVersionCode", but with beta updates
|
||||||
|
// it should still become the suggested version now.
|
||||||
|
Preferences.get().setUnstableUpdates(true);
|
||||||
|
recalculateMetadata();
|
||||||
|
App suggest3rdParty6 = highestPriorityApp("single.app");
|
||||||
|
assertEquals(6, suggest3rdParty6.suggestedVersionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertApk(Context context, App app, int versionCode, String signature) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(Schema.ApkTable.Cols.SIGNATURE, signature);
|
||||||
|
Assert.insertApk(context, app, versionCode, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App insertApp(Context context, String packageName, String appName, int upstreamVersionCode,
|
||||||
|
String repoUrl) {
|
||||||
|
Repo repo = ensureRepo(context, repoUrl);
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(Cols.REPO_ID, repo.getId());
|
||||||
|
values.put(Cols.UPSTREAM_VERSION_CODE, upstreamVersionCode);
|
||||||
|
return Assert.insertApp(context, packageName, appName, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Repo ensureRepo(Context context, String repoUrl) {
|
||||||
|
Repo existing = RepoProvider.Helper.findByAddress(context, repoUrl);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RepoProviderTest.insertRepo(context, repoUrl, "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package org.fdroid.fdroid.updater;
|
package org.fdroid.fdroid.updater;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
@ -177,7 +178,6 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
|||||||
Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
|
Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
|
||||||
indexV0Details.apps.size();
|
indexV0Details.apps.size();
|
||||||
|
|
||||||
System.out.println("total apps: " + apps.length + " " + indexV0Details.apps.size());
|
|
||||||
assertEquals(indexV0Details.apps.size(), apps.length);
|
assertEquals(indexV0Details.apps.size(), apps.length);
|
||||||
assertEquals(apps.length, packages.size());
|
assertEquals(apps.length, packages.size());
|
||||||
|
|
||||||
@ -336,13 +336,10 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
|||||||
fields.remove(field);
|
fields.remove(field);
|
||||||
}
|
}
|
||||||
if (fields.size() > 0) {
|
if (fields.size() > 0) {
|
||||||
System.out.print(instance.getClass() + " has fields not setup for Jackson: ");
|
String sb = String.valueOf(instance.getClass()) + " has fields not setup for Jackson: " +
|
||||||
for (String field : fields) {
|
TextUtils.join(", ", fields) + "\nRead class javadoc for more info.";
|
||||||
System.out.print("\"" + field + "\", ");
|
fail(sb);
|
||||||
}
|
|
||||||
System.out.println("\nRead class javadoc for more info.");
|
|
||||||
}
|
}
|
||||||
assertEquals(0, fields.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -408,7 +405,6 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Repo parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {
|
private Repo parseRepo(ObjectMapper mapper, JsonParser parser) throws IOException {
|
||||||
System.out.println("parseRepo ");
|
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
ObjectReader repoReader = mapper.readerFor(Repo.class);
|
ObjectReader repoReader = mapper.readerFor(Repo.class);
|
||||||
|
@ -38,7 +38,7 @@ public class Issue763MultiRepo extends MultiRepoUpdaterTest {
|
|||||||
|
|
||||||
public void setEnabled(Repo repo, boolean enabled) {
|
public void setEnabled(Repo repo, boolean enabled) {
|
||||||
ContentValues values = new ContentValues(1);
|
ContentValues values = new ContentValues(1);
|
||||||
values.put(Schema.RepoTable.Cols.IN_USE, enabled);
|
values.put(Schema.RepoTable.Cols.IN_USE, enabled ? 1 : 0);
|
||||||
RepoProvider.Helper.update(context, repo, values);
|
RepoProvider.Helper.update(context, repo, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user