diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index 6779bd78d..8e23089ca 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -729,13 +729,13 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog @Override public void installApk() { - Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode); + Apk apkToInstall = ApkProvider.Helper.findSuggestedApk(this, app); installApk(apkToInstall); } @Override public void upgradeApk() { - Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode); + Apk apkToInstall = ApkProvider.Helper.findSuggestedApk(this, app); installApk(apkToInstall); } diff --git a/app/src/main/java/org/fdroid/fdroid/UpdateService.java b/app/src/main/java/org/fdroid/fdroid/UpdateService.java index 99d0607f0..5efe407d2 100644 --- a/app/src/main/java/org/fdroid/fdroid/UpdateService.java +++ b/app/src/main/java/org/fdroid/fdroid/UpdateService.java @@ -27,7 +27,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; @@ -478,43 +477,25 @@ public class UpdateService extends IntentService { } private void performUpdateNotification() { - Cursor cursor = getContentResolver().query( - AppProvider.getCanUpdateUri(), - Schema.AppMetadataTable.Cols.ALL, - null, null, null); - if (cursor != null) { - if (cursor.getCount() > 0) { - showAppUpdatesNotification(cursor); - } - cursor.close(); + List canUpdate = AppProvider.Helper.findCanUpdate(this, Schema.AppMetadataTable.Cols.ALL); + if (canUpdate.size() > 0) { + showAppUpdatesNotification(canUpdate); } } public static void autoDownloadUpdates(Context context) { - Cursor cursor = context.getContentResolver().query( - AppProvider.getCanUpdateUri(), - Schema.AppMetadataTable.Cols.ALL, - null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - for (int i = 0; i < cursor.getCount(); i++) { - App app = new App(cursor); - Apk apk = ApkProvider.Helper.findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode); - InstallManagerService.queue(context, app, apk); - cursor.moveToNext(); - } - cursor.close(); + List canUpdate = AppProvider.Helper.findCanUpdate(context, Schema.AppMetadataTable.Cols.ALL); + for (App app : canUpdate) { + Apk apk = ApkProvider.Helper.findSuggestedApk(context, app); + InstallManagerService.queue(context, app, apk); } } - private void showAppUpdatesNotification(Cursor hasUpdates) { - if (hasUpdates != null) { - hasUpdates.moveToFirst(); - List apksToUpdate = new ArrayList<>(hasUpdates.getCount()); - for (int i = 0; i < hasUpdates.getCount(); i++) { - App app = new App(hasUpdates); - hasUpdates.moveToNext(); - apksToUpdate.add(ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode)); + private void showAppUpdatesNotification(List canUpdate) { + if (canUpdate.size() > 0) { + List apksToUpdate = new ArrayList<>(canUpdate.size()); + for (App app : canUpdate) { + apksToUpdate.add(ApkProvider.Helper.findSuggestedApk(this, app)); } appUpdateStatusManager.addApks(apksToUpdate, AppUpdateStatusManager.Status.UpdateAvailable); } 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 fc8bce7c9..e73cdfa2e 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -7,6 +7,7 @@ import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import org.fdroid.fdroid.data.Schema.ApkTable; @@ -74,8 +75,16 @@ public class ApkProvider extends FDroidProvider { return resolver.delete(uri, null, null); } + public static Apk findSuggestedApk(Context context, App app) { + return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.installedSig); + } + public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) { - return findApkFromAnyRepo(context, packageName, versionCode, Cols.ALL); + return findApkFromAnyRepo(context, packageName, versionCode, null, Cols.ALL); + } + + public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode, String signature) { + return findApkFromAnyRepo(context, packageName, versionCode, signature, Cols.ALL); } /** @@ -89,9 +98,9 @@ public class ApkProvider extends FDroidProvider { return cursorToList(cursor); } - public static Apk findApkFromAnyRepo(Context context, - String packageName, int versionCode, String[] projection) { - final Uri uri = getApkFromAnyRepoUri(packageName, versionCode); + public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode, + @Nullable String signature, String[] projection) { + final Uri uri = getApkFromAnyRepoUri(packageName, versionCode, signature); return findByUri(context, uri, projection); } @@ -113,8 +122,7 @@ public class ApkProvider extends FDroidProvider { return findByPackageName(context, packageName, Cols.ALL); } - public static List findByPackageName(Context context, - String packageName, String[] projection) { + public static List findByPackageName(Context context, String packageName, String[] projection) { ContentResolver resolver = context.getContentResolver(); final Uri uri = getAppUri(packageName); final String sort = "apk." + Cols.VERSION_CODE + " DESC"; @@ -218,6 +226,7 @@ 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_FROM_ANY_REPO + "/#/*/*", CODE_APK_FROM_ANY_REPO); 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); @@ -260,16 +269,21 @@ public class ApkProvider extends FDroidProvider { } public static Uri getApkFromAnyRepoUri(Apk apk) { - return getApkFromAnyRepoUri(apk.packageName, apk.versionCode); + return getApkFromAnyRepoUri(apk.packageName, apk.versionCode, null); } - public static Uri getApkFromAnyRepoUri(String packageName, int versionCode) { - return getContentUri() - .buildUpon() - .appendPath(PATH_APK_FROM_ANY_REPO) - .appendPath(Integer.toString(versionCode)) - .appendPath(packageName) - .build(); + public static Uri getApkFromAnyRepoUri(String packageName, int versionCode, @Nullable String signature) { + Uri.Builder builder = getContentUri() + .buildUpon() + .appendPath(PATH_APK_FROM_ANY_REPO) + .appendPath(Integer.toString(versionCode)) + .appendPath(packageName); + + if (signature != null) { + builder.appendPath(signature); + } + + return builder.build(); } public static Uri getContentUriForApps(Repo repo, List apps) { @@ -395,20 +409,20 @@ public class ApkProvider extends FDroidProvider { private QuerySelection querySingleFromAnyRepo(Uri uri, boolean includeAlias) { String alias = includeAlias ? "apk." : ""; - // 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. - uri.getPathSegments().get(1), - uri.getPathSegments().get(2), - }; + String selection = + alias + Cols.VERSION_CODE + " = ? AND " + + alias + Cols.APP_ID + " IN (" + getMetadataIdFromPackageNameQuery() + ")"; + + List pathSegments = uri.getPathSegments(); + List args = new ArrayList<>(3); + args.add(pathSegments.get(1)); // First (0th) path segment is the word "apk" and we are not interested in it. + args.add(pathSegments.get(2)); + + if (pathSegments.size() >= 4) { + selection += " AND " + alias + Cols.SIGNATURE + " = ? "; + args.add(pathSegments.get(3)); + } + return new QuerySelection(selection, args); } 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 b803bc95f..f4ec35ba6 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -1048,7 +1048,7 @@ public class AppProvider extends FDroidProvider { } /** - * Returns a query which requires two parameters to be bound. These are (in order): + * Returns a query which requires two parameters to be bdeatound. These are (in order): * 1) The repo version that introduced density specific icons * 2) The dir to density specific icons for the current device. */ diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index 491e53d24..d4061d835 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -114,7 +114,9 @@ public class AppDetailsRecyclerViewAdapter versions = new ArrayList<>(); final List apks = ApkProvider.Helper.findByPackageName(context, this.app.packageName); for (final Apk apk : apks) { - if (apk.compatible || Preferences.get().showIncompatibleVersions()) { + boolean allowByCompatability = apk.compatible || Preferences.get().showIncompatibleVersions(); + boolean allowBySig = this.app.installedSig == null || TextUtils.equals(this.app.installedSig, apk.sig); + if (allowByCompatability && allowBySig) { versions.add(apk); } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 9c2d32c71..46658d61e 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -535,7 +535,7 @@ public class AppListItemController extends RecyclerView.ViewHolder { Installer installer = InstallerFactory.create(activity, currentStatus.apk); installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), Uri.parse(currentStatus.apk.getUrl())); } else { - final Apk suggestedApk = ApkProvider.Helper.findApkFromAnyRepo(activity, currentApp.packageName, currentApp.suggestedVersionCode); + final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, currentApp); InstallManagerService.queue(activity, currentApp, suggestedApk); } } 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 343344fdb..8553b0509 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java @@ -68,7 +68,7 @@ public class ApkProviderTest extends FDroidProviderTest { Apk apk = new MockApk("org.fdroid.fdroid", 10); assertCantDelete(contentResolver, ApkProvider.getContentUri()); - assertCantDelete(contentResolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 10)); + assertCantDelete(contentResolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 10, null)); assertCantDelete(contentResolver, ApkProvider.getApkFromAnyRepoUri(apk)); assertCantDelete(contentResolver, Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path")); } @@ -432,7 +432,7 @@ public class ApkProviderTest extends FDroidProviderTest { Cols.HASH, }; - Apk apkLessFields = ApkProvider.Helper.findApkFromAnyRepo(context, "com.example", 11, projection); + Apk apkLessFields = ApkProvider.Helper.findApkFromAnyRepo(context, "com.example", 11, null, projection); assertNotNull(apkLessFields); 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 eeb7a485a..93ec81e7b 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -141,7 +141,7 @@ public class ProviderUriTests { 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-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-any-repo/100/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100, null), "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/data/SuggestedVersionTest.java b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java index 55c029541..f283f27d8 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/SuggestedVersionTest.java @@ -18,6 +18,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.List; import static org.junit.Assert.assertEquals; @@ -63,21 +65,12 @@ public class SuggestedVersionTest extends FDroidProviderTest { insertApk(context, singleApp, 1, FDROID_SIG); insertApk(context, singleApp, 2, FDROID_SIG); insertApk(context, singleApp, 3, FDROID_SIG); - AppProvider.Helper.calcSuggestedApks(context); - - App found2 = findApp(singleApp); - assertEquals(2, found2.suggestedVersionCode); + assertSuggested("single.app", 2); // By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should // suggest the latest version (3). Preferences.get().setUnstableUpdates(true); - AppProvider.Helper.calcSuggestedApks(context); - App found3 = findApp(singleApp); - assertEquals(3, found3.suggestedVersionCode); - } - - private App findApp(App app) { - return AppProvider.Helper.findSpecificApp(context.getContentResolver(), app.packageName, app.repoId); + assertSuggested("single.app", 3); } @Test @@ -91,42 +84,25 @@ public class SuggestedVersionTest extends FDroidProviderTest { insertApk(context, singleApp, 3, FDROID_SIG); insertApk(context, singleApp, 4, UPSTREAM_SIG); insertApk(context, singleApp, 5, UPSTREAM_SIG); - AppProvider.Helper.calcSuggestedApks(context); // Given we aren't installed yet, we don't care which signature. // Just get as close to upstreamVersionCode as possible. - App suggestUpstream4 = findApp(singleApp); - assertEquals(4, suggestUpstream4.suggestedVersionCode); + assertSuggested("single.app", 4); // Now install v1 with the f-droid signature. In response, we should only suggest // apps with that sig in the future. That is, version 4 from upstream is not considered. InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT); - AppProvider.Helper.calcSuggestedApks(context); - App suggestFDroid3 = findApp(singleApp); - assertEquals(3, suggestFDroid3.suggestedVersionCode); + assertSuggested("single.app", 3, FDROID_SIG, 1); // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. insertApk(context, singleApp, 4, FDROID_SIG); insertApk(context, singleApp, 5, FDROID_SIG); - AppProvider.Helper.calcSuggestedApks(context); - App suggestFDroid4 = findApp(singleApp); - assertEquals(4, suggestFDroid4.suggestedVersionCode); + assertSuggested("single.app", 4, FDROID_SIG, 1); // Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should // still become the suggested version now. Preferences.get().setUnstableUpdates(true); - AppProvider.Helper.calcSuggestedApks(context); - App suggestFDroid5 = findApp(singleApp); - assertEquals(5, suggestFDroid5.suggestedVersionCode); - } - - private void recalculateMetadata() { - AppProvider.Helper.calcSuggestedApks(context); - AppProvider.Helper.recalculatePreferredMetadata(context); - } - - private App highestPriorityApp(String packageName) { - return AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName); + assertSuggested("single.app", 5, FDROID_SIG, 1); } @Test @@ -148,41 +124,110 @@ public class SuggestedVersionTest extends FDroidProviderTest { insertApk(context, thirdPartyApp, 5, THIRD_PARTY_SIG); insertApk(context, thirdPartyApp, 6, THIRD_PARTY_SIG); - recalculateMetadata(); - // Given we aren't installed yet, we don't care which signature or even which repo. // Just get as close to upstreamVersionCode as possible. - App suggestAnyVersion4 = highestPriorityApp("single.app"); - assertEquals(4, suggestAnyVersion4.suggestedVersionCode); + assertSuggested("single.app", 4); // Now install v1 with the f-droid signature. In response, we should only suggest // apps with that sig in the future. That is, version 4 from upstream is not considered. InstalledAppTestUtils.install(context, "single.app", 1, "v1", FDROID_CERT); - recalculateMetadata(); - App suggestFDroid3 = highestPriorityApp("single.app"); - assertEquals(3, suggestFDroid3.suggestedVersionCode); + assertSuggested("single.app", 3, FDROID_SIG, 1); // This adds the "upstreamVersionCode" version of the app, but signed by f-droid. insertApk(context, mainApp, 4, FDROID_SIG); insertApk(context, mainApp, 5, FDROID_SIG); - recalculateMetadata(); - App suggestFDroid4 = highestPriorityApp("single.app"); - assertEquals(4, suggestFDroid4.suggestedVersionCode); + assertSuggested("single.app", 4, FDROID_SIG, 1); // Uninstalling the F-Droid build and installing v3 of the third party means we can now go // back to suggesting version 4. InstalledAppProviderService.deleteAppFromDb(context, "single.app"); InstalledAppTestUtils.install(context, "single.app", 3, "v3", THIRD_PARTY_CERT); - recalculateMetadata(); - suggestAnyVersion4 = highestPriorityApp("single.app"); - assertEquals(4, suggestAnyVersion4.suggestedVersionCode); + assertSuggested("single.app", 4, THIRD_PARTY_SIG, 3); // Version 6 from the 3rd party repo is not the "upstreamVersionCode", but with beta updates // it should still become the suggested version now. Preferences.get().setUnstableUpdates(true); - recalculateMetadata(); - App suggest3rdParty6 = highestPriorityApp("single.app"); - assertEquals(6, suggest3rdParty6.suggestedVersionCode); + assertSuggested("single.app", 6, THIRD_PARTY_SIG, 3); + } + + /** + * This is specifically for the {@link AppProvider.Helper#findCanUpdate(Context, String[])} method used by + * the {@link org.fdroid.fdroid.UpdateService#showAppUpdatesNotification(List)} method. We need to ensure + * that we don't prompt people to update to the wrong sig after an update. + */ + @Test + public void dontSuggestUpstreamVersions() { + // By setting the "upstreamVersionCode" to 0, we are letting F-Droid choose the highest compatible version. + App mainApp = insertApp(context, "single.app", "Single App (Main repo)", 0, "https://main.repo"); + + insertApk(context, mainApp, 1, FDROID_SIG); + insertApk(context, mainApp, 2, FDROID_SIG); + insertApk(context, mainApp, 3, FDROID_SIG); + insertApk(context, mainApp, 4, FDROID_SIG); + insertApk(context, mainApp, 5, FDROID_SIG); + + insertApk(context, mainApp, 4, UPSTREAM_SIG); + insertApk(context, mainApp, 5, UPSTREAM_SIG); + insertApk(context, mainApp, 6, UPSTREAM_SIG); + insertApk(context, mainApp, 7, UPSTREAM_SIG); + + // If the user was to manually install the app, they should be suggested version 7 from upstream... + assertSuggested("single.app", 7); + + // ... but we should not prompt them to update anything, because it isn't installed. + assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL)); + + // After installing an early F-Droid version, we should then suggest the latest F-Droid version. + InstalledAppTestUtils.install(context, "single.app", 2, "v2", FDROID_CERT); + assertSuggested("single.app", 5, FDROID_SIG, 2); + + // However once we've reached the maximum F-Droid version, then we should not suggest higher versions + // with different signatures. + InstalledAppProviderService.deleteAppFromDb(context, "single.app"); + InstalledAppTestUtils.install(context, "single.app", 5, "v5", FDROID_CERT); + assertEquals(Collections.EMPTY_LIST, AppProvider.Helper.findCanUpdate(context, Cols.ALL)); + } + + /** + * Same as {@link #assertSuggested(String, int, String, int)} except only for non installed apps. + * @see #assertSuggested(String, int, String, int) + */ + private void assertSuggested(String packageName, int suggestedVersion) { + assertSuggested(packageName, suggestedVersion, null, 0); + } + + /** + * Checks that the app exists, that its suggested version code is correct, and that the apk which is "suggested" + * has the correct signature. + * + * If {@param installedSig} is null then {@param installedVersion} is ignored and the signature of the suggested + * apk is not checked. + */ + public void assertSuggested(String packageName, int suggestedVersion, String installedSig, int installedVersion) { + AppProvider.Helper.calcSuggestedApks(context); + AppProvider.Helper.recalculatePreferredMetadata(context); + + App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName); + assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode); + assertEquals("Installed signature on App", installedSig, suggestedApp.installedSig); + + Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(context, suggestedApp); + assertEquals("Suggested version on Apk", suggestedVersion, suggestedApk.versionCode); + if (installedSig != null) { + assertEquals("Installed signature on Apk", installedSig, suggestedApk.sig); + } + + List appsToUpdate = AppProvider.Helper.findCanUpdate(context, Schema.AppMetadataTable.Cols.ALL); + if (installedSig == null) { + assertEquals("Should not be able to update anything", 0, appsToUpdate.size()); + } else { + assertEquals("Apps to update", 1, appsToUpdate.size()); + App canUpdateApp = appsToUpdate.get(0); + assertEquals("Package name of updatable app", packageName, canUpdateApp.packageName); + assertEquals("Installed version of updatable app", installedVersion, canUpdateApp.installedVersionCode); + assertEquals("Suggested version to update to", suggestedVersion, canUpdateApp.suggestedVersionCode); + assertEquals("Installed signature of updatable app", installedSig, canUpdateApp.installedSig); + } } private void insertApk(Context context, App app, int versionCode, String signature) {