Merge branch 'multi-sig--install' into 'master'
Multi signature improvements for AppDetails + install workflow See merge request !536
This commit is contained in:
		
						commit
						e6ba0de8b2
					
				@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<App> 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<App> 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<Apk> 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<App> canUpdate) {
 | 
			
		||||
        if (canUpdate.size() > 0) {
 | 
			
		||||
            List<Apk> apksToUpdate = new ArrayList<>(canUpdate.size());
 | 
			
		||||
            for (App app : canUpdate) {
 | 
			
		||||
                apksToUpdate.add(ApkProvider.Helper.findSuggestedApk(this, app));
 | 
			
		||||
            }
 | 
			
		||||
            appUpdateStatusManager.addApks(apksToUpdate, AppUpdateStatusManager.Status.UpdateAvailable);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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<Apk> findByPackageName(Context context,
 | 
			
		||||
                                                  String packageName, String[] projection) {
 | 
			
		||||
        public static List<Apk> 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<App> 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<String> pathSegments = uri.getPathSegments();
 | 
			
		||||
        List<String> 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -114,7 +114,9 @@ public class AppDetailsRecyclerViewAdapter
 | 
			
		||||
        versions = new ArrayList<>();
 | 
			
		||||
        final List<Apk> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<App> 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) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user