From 5efa53b46602aeef541cc0abfc5d1b687b9abb02 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 6 Oct 2016 01:57:49 +1100 Subject: [PATCH 01/16] Added package table. Required for future work which will be better able to deal with multiple repos providing the same app. Instead of migrating data into that table, we will drop and recreate the tables. This is because before this feature is out, we'll need to do that anyway. --- .../java/org/fdroid/fdroid/data/DBHelper.java | 25 ++++++++++++++++++- .../java/org/fdroid/fdroid/data/Schema.java | 14 +++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 36d7e93e8..016f56d07 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -34,6 +34,7 @@ import android.util.Log; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Schema.ApkTable; +import org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.AppPrefsTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.InstalledAppTable; @@ -50,6 +51,11 @@ class DBHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "fdroid"; + private static final String CREATE_TABLE_PACKAGE = "CREATE TABLE " + PackageTable.NAME + + " ( " + + PackageTable.Cols.PACKAGE_NAME + " text not null" + + ");"; + private static final String CREATE_TABLE_REPO = "create table " + RepoTable.NAME + " (" + RepoTable.Cols._ID + " integer primary key, " @@ -146,7 +152,7 @@ class DBHelper extends SQLiteOpenHelper { + " );"; private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + InstalledAppTable.NAME + ";"; - private static final int DB_VERSION = 62; + private static final int DB_VERSION = 63; private final Context context; @@ -248,6 +254,7 @@ class DBHelper extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_PACKAGE); db.execSQL(CREATE_TABLE_APP_METADATA); db.execSQL(CREATE_TABLE_APK); db.execSQL(CREATE_TABLE_INSTALLED_APP); @@ -344,6 +351,13 @@ class DBHelper extends SQLiteOpenHelper { addAppPrefsTable(db, oldVersion); lowerCaseApkHashes(db, oldVersion); supportRepoPushRequests(db, oldVersion); + migrateToPackageTable(db, oldVersion); + } + + private void migrateToPackageTable(SQLiteDatabase db, int oldVersion) { + if (oldVersion < 63) { + resetTransient(db); + } } private void lowerCaseApkHashes(SQLiteDatabase db, int oldVersion) { @@ -733,8 +747,12 @@ class DBHelper extends SQLiteOpenHelper { db.beginTransaction(); try { + if (tableExists(db, PackageTable.NAME)) { + db.execSQL("DROP TABLE " + PackageTable.NAME); + } db.execSQL("DROP TABLE " + AppMetadataTable.NAME); db.execSQL("DROP TABLE " + ApkTable.NAME); + db.execSQL(CREATE_TABLE_PACKAGE); db.execSQL(CREATE_TABLE_APP_METADATA); db.execSQL(CREATE_TABLE_APK); clearRepoEtags(db); @@ -765,6 +783,11 @@ class DBHelper extends SQLiteOpenHelper { } private static void ensureIndexes(SQLiteDatabase db) { + if (tableExists(db, PackageTable.NAME)) { + Utils.debugLog(TAG, "Ensuring indexes exist for " + PackageTable.NAME); + db.execSQL("CREATE INDEX IF NOT EXISTS package_packageName on " + PackageTable.NAME + " (" + PackageTable.Cols.PACKAGE_NAME + ");"); + } + Utils.debugLog(TAG, "Ensuring indexes exist for " + AppMetadataTable.NAME); db.execSQL("CREATE INDEX IF NOT EXISTS app_id on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS name on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.NAME + ");"); // Used for sorting most lists diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index dc5432958..b9037deab 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -9,6 +9,20 @@ import android.provider.BaseColumns; */ public interface Schema { + interface PackageTable { + + String NAME = "fdroid_package"; + + interface Cols { + String ROW_ID = "rowid"; + String PACKAGE_NAME = "packageName"; + + String[] ALL = { + ROW_ID, PACKAGE_NAME, + }; + } + } + interface AppPrefsTable { String NAME = "fdroid_appPrefs"; From 44a82c4af7a5c3f805c778ad1168139b91d632c0 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 6 Oct 2016 02:01:58 +1100 Subject: [PATCH 02/16] Migrate priorities safely. --- .../java/org/fdroid/fdroid/data/DBHelper.java | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 016f56d07..14e87a7eb 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -355,9 +355,59 @@ class DBHelper extends SQLiteOpenHelper { } private void migrateToPackageTable(SQLiteDatabase db, int oldVersion) { - if (oldVersion < 63) { - resetTransient(db); + if (oldVersion >= 63) { + return; } + + resetTransient(db); + + // By pushing _ALL_ repositories to a priority of 10, it makes it slightly easier + // to query for the non-default repositories later on in this method. + ContentValues highPriority = new ContentValues(1); + highPriority.put(RepoTable.Cols.PRIORITY, 10); + db.update(RepoTable.NAME, highPriority, null, null); + + String[] defaultRepos = context.getResources().getStringArray(R.array.default_repos); + String fdroidPubKey = defaultRepos[7]; + String fdroidAddress = defaultRepos[1]; + String fdroidArchiveAddress = defaultRepos[REPO_XML_ARG_COUNT + 1]; + String gpPubKey = defaultRepos[REPO_XML_ARG_COUNT * 2 + 7]; + String gpAddress = defaultRepos[REPO_XML_ARG_COUNT * 2 + 1]; + String gpArchiveAddress = defaultRepos[REPO_XML_ARG_COUNT * 3 + 1]; + + updateRepoPriority(db, fdroidPubKey, fdroidAddress, 1); + updateRepoPriority(db, fdroidPubKey, fdroidArchiveAddress, 2); + updateRepoPriority(db, gpPubKey, gpAddress, 3); + updateRepoPriority(db, gpPubKey, gpArchiveAddress, 4); + + int priority = 5; + String[] projection = new String[] {RepoTable.Cols.SIGNING_CERT, RepoTable.Cols.ADDRESS}; + + // Order by ID, because that is a good analogy for the order in which they were added. + // The order in which they were added is likely the order they present in the ManageRepos activity. + Cursor cursor = db.query(RepoTable.NAME, projection, RepoTable.Cols.PRIORITY + " > 4", null, null, null, RepoTable.Cols._ID); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + String signingCert = cursor.getString(cursor.getColumnIndex(RepoTable.Cols.SIGNING_CERT)); + String address = cursor.getString(cursor.getColumnIndex(RepoTable.Cols.ADDRESS)); + updateRepoPriority(db, signingCert, address, priority); + cursor.moveToNext(); + priority++; + } + cursor.close(); + } + + private void updateRepoPriority(SQLiteDatabase db, String signingCert, String address, int priority) { + ContentValues values = new ContentValues(1); + values.put(RepoTable.Cols.PRIORITY, Integer.toString(priority)); + + Utils.debugLog(TAG, "Setting priority of repo " + address + " to " + priority); + db.update( + RepoTable.NAME, + values, + RepoTable.Cols.SIGNING_CERT + " = ? AND " + RepoTable.Cols.ADDRESS + " = ?", + new String[] {signingCert, address} + ); } private void lowerCaseApkHashes(SQLiteDatabase db, int oldVersion) { @@ -750,6 +800,7 @@ class DBHelper extends SQLiteOpenHelper { if (tableExists(db, PackageTable.NAME)) { db.execSQL("DROP TABLE " + PackageTable.NAME); } + db.execSQL("DROP TABLE " + AppMetadataTable.NAME); db.execSQL("DROP TABLE " + ApkTable.NAME); db.execSQL(CREATE_TABLE_PACKAGE); From 8ed884887a3a2234834b676a40c23c1c465504a6 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 28 Jul 2016 12:25:17 +1000 Subject: [PATCH 03/16] WIP: Making metadata table work. Requires significant refactorings throughout :( --- app/src/main/AndroidManifest.xml | 5 + .../java/org/fdroid/fdroid/AppDetails.java | 4 +- .../java/org/fdroid/fdroid/RepoUpdater.java | 7 +- .../org/fdroid/fdroid/RepoXMLHandler.java | 1 + .../main/java/org/fdroid/fdroid/data/Apk.java | 4 +- .../org/fdroid/fdroid/data/ApkProvider.java | 42 +++-- .../main/java/org/fdroid/fdroid/data/App.java | 11 +- .../org/fdroid/fdroid/data/AppProvider.java | 77 ++++++-- .../java/org/fdroid/fdroid/data/DBHelper.java | 18 +- .../fdroid/fdroid/data/FDroidProvider.java | 4 + .../fdroid/fdroid/data/PackageProvider.java | 173 ++++++++++++++++++ .../org/fdroid/fdroid/data/RepoPersister.java | 12 +- .../java/org/fdroid/fdroid/data/Schema.java | 19 +- .../fdroid/fdroid/data/TempAppProvider.java | 24 ++- .../installer/InstallManagerService.java | 4 +- .../views/InstallConfirmActivity.java | 2 +- .../views/fragments/AppListFragment.java | 2 +- .../fdroid/views/swap/SwapAppsView.java | 5 +- .../test/java/org/fdroid/fdroid/Assert.java | 9 +- .../fdroid/fdroid/data/ApkProviderTest.java | 4 +- .../fdroid/fdroid/data/AppProviderTest.java | 33 ++-- .../fdroid/fdroid/data/ProviderUriTests.java | 4 +- 22 files changed, 368 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 70cecf9cb..eb2332ace 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -100,6 +100,11 @@ android:name="org.fdroid.fdroid.data.AppPrefsProvider" android:exported="false"/> + + , Parcelable { public String[] incompatibleReasons; /** - * The numeric primary key of the App table, which is used to join apks. + * The numeric primary key of the Package table, which is used to join apks. */ public long appId; @@ -91,7 +91,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { case Cols.FEATURES: features = Utils.parseCommaSeparatedString(cursor.getString(i)); break; - case Cols.App.PACKAGE_NAME: + case Cols.Package.PACKAGE_NAME: packageName = cursor.getString(i); break; case Cols.IS_COMPATIBLE: diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index 3c635c35f..e79380132 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -11,6 +11,7 @@ import android.util.Log; import org.fdroid.fdroid.data.Schema.ApkTable; import org.fdroid.fdroid.data.Schema.ApkTable.Cols; import org.fdroid.fdroid.data.Schema.AppMetadataTable; +import org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.RepoTable; import java.util.ArrayList; @@ -189,7 +190,7 @@ public class ApkProvider extends FDroidProvider { static { REPO_FIELDS.put(Cols.Repo.VERSION, RepoTable.Cols.VERSION); REPO_FIELDS.put(Cols.Repo.ADDRESS, RepoTable.Cols.ADDRESS); - PACKAGE_FIELDS.put(Cols.App.PACKAGE_NAME, AppMetadataTable.Cols.PACKAGE_NAME); + PACKAGE_FIELDS.put(Cols.Package.PACKAGE_NAME, PackageTable.Cols.PACKAGE_NAME); MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO); MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE); @@ -317,9 +318,11 @@ public class ApkProvider extends FDroidProvider { protected String getRequiredTables() { final String apk = getTableName(); final String app = getAppTableName(); + final String pkg = PackageTable.NAME; return apk + " AS apk " + - " LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")"; + " LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")" + + " LEFT JOIN " + pkg + " AS pkg ON (pkg." + PackageTable.Cols.ROW_ID + " = app." + AppMetadataTable.Cols.PACKAGE_ID + ")"; } @Override @@ -340,7 +343,7 @@ public class ApkProvider extends FDroidProvider { } private void addPackageField(String field, String alias) { - appendField(field, "app", alias); + appendField(field, "pkg", alias); } private void addRepoField(String field, String alias) { @@ -354,12 +357,7 @@ public class ApkProvider extends FDroidProvider { } private QuerySelection queryPackage(String packageName) { - return queryPackage(packageName, true); - } - - private QuerySelection queryPackage(String packageName, boolean includeTableAlias) { - String alias = includeTableAlias ? "apk." : ""; - final String selection = alias + Cols.APP_ID + " = (" + getAppIdFromPackageNameQuery() + ")"; + final String selection = "pkg." + PackageTable.Cols.PACKAGE_NAME + " = ?"; final String[] args = {packageName}; return new QuerySelection(selection, args); } @@ -370,7 +368,9 @@ public class ApkProvider extends FDroidProvider { private QuerySelection querySingleFromAnyRepo(Uri uri, boolean includeAlias) { String alias = includeAlias ? "apk." : ""; - final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " = (" + getAppIdFromPackageNameQuery() + ")"; + + // TODO: Change the = to an IN to deal with multiple apps? + final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " = (" + getMetadataIdFromPackageNameQuery() + ")"; final String[] args = { // First (0th) path segment is the word "apk", // and we are not interested in it. @@ -403,7 +403,7 @@ public class ApkProvider extends FDroidProvider { } private QuerySelection queryRepoApps(long repoId, String packageNames) { - return queryRepo(repoId).add(AppProvider.queryPackageNames(packageNames, "app." + AppMetadataTable.Cols.PACKAGE_NAME)); + return queryRepo(repoId).add(AppProvider.queryPackageNames(packageNames, "pkg." + PackageTable.Cols.PACKAGE_NAME)); } protected QuerySelection queryApks(String apkKeys) { @@ -418,6 +418,7 @@ public class ApkProvider extends FDroidProvider { "You tried to query " + apkDetails.length); } String alias = includeAlias ? "apk." : ""; + String metadataAlias = includeAlias ? "app." : ""; final String[] args = new String[apkDetails.length * 2]; StringBuilder sb = new StringBuilder(); for (int i = 0; i < apkDetails.length; i++) { @@ -430,10 +431,10 @@ public class ApkProvider extends FDroidProvider { sb.append(" OR "); } sb.append(" ( ") - .append(alias) - .append(Cols.APP_ID) + .append(metadataAlias) + .append(AppMetadataTable.Cols.PACKAGE_ID) .append(" = (") - .append(getAppIdFromPackageNameQuery()) + .append(getPackageIdFromPackageNameQuery()) .append(") AND ") .append(alias) .append(Cols.VERSION_CODE) @@ -442,8 +443,13 @@ public class ApkProvider extends FDroidProvider { return new QuerySelection(sb.toString(), args); } - private String getAppIdFromPackageNameQuery() { - return "SELECT " + AppMetadataTable.Cols.ROW_ID + " FROM " + getAppTableName() + " WHERE " + AppMetadataTable.Cols.PACKAGE_NAME + " = ?"; + // TODO: This could return many rows of app metadata + private String getMetadataIdFromPackageNameQuery() { + return "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " + + "FROM " + AppMetadataTable.NAME + " AS app " + + "JOIN " + PackageTable.NAME + " AS pkg ON ( " + + " app." + AppMetadataTable.Cols.PACKAGE_ID + " = pkg." + PackageTable.Cols.ROW_ID + " ) " + + "WHERE pkg." + PackageTable.Cols.PACKAGE_NAME + " = ?"; } @Override @@ -535,10 +541,6 @@ public class ApkProvider extends FDroidProvider { query = query.add(queryRepo(Long.parseLong(uri.getLastPathSegment()), false)); break; - case CODE_PACKAGE: - query = query.add(queryPackage(uri.getLastPathSegment(), false)); - break; - case CODE_APKS: query = query.add(queryApks(uri.getLastPathSegment(), false)); break; diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index cfd6c7c74..1f5c257e8 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -46,6 +46,7 @@ public class App extends ValueObject implements Comparable, Parcelable { public String packageName = "unknown"; public String name = "Unknown"; + public long repoId; public String summary = "Unknown application"; public String icon; @@ -147,10 +148,13 @@ public class App extends ValueObject implements Comparable, Parcelable { case Cols.ROW_ID: id = cursor.getLong(i); break; + case Cols.REPO_ID: + repoId = cursor.getLong(i); + break; case Cols.IS_COMPATIBLE: compatible = cursor.getInt(i) == 1; break; - case Cols.PACKAGE_NAME: + case Cols.Package.PACKAGE_NAME: packageName = cursor.getString(i); break; case Cols.NAME: @@ -430,8 +434,9 @@ public class App extends ValueObject implements Comparable, Parcelable { final ContentValues values = new ContentValues(); // Intentionally don't put "ROW_ID" in here, because we don't ever want to change that // primary key generated by sqlite. - values.put(Cols.PACKAGE_NAME, packageName); + values.put(Cols.Package.PACKAGE_NAME, packageName); values.put(Cols.NAME, name); + values.put(Cols.REPO_ID, repoId); values.put(Cols.SUMMARY, summary); values.put(Cols.ICON, icon); values.put(Cols.ICON_URL, iconUrl); @@ -560,6 +565,7 @@ public class App extends ValueObject implements Comparable, Parcelable { dest.writeByte(this.compatible ? (byte) 1 : (byte) 0); dest.writeString(this.packageName); dest.writeString(this.name); + dest.writeLong(this.repoId); dest.writeString(this.summary); dest.writeString(this.icon); dest.writeString(this.description); @@ -596,6 +602,7 @@ public class App extends ValueObject implements Comparable, Parcelable { this.compatible = in.readByte() != 0; this.packageName = in.readString(); this.name = in.readString(); + this.repoId = in.readLong(); this.summary = in.readString(); this.icon = in.readString(); this.description = in.readString(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 256884f35..3d4bbe282 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -18,6 +18,7 @@ import org.fdroid.fdroid.data.Schema.AppPrefsTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.data.Schema.InstalledAppTable; +import org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.RepoTable; import java.util.ArrayList; @@ -120,13 +121,17 @@ public class AppProvider extends FDroidProvider { return categories; } - public static App findByPackageName(ContentResolver resolver, String packageName) { - return findByPackageName(resolver, packageName, Cols.ALL); + public static App findHighestPriorityMetadata(ContentResolver resolver, String packageName) { + throw new UnsupportedOperationException("TODO: Pull back the metadata with the highest priority for packageName"); } - public static App findByPackageName(ContentResolver resolver, String packageName, + public static App findByPackageName(ContentResolver resolver, String packageName, long repoId) { + return findByPackageName(resolver, packageName, repoId, Cols.ALL); + } + + public static App findByPackageName(ContentResolver resolver, String packageName, long repoId, String[] projection) { - final Uri uri = getContentUri(packageName); + final Uri uri = getAppUri(packageName, repoId); return cursorToApp(resolver.query(uri, projection, null, null, null)); } @@ -254,11 +259,13 @@ public class AppProvider extends FDroidProvider { @Override protected String getRequiredTables() { + final String pkg = PackageTable.NAME; final String app = getTableName(); final String apk = getApkTableName(); final String repo = RepoTable.NAME; - return app + + return pkg + + " JOIN " + app + " ON (" + app + "." + Cols.PACKAGE_ID + " = " + pkg + "." + PackageTable.Cols.ROW_ID + ") " + " LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") " + " LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") "; } @@ -291,7 +298,7 @@ public class AppProvider extends FDroidProvider { join( InstalledAppTable.NAME, "installed", - "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME); + "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); requiresInstalledTable = true; } } @@ -301,7 +308,7 @@ public class AppProvider extends FDroidProvider { leftJoin( AppPrefsTable.NAME, "prefs", - "prefs." + AppPrefsTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME); + "prefs." + AppPrefsTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); requiresLeftJoinToPrefs = true; } } @@ -311,7 +318,7 @@ public class AppProvider extends FDroidProvider { leftJoin( InstalledAppTable.NAME, "installed", - "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME); + "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); requiresInstalledTable = true; } } @@ -319,6 +326,9 @@ public class AppProvider extends FDroidProvider { @Override public void addField(String field) { switch (field) { + case Cols.Package.PACKAGE_NAME: + appendField(PackageTable.Cols.PACKAGE_NAME, PackageTable.NAME, Cols.Package.PACKAGE_NAME); + break; case Cols.SuggestedApk.VERSION_NAME: addSuggestedApkVersionField(); break; @@ -404,6 +414,7 @@ public class AppProvider extends FDroidProvider { private static final String PATH_SEARCH_REPO = "searchRepo"; private static final String PATH_NO_APKS = "noApks"; protected static final String PATH_APPS = "apps"; + protected static final String PATH_APP = "app"; private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated"; private static final String PATH_NEWLY_ADDED = "newlyAdded"; private static final String PATH_CATEGORY = "category"; @@ -437,7 +448,7 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE); MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED); MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS); - MATCHER.addURI(getAuthority(), "*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE); } public static Uri getContentUri() { @@ -486,6 +497,19 @@ public class AppProvider extends FDroidProvider { return getContentUri(app.packageName); } + public static Uri getAppUri(App app) { + return getAppUri(app.packageName, app.repoId); + } + + public static Uri getAppUri(String packageName, long repoId) { + return getContentUri() + .buildUpon() + .appendPath(PATH_APP) + .appendPath(Long.toString(repoId)) + .appendPath(packageName) + .build(); + } + public static Uri getContentUri(String packageName) { return Uri.withAppendedPath(getContentUri(), packageName); } @@ -590,7 +614,7 @@ public class AppProvider extends FDroidProvider { final String app = getTableName(); final String[] columns = { - app + "." + Cols.PACKAGE_NAME, + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME, app + "." + Cols.NAME, app + "." + Cols.SUMMARY, app + "." + Cols.DESCRIPTION, @@ -624,12 +648,25 @@ public class AppProvider extends FDroidProvider { return new AppQuerySelection(selection.toString(), selectionKeywords); } - protected AppQuerySelection querySingle(String packageName) { - final String selection = getTableName() + "." + Cols.PACKAGE_NAME + " = ?"; + protected AppQuerySelection querySingle(String packageName, long repoId) { + final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ?"; final String[] args = {packageName}; return new AppQuerySelection(selection, args); } + /** + * Same as {@link AppProvider#querySingle(String, long)} except it is used for the purpose + * of an UPDATE query rather than a SELECT query. This means that it must use a subquery to get + * the {@link Cols.Package#PACKAGE_ID} rather than the join which is already in place for that + * table. + */ + protected AppQuerySelection querySingleForUpdate(String packageName, long repoId) { + final String selection = Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + + ") AND " + Cols.REPO_ID + " = ? "; + final String[] args = {packageName, Long.toString(repoId)}; + return new AppQuerySelection(selection, args); + } + private AppQuerySelection queryExcludeSwap() { // fdroid_repo will have null fields if the LEFT JOIN didn't resolve, e.g. due to there // being no apks for the app in the result set. In that case, we can't tell if it is from @@ -697,7 +734,10 @@ public class AppProvider extends FDroidProvider { break; case CODE_SINGLE: - selection = selection.add(querySingle(uri.getLastPathSegment())); + List pathParts = uri.getPathSegments(); + long repoId = Long.parseLong(pathParts.get(1)); + String packageName = pathParts.get(2); + selection = selection.add(querySingle(packageName, repoId)); break; case CAN_UPDATE: @@ -799,11 +839,15 @@ public class AppProvider extends FDroidProvider { @Override public Uri insert(Uri uri, ContentValues values) { + long packageId = PackageProvider.Helper.ensureExists(getContext(), values.getAsString(Cols.Package.PACKAGE_NAME)); + values.remove(Cols.Package.PACKAGE_NAME); + values.put(Cols.PACKAGE_ID, packageId); + db().insertOrThrow(getTableName(), null, values); if (!isApplyingBatch()) { getContext().getContentResolver().notifyChange(uri, null); } - return getContentUri(values.getAsString(Cols.PACKAGE_NAME)); + return getAppUri(values.getAsString(PackageTable.Cols.PACKAGE_NAME), values.getAsLong(Cols.REPO_ID)); } @Override @@ -816,7 +860,10 @@ public class AppProvider extends FDroidProvider { return 0; case CODE_SINGLE: - query = query.add(querySingle(uri.getLastPathSegment())); + List pathParts = uri.getPathSegments(); + long repoId = Long.parseLong(pathParts.get(1)); + String packageName = pathParts.get(2); + query = query.add(querySingleForUpdate(packageName, repoId)); break; default: diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 14e87a7eb..d79335940 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -103,7 +103,8 @@ class DBHelper extends SQLiteOpenHelper { static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME + " ( " - + AppMetadataTable.Cols.PACKAGE_NAME + " text not null, " + + AppMetadataTable.Cols.PACKAGE_ID + " integer not null, " + + AppMetadataTable.Cols.REPO_ID + " integer not null, " + AppMetadataTable.Cols.NAME + " text not null, " + AppMetadataTable.Cols.SUMMARY + " text not null, " + AppMetadataTable.Cols.ICON + " text, " @@ -130,7 +131,7 @@ class DBHelper extends SQLiteOpenHelper { + AppMetadataTable.Cols.IS_COMPATIBLE + " int not null," + AppMetadataTable.Cols.ICON_URL + " text, " + AppMetadataTable.Cols.ICON_URL_LARGE + " text, " - + "primary key(" + AppMetadataTable.Cols.PACKAGE_NAME + "));"; + + "primary key(" + AppMetadataTable.Cols.PACKAGE_ID + ", " + AppMetadataTable.Cols.REPO_ID + "));"; private static final String CREATE_TABLE_APP_PREFS = "CREATE TABLE " + AppPrefsTable.NAME + " ( " @@ -434,7 +435,7 @@ class DBHelper extends SQLiteOpenHelper { + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", " + AppPrefsTable.Cols.IGNORE_ALL_UPDATES + ") SELECT " - + AppMetadataTable.Cols.PACKAGE_NAME + ", " + + "id, " + "ignoreThisUpdate, " + "ignoreAllUpdates " + "FROM " + AppMetadataTable.NAME + " " @@ -543,7 +544,7 @@ class DBHelper extends SQLiteOpenHelper { final String update = "UPDATE " + ApkTable.NAME + " SET " + ApkTable.Cols.APP_ID + " = ( " + "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " + "FROM " + AppMetadataTable.NAME + " AS app " + - "WHERE " + ApkTable.NAME + ".id = app." + AppMetadataTable.Cols.PACKAGE_NAME + ")"; + "WHERE " + ApkTable.NAME + ".id = app.id)"; Log.i(TAG, "Updating foreign key from " + ApkTable.NAME + " to " + AppMetadataTable.NAME + " to use numeric foreign key."); Utils.debugLog(TAG, update); db.execSQL(update); @@ -840,10 +841,17 @@ class DBHelper extends SQLiteOpenHelper { } Utils.debugLog(TAG, "Ensuring indexes exist for " + AppMetadataTable.NAME); - db.execSQL("CREATE INDEX IF NOT EXISTS app_id on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS name on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.NAME + ");"); // Used for sorting most lists db.execSQL("CREATE INDEX IF NOT EXISTS added on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.ADDED + ");"); // Used for sorting "newly added" + if (columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.PACKAGE_ID)) { + db.execSQL("CREATE INDEX IF NOT EXISTS metadata_packageId ON " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.PACKAGE_ID + ");"); + } + + if (columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.REPO_ID)) { + db.execSQL("CREATE INDEX IF NOT EXISTS metadata_repoId ON " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.REPO_ID + ");"); + } + Utils.debugLog(TAG, "Ensuring indexes exist for " + ApkTable.NAME); db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + ApkTable.NAME + " (" + ApkTable.Cols.VERSION_CODE + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS apk_appId on " + ApkTable.NAME + " (" + ApkTable.Cols.APP_ID + ");"); diff --git a/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java b/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java index c1c046be7..26f6f28fb 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java @@ -157,4 +157,8 @@ public abstract class FDroidProvider extends ContentProvider { } } } + + protected String getPackageIdFromPackageNameQuery() { + return "SELECT " + Schema.PackageTable.Cols.ROW_ID + " FROM " + Schema.PackageTable.NAME + " WHERE " + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?"; + } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java new file mode 100644 index 000000000..9c3d6942c --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java @@ -0,0 +1,173 @@ +package org.fdroid.fdroid.data; + +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import org.fdroid.fdroid.data.Schema.PackageTable; +import org.fdroid.fdroid.data.Schema.PackageTable.Cols; + +public class PackageProvider extends FDroidProvider { + + private static final String TAG = "PackageProvider"; + + public static final class Helper { + private Helper() { } + + public static long ensureExists(Context context, String packageName) { + long id = getPackageId(context, packageName); + if (id <= 0) { + ContentValues values = new ContentValues(1); + values.put(Cols.PACKAGE_NAME, packageName); + Uri uri = context.getContentResolver().insert(getContentUri(), values); + id = Long.parseLong(uri.getLastPathSegment()); + } + return id; + } + + public static long getPackageId(Context context, String packageName) { + Cursor cursor = context.getContentResolver().query(getPackageUri(packageName), Cols.ALL, null, null, null); + if (cursor == null) { + return 0; + } + + try { + if (cursor.getCount() == 0) { + return 0; + } else { + cursor.moveToFirst(); + return cursor.getLong(cursor.getColumnIndexOrThrow(Cols.PACKAGE_NAME)); + } + } finally { + cursor.close(); + } + } + } + + private class Query extends QueryBuilder { + + @Override + protected String getRequiredTables() { + return PackageTable.NAME; + } + + @Override + public void addField(String field) { + appendField(field, getTableName()); + } + } + + private static final String PROVIDER_NAME = "PackageProvider"; + + private static final UriMatcher MATCHER = new UriMatcher(-1); + + private static final String PATH_PACKAGE_NAME = "packageName"; + private static final String PATH_PACKAGE_ID = "packageId"; + + static { + MATCHER.addURI(getAuthority(), PATH_PACKAGE_NAME + "/*", CODE_SINGLE); + } + + private static Uri getContentUri() { + return Uri.parse("content://" + getAuthority()); + } + + public static Uri getPackageUri(String packageName) { + return getContentUri() + .buildUpon() + .appendPath(PATH_PACKAGE_NAME) + .appendPath(packageName) + .build(); + } + + /** + * Not actually used as part of the external API to this content provider. + * Rather, used as a mechanism for returning the ID of a newly inserted row after calling + * {@link android.content.ContentProvider#insert(Uri, ContentValues)}, as that is only able + * to return a {@link Uri}. The {@link Uri#getLastPathSegment()} of this URI contains a + * {@link Long} which is the {@link PackageTable.Cols#ROW_ID} of the newly inserted row. + */ + private static Uri getPackageIdUri(long packageId) { + return getContentUri() + .buildUpon() + .appendPath(PATH_PACKAGE_ID) + .appendPath(Long.toString(packageId)) + .build(); + } + + @Override + protected String getTableName() { + return PackageTable.NAME; + } + + @Override + protected String getProviderName() { + return "PackageProvider"; + } + + public static String getAuthority() { + return AUTHORITY + "." + PROVIDER_NAME; + } + + @Override + protected UriMatcher getMatcher() { + return MATCHER; + } + + protected QuerySelection querySingle(String packageName) { + final String selection = getTableName() + "." + Cols.PACKAGE_NAME + " = ?"; + final String[] args = {packageName}; + return new QuerySelection(selection, args); + } + + @Override + public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) { + QuerySelection selection = new QuerySelection(customSelection, selectionArgs); + + switch (MATCHER.match(uri)) { + case CODE_SINGLE: + selection = selection.add(querySingle(uri.getLastPathSegment())); + break; + + default: + Log.e(TAG, "Invalid URI for content provider: " + uri); + throw new UnsupportedOperationException("Invalid URI for content provider: " + uri); + } + + Query query = new Query(); + query.addSelection(selection); + query.addFields(projection); + query.addOrderBy(sortOrder); + + Cursor cursor = LoggingQuery.query(db(), query.toString(), query.getArgs()); + cursor.setNotificationUri(getContext().getContentResolver(), uri); + return cursor; + } + + /** + * Deleting of packages is not required. + * It doesn't matter if we have a package name in the database after the package is no longer + * present in the repo any more. They wont take up much space, and it is the presence of rows + * in the {@link Schema.MetadataTable} which decides whether something is available in the + * F-Droid client or not. + */ + @Override + public int delete(Uri uri, String where, String[] whereArgs) { + throw new UnsupportedOperationException("Delete not supported for " + uri + "."); + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + long rowId = db().insertOrThrow(getTableName(), null, values); + getContext().getContentResolver().notifyChange(AppProvider.getCanUpdateUri(), null); + return getPackageIdUri(rowId); + } + + @Override + public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { + throw new UnsupportedOperationException("Update not supported for " + uri + "."); + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 68e2912fc..9e403af20 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -143,7 +143,7 @@ public class RepoPersister { for (App app : apps) { packageNames.add(app.packageName); } - String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.PACKAGE_NAME}; + String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME}; List fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection); Map ids = new HashMap<>(fromDb.size()); @@ -175,7 +175,7 @@ public class RepoPersister { */ private ArrayList insertOrUpdateApks(List packages) { String[] projection = new String[]{ - Schema.ApkTable.Cols.App.PACKAGE_NAME, + Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE, }; List existingApks = ApkProvider.Helper.knownApks(context, packages, projection); @@ -204,7 +204,7 @@ public class RepoPersister { * Does not do any checks to see if the app already exists or not. */ private ContentProviderOperation updateExistingApp(App app) { - Uri uri = TempAppProvider.getAppUri(app); + Uri uri = TempAppProvider.getAppUri(app.packageName, app.repoId); return ContentProviderOperation.newUpdate(uri).withValues(app.toContentValues()).build(); } @@ -224,8 +224,8 @@ public class RepoPersister { * array. */ private boolean isAppInDatabase(App app) { - String[] fields = {Schema.AppMetadataTable.Cols.PACKAGE_NAME}; - App found = AppProvider.Helper.findByPackageName(context.getContentResolver(), app.packageName, fields); + String[] fields = {Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME}; + App found = AppProvider.Helper.findByPackageName(context.getContentResolver(), app.packageName, repo.id, fields); return found != null; } @@ -255,7 +255,7 @@ public class RepoPersister { */ @Nullable private ContentProviderOperation deleteOrphanedApks(List apps, Map> packages) { - String[] projection = new String[]{Schema.ApkTable.Cols.App.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE}; + String[] projection = new String[]{Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE}; List existing = ApkProvider.Helper.find(context, repo, apps, projection); List toDelete = new ArrayList<>(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index b9037deab..eee638a18 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -53,7 +53,8 @@ public interface Schema { String ROW_ID = "rowid"; String _COUNT = "_count"; String IS_COMPATIBLE = "compatible"; - String PACKAGE_NAME = "id"; + String PACKAGE_ID = "packageId"; + String REPO_ID = "repoId"; String NAME = "name"; String SUMMARY = "summary"; String ICON = "icon"; @@ -90,13 +91,17 @@ public interface Schema { String SIGNATURE = "installedSig"; } + interface Package { + String PACKAGE_NAME = "package_packageName"; + } + /** * Each of the physical columns in the sqlite table. Differs from {@link Cols#ALL} in * that it doesn't include fields which are aliases of other fields (e.g. {@link Cols#_ID} * or which are from other related tables (e.g. {@link Cols.SuggestedApk#VERSION_NAME}). */ String[] ALL_COLS = { - ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION, + ROW_ID, PACKAGE_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID, UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED, @@ -110,14 +115,14 @@ public interface Schema { * @see Cols#ALL_COLS */ String[] ALL = { - _ID, ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION, + _ID, ROW_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID, UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED, CATEGORIES, ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE, SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME, InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME, - InstalledApp.SIGNATURE, + InstalledApp.SIGNATURE, Package.PACKAGE_NAME }; } } @@ -163,8 +168,8 @@ public interface Schema { String ADDRESS = "repoAddress"; } - interface App { - String PACKAGE_NAME = "appPackageName"; + interface Package { + String PACKAGE_NAME = "package_packageName"; } /** @@ -181,7 +186,7 @@ public interface Schema { * @see AppMetadataTable.Cols#ALL */ String[] ALL = { - _ID, APP_ID, App.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, + _ID, APP_ID, Package.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS, diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 955e82cde..9927fc28d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -13,6 +13,7 @@ import java.util.List; import org.fdroid.fdroid.data.Schema.ApkTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; +import org.fdroid.fdroid.data.Schema.PackageTable; /** * This class does all of its operations in a temporary sqlite table. @@ -41,7 +42,7 @@ public class TempAppProvider extends AppProvider { MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT); MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT); MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS); - MATCHER.addURI(getAuthority(), "*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE); } @Override @@ -57,8 +58,13 @@ public class TempAppProvider extends AppProvider { return Uri.parse("content://" + getAuthority()); } - public static Uri getAppUri(App app) { - return Uri.withAppendedPath(getContentUri(), app.packageName); + public static Uri getAppUri(String packageName, long repoId) { + return getContentUri() + .buildUpon() + .appendPath(PATH_APP) + .appendPath(Long.toString(repoId)) + .appendPath(packageName) + .build(); } public static Uri getAppsUri(List apps) { @@ -69,7 +75,7 @@ public class TempAppProvider extends AppProvider { } private AppQuerySelection queryApps(String packageNames) { - return queryPackageNames(packageNames, getTableName() + "." + AppMetadataTable.Cols.PACKAGE_NAME); + return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); } public static class Helper { @@ -126,7 +132,13 @@ public class TempAppProvider extends AppProvider { throw new UnsupportedOperationException("Update not supported for " + uri + "."); } - QuerySelection query = new QuerySelection(where, whereArgs).add(querySingle(uri.getLastPathSegment())); + List pathParts = uri.getPathSegments(); + String packageName = pathParts.get(2); + long repoId = Long.parseLong(pathParts.get(1)); + QuerySelection query = new QuerySelection(where, whereArgs).add(querySingleForUpdate(packageName, repoId)); + + // Package names for apps cannot change... + values.remove(AppMetadataTable.Cols.Package.PACKAGE_NAME); int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); if (!isApplyingBatch()) { @@ -163,7 +175,7 @@ public class TempAppProvider extends AppProvider { db.execSQL("ATTACH DATABASE ':memory:' AS " + DB); db.execSQL(DBHelper.CREATE_TABLE_APP_METADATA.replaceFirst(AppMetadataTable.NAME, DB + "." + getTableName())); db.execSQL(copyData(AppMetadataTable.Cols.ALL_COLS, AppMetadataTable.NAME, DB + "." + getTableName())); - db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");"); + db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_ID + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (" + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (" + AppMetadataTable.Cols.IS_COMPATIBLE + ");"); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 2e3564507..d97e58341 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -259,7 +259,7 @@ public class InstallManagerService extends Service { App app = getAppFromActive(downloadUrl); if (app == null) { ContentResolver resolver = context.getContentResolver(); - app = AppProvider.Helper.findByPackageName(resolver, apk.packageName); + app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo); } // show notification if app details is not visible if (app != null && AppDetails.isAppVisible(app.packageName)) { @@ -346,7 +346,7 @@ public class InstallManagerService extends Service { String name = getAppName(apk); if (TextUtils.isEmpty(name) || name.equals(new App().name)) { ContentResolver resolver = getContentResolver(); - App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName); + App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo); if (app == null || TextUtils.isEmpty(app.name)) { return; // do not have a name to display, so leave notification as is } diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java index a14996357..967df1ac5 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java @@ -191,7 +191,7 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel intent = getIntent(); Uri uri = intent.getData(); Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL); - app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName); + app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL); appDiff = new AppDiff(getPackageManager(), apk); diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java index 7ffa038b5..188861f01 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/AppListFragment.java @@ -39,7 +39,7 @@ public abstract class AppListFragment extends ListFragment implements private static final String[] APP_PROJECTION = { AppMetadataTable.Cols._ID, // Required for cursor loader to work. - AppMetadataTable.Cols.PACKAGE_NAME, + AppMetadataTable.Cols.Package.PACKAGE_NAME, AppMetadataTable.Cols.NAME, AppMetadataTable.Cols.SUMMARY, AppMetadataTable.Cols.IS_COMPATIBLE, diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index 0b570506c..d14f831f4 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -289,7 +289,8 @@ public class SwapAppsView extends ListView implements public void onChange(boolean selfChange) { Activity activity = getActivity(); if (activity != null) { - app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(), app.packageName); + app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(), + app.packageName, app.repoId, AppMetadataTable.Cols.ALL); resetView(); } } @@ -318,7 +319,7 @@ public class SwapAppsView extends ListView implements // implemented on API-16, so leaving like this for now. getActivity().getContentResolver().unregisterContentObserver(appObserver); getActivity().getContentResolver().registerContentObserver( - AppProvider.getContentUri(this.app.packageName), true, appObserver); + AppProvider.getAppUri(this.app), true, appObserver); } resetView(); } diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java index 61f6fe223..adbd30db4 100644 --- a/app/src/test/java/org/fdroid/fdroid/Assert.java +++ b/app/src/test/java/org/fdroid/fdroid/Assert.java @@ -183,7 +183,8 @@ public class Assert { public static App insertApp(Context context, String packageName, String name, ContentValues additionalValues) { ContentValues values = new ContentValues(); - values.put(AppMetadataTable.Cols.PACKAGE_NAME, packageName); + values.put(AppMetadataTable.Cols.REPO_ID, 1); + values.put(AppMetadataTable.Cols.Package.PACKAGE_NAME, packageName); values.put(AppMetadataTable.Cols.NAME, name); // Required fields (NOT NULL in the database). @@ -197,14 +198,14 @@ public class Assert { Uri uri = AppProvider.getContentUri(); context.getContentResolver().insert(uri, values); - return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName); + return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); } private static App ensureApp(Context context, String packageName) { - App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName); + App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); if (app == null) { insertApp(context, packageName, packageName); - app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName); + app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); } assertNotNull(app); return app; diff --git a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java index 95a153071..efbfada6e 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java @@ -284,7 +284,7 @@ public class ApkProviderTest extends FDroidProviderTest { Collections.addAll(apksToCheck, unknown); String[] projection = { - Cols.App.PACKAGE_NAME, + Cols.Package.PACKAGE_NAME, Cols.VERSION_CODE, }; @@ -424,7 +424,7 @@ public class ApkProviderTest extends FDroidProviderTest { assertEquals("a hash type", apk.hashType); String[] projection = { - Cols.App.PACKAGE_NAME, + Cols.Package.PACKAGE_NAME, Cols.HASH, }; diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java index f6dd4995a..b0ccd5e6e 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -68,7 +68,7 @@ public class AppProviderTest extends FDroidProviderTest { @Test public void testCantFindApp() { - assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist")); + assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist", 1)); } @Test @@ -111,14 +111,14 @@ public class AppProviderTest extends FDroidProviderTest { ContentResolver r = context.getContentResolver(); // Can't "update", although can "install"... - App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed"); + App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed", 1); assertFalse(notInstalled.canAndWantToUpdate(context)); - App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available"); - App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore"); - App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all"); - App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest"); - App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old"); + App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available", 1); + App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore", 1); + App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all", 1); + App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest", 1); + App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old", 1); assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context)); assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context)); @@ -126,10 +126,10 @@ public class AppProviderTest extends FDroidProviderTest { assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context)); assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context)); - App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore"); - App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all"); - App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest"); - App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest"); + App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore", 1); + App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all", 1); + App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest", 1); + App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest", 1); assertTrue(installedOldNoIgnore.canAndWantToUpdate(context)); assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context)); @@ -169,7 +169,7 @@ public class AppProviderTest extends FDroidProviderTest { assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ); - String[] projection = {Cols.PACKAGE_NAME}; + String[] projection = {Cols.Package.PACKAGE_NAME}; List canUpdateApps = AppProvider.Helper.findCanUpdate(context, projection); String[] expectedCanUpdate = { @@ -239,7 +239,7 @@ public class AppProviderTest extends FDroidProviderTest { assertEquals("org.fdroid.fdroid", app.packageName); assertEquals("F-Droid", app.name); - App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid"); + App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid", 1); assertNotNull(otherApp); assertEquals("org.fdroid.fdroid", otherApp.packageName); assertEquals("F-Droid", otherApp.name); @@ -260,7 +260,7 @@ public class AppProviderTest extends FDroidProviderTest { String[] projection = new String[] { Cols._ID, Cols.NAME, - Cols.PACKAGE_NAME, + Cols.Package.PACKAGE_NAME, }; return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null); } @@ -356,7 +356,8 @@ public class AppProviderTest extends FDroidProviderTest { public App insertApp(String id, String name, ContentValues additionalValues) { ContentValues values = new ContentValues(); - values.put(Cols.PACKAGE_NAME, id); + values.put(Cols.Package.PACKAGE_NAME, id); + values.put(Cols.REPO_ID, 1); values.put(Cols.NAME, name); // Required fields (NOT NULL in the database). @@ -370,6 +371,6 @@ public class AppProviderTest extends FDroidProviderTest { Uri uri = AppProvider.getContentUri(); contentResolver.insert(uri, values); - return AppProvider.Helper.findByPackageName(context.getContentResolver(), id); + return AppProvider.Helper.findByPackageName(context.getContentResolver(), id, 1); } } diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java index 2c5f067e0..5f4d445bb 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -93,10 +93,10 @@ public class ProviderUriTests { assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection); App app = new App(); + app.repoId = 1; app.packageName = "org.fdroid.fdroid"; - assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection); - assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection); + assertValidUri(resolver, AppProvider.getAppUri(app), "content://org.fdroid.fdroid.data.AppProvider/app/1/org.fdroid.fdroid", projection); } @Test From 6d2fdb284369273f90991f4fa5d9120b7a8b9163 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 1 Aug 2016 22:25:21 +1000 Subject: [PATCH 04/16] Enable multirepo tests, make them pass. Tests were failing because the repository of existing and new apks were not considered when deciding whether to update or insert them. --- .../org/fdroid/fdroid/data/AppProvider.java | 5 +- .../org/fdroid/fdroid/data/RepoPersister.java | 3 +- .../AcceptableMultiRepoUpdaterTest.java | 54 ++++-- .../fdroid/fdroid/MultiRepoUpdaterTest.java | 15 +- .../fdroid/ProperMultiRepoUpdaterTest.java | 180 ++++++++++++++---- app/src/test/resources/multiRepo.archive.jar | Bin 5971 -> 5994 bytes .../test/resources/multiRepo.conflicting.jar | Bin 5705 -> 5730 bytes app/src/test/resources/multiRepo.normal.jar | Bin 5809 -> 5854 bytes 8 files changed, 193 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 3d4bbe282..b1a02f932 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -649,8 +649,9 @@ public class AppProvider extends FDroidProvider { } protected AppQuerySelection querySingle(String packageName, long repoId) { - final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ?"; - final String[] args = {packageName}; + final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ? " + + " AND " + getTableName() + "." + Cols.REPO_ID + " = ? "; + final String[] args = {packageName, Long.toString(repoId)}; return new AppQuerySelection(selection, args); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 9e403af20..6ddee8a9e 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -177,13 +177,14 @@ public class RepoPersister { String[] projection = new String[]{ Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE, + Schema.ApkTable.Cols.REPO_ID, }; List existingApks = ApkProvider.Helper.knownApks(context, packages, projection); ArrayList operations = new ArrayList<>(packages.size()); for (Apk apk : packages) { boolean exists = false; for (Apk existing : existingApks) { - if (existing.packageName.equals(apk.packageName) && existing.versionCode == apk.versionCode) { + if (existing.repo == apk.repo && existing.packageName.equals(apk.packageName) && existing.versionCode == apk.versionCode) { exists = true; break; } diff --git a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java index d0f7b9f13..aa7a09308 100644 --- a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java @@ -39,49 +39,67 @@ public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest { @Test public void testAcceptableConflictingThenMainThenArchive() throws UpdateException { assertEmpty(); - if (updateConflicting() && updateMain() && updateArchive()) { - assertSomewhatAcceptable(); - } + + updateConflicting(); + updateMain(); + updateArchive(); + + assertSomewhatAcceptable(); } @Test public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException { assertEmpty(); - if (updateConflicting() && updateArchive() && updateMain()) { - assertSomewhatAcceptable(); - } + + updateConflicting(); + updateArchive(); + updateMain(); + + assertSomewhatAcceptable(); } @Test public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException { assertEmpty(); - if (updateArchive() && updateMain() && updateConflicting()) { - assertSomewhatAcceptable(); - } + + updateArchive(); + updateMain(); + updateConflicting(); + + assertSomewhatAcceptable(); } @Test public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException { assertEmpty(); - if (updateArchive() && updateConflicting() && updateMain()) { - assertSomewhatAcceptable(); - } + + updateArchive(); + updateConflicting(); + updateMain(); + + assertSomewhatAcceptable(); } @Test public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException { assertEmpty(); - if (updateMain() && updateArchive() && updateConflicting()) { - assertSomewhatAcceptable(); - } + + updateMain(); + updateArchive(); + updateConflicting(); + + assertSomewhatAcceptable(); } @Test public void testAcceptableMainThenConflictingThenArchive() throws UpdateException { assertEmpty(); - if (updateMain() && updateConflicting() && updateArchive()) { - assertSomewhatAcceptable(); - } + + updateMain(); + updateConflicting(); + updateArchive(); + + assertSomewhatAcceptable(); } @NonNull diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java index f4c529059..ec94df85e 100644 --- a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java @@ -178,19 +178,19 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { return new RepoUpdater(context, createRepo(name, uri, context)); } - protected boolean updateConflicting() throws UpdateException { - return updateRepo(createUpdater(REPO_CONFLICTING, REPO_CONFLICTING_URI, context), "multiRepo.conflicting.jar"); + protected void updateConflicting() throws UpdateException { + updateRepo(createUpdater(REPO_CONFLICTING, REPO_CONFLICTING_URI, context), "multiRepo.conflicting.jar"); } - protected boolean updateMain() throws UpdateException { - return updateRepo(createUpdater(REPO_MAIN, REPO_MAIN_URI, context), "multiRepo.normal.jar"); + protected void updateMain() throws UpdateException { + updateRepo(createUpdater(REPO_MAIN, REPO_MAIN_URI, context), "multiRepo.normal.jar"); } - protected boolean updateArchive() throws UpdateException { - return updateRepo(createUpdater(REPO_ARCHIVE, REPO_ARCHIVE_URI, context), "multiRepo.archive.jar"); + protected void updateArchive() throws UpdateException { + updateRepo(createUpdater(REPO_ARCHIVE, REPO_ARCHIVE_URI, context), "multiRepo.archive.jar"); } - protected boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { + protected void updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { File indexJar = TestUtils.copyResourceToTempFile(indexJarPath); try { updater.processDownloadedFile(indexJar); @@ -199,7 +199,6 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { indexJar.delete(); } } - return true; } } diff --git a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java index 3d6e18abd..257e9f0f8 100644 --- a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java @@ -1,77 +1,123 @@ package org.fdroid.fdroid; +import android.support.annotation.StringDef; import android.util.Log; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; +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.Schema; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; -/* -At time fo writing, the following tests did not pass. This is because the multi-repo support -in F-Droid was not sufficient. When working on proper multi repo support than this should be -uncommented and all these tests will be required to pass: - -@Config(constants = BuildConfig.class) +@Config(constants = BuildConfig.class, sdk = 23) @RunWith(RobolectricGradleTestRunner.class) -*/ -@SuppressWarnings("unused") public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { private static final String TAG = "ProperMultiRepoSupport"; - /*@Test - public void testCorrectConflictingThenMainThenArchive() throws UpdateException { + @Retention(RetentionPolicy.SOURCE) + @StringDef({"Conflicting", "Normal"}) + public @interface RepoIdentifier {} + + @Test + public void mainRepo() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateConflicting() && updateMain() && updateArchive()) { - assertExpected(); - } + updateMain(); + assertMainRepo(RepoProvider.Helper.all(context)); } @Test - public void testCorrectConflictingThenArchiveThenMain() throws UpdateException { + public void archiveRepo() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateConflicting() && updateArchive() && updateMain()) { - assertExpected(); - } + updateArchive(); + assertMainArchiveRepo(RepoProvider.Helper.all(context)); } @Test - public void testCorrectArchiveThenMainThenConflicting() throws UpdateException { + public void conflictingRepo() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateArchive() && updateMain() && updateConflicting()) { - assertExpected(); - } + updateConflicting(); + assertConflictingRepo(RepoProvider.Helper.all(context)); } @Test - public void testCorrectArchiveThenConflictingThenMain() throws UpdateException { + public void testCorrectConflictingThenMainThenArchive() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateArchive() && updateConflicting() && updateMain()) { - assertExpected(); - } + + updateConflicting(); + updateMain(); + updateArchive(); + + assertExpected(); } @Test - public void testCorrectMainThenArchiveThenConflicting() throws UpdateException { + public void testCorrectConflictingThenArchiveThenMain() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateMain() && updateArchive() && updateConflicting()) { - assertExpected(); - } + + updateConflicting(); + updateArchive(); + updateMain(); + + assertExpected(); } @Test - public void testCorrectMainThenConflictingThenArchive() throws UpdateException { + public void testCorrectArchiveThenMainThenConflicting() throws RepoUpdater.UpdateException { assertEmpty(); - if (updateMain() && updateConflicting() && updateArchive()) { - assertExpected(); - } - }*/ + + updateArchive(); + updateMain(); + updateConflicting(); + + assertExpected(); + } + + @Test + public void testCorrectArchiveThenConflictingThenMain() throws RepoUpdater.UpdateException { + assertEmpty(); + + updateArchive(); + updateConflicting(); + updateMain(); + + assertExpected(); + } + + @Test + public void testCorrectMainThenArchiveThenConflicting() throws RepoUpdater.UpdateException { + assertEmpty(); + + updateMain(); + updateArchive(); + updateConflicting(); + + assertExpected(); + } + + @Test + public void testCorrectMainThenConflictingThenArchive() throws RepoUpdater.UpdateException { + assertEmpty(); + + updateMain(); + updateConflicting(); + updateArchive(); + + assertExpected(); + } /** * Check that all of the expected apps and apk versions are available in the database. This @@ -107,6 +153,65 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19}); assertApksExist(apks, "org.adaway", new int[]{52, 53, 54}); assertApksExist(apks, "siir.es.adbWireless", new int[]{12}); + + assert2048(repo, "Normal"); + assertAdAway(repo, "Normal"); + assertAdb(repo, "Normal"); + } + + /** + * @param id An identifier that we've put in the metadata for each repo to ensure that + * we can identify the metadata is coming from the correct repo. + */ + private void assert2048(Repo repo, @RepoIdentifier String id) { + App a2048 = AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.uberspot.a2048", repo.getId()); + assertNotNull(a2048); + assertEquals("2048", a2048.name); + assertEquals(String.format("

2048 from %s repo.

", id), a2048.description); + assertEquals(String.format("Puzzle game (%s)", id), a2048.summary); + assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webURL); + assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceURL); + assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.trackerURL); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ + private void assertAdAway(Repo repo, @RepoIdentifier String id) { + App adaway = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.adaway", repo.getId()); + assertNotNull(adaway); + assertEquals(String.format("AdAway", id), adaway.name); + assertEquals(String.format("

AdAway from %s repo.

", id), adaway.description); + assertEquals(String.format("Block advertisements (%s)", id), adaway.summary); + assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webURL); + assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceURL); + assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.trackerURL); + assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelogURL); + assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donateURL); + assertEquals(String.format("369138", id), adaway.flattrID); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ + private void assertAdb(Repo repo, @RepoIdentifier String id) { + App adb = AppProvider.Helper.findByPackageName(context.getContentResolver(), "siir.es.adbWireless", repo.getId()); + assertNotNull(adb); + assertEquals("adbWireless", adb.name); + assertEquals(String.format("

adbWireless from %s repo.

", id), adb.description); + assertEquals(String.format("Wireless adb (%s)", id), adb.summary); + assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webURL); + assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceURL); + assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.trackerURL); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ + private void assertCalendar(Repo repo, @RepoIdentifier String id) { + App calendar = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.dgtale.icsimport", repo.getId()); + assertNotNull(calendar); + assertEquals("Add to calendar", calendar.name); + assertEquals(String.format("

Add to calendar from %s repo.

", id), calendar.description); + assertEquals(String.format("Import .ics files into calendar (%s)", id), calendar.summary); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webURL); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceURL); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.trackerURL); + assertEquals("2225390", calendar.flattrID); } /** @@ -131,6 +236,8 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { List apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL); assertEquals("Apks for main archive repo", 13, apks.size()); assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51}); + + assertAdAway(repo, "Normal"); } /** @@ -147,9 +254,12 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { Repo repo = findRepo(REPO_CONFLICTING, allRepos); List apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL); - assertEquals("Apks for main repo", 6, apks.size()); + assertEquals("Apks for conflicting repo", 6, apks.size()); assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53}); assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3}); + + assertAdAway(repo, "Conflicting"); + assertCalendar(repo, "Conflicting"); } } diff --git a/app/src/test/resources/multiRepo.archive.jar b/app/src/test/resources/multiRepo.archive.jar index f5c505c073234f2bed24b4c4888d61e1d395c601..f1742ceb8f201bc3c4ee4bbd77e510456b8a40b3 100644 GIT binary patch delta 5619 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 AmjD0& delta 5592 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 A!2kdN delta 5358 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 b`X}K3_wIk*_4n*QA}U^*ngRFf-v|8*B9{Q& diff --git a/app/src/test/resources/multiRepo.normal.jar b/app/src/test/resources/multiRepo.normal.jar index 6a53256ebc4f7a670e7ee7af052814e3df29d1af..d42de35bf0adda2fe7caf7dd82e0c2a4242bfe9b 100644 GIT binary patch delta 5436 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 From e25d26aca0859262409d85299358ee5610162873 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 1 Aug 2016 22:48:25 +1000 Subject: [PATCH 05/16] Moved tests into updater package, updated multiRepo.*.jar repos. The tests are in the .updater packate to make them easier to run as a suite in Android Studio. Now the package can be right clicked and ran to run all the tests to do with updating. The index jar files were updated so as to include info in most metadata fields (e.g. URLs/descriptions/summary/etc) to show that that particular part of metadata came from a specific repo. This will allow more specific tests to show that we can indeed query for an app with metadata provided by the repo with the highest priority. --- app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java | 2 +- .../fdroid/{ => updater}/AcceptableMultiRepoUpdaterTest.java | 3 ++- .../fdroid/fdroid/{ => updater}/MultiRepoUpdaterTest.java | 5 ++++- .../fdroid/{ => updater}/ProperMultiRepoUpdaterTest.java | 5 ++++- .../org/fdroid/fdroid/{ => updater}/RepoXMLHandlerTest.java | 4 +++- 5 files changed, 14 insertions(+), 5 deletions(-) rename app/src/test/java/org/fdroid/fdroid/{ => updater}/AcceptableMultiRepoUpdaterTest.java (98%) rename app/src/test/java/org/fdroid/fdroid/{ => updater}/MultiRepoUpdaterTest.java (98%) rename app/src/test/java/org/fdroid/fdroid/{ => updater}/ProperMultiRepoUpdaterTest.java (98%) rename app/src/test/java/org/fdroid/fdroid/{ => updater}/RepoXMLHandlerTest.java (99%) diff --git a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java index 21fa68d1e..984b99de7 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java @@ -61,7 +61,7 @@ public class RepoXMLHandler extends DefaultHandler { private final StringBuilder curchars = new StringBuilder(); - interface IndexReceiver { + public interface IndexReceiver { void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp); void receiveApp(App app, List packages); diff --git a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java similarity index 98% rename from app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java rename to app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java index aa7a09308..0b2363b80 100644 --- a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java @@ -1,10 +1,11 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.updater; import android.content.ContentValues; import android.support.annotation.NonNull; import android.util.Log; +import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.RepoUpdater.UpdateException; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java similarity index 98% rename from app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java rename to app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java index ec94df85e..a44769e74 100644 --- a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java @@ -1,12 +1,15 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.updater; import android.content.ContentValues; import android.content.Context; import android.support.annotation.NonNull; import android.text.TextUtils; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.RepoUpdater; import org.fdroid.fdroid.RepoUpdater.UpdateException; +import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.AppProvider; diff --git a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java similarity index 98% rename from app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java rename to app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 257e9f0f8..5463a7f88 100644 --- a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -1,9 +1,11 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.updater; import android.support.annotation.StringDef; import android.util.Log; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.RepoUpdater; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.App; @@ -23,6 +25,7 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +// TODO: Use sdk=24 when Robolectric supports this @Config(constants = BuildConfig.class, sdk = 23) @RunWith(RobolectricGradleTestRunner.class) public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { diff --git a/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java similarity index 99% rename from app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java rename to app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java index 2f40767db..12751a40a 100644 --- a/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java @@ -20,12 +20,14 @@ * MA 02110-1301, USA. */ -package org.fdroid.fdroid; +package org.fdroid.fdroid.updater; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.RepoXMLHandler; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Repo; From 1d1c1ebb749bbd25e8ad5029a09152135f162c26 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 4 Aug 2016 16:12:12 +1000 Subject: [PATCH 06/16] Add test for repo priorities + app metadata. Not passing yet. --- .../org/fdroid/fdroid/data/RepoProvider.java | 2 +- .../fdroid/updater/MultiRepoUpdaterTest.java | 2 +- .../updater/ProperMultiRepoUpdaterTest.java | 98 +++++++++++++++---- 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java index f08fc1589..da0240a99 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java @@ -301,7 +301,7 @@ public class RepoProvider extends FDroidProvider { public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (TextUtils.isEmpty(sortOrder)) { - sortOrder = "_ID ASC"; + sortOrder = Cols.PRIORITY + " ASC"; } switch (MATCHER.match(uri)) { diff --git a/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java index a44769e74..6b761c15b 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/MultiRepoUpdaterTest.java @@ -165,7 +165,7 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { repo.address = uri; repo.name = name; - ContentValues values = new ContentValues(2); + ContentValues values = new ContentValues(3); values.put(Schema.RepoTable.Cols.SIGNING_CERT, repo.signingCertificate); values.put(Schema.RepoTable.Cols.ADDRESS, repo.address); values.put(Schema.RepoTable.Cols.NAME, repo.name); diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 5463a7f88..68ed05c9b 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -39,21 +39,53 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { public void mainRepo() throws RepoUpdater.UpdateException { assertEmpty(); updateMain(); - assertMainRepo(RepoProvider.Helper.all(context)); + assertMainRepo(); } @Test public void archiveRepo() throws RepoUpdater.UpdateException { assertEmpty(); updateArchive(); - assertMainArchiveRepo(RepoProvider.Helper.all(context)); + assertMainArchiveRepoMetadata(); } @Test public void conflictingRepo() throws RepoUpdater.UpdateException { assertEmpty(); updateConflicting(); - assertConflictingRepo(RepoProvider.Helper.all(context)); + assertConflictingRepo(); + } + + @Test + public void conflictingMetadataTakesPriority() throws RepoUpdater.UpdateException { + updateConflicting(); + updateMain(); + updateArchive(); + + assertEquals(1, RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI).priority); + assertEquals(2, RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI).priority); + assertEquals(3, RepoProvider.Helper.findByAddress(context, REPO_ARCHIVE_URI).priority); + + assertMainRepo(); + assertMainArchiveRepoMetadata(); + assertConflictingRepo(); + + App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "com.uberspot.a2048"); + assert2048Metadata(a2048, "Conflicting"); + + // This is only provided by the "Conflicting" repo. + App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.dgtale.icsimport"); + assertCalendarMetadata(calendar, "Conflicting"); + + // This is only provided by the "Main" or "Archive" repo. Both the main and archive repo both + // pull their metadata from the same build recipe in fdroidserver. The only difference is that + // the archive repository contains .apks from further back, but their metadata is the same. + App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.adaway"); + assertAdAwayMetadata(adAway, "Normal"); + + // This is only provided by the "Main" repo. + App adb = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "siir.es.adbWireless"); + assertAdAwayMetadata(adb, "Normal"); } @Test @@ -133,10 +165,14 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals("Repos", 3, repos.size()); assertMainRepo(repos); - assertMainArchiveRepo(repos); + assertMainArchiveRepoMetadata(repos); assertConflictingRepo(repos); } + private void assertMainRepo() { + assertMainRepo(RepoProvider.Helper.all(context)); + } + /** * + 2048 (com.uberspot.a2048) * - Version 1.96 (19) @@ -157,17 +193,21 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertApksExist(apks, "org.adaway", new int[]{52, 53, 54}); assertApksExist(apks, "siir.es.adbWireless", new int[]{12}); - assert2048(repo, "Normal"); - assertAdAway(repo, "Normal"); - assertAdb(repo, "Normal"); + assert2048Metadata(repo, "Normal"); + assertAdAwayMetadata(repo, "Normal"); + assertAdbMetadata(repo, "Normal"); + } + + private void assert2048Metadata(Repo repo, @RepoIdentifier String id) { + App a2048 = AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.uberspot.a2048", repo.getId()); + assert2048Metadata(a2048, id); } /** * @param id An identifier that we've put in the metadata for each repo to ensure that * we can identify the metadata is coming from the correct repo. */ - private void assert2048(Repo repo, @RepoIdentifier String id) { - App a2048 = AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.uberspot.a2048", repo.getId()); + private void assert2048Metadata(App a2048, @RepoIdentifier String id) { assertNotNull(a2048); assertEquals("2048", a2048.name); assertEquals(String.format("

2048 from %s repo.

", id), a2048.description); @@ -177,9 +217,13 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.trackerURL); } - /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ - private void assertAdAway(Repo repo, @RepoIdentifier String id) { + private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) { App adaway = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.adaway", repo.getId()); + assertAdAwayMetadata(adaway, id); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048Metadata(Repo, String) */ + private void assertAdAwayMetadata(App adaway, @RepoIdentifier String id) { assertNotNull(adaway); assertEquals(String.format("AdAway", id), adaway.name); assertEquals(String.format("

AdAway from %s repo.

", id), adaway.description); @@ -192,9 +236,13 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals(String.format("369138", id), adaway.flattrID); } - /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ - private void assertAdb(Repo repo, @RepoIdentifier String id) { + private void assertAdbMetadata(Repo repo, @RepoIdentifier String id) { App adb = AppProvider.Helper.findByPackageName(context.getContentResolver(), "siir.es.adbWireless", repo.getId()); + assertAdbMetadata(adb, id); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048Metadata(Repo, String) */ + private void assertAdbMetadata(App adb, @RepoIdentifier String id) { assertNotNull(adb); assertEquals("adbWireless", adb.name); assertEquals(String.format("

adbWireless from %s repo.

", id), adb.description); @@ -204,9 +252,13 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.trackerURL); } - /** @see ProperMultiRepoUpdaterTest#assert2048(Repo, String) */ - private void assertCalendar(Repo repo, @RepoIdentifier String id) { + private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) { App calendar = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.dgtale.icsimport", repo.getId()); + assertCalendarMetadata(calendar, id); + } + + /** @see ProperMultiRepoUpdaterTest#assert2048Metadata(Repo, String) */ + private void assertCalendarMetadata(App calendar, @RepoIdentifier String id) { assertNotNull(calendar); assertEquals("Add to calendar", calendar.name); assertEquals(String.format("

Add to calendar from %s repo.

", id), calendar.description); @@ -217,6 +269,10 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals("2225390", calendar.flattrID); } + private void assertMainArchiveRepoMetadata() { + assertMainArchiveRepoMetadata(RepoProvider.Helper.all(context)); + } + /** * + AdAway (org.adaway) * - Version 2.9.2 (51) @@ -233,14 +289,18 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { * - Version 1.35 (36) * - Version 1.34 (35) */ - private void assertMainArchiveRepo(List allRepos) { + private void assertMainArchiveRepoMetadata(List allRepos) { Repo repo = findRepo(REPO_ARCHIVE, allRepos); List apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL); assertEquals("Apks for main archive repo", 13, apks.size()); assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51}); - assertAdAway(repo, "Normal"); + assertAdAwayMetadata(repo, "Normal"); + } + + private void assertConflictingRepo() { + assertConflictingRepo(RepoProvider.Helper.all(context)); } /** @@ -261,8 +321,8 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53}); assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3}); - assertAdAway(repo, "Conflicting"); - assertCalendar(repo, "Conflicting"); + assertAdAwayMetadata(repo, "Conflicting"); + assertCalendarMetadata(repo, "Conflicting"); } } From 3ec64d6d82b965305757e630ba5eed7a70902e2f Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 8 Aug 2016 22:13:09 +1000 Subject: [PATCH 07/16] Finalise tests for repo priorities + app metadata --- .../org/fdroid/fdroid/data/AppProvider.java | 61 ++++++++++++++++--- .../fdroid/fdroid/data/PackageProvider.java | 5 +- .../java/org/fdroid/fdroid/data/Schema.java | 2 +- .../org/fdroid/fdroid/Issue763MultiRepo.java | 1 + .../updater/ProperMultiRepoUpdaterTest.java | 57 ++++++++++++++--- 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index b1a02f932..2d84d1d84 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -122,7 +122,8 @@ public class AppProvider extends FDroidProvider { } public static App findHighestPriorityMetadata(ContentResolver resolver, String packageName) { - throw new UnsupportedOperationException("TODO: Pull back the metadata with the highest priority for packageName"); + final Uri uri = getHighestPriorityMetadataUri(packageName); + return cursorToApp(resolver.query(uri, Cols.ALL, null, null, null)); } public static App findByPackageName(ContentResolver resolver, String packageName, long repoId) { @@ -266,8 +267,8 @@ public class AppProvider extends FDroidProvider { return pkg + " JOIN " + app + " ON (" + app + "." + Cols.PACKAGE_ID + " = " + pkg + "." + PackageTable.Cols.ROW_ID + ") " + - " LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") " + - " LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") "; + " JOIN " + repo + " ON (" + app + "." + Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") " + + " LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") "; } @Override @@ -420,6 +421,7 @@ public class AppProvider extends FDroidProvider { private static final String PATH_CATEGORY = "category"; private static final String PATH_CALC_APP_DETAILS_FROM_INDEX = "calcDetailsFromIndex"; private static final String PATH_REPO = "repo"; + private static final String PATH_HIGHEST_PRIORITY = "highestPriority"; private static final int CAN_UPDATE = CODE_SINGLE + 1; private static final int INSTALLED = CAN_UPDATE + 1; @@ -433,6 +435,7 @@ public class AppProvider extends FDroidProvider { private static final int SEARCH_REPO = REPO + 1; private static final int SEARCH_INSTALLED = SEARCH_REPO + 1; private static final int SEARCH_CAN_UPDATE = SEARCH_INSTALLED + 1; + private static final int HIGHEST_PRIORITY = SEARCH_CAN_UPDATE + 1; static { MATCHER.addURI(getAuthority(), null, CODE_LIST); @@ -448,6 +451,7 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE); MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED); MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS); + MATCHER.addURI(getAuthority(), PATH_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY); MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE); } @@ -510,6 +514,13 @@ public class AppProvider extends FDroidProvider { .build(); } + private static Uri getHighestPriorityMetadataUri(String packageName) { + return getContentUri().buildUpon() + .appendPath(PATH_HIGHEST_PRIORITY) + .appendPath(packageName) + .build(); + } + public static Uri getContentUri(String packageName) { return Uri.withAppendedPath(getContentUri(), packageName); } @@ -649,10 +660,9 @@ public class AppProvider extends FDroidProvider { } protected AppQuerySelection querySingle(String packageName, long repoId) { - final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ? " + - " AND " + getTableName() + "." + Cols.REPO_ID + " = ? "; - final String[] args = {packageName, Long.toString(repoId)}; - return new AppQuerySelection(selection, args); + final String selection = getTableName() + "." + Cols.REPO_ID + " = ? "; + final String[] args = {Long.toString(repoId)}; + return new AppQuerySelection(selection, args).add(queryPackageName(packageName)); } /** @@ -683,6 +693,23 @@ public class AppProvider extends FDroidProvider { return new AppQuerySelection(selection, args); } + private AppQuerySelection queryHighestPriority() { + final String highestPriority = + "SELECT MIN(r." + RepoTable.Cols.PRIORITY + ") " + + "FROM " + RepoTable.NAME + " AS r " + + "JOIN " + AppMetadataTable.NAME + " AS m ON (m." + Cols.REPO_ID + " = r." + RepoTable.Cols._ID + ") " + + "WHERE m." + Cols.PACKAGE_ID + " = " + getTableName() + "." + Cols.PACKAGE_ID; + + final String selection = RepoTable.NAME + "." + RepoTable.Cols.PRIORITY + " = (" + highestPriority + ")"; + return new AppQuerySelection(selection); + } + + private AppQuerySelection queryPackageName(String packageName) { + final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ? "; + final String[] args = {packageName}; + return new AppQuerySelection(selection, args); + } + private AppQuerySelection queryRecentlyUpdated() { final String app = getTableName(); final String lastUpdated = app + "." + Cols.LAST_UPDATED; @@ -729,6 +756,14 @@ public class AppProvider extends FDroidProvider { // Queries which are for the main list of apps should not include swap apps. boolean includeSwap = true; + // It is usually the case that we ask for app(s) for which we don't care what repo is + // responsible for providing them. In that case, we need to populate the metadata with + // that form the repo with the highest priority. + // Whenever we know which repo it is coming from, then it is important that we don't + // delegate to the repo with the highest priority, but rather the specific repo we are + // querying from. + boolean repoIsKnown = false; + switch (MATCHER.match(uri)) { case CODE_LIST: includeSwap = false; @@ -739,6 +774,7 @@ public class AppProvider extends FDroidProvider { long repoId = Long.parseLong(pathParts.get(1)); String packageName = pathParts.get(2); selection = selection.add(querySingle(packageName, repoId)); + repoIsKnown = true; break; case CAN_UPDATE: @@ -748,6 +784,7 @@ public class AppProvider extends FDroidProvider { case REPO: selection = selection.add(queryRepo(Long.parseLong(uri.getLastPathSegment()))); + repoIsKnown = true; break; case INSTALLED: @@ -774,6 +811,7 @@ public class AppProvider extends FDroidProvider { selection = selection .add(querySearch(uri.getPathSegments().get(2))) .add(queryRepo(Long.parseLong(uri.getPathSegments().get(1)))); + repoIsKnown = true; break; case NO_APKS: @@ -797,11 +835,20 @@ public class AppProvider extends FDroidProvider { includeSwap = false; break; + case HIGHEST_PRIORITY: + selection = selection.add(queryPackageName(uri.getLastPathSegment())); + includeSwap = false; + break; + default: Log.e(TAG, "Invalid URI for app content provider: " + uri); throw new UnsupportedOperationException("Invalid URI for app content provider: " + uri); } + if (!repoIsKnown) { + selection = selection.add(queryHighestPriority()); + } + return runQuery(uri, selection, projection, includeSwap, sortOrder); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java index 9c3d6942c..5667cb5f9 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java @@ -29,7 +29,8 @@ public class PackageProvider extends FDroidProvider { } public static long getPackageId(Context context, String packageName) { - Cursor cursor = context.getContentResolver().query(getPackageUri(packageName), Cols.ALL, null, null, null); + String[] projection = new String[] {Cols.ROW_ID}; + Cursor cursor = context.getContentResolver().query(getPackageUri(packageName), projection, null, null, null); if (cursor == null) { return 0; } @@ -39,7 +40,7 @@ public class PackageProvider extends FDroidProvider { return 0; } else { cursor.moveToFirst(); - return cursor.getLong(cursor.getColumnIndexOrThrow(Cols.PACKAGE_NAME)); + return cursor.getLong(cursor.getColumnIndexOrThrow(Cols.ROW_ID)); } } finally { cursor.close(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index eee638a18..5b1f1779f 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -115,7 +115,7 @@ public interface Schema { * @see Cols#ALL_COLS */ String[] ALL = { - _ID, ROW_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, + _ID, ROW_ID, REPO_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID, UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED, diff --git a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java index 8df75aedb..c236a5ad2 100644 --- a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java +++ b/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java @@ -7,6 +7,7 @@ import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.updater.MultiRepoUpdaterTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 68ed05c9b..2878aff2e 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -1,6 +1,7 @@ package org.fdroid.fdroid.updater; +import android.content.ContentValues; import android.support.annotation.StringDef; import android.util.Log; @@ -13,6 +14,7 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.data.Schema.RepoTable.Cols; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricGradleTestRunner; @@ -20,7 +22,9 @@ import org.robolectric.annotation.Config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -56,13 +60,24 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertConflictingRepo(); } + private Map allApps() { + List apps = AppProvider.Helper.all(context.getContentResolver()); + Map appsIndexedByPackageName = new HashMap<>(apps.size()); + for (App app : apps) { + appsIndexedByPackageName.put(app.packageName, app); + } + return appsIndexedByPackageName; + } + @Test - public void conflictingMetadataTakesPriority() throws RepoUpdater.UpdateException { + public void metadataWithRepoPriority() throws RepoUpdater.UpdateException { updateConflicting(); updateMain(); updateArchive(); - assertEquals(1, RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI).priority); + Repo conflictingRepo = RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI); + + assertEquals(1, conflictingRepo.priority); assertEquals(2, RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI).priority); assertEquals(3, RepoProvider.Helper.findByAddress(context, REPO_ARCHIVE_URI).priority); @@ -70,22 +85,44 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertMainArchiveRepoMetadata(); assertConflictingRepo(); - App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "com.uberspot.a2048"); - assert2048Metadata(a2048, "Conflicting"); + assertRepoTakesPriority("Conflicting"); + + // Make the conflicting repo less important than the main repo. + ContentValues values = new ContentValues(1); + values.put(Cols.PRIORITY, 5); + RepoProvider.Helper.update(context, conflictingRepo, values); + Repo updatedConflictingRepo = RepoProvider.Helper.findByAddress(context, REPO_CONFLICTING_URI); + assertEquals(5, updatedConflictingRepo.priority); + + assertRepoTakesPriority("Normal"); + } + + private void assertRepoTakesPriority(@RepoIdentifier String higherPriority) { + Map allApps = allApps(); + + // Provided by both the "Main" and "Conflicting" repo, so need to fetch metdata from the + // repo with the higher "Conflicting" repo has a higher priority. + App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.adaway"); + assertAdAwayMetadata(adAway, higherPriority); + assertAdAwayMetadata(allApps.get("org.adaway"), higherPriority); - // This is only provided by the "Conflicting" repo. - App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.dgtale.icsimport"); - assertCalendarMetadata(calendar, "Conflicting"); // This is only provided by the "Main" or "Archive" repo. Both the main and archive repo both // pull their metadata from the same build recipe in fdroidserver. The only difference is that // the archive repository contains .apks from further back, but their metadata is the same. - App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.adaway"); - assertAdAwayMetadata(adAway, "Normal"); + App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "com.uberspot.a2048"); + assert2048Metadata(a2048, "Normal"); + assert2048Metadata(allApps.get("com.uberspot.a2048"), "Normal"); + + // This is only provided by the "Conflicting" repo. + App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.dgtale.icsimport"); + assertCalendarMetadata(calendar, "Conflicting"); + assertCalendarMetadata(allApps.get("org.dgtale.icsimport"), "Conflicting"); // This is only provided by the "Main" repo. App adb = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "siir.es.adbWireless"); - assertAdAwayMetadata(adb, "Normal"); + assertAdbMetadata(adb, "Normal"); + assertAdbMetadata(allApps.get("siir.es.adbWireless"), "Normal"); } @Test From efdf328fd3fb3a4595b05e91b72bb1af36d311fb Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 11 Aug 2016 10:31:11 +1000 Subject: [PATCH 08/16] Clarify a limitation in the current implementation Two repositories can (and always could) end up with the same exact .apk file. If that .apk is the "suggested version", then we should eliminate the idea of "suggested version code" and instead have a "suggested apk" (which implicitly includes the repository it comes from, so we choose the one with the better priority). Right now, we kind of assume that it doesn't matter which repo provides the suggested apk, as long as one of them has an .apk with the correct version code and signing key. It shouldn't _particularly_ matter from a security perspective, because a malicious repo wont be able to trick a user into installing an apk with a different signing key, but it would be good to iron this out. This commit adds a TODO explaining this for th ebenefit of any CRer. --- .../main/java/org/fdroid/fdroid/data/Apk.java | 2 +- .../org/fdroid/fdroid/data/ApkProvider.java | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index d0ab34931..5580196ca 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -63,7 +63,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { public String[] incompatibleReasons; /** - * The numeric primary key of the Package table, which is used to join apks. + * The numeric primary key of the Metadata table, which is used to join apks. */ public long appId; diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index e79380132..eeac35a98 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -369,8 +369,14 @@ public class ApkProvider extends FDroidProvider { private QuerySelection querySingleFromAnyRepo(Uri uri, boolean includeAlias) { String alias = includeAlias ? "apk." : ""; - // TODO: Change the = to an IN to deal with multiple apps? - final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " = (" + getMetadataIdFromPackageNameQuery() + ")"; + // TODO: Technically multiple repositories can provide the apk with this version code. + // Therefore, in the very near future we'll need to change from calculating a + // "suggested version code" to a "suggested apk" and join directly onto the apk table. + // This way, we can take into account both repo priorities and signing keys of any + // already installed apks to ensure that the best version is suggested to the user. + // At this point, we may pull back the "wrong" apk in weird edge cases, but the user + // wont be tricked into installing it, as it will (likely) have a different signing key. + final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " IN (" + getMetadataIdFromPackageNameQuery() + ")"; final String[] args = { // First (0th) path segment is the word "apk", // and we are not interested in it. @@ -443,13 +449,12 @@ public class ApkProvider extends FDroidProvider { return new QuerySelection(sb.toString(), args); } - // TODO: This could return many rows of app metadata private String getMetadataIdFromPackageNameQuery() { - return "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " + - "FROM " + AppMetadataTable.NAME + " AS app " + - "JOIN " + PackageTable.NAME + " AS pkg ON ( " + - " app." + AppMetadataTable.Cols.PACKAGE_ID + " = pkg." + PackageTable.Cols.ROW_ID + " ) " + - "WHERE pkg." + PackageTable.Cols.PACKAGE_NAME + " = ?"; + return "SELECT m." + AppMetadataTable.Cols.ROW_ID + " " + + "FROM " + AppMetadataTable.NAME + " AS m " + + "JOIN " + PackageTable.NAME + " AS p ON ( " + + " m." + AppMetadataTable.Cols.PACKAGE_ID + " = p." + PackageTable.Cols.ROW_ID + " ) " + + "WHERE p." + PackageTable.Cols.PACKAGE_NAME + " = ?"; } @Override From c08a2a7bdc2386ac4ad2ba401992cd81e58a9dee Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 11 Aug 2016 11:42:24 +1000 Subject: [PATCH 09/16] Cleaning up/commenting AppProvider --- .../java/org/fdroid/fdroid/AppDetails.java | 2 +- .../main/java/org/fdroid/fdroid/data/App.java | 7 ++ .../org/fdroid/fdroid/data/AppProvider.java | 81 ++++++++++--------- .../org/fdroid/fdroid/data/RepoPersister.java | 2 +- .../fdroid/fdroid/data/TempAppProvider.java | 2 +- .../fdroid/views/swap/SwapAppsView.java | 2 +- .../test/java/org/fdroid/fdroid/Assert.java | 6 +- .../fdroid/fdroid/data/AppProviderTest.java | 26 +++--- .../fdroid/fdroid/data/ProviderUriTests.java | 2 +- .../updater/ProperMultiRepoUpdaterTest.java | 9 ++- 10 files changed, 77 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index ccb9c4edc..5ab62ded9 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -425,7 +425,7 @@ public class AppDetails extends AppCompatActivity { // register observer to know when install status changes myAppObserver = new AppObserver(new Handler()); getContentResolver().registerContentObserver( - AppProvider.getAppUri(app), + AppProvider.getHighestPriorityMetadataUri(app.packageName), true, myAppObserver); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 1f5c257e8..611072bf9 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -46,6 +46,13 @@ public class App extends ValueObject implements Comparable, Parcelable { public String packageName = "unknown"; public String name = "Unknown"; + + /** + * This is primarily for the purpose of saving app metadata when parsing an index.xml file. + * At most other times, we don't particularly care which repo an {@link App} object came from. + * It is pretty much transparent, because the metadata will be populated from the repo with + * the highest priority. The UI doesn't care normally _which_ repo provided the metadata. + */ public long repoId; public String summary = "Unknown application"; public String icon; diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 2d84d1d84..299875313 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -28,6 +28,23 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +/** + * Each app has a bunch of metadata that it associates with a package name (such as org.fdroid.fdroid). + * Multiple repositories can host the same package, and provide different metadata for that app. + * + * As such, it is usually the case that you are interested in an {@link App} which has its metadata + * provided by "the repo with the best priority", rather than "specific repo X". This is important + * when asking for an apk, whereby the preferable way is likely using: + * + * * {@link AppProvider.Helper#findHighestPriorityMetadata(ContentResolver, String)} + * + * rather than: + * + * * {@link AppProvider.Helper#findSpecificApp(ContentResolver, String, long, String[])} + * + * The same can be said of retrieving a list of {@link App} objects, where the metadata for each app + * in the result set should be populated from the repository with the best priority. + */ public class AppProvider extends FDroidProvider { private static final String TAG = "AppProvider"; @@ -126,13 +143,15 @@ public class AppProvider extends FDroidProvider { return cursorToApp(resolver.query(uri, Cols.ALL, null, null, null)); } - public static App findByPackageName(ContentResolver resolver, String packageName, long repoId) { - return findByPackageName(resolver, packageName, repoId, Cols.ALL); - } - - public static App findByPackageName(ContentResolver resolver, String packageName, long repoId, - String[] projection) { - final Uri uri = getAppUri(packageName, repoId); + /** + * Returns an {@link App} with metadata provided by a specific {@code repoId}. Keep in mind + * that most of the time we don't care which repo provides the metadata for a particular app, + * as long as it is the repo with the best priority. In those cases, you should instead use + * {@link AppProvider.Helper#findHighestPriorityMetadata(ContentResolver, String)}. + */ + public static App findSpecificApp(ContentResolver resolver, String packageName, long repoId, + String[] projection) { + final Uri uri = getSpecificAppUri(packageName, repoId); return cursorToApp(resolver.query(uri, projection, null, null, null)); } @@ -501,11 +520,12 @@ public class AppProvider extends FDroidProvider { return getContentUri(app.packageName); } - public static Uri getAppUri(App app) { - return getAppUri(app.packageName, app.repoId); - } - - public static Uri getAppUri(String packageName, long repoId) { + /** + * @see AppProvider.Helper#findSpecificApp(ContentResolver, String, long, String[]) for details + * of why you should usually prefer {@link AppProvider#getHighestPriorityMetadataUri(String)} to + * this method. + */ + public static Uri getSpecificAppUri(String packageName, long repoId) { return getContentUri() .buildUpon() .appendPath(PATH_APP) @@ -514,7 +534,7 @@ public class AppProvider extends FDroidProvider { .build(); } - private static Uri getHighestPriorityMetadataUri(String packageName) { + public static Uri getHighestPriorityMetadataUri(String packageName) { return getContentUri().buildUpon() .appendPath(PATH_HIGHEST_PRIORITY) .appendPath(packageName) @@ -669,7 +689,7 @@ public class AppProvider extends FDroidProvider { * Same as {@link AppProvider#querySingle(String, long)} except it is used for the purpose * of an UPDATE query rather than a SELECT query. This means that it must use a subquery to get * the {@link Cols.Package#PACKAGE_ID} rather than the join which is already in place for that - * table. + * table. The reason is because UPDATE queries cannot include joins in SQLite. */ protected AppQuerySelection querySingleForUpdate(String packageName, long repoId) { final String selection = Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + @@ -693,6 +713,10 @@ public class AppProvider extends FDroidProvider { return new AppQuerySelection(selection, args); } + /** + * Ensures that for each app metadata row with the same package name, only the one from the repo + * with the best priority is represented in the result set. + */ private AppQuerySelection queryHighestPriority() { final String highestPriority = "SELECT MIN(r." + RepoTable.Cols.PRIORITY + ") " + @@ -895,34 +919,17 @@ public class AppProvider extends FDroidProvider { if (!isApplyingBatch()) { getContext().getContentResolver().notifyChange(uri, null); } - return getAppUri(values.getAsString(PackageTable.Cols.PACKAGE_NAME), values.getAsLong(Cols.REPO_ID)); + return getSpecificAppUri(values.getAsString(PackageTable.Cols.PACKAGE_NAME), values.getAsLong(Cols.REPO_ID)); } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { - QuerySelection query = new QuerySelection(where, whereArgs); - switch (MATCHER.match(uri)) { - - case CALC_APP_DETAILS_FROM_INDEX: - updateAppDetails(); - return 0; - - case CODE_SINGLE: - List pathParts = uri.getPathSegments(); - long repoId = Long.parseLong(pathParts.get(1)); - String packageName = pathParts.get(2); - query = query.add(querySingleForUpdate(packageName, repoId)); - break; - - default: - throw new UnsupportedOperationException("Update not supported for " + uri + "."); - + if (MATCHER.match(uri) != CALC_APP_DETAILS_FROM_INDEX) { + throw new UnsupportedOperationException("Update not supported for " + uri + "."); } - int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); - if (!isApplyingBatch()) { - getContext().getContentResolver().notifyChange(uri, null); - } - return count; + + updateAppDetails(); + return 0; } protected void updateAppDetails() { diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 6ddee8a9e..98247bbeb 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -226,7 +226,7 @@ public class RepoPersister { */ private boolean isAppInDatabase(App app) { String[] fields = {Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME}; - App found = AppProvider.Helper.findByPackageName(context.getContentResolver(), app.packageName, repo.id, fields); + App found = AppProvider.Helper.findSpecificApp(context.getContentResolver(), app.packageName, repo.id, fields); return found != null; } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 9927fc28d..47896dbb1 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -142,7 +142,7 @@ public class TempAppProvider extends AppProvider { int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); if (!isApplyingBatch()) { - getContext().getContentResolver().notifyChange(uri, null); + getContext().getContentResolver().notifyChange(getHighestPriorityMetadataUri(packageName), null); } return count; } diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index d14f831f4..325ffc630 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -319,7 +319,7 @@ public class SwapAppsView extends ListView implements // implemented on API-16, so leaving like this for now. getActivity().getContentResolver().unregisterContentObserver(appObserver); getActivity().getContentResolver().registerContentObserver( - AppProvider.getAppUri(this.app), true, appObserver); + AppProvider.getSpecificAppUri(this.app.packageName, this.app.repoId), true, appObserver); } resetView(); } diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java index adbd30db4..ba7085050 100644 --- a/app/src/test/java/org/fdroid/fdroid/Assert.java +++ b/app/src/test/java/org/fdroid/fdroid/Assert.java @@ -198,14 +198,14 @@ public class Assert { Uri uri = AppProvider.getContentUri(); context.getContentResolver().insert(uri, values); - return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); + return AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL); } private static App ensureApp(Context context, String packageName) { - App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); + App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL); if (app == null) { insertApp(context, packageName, packageName); - app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1); + app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL); } assertNotNull(app); return app; diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java index b0ccd5e6e..73b8cd1da 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -68,7 +68,7 @@ public class AppProviderTest extends FDroidProviderTest { @Test public void testCantFindApp() { - assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist", 1)); + assertNull(AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.example.doesnt-exist", 1, Cols.ALL)); } @Test @@ -111,14 +111,14 @@ public class AppProviderTest extends FDroidProviderTest { ContentResolver r = context.getContentResolver(); // Can't "update", although can "install"... - App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed", 1); + App notInstalled = AppProvider.Helper.findSpecificApp(r, "not installed", 1, Cols.ALL); assertFalse(notInstalled.canAndWantToUpdate(context)); - App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available", 1); - App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore", 1); - App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all", 1); - App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest", 1); - App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old", 1); + App installedOnlyOneVersionAvailable = AppProvider.Helper.findSpecificApp(r, "installed, only one version available", 1, Cols.ALL); + App installedAlreadyLatestNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, already latest, no ignore", 1, Cols.ALL); + App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore all", 1, Cols.ALL); + App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore latest", 1, Cols.ALL); + App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore old", 1, Cols.ALL); assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context)); assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context)); @@ -126,10 +126,10 @@ public class AppProviderTest extends FDroidProviderTest { assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context)); assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context)); - App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore", 1); - App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all", 1); - App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest", 1); - App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest", 1); + App installedOldNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, old version, no ignore", 1, Cols.ALL); + App installedOldIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore all", 1, Cols.ALL); + App installedOldIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore latest", 1, Cols.ALL); + App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore newer, but not latest", 1, Cols.ALL); assertTrue(installedOldNoIgnore.canAndWantToUpdate(context)); assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context)); @@ -239,7 +239,7 @@ public class AppProviderTest extends FDroidProviderTest { assertEquals("org.fdroid.fdroid", app.packageName); assertEquals("F-Droid", app.name); - App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid", 1); + App otherApp = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.fdroid.fdroid", 1, Cols.ALL); assertNotNull(otherApp); assertEquals("org.fdroid.fdroid", otherApp.packageName); assertEquals("F-Droid", otherApp.name); @@ -371,6 +371,6 @@ public class AppProviderTest extends FDroidProviderTest { Uri uri = AppProvider.getContentUri(); contentResolver.insert(uri, values); - return AppProvider.Helper.findByPackageName(context.getContentResolver(), id, 1); + return AppProvider.Helper.findSpecificApp(context.getContentResolver(), id, 1, Cols.ALL); } } diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java index 5f4d445bb..cda22e331 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -96,7 +96,7 @@ public class ProviderUriTests { app.repoId = 1; app.packageName = "org.fdroid.fdroid"; - assertValidUri(resolver, AppProvider.getAppUri(app), "content://org.fdroid.fdroid.data.AppProvider/app/1/org.fdroid.fdroid", projection); + assertValidUri(resolver, AppProvider.getSpecificAppUri(app.packageName, app.repoId), "content://org.fdroid.fdroid.data.AppProvider/app/1/org.fdroid.fdroid", projection); } @Test diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 2878aff2e..a031289c0 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -14,6 +14,7 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.RepoTable.Cols; import org.junit.Test; import org.junit.runner.RunWith; @@ -236,7 +237,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { } private void assert2048Metadata(Repo repo, @RepoIdentifier String id) { - App a2048 = AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.uberspot.a2048", repo.getId()); + App a2048 = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.uberspot.a2048", repo.getId(), AppMetadataTable.Cols.ALL); assert2048Metadata(a2048, id); } @@ -255,7 +256,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { } private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) { - App adaway = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.adaway", repo.getId()); + App adaway = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.adaway", repo.getId(), AppMetadataTable.Cols.ALL); assertAdAwayMetadata(adaway, id); } @@ -274,7 +275,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { } private void assertAdbMetadata(Repo repo, @RepoIdentifier String id) { - App adb = AppProvider.Helper.findByPackageName(context.getContentResolver(), "siir.es.adbWireless", repo.getId()); + App adb = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "siir.es.adbWireless", repo.getId(), AppMetadataTable.Cols.ALL); assertAdbMetadata(adb, id); } @@ -290,7 +291,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { } private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) { - App calendar = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.dgtale.icsimport", repo.getId()); + App calendar = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.dgtale.icsimport", repo.getId(), AppMetadataTable.Cols.ALL); assertCalendarMetadata(calendar, id); } From f2a58ad67f2cbf31ed47601003728b52c6a1fce6 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 11 Aug 2016 14:24:23 +1000 Subject: [PATCH 10/16] Update priorities for default repos to go from 1-4 instead of 10 + 20 --- app/src/main/res/values/default_repos.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/default_repos.xml b/app/src/main/res/values/default_repos.xml index 6a09a6920..3a895f579 100644 --- a/app/src/main/res/values/default_repos.xml +++ b/app/src/main/res/values/default_repos.xml @@ -17,7 +17,7 @@ 1 - 10 + 1 ignore @@ -38,7 +38,7 @@ 0 - 20 + 2 ignore @@ -60,7 +60,7 @@ 0 - 10 + 3 ignore @@ -81,7 +81,7 @@ 0 - 20 + 4 ignore From 01b8f7f4bf8c92327b19c37c79fe2f6a015c14ae Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 6 Oct 2016 01:44:08 +1100 Subject: [PATCH 11/16] Clarify some of the database stuff around database providers. Includes: * One of the functions querying for apps did not correctly specify the repository the repos came from. * Fix deletion code which refered to incorrect field. * Cleanup code style in some places. --- .../org/fdroid/fdroid/data/ApkProvider.java | 25 +++++++++---- .../org/fdroid/fdroid/data/AppProvider.java | 10 ++++-- .../fdroid/fdroid/data/FDroidProvider.java | 6 +++- .../fdroid/fdroid/data/PackageProvider.java | 24 ++++++------- .../org/fdroid/fdroid/data/RepoPersister.java | 4 +-- .../fdroid/fdroid/data/TempAppProvider.java | 36 +++++++++++++------ .../installer/InstallManagerService.java | 6 ++-- .../views/InstallConfirmActivity.java | 2 +- .../fdroid/views/swap/SwapAppsView.java | 2 +- .../fdroid/fdroid/data/ProviderUriTests.java | 2 +- .../AcceptableMultiRepoUpdaterTest.java | 4 +-- .../updater/ProperMultiRepoUpdaterTest.java | 24 +++++++++++++ 12 files changed, 100 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index eeac35a98..251c4fb32 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -424,7 +424,6 @@ public class ApkProvider extends FDroidProvider { "You tried to query " + apkDetails.length); } String alias = includeAlias ? "apk." : ""; - String metadataAlias = includeAlias ? "app." : ""; final String[] args = new String[apkDetails.length * 2]; StringBuilder sb = new StringBuilder(); for (int i = 0; i < apkDetails.length; i++) { @@ -436,12 +435,24 @@ public class ApkProvider extends FDroidProvider { if (i != 0) { sb.append(" OR "); } - sb.append(" ( ") - .append(metadataAlias) - .append(AppMetadataTable.Cols.PACKAGE_ID) - .append(" = (") - .append(getPackageIdFromPackageNameQuery()) - .append(") AND ") + sb.append(" ( "); + + if (includeAlias) { + // This is the simpler way to figure out the package name of a row in the apk table. + // It requires slightly less work for sqlite3 than the alternative below. + sb.append("app.") + .append(AppMetadataTable.Cols.PACKAGE_ID) + .append(" = (") + .append(getPackageIdFromPackageNameQuery()) + .append(") "); + } else { + sb.append(Cols.APP_ID) + .append(" IN (") + .append(getMetadataIdFromPackageNameQuery()) + .append(")"); + } + + sb.append(" AND ") .append(alias) .append(Cols.VERSION_CODE) .append(" = ? ) "); diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 299875313..0fd2e568a 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -155,6 +155,10 @@ public class AppProvider extends FDroidProvider { return cursorToApp(resolver.query(uri, projection, null, null, null)); } + public static App findSpecificApp(ContentResolver resolver, String packageName, long repoId) { + return findSpecificApp(resolver, packageName, repoId, Cols.ALL); + } + private static App cursorToApp(Cursor cursor) { App app = null; if (cursor != null) { @@ -434,7 +438,7 @@ public class AppProvider extends FDroidProvider { private static final String PATH_SEARCH_REPO = "searchRepo"; private static final String PATH_NO_APKS = "noApks"; protected static final String PATH_APPS = "apps"; - protected static final String PATH_APP = "app"; + protected static final String PATH_SPECIFIC_APP = "app"; private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated"; private static final String PATH_NEWLY_ADDED = "newlyAdded"; private static final String PATH_CATEGORY = "category"; @@ -471,7 +475,7 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED); MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS); MATCHER.addURI(getAuthority(), PATH_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY); - MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE); } public static Uri getContentUri() { @@ -528,7 +532,7 @@ public class AppProvider extends FDroidProvider { public static Uri getSpecificAppUri(String packageName, long repoId) { return getContentUri() .buildUpon() - .appendPath(PATH_APP) + .appendPath(PATH_SPECIFIC_APP) .appendPath(Long.toString(repoId)) .appendPath(packageName) .build(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java b/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java index 26f6f28fb..8c341264d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/FDroidProvider.java @@ -158,7 +158,11 @@ public abstract class FDroidProvider extends ContentProvider { } } - protected String getPackageIdFromPackageNameQuery() { + /** + * Helper function to be used when you need to know the primary key from the package table + * when all you have is the package name. + */ + protected static String getPackageIdFromPackageNameQuery() { return "SELECT " + Schema.PackageTable.Cols.ROW_ID + " FROM " + Schema.PackageTable.NAME + " WHERE " + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?"; } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java index 5667cb5f9..a12be32c5 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java @@ -5,15 +5,12 @@ import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; -import android.util.Log; import org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.PackageTable.Cols; public class PackageProvider extends FDroidProvider { - private static final String TAG = "PackageProvider"; - public static final class Helper { private Helper() { } @@ -126,18 +123,13 @@ public class PackageProvider extends FDroidProvider { @Override public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) { - QuerySelection selection = new QuerySelection(customSelection, selectionArgs); - - switch (MATCHER.match(uri)) { - case CODE_SINGLE: - selection = selection.add(querySingle(uri.getLastPathSegment())); - break; - - default: - Log.e(TAG, "Invalid URI for content provider: " + uri); - throw new UnsupportedOperationException("Invalid URI for content provider: " + uri); + if (MATCHER.match(uri) != CODE_SINGLE) { + throw new UnsupportedOperationException("Invalid URI for content provider: " + uri); } + QuerySelection selection = new QuerySelection(customSelection, selectionArgs) + .add(querySingle(uri.getLastPathSegment())); + Query query = new Query(); query.addSelection(selection); query.addFields(projection); @@ -152,7 +144,7 @@ public class PackageProvider extends FDroidProvider { * Deleting of packages is not required. * It doesn't matter if we have a package name in the database after the package is no longer * present in the repo any more. They wont take up much space, and it is the presence of rows - * in the {@link Schema.MetadataTable} which decides whether something is available in the + * in the {@link Schema.AppMetadataTable} which decides whether something is available in the * F-Droid client or not. */ @Override @@ -167,6 +159,10 @@ public class PackageProvider extends FDroidProvider { return getPackageIdUri(rowId); } + /** + * Package names never change. If a package name has changed, then that means that it is a + * new app all together as far as Android is concerned. + */ @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { throw new UnsupportedOperationException("Update not supported for " + uri + "."); diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 98247bbeb..9c05f3087 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -144,7 +144,7 @@ public class RepoPersister { packageNames.add(app.packageName); } String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME}; - List fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection); + List fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, repo.id, projection); Map ids = new HashMap<>(fromDb.size()); for (App app : fromDb) { @@ -205,7 +205,7 @@ public class RepoPersister { * Does not do any checks to see if the app already exists or not. */ private ContentProviderOperation updateExistingApp(App app) { - Uri uri = TempAppProvider.getAppUri(app.packageName, app.repoId); + Uri uri = TempAppProvider.getSpecificTempAppUri(app.packageName, app.repoId); return ContentProviderOperation.newUpdate(uri).withValues(app.toContentValues()).build(); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 47896dbb1..6ebc1112f 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -13,6 +13,7 @@ import java.util.List; import org.fdroid.fdroid.data.Schema.ApkTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; +import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.data.Schema.PackageTable; /** @@ -41,8 +42,8 @@ public class TempAppProvider extends AppProvider { static { MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT); MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT); - MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS); - MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_APPS + "/#/*", APPS); + MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE); } @Override @@ -58,24 +59,36 @@ public class TempAppProvider extends AppProvider { return Uri.parse("content://" + getAuthority()); } - public static Uri getAppUri(String packageName, long repoId) { + /** + * Same as {@link AppProvider#getSpecificAppUri(String, long)}, except loads data from the temp + * table being used during a repo update rather than the persistent table. + */ + public static Uri getSpecificTempAppUri(String packageName, long repoId) { return getContentUri() .buildUpon() - .appendPath(PATH_APP) + .appendPath(PATH_SPECIFIC_APP) .appendPath(Long.toString(repoId)) .appendPath(packageName) .build(); } - public static Uri getAppsUri(List apps) { + public static Uri getAppsUri(List apps, long repoId) { return getContentUri().buildUpon() .appendPath(PATH_APPS) + .appendPath(Long.toString(repoId)) .appendPath(TextUtils.join(",", apps)) .build(); } - private AppQuerySelection queryApps(String packageNames) { - return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); + private AppQuerySelection queryRepoApps(long repoId, String packageNames) { + return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME) + .add(queryRepo(repoId)); + } + + private AppQuerySelection queryRepo(long repoId) { + String[] args = new String[] {Long.toString(repoId)}; + String selection = getTableName() + "." + Cols.REPO_ID + " = ? "; + return new AppQuerySelection(selection, args); } public static class Helper { @@ -90,8 +103,8 @@ public class TempAppProvider extends AppProvider { TempApkProvider.Helper.init(context); } - public static List findByPackageNames(Context context, List packageNames, String[] projection) { - Uri uri = getAppsUri(packageNames); + public static List findByPackageNames(Context context, List packageNames, long repoId, String[] projection) { + Uri uri = getAppsUri(packageNames, repoId); Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); return AppProvider.Helper.cursorToList(cursor); } @@ -138,7 +151,7 @@ public class TempAppProvider extends AppProvider { QuerySelection query = new QuerySelection(where, whereArgs).add(querySingleForUpdate(packageName, repoId)); // Package names for apps cannot change... - values.remove(AppMetadataTable.Cols.Package.PACKAGE_NAME); + values.remove(Cols.Package.PACKAGE_NAME); int count = db().update(getTableName(), values, query.getSelection(), query.getArgs()); if (!isApplyingBatch()) { @@ -152,7 +165,8 @@ public class TempAppProvider extends AppProvider { AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs); switch (MATCHER.match(uri)) { case APPS: - selection = selection.add(queryApps(uri.getLastPathSegment())); + List segments = uri.getPathSegments(); + selection = selection.add(queryRepoApps(Long.parseLong(segments.get(1)), segments.get(2))); break; } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index d97e58341..63e25b9e3 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -24,6 +24,7 @@ import org.fdroid.fdroid.compat.PackageManagerCompat; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.DownloaderService; @@ -259,7 +260,7 @@ public class InstallManagerService extends Service { App app = getAppFromActive(downloadUrl); if (app == null) { ContentResolver resolver = context.getContentResolver(); - app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo); + app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo); } // show notification if app details is not visible if (app != null && AppDetails.isAppVisible(app.packageName)) { @@ -346,7 +347,8 @@ public class InstallManagerService extends Service { String name = getAppName(apk); if (TextUtils.isEmpty(name) || name.equals(new App().name)) { ContentResolver resolver = getContentResolver(); - App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo); + App app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo, + new String[] {Schema.AppMetadataTable.Cols.NAME}); if (app == null || TextUtils.isEmpty(app.name)) { return; // do not have a name to display, so leave notification as is } diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java index 967df1ac5..230efd70d 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java @@ -191,7 +191,7 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel intent = getIntent(); Uri uri = intent.getData(); Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL); - app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL); + app = AppProvider.Helper.findSpecificApp(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL); appDiff = new AppDiff(getPackageManager(), apk); diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index 325ffc630..a252b76a2 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -289,7 +289,7 @@ public class SwapAppsView extends ListView implements public void onChange(boolean selfChange) { Activity activity = getActivity(); if (activity != null) { - app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(), + app = AppProvider.Helper.findSpecificApp(getActivity().getContentResolver(), app.packageName, app.repoId, AppMetadataTable.Cols.ALL); resetView(); } diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java index cda22e331..5cfa3c37a 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -112,7 +112,7 @@ public class ProviderUriTests { packageNames.add("org.fdroid.fdroid"); packageNames.add("com.example.com"); - assertValidUri(resolver, TempAppProvider.getAppsUri(packageNames), "content://org.fdroid.fdroid.data.TempAppProvider/apps/org.fdroid.fdroid%2Ccom.example.com", projection); + assertValidUri(resolver, TempAppProvider.getAppsUri(packageNames, 1), "content://org.fdroid.fdroid.data.TempAppProvider/apps/1/org.fdroid.fdroid%2Ccom.example.com", projection); assertValidUri(resolver, TempAppProvider.getContentUri(), "content://org.fdroid.fdroid.data.TempAppProvider", projection); } diff --git a/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java index 0b2363b80..90ced14d3 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiRepoUpdaterTest.java @@ -9,7 +9,7 @@ import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.RepoUpdater.UpdateException; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.data.Schema.RepoTable.Cols; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricGradleTestRunner; @@ -147,7 +147,7 @@ public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest { private void disableRepo(Repo repo) { ContentValues values = new ContentValues(1); - values.put(Schema.RepoTable.Cols.IN_USE, 0); + values.put(Cols.IN_USE, 0); RepoProvider.Helper.update(context, repo, values); } diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index a031289c0..d0e70b68b 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -40,6 +40,30 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { @StringDef({"Conflicting", "Normal"}) public @interface RepoIdentifier {} + /* + *This test fails due to issue #568 (https://gitlab.com/fdroid/fdroidclient/issues/568). + @Test + public void appsRemovedFromRepo() throws RepoUpdater.UpdateException { + assertEquals(0, AppProvider.Helper.all(context.getContentResolver()).size()); + + updateMain(); + Repo repo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI); + + assertEquals(3, AppProvider.Helper.all(context.getContentResolver()).size()); + assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size()); + assertEquals(3, ApkProvider.Helper.findByPackageName(context, "org.adaway").size()); + assertEquals(2, ApkProvider.Helper.findByPackageName(context, "com.uberspot.a2048").size()); + assertEquals(1, ApkProvider.Helper.findByPackageName(context, "siir.es.adbWireless").size()); + + RepoUpdater updater = new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address)); + updateRepo(updater, "multiRepo.conflicting.jar"); + + assertEquals(2, AppProvider.Helper.all(context.getContentResolver()).size()); + assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size()); + assertEquals(4, ApkProvider.Helper.findByPackageName(context, "org.adaway").size()); + assertEquals(2, ApkProvider.Helper.findByPackageName(context, "org.dgtale.icsimport").size()); + }*/ + @Test public void mainRepo() throws RepoUpdater.UpdateException { assertEmpty(); From ab02058ecee2fd04a6a09c7d3b77a492d72f81a0 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 26 Sep 2016 18:32:12 +1000 Subject: [PATCH 12/16] Precalculate the preferred metadata, rather than always at runtime The query which dynamically figured out the preferred metadata based on repo priority ended up being quite slow (although it did work). On lower end devices, it has the potential to make F-Droid quite sluggish. By optimistically precalculating the preferred metadata where possible, we don't need to ask the question during the usual usage of F-Droid, only when: * Repo priorities are changed (there is not currently a UI for this, but there are tests) * Repos are enabled/disabled * Repo updates are performed --- .../org/fdroid/fdroid/data/AppProvider.java | 48 +++++++++++++++---- .../java/org/fdroid/fdroid/data/DBHelper.java | 4 +- .../org/fdroid/fdroid/data/RepoProvider.java | 32 +++++++++++++ .../java/org/fdroid/fdroid/data/Schema.java | 14 +++++- .../fdroid/fdroid/data/AppProviderTest.java | 3 ++ 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 0fd2e568a..ff9cafa66 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -184,6 +184,11 @@ public class AppProvider extends FDroidProvider { public static List findCanUpdate(Context context, String[] projection) { return cursorToList(context.getContentResolver().query(AppProvider.getCanUpdateUri(), projection, null, null, null)); } + + public static void recalculatePreferredMetadata(Context context) { + Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA); + context.getContentResolver().query(uri, null, null, null, null); + } } /** @@ -445,6 +450,7 @@ public class AppProvider extends FDroidProvider { private static final String PATH_CALC_APP_DETAILS_FROM_INDEX = "calcDetailsFromIndex"; private static final String PATH_REPO = "repo"; private static final String PATH_HIGHEST_PRIORITY = "highestPriority"; + private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata"; private static final int CAN_UPDATE = CODE_SINGLE + 1; private static final int INSTALLED = CAN_UPDATE + 1; @@ -459,6 +465,7 @@ public class AppProvider extends FDroidProvider { private static final int SEARCH_INSTALLED = SEARCH_REPO + 1; private static final int SEARCH_CAN_UPDATE = SEARCH_INSTALLED + 1; private static final int HIGHEST_PRIORITY = SEARCH_CAN_UPDATE + 1; + private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1; static { MATCHER.addURI(getAuthority(), null, CODE_LIST); @@ -476,6 +483,7 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS); MATCHER.addURI(getAuthority(), PATH_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY); MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA); } public static Uri getContentUri() { @@ -719,16 +727,11 @@ public class AppProvider extends FDroidProvider { /** * Ensures that for each app metadata row with the same package name, only the one from the repo - * with the best priority is represented in the result set. + * with the best priority is represented in the result set. While possible to calculate this + * dynamically each time the query is run, we precalculate it during repo updates for performance. */ private AppQuerySelection queryHighestPriority() { - final String highestPriority = - "SELECT MIN(r." + RepoTable.Cols.PRIORITY + ") " + - "FROM " + RepoTable.NAME + " AS r " + - "JOIN " + AppMetadataTable.NAME + " AS m ON (m." + Cols.REPO_ID + " = r." + RepoTable.Cols._ID + ") " + - "WHERE m." + Cols.PACKAGE_ID + " = " + getTableName() + "." + Cols.PACKAGE_ID; - - final String selection = RepoTable.NAME + "." + RepoTable.Cols.PRIORITY + " = (" + highestPriority + ")"; + final String selection = PackageTable.NAME + "." + PackageTable.Cols.PREFERRED_METADATA + " = " + getTableName() + "." + Cols.ROW_ID; return new AppQuerySelection(selection); } @@ -793,6 +796,10 @@ public class AppProvider extends FDroidProvider { boolean repoIsKnown = false; switch (MATCHER.match(uri)) { + case CALC_PREFERRED_METADATA: + updatePreferredMetadata(); + return null; + case CODE_LIST: includeSwap = false; break; @@ -937,12 +944,37 @@ public class AppProvider extends FDroidProvider { } protected void updateAppDetails() { + updatePreferredMetadata(); updateCompatibleFlags(); updateSuggestedFromUpstream(); updateSuggestedFromLatest(); updateIconUrls(getContext(), db(), getTableName(), getApkTableName()); } + private void updatePreferredMetadata() { + Utils.debugLog(TAG, "Deciding on which metadata should take priority for each package."); + + final String app = getTableName(); + + final String highestPriority = + "SELECT MIN(r." + RepoTable.Cols.PRIORITY + ") " + + "FROM " + RepoTable.NAME + " AS r " + + "JOIN " + getTableName() + " AS m ON (m." + Cols.REPO_ID + " = r." + RepoTable.Cols._ID + ") " + + "WHERE m." + Cols.PACKAGE_ID + " = " + "metadata." + Cols.PACKAGE_ID; + + String updateSql = + "UPDATE " + PackageTable.NAME + " " + + "SET " + PackageTable.Cols.PREFERRED_METADATA + " = ( " + + " SELECT metadata." + Cols.ROW_ID + + " FROM " + app + " AS metadata " + + " JOIN " + RepoTable.NAME + " AS repo ON (metadata." + Cols.REPO_ID + " = repo." + RepoTable.Cols._ID + ") " + + " WHERE metadata." + Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID + + " AND repo." + RepoTable.Cols.PRIORITY + " = (" + highestPriority + ")" + + ");"; + + db().execSQL(updateSql); + } + /** * For each app, we want to set the isCompatible flag to 1 if any of the apks we know * about are compatible, and 0 otherwise. diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index d79335940..9c238295d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -53,7 +53,8 @@ class DBHelper extends SQLiteOpenHelper { private static final String CREATE_TABLE_PACKAGE = "CREATE TABLE " + PackageTable.NAME + " ( " - + PackageTable.Cols.PACKAGE_NAME + " text not null" + + PackageTable.Cols.PACKAGE_NAME + " text not null, " + + PackageTable.Cols.PREFERRED_METADATA + " integer" + ");"; private static final String CREATE_TABLE_REPO = "create table " @@ -838,6 +839,7 @@ class DBHelper extends SQLiteOpenHelper { if (tableExists(db, PackageTable.NAME)) { Utils.debugLog(TAG, "Ensuring indexes exist for " + PackageTable.NAME); db.execSQL("CREATE INDEX IF NOT EXISTS package_packageName on " + PackageTable.NAME + " (" + PackageTable.Cols.PACKAGE_NAME + ");"); + db.execSQL("CREATE INDEX IF NOT EXISTS package_preferredMetadata on " + PackageTable.NAME + " (" + PackageTable.Cols.PREFERRED_METADATA + ");"); } Utils.debugLog(TAG, "Ensuring indexes exist for " + AppMetadataTable.NAME); diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java index da0240a99..e5a6b5347 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java @@ -232,6 +232,8 @@ public class RepoProvider extends FDroidProvider { Uri appUri = AppProvider.getNoApksUri(); int appCount = resolver.delete(appUri, null, null); Utils.debugLog(TAG, "Removed " + appCount + " apps with no apks."); + + AppProvider.Helper.recalculatePreferredMetadata(context); } public static int countAppsForRepo(Context context, long repoId) { @@ -399,7 +401,37 @@ public class RepoProvider extends FDroidProvider { @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { + + // When the priority of a repo changes, we need to update the "preferred metadata" foreign + // key in the package table to point to the best possible record in the app metadata table. + // The full list of times when we need to recalculate the preferred metadata includes: + // * After the priority of a repo changes + // * After a repo is disabled + // * After a repo is enabled + // * After an update is performed + // This code only checks for the priority changing. All other occasions we can't do the + // recalculation right now, because we likely haven't added/removed the relevant apps + // from the metadata table yet. Usually the repo details are updated, then a request is + // made to do the heavier work (e.g. a repo update to get new list of apps from server). + // After the heavier work is complete, then that process can request the preferred metadata + // to be recalculated. + boolean priorityChanged = false; + if (values.containsKey(Cols.PRIORITY)) { + Cursor priorityCursor = db().query(getTableName(), new String[]{Cols.PRIORITY}, where, whereArgs, null, null, null); + if (priorityCursor.getCount() > 0) { + priorityCursor.moveToFirst(); + int oldPriority = priorityCursor.getInt(priorityCursor.getColumnIndex(Cols.PRIORITY)); + priorityChanged = oldPriority != values.getAsInteger(Cols.PRIORITY); + } + priorityCursor.close(); + } + int numRows = db().update(getTableName(), values, where, whereArgs); + + if (priorityChanged) { + AppProvider.Helper.recalculatePreferredMetadata(getContext()); + } + Utils.debugLog(TAG, "Updated repo. Notifying provider change: '" + uri + "'."); getContext().getContentResolver().notifyChange(uri, null); return numRows; diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index 5b1f1779f..edaa7ae14 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -17,8 +17,18 @@ public interface Schema { String ROW_ID = "rowid"; String PACKAGE_NAME = "packageName"; + /** + * Metadata about a package (e.g. description, icon, etc) can come from multiple + * different repos. This is a foreign key to the row in {@link AppMetadataTable} for + * this package that comes from the repo with the best priority. Although it can be + * calculated at runtime using an SQL query, it is more efficient to figure out the + * preferred metadata once, after a repo update, rather than every time we need to know + * about a package. + */ + String PREFERRED_METADATA = "preferredMetadata"; + String[] ALL = { - ROW_ID, PACKAGE_NAME, + ROW_ID, PACKAGE_NAME, PREFERRED_METADATA, }; } } @@ -101,7 +111,7 @@ public interface Schema { * or which are from other related tables (e.g. {@link Cols.SuggestedApk#VERSION_NAME}). */ String[] ALL_COLS = { - ROW_ID, PACKAGE_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, + ROW_ID, PACKAGE_ID, REPO_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION, LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL, CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID, UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED, diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java index 73b8cd1da..0d8102b8d 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -371,6 +371,9 @@ public class AppProviderTest extends FDroidProviderTest { Uri uri = AppProvider.getContentUri(); contentResolver.insert(uri, values); + + AppProvider.Helper.recalculatePreferredMetadata(context); + return AppProvider.Helper.findSpecificApp(context.getContentResolver(), id, 1, Cols.ALL); } } From 2cc155355b555421e7956729ebb2468f2710b2db Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 28 Sep 2016 23:00:37 +1000 Subject: [PATCH 13/16] Moved regression test to appropriate package. --- .../org/fdroid/fdroid/{ => updater}/Issue763MultiRepo.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename app/src/test/java/org/fdroid/fdroid/{ => updater}/Issue763MultiRepo.java (98%) diff --git a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java similarity index 98% rename from app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java rename to app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java index c236a5ad2..1a797a793 100644 --- a/app/src/test/java/org/fdroid/fdroid/Issue763MultiRepo.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java @@ -1,7 +1,9 @@ -package org.fdroid.fdroid; +package org.fdroid.fdroid.updater; import android.content.ContentValues; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.RepoUpdater; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.Repo; From 3a24d21f5931f205488230d5c807a8fd9543c90a Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 29 Sep 2016 23:15:02 +1000 Subject: [PATCH 14/16] WIP: Making correct apks get found when updating repo. --- .../org/fdroid/fdroid/data/ApkProvider.java | 28 ++++++------------- .../org/fdroid/fdroid/data/RepoPersister.java | 1 + 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index 251c4fb32..61e310d51 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -275,7 +275,7 @@ public class ApkProvider extends FDroidProvider { builder.append(','); } final Apk apk = apks.get(i); - builder.append(apk.packageName).append(':').append(apk.versionCode); + builder.append(apk.appId).append(':').append(apk.versionCode); } return builder.toString(); } @@ -428,35 +428,23 @@ public class ApkProvider extends FDroidProvider { StringBuilder sb = new StringBuilder(); for (int i = 0; i < apkDetails.length; i++) { String[] parts = apkDetails[i].split(":"); - String packageName = parts[0]; + String appId = parts[0]; String versionCode = parts[1]; - args[i * 2] = packageName; + args[i * 2] = appId; args[i * 2 + 1] = versionCode; if (i != 0) { sb.append(" OR "); } - sb.append(" ( "); - if (includeAlias) { - // This is the simpler way to figure out the package name of a row in the apk table. - // It requires slightly less work for sqlite3 than the alternative below. - sb.append("app.") - .append(AppMetadataTable.Cols.PACKAGE_ID) - .append(" = (") - .append(getPackageIdFromPackageNameQuery()) - .append(") "); - } else { - sb.append(Cols.APP_ID) - .append(" IN (") - .append(getMetadataIdFromPackageNameQuery()) - .append(")"); - } - - sb.append(" AND ") + sb.append(" ( ") + .append(Cols.APP_ID) + .append(" = ? ") + .append(" AND ") .append(alias) .append(Cols.VERSION_CODE) .append(" = ? ) "); } + return new QuerySelection(sb.toString(), args); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 9c05f3087..6a405a9ba 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -178,6 +178,7 @@ public class RepoPersister { Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE, Schema.ApkTable.Cols.REPO_ID, + Schema.ApkTable.Cols.APP_ID, }; List existingApks = ApkProvider.Helper.knownApks(context, packages, projection); ArrayList operations = new ArrayList<>(packages.size()); From d062af0975f6fd8bc6f0d8b07f0d7ce57b52d866 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Fri, 30 Sep 2016 00:06:08 +1000 Subject: [PATCH 15/16] Clarify that sometimes we don't know which repos apk we are asking for. Many times in the past, we would ask for an apk based on its package name and its version code. However multiple repositories provide apks with the same package name and version code, and such queries would (seemingly) nondeterministically choose one of these matching apks. This clarifies the wording in the code around when we explicitly ask for a given apk, and when we kind of guess which one we want. Most the time we have an `App` handy, which has a specific repo associated with it. This allows us to be more specific about requesting `Apk`s. The times we are kind of guessing is when we rely on the "suggested version code" of an apk by clicking the misc "Install" or "Upgrade" button in app details. In the future, we'll need to clear this up so that a more specific apk is chosen when touching these buttons. --- .../org/fdroid/fdroid/data/ApkProvider.java | 44 +++++++++++++++---- .../org/fdroid/fdroid/data/RepoPersister.java | 2 +- .../fdroid/fdroid/data/TempApkProvider.java | 9 ++-- .../test/java/org/fdroid/fdroid/Assert.java | 2 +- .../fdroid/fdroid/data/ApkProviderTest.java | 41 +++++++++-------- .../fdroid/fdroid/data/ProviderUriTests.java | 4 +- .../java/org/fdroid/fdroid/mock/MockApk.java | 11 +++++ 7 files changed, 79 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index 61e310d51..138f96132 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -37,10 +37,19 @@ public class ApkProvider extends FDroidProvider { public static void update(Context context, Apk apk) { ContentResolver resolver = context.getContentResolver(); - Uri uri = getApkFromAnyRepoUri(apk.packageName, apk.versionCode); + Uri uri = getApkFromRepoUri(apk); resolver.update(uri, apk.toContentValues(), null, null); } + public static Uri getApkFromRepoUri(Apk apk) { + return getContentUri() + .buildUpon() + .appendPath(PATH_APK_FROM_REPO) + .appendPath(Long.toString(apk.appId)) + .appendPath(Integer.toString(apk.versionCode)) + .build(); + } + public static List cursorToList(Cursor cursor) { int knownApkCount = cursor != null ? cursor.getCount() : 0; List apks = new ArrayList<>(knownApkCount); @@ -71,7 +80,7 @@ public class ApkProvider extends FDroidProvider { * Find all apks for a particular app, but limit it to those originating from the * specified repo. */ - public static List find(Context context, Repo repo, List apps, String[] projection) { + public static List findByUri(Context context, Repo repo, List apps, String[] projection) { ContentResolver resolver = context.getContentResolver(); final Uri uri = getContentUriForApps(repo, apps); Cursor cursor = resolver.query(uri, projection, null, null, null); @@ -172,9 +181,12 @@ public class ApkProvider extends FDroidProvider { private static final int CODE_REPO_APPS = CODE_APKS + 1; protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1; private static final int CODE_APK_ROW_ID = CODE_REPO_APK + 1; + static final int CODE_APK_FROM_ANY_REPO = CODE_APK_ROW_ID + 1; + static final int CODE_APK_FROM_REPO = CODE_APK_FROM_ANY_REPO + 1; private static final String PROVIDER_NAME = "ApkProvider"; - protected static final String PATH_APK = "apk"; + protected static final String PATH_APK_FROM_ANY_REPO = "apk-any-repo"; + protected static final String PATH_APK_FROM_REPO = "apk-from-repo"; private static final String PATH_APKS = "apks"; private static final String PATH_APP = "app"; private static final String PATH_REPO = "repo"; @@ -193,7 +205,8 @@ public class ApkProvider extends FDroidProvider { PACKAGE_FIELDS.put(Cols.Package.PACKAGE_NAME, PackageTable.Cols.PACKAGE_NAME); MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO); - MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_APK_FROM_ANY_REPO + "/#/*", CODE_APK_FROM_ANY_REPO); + MATCHER.addURI(getAuthority(), PATH_APK_FROM_REPO + "/#/#", CODE_APK_FROM_REPO); MATCHER.addURI(getAuthority(), PATH_APKS + "/*", CODE_APKS); MATCHER.addURI(getAuthority(), PATH_APP + "/*", CODE_PACKAGE); MATCHER.addURI(getAuthority(), PATH_REPO_APPS + "/#/*", CODE_REPO_APPS); @@ -240,7 +253,7 @@ public class ApkProvider extends FDroidProvider { public static Uri getApkFromAnyRepoUri(String packageName, int versionCode) { return getContentUri() .buildUpon() - .appendPath(PATH_APK) + .appendPath(PATH_APK_FROM_ANY_REPO) .appendPath(Integer.toString(versionCode)) .appendPath(packageName) .build(); @@ -397,6 +410,21 @@ public class ApkProvider extends FDroidProvider { return new QuerySelection(selection, args); } + /** + * Doesn't prefix column names with table alias. This is so that it can be used in UPDATE + * queries. Note that this lack of table alias prefixes means this can't be used for general + * constraints in a regular select query within {@link ApkProvider} as the queries specify + * aliases for the apk table. + */ + private QuerySelection querySingleWithAppId(Uri uri) { + List path = uri.getPathSegments(); + String appId = path.get(1); + String versionCode = path.get(2); + final String selection = Cols.APP_ID + " = ? AND " + Cols.VERSION_CODE + " = ? "; + final String[] args = {appId, versionCode}; + return new QuerySelection(selection, args); + } + protected QuerySelection queryRepo(long repoId) { return queryRepo(repoId, true); } @@ -465,7 +493,7 @@ public class ApkProvider extends FDroidProvider { case CODE_LIST: break; - case CODE_SINGLE: + case CODE_APK_FROM_ANY_REPO: query = query.add(querySingleFromAnyRepo(uri)); break; @@ -568,7 +596,7 @@ public class ApkProvider extends FDroidProvider { @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { - if (MATCHER.match(uri) != CODE_SINGLE) { + if (MATCHER.match(uri) != CODE_APK_FROM_REPO) { throw new UnsupportedOperationException("Cannot update anything other than a single apk."); } return performUpdateUnchecked(uri, values, where, whereArgs); @@ -579,7 +607,7 @@ public class ApkProvider extends FDroidProvider { removeFieldsFromOtherTables(values); QuerySelection query = new QuerySelection(where, whereArgs); - query = query.add(querySingleFromAnyRepo(uri, false)); + query = query.add(querySingleWithAppId(uri)); int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs()); if (!isApplyingBatch()) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 6a405a9ba..63e9d76fc 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -258,7 +258,7 @@ public class RepoPersister { @Nullable private ContentProviderOperation deleteOrphanedApks(List apps, Map> packages) { String[] projection = new String[]{Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE}; - List existing = ApkProvider.Helper.find(context, repo, apps, projection); + List existing = ApkProvider.Helper.findByUri(context, repo, apps, projection); List toDelete = new ArrayList<>(); for (Apk existingApk : existing) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java index 8cc83f773..e7465e30b 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java @@ -27,7 +27,8 @@ public class TempApkProvider extends ApkProvider { static { MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT); - MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE); + MATCHER.addURI(getAuthority(), PATH_APK_FROM_ANY_REPO + "/#/*", CODE_APK_FROM_ANY_REPO); + MATCHER.addURI(getAuthority(), PATH_APK_FROM_REPO + "/#/#", CODE_APK_FROM_REPO); MATCHER.addURI(getAuthority(), PATH_REPO_APK + "/#/*", CODE_REPO_APK); } @@ -52,9 +53,9 @@ public class TempApkProvider extends ApkProvider { public static Uri getApkUri(Apk apk) { return getContentUri() .buildUpon() - .appendPath(PATH_APK) + .appendPath(PATH_APK_FROM_REPO) + .appendPath(Long.toString(apk.appId)) .appendPath(Integer.toString(apk.versionCode)) - .appendPath(apk.packageName) .build(); } @@ -96,7 +97,7 @@ public class TempApkProvider extends ApkProvider { @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { - if (MATCHER.match(uri) != CODE_SINGLE) { + if (MATCHER.match(uri) != CODE_APK_FROM_REPO) { throw new UnsupportedOperationException("Cannot update anything other than a single apk."); } diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java index ba7085050..27036a5f3 100644 --- a/app/src/test/java/org/fdroid/fdroid/Assert.java +++ b/app/src/test/java/org/fdroid/fdroid/Assert.java @@ -201,7 +201,7 @@ public class Assert { return AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL); } - private static App ensureApp(Context context, String packageName) { + public static App ensureApp(Context context, String packageName) { App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL); if (app == null) { insertApp(context, packageName, packageName); diff --git a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java index efbfada6e..0f21dd119 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java @@ -242,41 +242,45 @@ public class ApkProviderTest extends FDroidProviderTest { @Test public void testKnownApks() { + App fdroid = Assert.ensureApp(context, "org.fdroid.fdroid"); for (int i = 0; i < 7; i++) { - Assert.insertApk(context, "org.fdroid.fdroid", i); + Assert.insertApk(context, fdroid, i); } + App exampleOrg = Assert.ensureApp(context, "org.example"); for (int i = 0; i < 9; i++) { - Assert.insertApk(context, "org.example", i); + Assert.insertApk(context, exampleOrg, i); } + App exampleCom = Assert.ensureApp(context, "com.example"); for (int i = 0; i < 3; i++) { - Assert.insertApk(context, "com.example", i); + Assert.insertApk(context, exampleCom, i); } - Assert.insertApk(context, "com.apk.thingo", 1); + App thingo = Assert.ensureApp(context, "com.apk.thingo"); + Assert.insertApk(context, thingo, 1); Apk[] known = { - new MockApk("org.fdroid.fdroid", 1), - new MockApk("org.fdroid.fdroid", 3), - new MockApk("org.fdroid.fdroid", 5), + new MockApk(fdroid, 1), + new MockApk(fdroid, 3), + new MockApk(fdroid, 5), - new MockApk("com.example", 1), - new MockApk("com.example", 2), + new MockApk(exampleCom, 1), + new MockApk(exampleCom, 2), }; Apk[] unknown = { - new MockApk("org.fdroid.fdroid", 7), - new MockApk("org.fdroid.fdroid", 9), - new MockApk("org.fdroid.fdroid", 11), - new MockApk("org.fdroid.fdroid", 13), + new MockApk(fdroid, 7), + new MockApk(fdroid, 9), + new MockApk(fdroid, 11), + new MockApk(fdroid, 13), - new MockApk("com.example", 3), - new MockApk("com.example", 4), - new MockApk("com.example", 5), + new MockApk(exampleCom, 3), + new MockApk(exampleCom, 4), + new MockApk(exampleCom, 5), - new MockApk("info.example", 1), - new MockApk("info.example", 2), + new MockApk(-10, 1), + new MockApk(-10, 2), }; List apksToCheck = new ArrayList<>(known.length + unknown.length); @@ -285,6 +289,7 @@ public class ApkProviderTest extends FDroidProviderTest { String[] projection = { Cols.Package.PACKAGE_NAME, + Cols.APP_ID, Cols.VERSION_CODE, }; diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java index 5cfa3c37a..788c3aa31 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -135,9 +135,9 @@ public class ProviderUriTests { assertValidUri(resolver, ApkProvider.getContentUri(), "content://org.fdroid.fdroid.data.ApkProvider", projection); assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.ApkProvider/app/org.fdroid.fdroid", projection); - assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk-any-repo/100/org.fdroid.fdroid", projection); assertValidUri(resolver, ApkProvider.getContentUri(apks), projection); - assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk-any-repo/100/org.fdroid.fdroid", projection); assertValidUri(resolver, ApkProvider.getRepoUri(1000), "content://org.fdroid.fdroid.data.ApkProvider/repo/1000", projection); } diff --git a/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java b/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java index be7dff5a5..35592526e 100644 --- a/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java +++ b/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java @@ -1,6 +1,7 @@ package org.fdroid.fdroid.mock; import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.App; public class MockApk extends Apk { @@ -9,4 +10,14 @@ public class MockApk extends Apk { this.versionCode = versionCode; } + public MockApk(App app, int versionCode) { + this.appId = app.getId(); + this.versionCode = versionCode; + } + + public MockApk(long appId, int versionCode) { + this.appId = appId; + this.versionCode = versionCode; + } + } From e0a1d2384d024334115b7a1d3127da4417be97eb Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 8 Aug 2016 22:15:47 +1000 Subject: [PATCH 16/16] Appease checkstyle + pmd --- app/src/main/java/org/fdroid/fdroid/data/Schema.java | 2 +- .../test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java | 1 - .../org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index edaa7ae14..f14cb1484 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -132,7 +132,7 @@ public interface Schema { CATEGORIES, ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE, SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME, InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME, - InstalledApp.SIGNATURE, Package.PACKAGE_NAME + InstalledApp.SIGNATURE, Package.PACKAGE_NAME, }; } } diff --git a/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java index 1a797a793..f4f169092 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java @@ -9,7 +9,6 @@ import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; -import org.fdroid.fdroid.updater.MultiRepoUpdaterTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index d0e70b68b..19dea49bb 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -38,7 +38,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { @Retention(RetentionPolicy.SOURCE) @StringDef({"Conflicting", "Normal"}) - public @interface RepoIdentifier {} + public @interface RepoIdentifier { } /* *This test fails due to issue #568 (https://gitlab.com/fdroid/fdroidclient/issues/568).