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();