Merge branch 'issue-1070--known-vuln--ui' into 'master'
Show apps with known vulnerabilities in the updates tab. Closes #1070 See merge request fdroid/fdroidclient!558
This commit is contained in:
		
						commit
						8bececfa58
					
				@ -29,13 +29,10 @@ import android.content.Context;
 | 
				
			|||||||
import android.content.DialogInterface;
 | 
					import android.content.DialogInterface;
 | 
				
			||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
import android.content.IntentFilter;
 | 
					import android.content.IntentFilter;
 | 
				
			||||||
import android.content.pm.PackageInfo;
 | 
					 | 
				
			||||||
import android.content.pm.PackageManager;
 | 
					 | 
				
			||||||
import android.database.ContentObserver;
 | 
					import android.database.ContentObserver;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.os.Handler;
 | 
					import android.os.Handler;
 | 
				
			||||||
import android.support.annotation.NonNull;
 | 
					 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
import android.support.design.widget.AppBarLayout;
 | 
					import android.support.design.widget.AppBarLayout;
 | 
				
			||||||
import android.support.design.widget.CoordinatorLayout;
 | 
					import android.support.design.widget.CoordinatorLayout;
 | 
				
			||||||
@ -60,8 +57,6 @@ import org.fdroid.fdroid.data.ApkProvider;
 | 
				
			|||||||
import org.fdroid.fdroid.data.App;
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
import org.fdroid.fdroid.data.AppPrefsProvider;
 | 
					import org.fdroid.fdroid.data.AppPrefsProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.AppProvider;
 | 
					import org.fdroid.fdroid.data.AppProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.InstalledApp;
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.data.Schema;
 | 
					import org.fdroid.fdroid.data.Schema;
 | 
				
			||||||
import org.fdroid.fdroid.installer.InstallManagerService;
 | 
					import org.fdroid.fdroid.installer.InstallManagerService;
 | 
				
			||||||
import org.fdroid.fdroid.installer.Installer;
 | 
					import org.fdroid.fdroid.installer.Installer;
 | 
				
			||||||
@ -740,40 +735,6 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
 | 
				
			|||||||
        installApk(apkToInstall);
 | 
					        installApk(apkToInstall);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Attempts to find the installed {@link Apk} from the database. If not found, will lookup the
 | 
					 | 
				
			||||||
     * {@link InstalledAppProvider} to find the details of the installed app and use that to
 | 
					 | 
				
			||||||
     * instantiate an {@link Apk} to be returned.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Cases where an {@link Apk} will not be found in the database and for which we fall back to
 | 
					 | 
				
			||||||
     * the {@link InstalledAppProvider} include:
 | 
					 | 
				
			||||||
     *  + System apps which are provided by a repository, but for which the version code bundled
 | 
					 | 
				
			||||||
     *    with the system is not included in the repository.
 | 
					 | 
				
			||||||
     *  + Regular apps from a repository, where the installed version is old enough that it is no
 | 
					 | 
				
			||||||
     *    longer available in the repository.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws IllegalStateException If neither the {@link PackageManager} or the
 | 
					 | 
				
			||||||
     * {@link InstalledAppProvider} can't find a reference to the installed apk.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @NonNull
 | 
					 | 
				
			||||||
    private Apk getInstalledApk() {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            PackageInfo pi = getPackageManager().getPackageInfo(app.packageName, 0);
 | 
					 | 
				
			||||||
            Apk apk = ApkProvider.Helper.findApkFromAnyRepo(this, pi.packageName, pi.versionCode);
 | 
					 | 
				
			||||||
            if (apk == null) {
 | 
					 | 
				
			||||||
                InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(this, pi.packageName);
 | 
					 | 
				
			||||||
                if (installedApp == null) {
 | 
					 | 
				
			||||||
                    throw new IllegalStateException("No installed app found when trying to uninstall");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                apk = new Apk(installedApp);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return apk;
 | 
					 | 
				
			||||||
        } catch (PackageManager.NameNotFoundException e) {
 | 
					 | 
				
			||||||
            e.printStackTrace();
 | 
					 | 
				
			||||||
            throw new IllegalStateException("Couldn't find installed apk for " + app.packageName, e);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void uninstallApk() {
 | 
					    public void uninstallApk() {
 | 
				
			||||||
        Apk apk = app.installedApk;
 | 
					        Apk apk = app.installedApk;
 | 
				
			||||||
@ -783,7 +744,10 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
 | 
				
			|||||||
            apk = app.getMediaApkifInstalled(getApplicationContext());
 | 
					            apk = app.getMediaApkifInstalled(getApplicationContext());
 | 
				
			||||||
            if (apk == null) {
 | 
					            if (apk == null) {
 | 
				
			||||||
                // When the app isn't a media file - the above workaround refers to this.
 | 
					                // When the app isn't a media file - the above workaround refers to this.
 | 
				
			||||||
                apk = getInstalledApk();
 | 
					                apk = app.getInstalledApk(this);
 | 
				
			||||||
 | 
					                if (apk == null) {
 | 
				
			||||||
 | 
					                    throw new IllegalStateException("Couldn't find installed apk for " + app.packageName);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            app.installedApk = apk;
 | 
					            app.installedApk = apk;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -241,7 +241,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
 | 
				
			|||||||
                case Cols.Repo.ADDRESS:
 | 
					                case Cols.Repo.ADDRESS:
 | 
				
			||||||
                    repoAddress = cursor.getString(i);
 | 
					                    repoAddress = cursor.getString(i);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case Cols.ANTI_FEATURES:
 | 
					                case Cols.AntiFeatures.ANTI_FEATURES:
 | 
				
			||||||
                    antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
					                    antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -348,7 +348,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
 | 
				
			|||||||
        values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features));
 | 
					        values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features));
 | 
				
			||||||
        values.put(Cols.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode));
 | 
					        values.put(Cols.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode));
 | 
				
			||||||
        values.put(Cols.INCOMPATIBLE_REASONS, Utils.serializeCommaSeparatedString(incompatibleReasons));
 | 
					        values.put(Cols.INCOMPATIBLE_REASONS, Utils.serializeCommaSeparatedString(incompatibleReasons));
 | 
				
			||||||
        values.put(Cols.ANTI_FEATURES, Utils.serializeCommaSeparatedString(antiFeatures));
 | 
					        values.put(Cols.AntiFeatures.ANTI_FEATURES, Utils.serializeCommaSeparatedString(antiFeatures));
 | 
				
			||||||
        values.put(Cols.IS_COMPATIBLE, compatible ? 1 : 0);
 | 
					        values.put(Cols.IS_COMPATIBLE, compatible ? 1 : 0);
 | 
				
			||||||
        return values;
 | 
					        return values;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,10 @@ import android.net.Uri;
 | 
				
			|||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.AntiFeatureTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
				
			||||||
@ -17,8 +21,10 @@ import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SuppressWarnings("LineLength")
 | 
					@SuppressWarnings("LineLength")
 | 
				
			||||||
public class ApkProvider extends FDroidProvider {
 | 
					public class ApkProvider extends FDroidProvider {
 | 
				
			||||||
@ -266,6 +272,10 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
        return ApkTable.NAME;
 | 
					        return ApkTable.NAME;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected String getApkAntiFeatureJoinTableName() {
 | 
				
			||||||
 | 
					        return ApkAntiFeatureJoinTable.NAME;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected String getAppTableName() {
 | 
					    protected String getAppTableName() {
 | 
				
			||||||
        return AppMetadataTable.NAME;
 | 
					        return AppMetadataTable.NAME;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -283,6 +293,18 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
    private class Query extends QueryBuilder {
 | 
					    private class Query extends QueryBuilder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private boolean repoTableRequired;
 | 
					        private boolean repoTableRequired;
 | 
				
			||||||
 | 
					        private boolean antiFeaturesRequested;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * If the query includes anti features, then we group by apk id. This is because joining onto the anti-features
 | 
				
			||||||
 | 
					         * table will result in multiple result rows for each apk (potentially), so we will GROUP_CONCAT each of the
 | 
				
			||||||
 | 
					         * anti features into a single comma separated list for each apk. If we are _not_ including anti features, then
 | 
				
			||||||
 | 
					         * don't group by apk, because when doing a COUNT(*) this will result in the wrong result.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected String groupBy() {
 | 
				
			||||||
 | 
					            return antiFeaturesRequested ? "apk." + Cols.ROW_ID : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        protected String getRequiredTables() {
 | 
					        protected String getRequiredTables() {
 | 
				
			||||||
@ -301,6 +323,9 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
                addPackageField(PACKAGE_FIELDS.get(field), field);
 | 
					                addPackageField(PACKAGE_FIELDS.get(field), field);
 | 
				
			||||||
            } else if (REPO_FIELDS.containsKey(field)) {
 | 
					            } else if (REPO_FIELDS.containsKey(field)) {
 | 
				
			||||||
                addRepoField(REPO_FIELDS.get(field), field);
 | 
					                addRepoField(REPO_FIELDS.get(field), field);
 | 
				
			||||||
 | 
					            } else if (Cols.AntiFeatures.ANTI_FEATURES.equals(field)) {
 | 
				
			||||||
 | 
					                antiFeaturesRequested = true;
 | 
				
			||||||
 | 
					                addAntiFeatures();
 | 
				
			||||||
            } else if (field.equals(Cols._ID)) {
 | 
					            } else if (field.equals(Cols._ID)) {
 | 
				
			||||||
                appendField("rowid", "apk", "_id");
 | 
					                appendField("rowid", "apk", "_id");
 | 
				
			||||||
            } else if (field.equals(Cols._COUNT)) {
 | 
					            } else if (field.equals(Cols._COUNT)) {
 | 
				
			||||||
@ -324,6 +349,18 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
            appendField(field, "repo", alias);
 | 
					            appendField(field, "repo", alias);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void addAntiFeatures() {
 | 
				
			||||||
 | 
					            String apkAntiFeature = "apkAntiFeatureJoin";
 | 
				
			||||||
 | 
					            String antiFeature = "antiFeature";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            leftJoin(getApkAntiFeatureJoinTableName(), apkAntiFeature,
 | 
				
			||||||
 | 
					                    "apk." + Cols.ROW_ID + " = " + apkAntiFeature + "." + ApkAntiFeatureJoinTable.Cols.APK_ID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            leftJoin(AntiFeatureTable.NAME, antiFeature,
 | 
				
			||||||
 | 
					                    apkAntiFeature + "." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + " = " + antiFeature + "." + AntiFeatureTable.Cols.ROW_ID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            appendField("group_concat(" + antiFeature + "." + AntiFeatureTable.Cols.NAME + ") as " + Cols.AntiFeatures.ANTI_FEATURES);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private QuerySelection queryPackage(String packageName) {
 | 
					    private QuerySelection queryPackage(String packageName) {
 | 
				
			||||||
@ -508,15 +545,73 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Uri insert(Uri uri, ContentValues values) {
 | 
					    public Uri insert(Uri uri, ContentValues values) {
 | 
				
			||||||
 | 
					        boolean saveAntiFeatures = false;
 | 
				
			||||||
 | 
					        String[] antiFeatures = null;
 | 
				
			||||||
 | 
					        if (values.containsKey(Cols.AntiFeatures.ANTI_FEATURES)) {
 | 
				
			||||||
 | 
					            saveAntiFeatures = true;
 | 
				
			||||||
 | 
					            String antiFeaturesString = values.getAsString(Cols.AntiFeatures.ANTI_FEATURES);
 | 
				
			||||||
 | 
					            antiFeatures = Utils.parseCommaSeparatedString(antiFeaturesString);
 | 
				
			||||||
 | 
					            values.remove(Cols.AntiFeatures.ANTI_FEATURES);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        removeFieldsFromOtherTables(values);
 | 
					        removeFieldsFromOtherTables(values);
 | 
				
			||||||
        validateFields(Cols.ALL, values);
 | 
					        validateFields(Cols.ALL, values);
 | 
				
			||||||
        long newId = db().insertOrThrow(getTableName(), null, values);
 | 
					        long newId = db().insertOrThrow(getTableName(), null, values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (saveAntiFeatures) {
 | 
				
			||||||
 | 
					            ensureAntiFeatures(antiFeatures, newId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!isApplyingBatch()) {
 | 
					        if (!isApplyingBatch()) {
 | 
				
			||||||
            getContext().getContentResolver().notifyChange(uri, null);
 | 
					            getContext().getContentResolver().notifyChange(uri, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return getApkUri(newId);
 | 
					        return getApkUri(newId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void ensureAntiFeatures(String[] antiFeatures, long apkId) {
 | 
				
			||||||
 | 
					        db().delete(getApkAntiFeatureJoinTableName(), ApkAntiFeatureJoinTable.Cols.APK_ID + " = ?", new String[] {Long.toString(apkId)});
 | 
				
			||||||
 | 
					        if (antiFeatures != null) {
 | 
				
			||||||
 | 
					            Set<String> antiFeatureSet = new HashSet<>();
 | 
				
			||||||
 | 
					            for (String antiFeatureName : antiFeatures) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // There is nothing stopping a server repeating a category name in the metadata of
 | 
				
			||||||
 | 
					                // an app. In order to prevent unique constraint violations, only insert once into
 | 
				
			||||||
 | 
					                // the join table.
 | 
				
			||||||
 | 
					                if (antiFeatureSet.contains(antiFeatureName)) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                antiFeatureSet.add(antiFeatureName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                long antiFeatureId = ensureAntiFeature(antiFeatureName);
 | 
				
			||||||
 | 
					                ContentValues categoryValues = new ContentValues(2);
 | 
				
			||||||
 | 
					                categoryValues.put(ApkAntiFeatureJoinTable.Cols.APK_ID, apkId);
 | 
				
			||||||
 | 
					                categoryValues.put(ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID, antiFeatureId);
 | 
				
			||||||
 | 
					                db().insert(getApkAntiFeatureJoinTableName(), null, categoryValues);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected long ensureAntiFeature(String antiFeatureName) {
 | 
				
			||||||
 | 
					        long antiFeatureId = 0;
 | 
				
			||||||
 | 
					        Cursor cursor = db().query(AntiFeatureTable.NAME, new String[] {AntiFeatureTable.Cols.ROW_ID}, AntiFeatureTable.Cols.NAME + " = ?", new String[]{antiFeatureName}, null, null, null);
 | 
				
			||||||
 | 
					        if (cursor != null) {
 | 
				
			||||||
 | 
					            if (cursor.getCount() > 0) {
 | 
				
			||||||
 | 
					                cursor.moveToFirst();
 | 
				
			||||||
 | 
					                antiFeatureId = cursor.getLong(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            cursor.close();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (antiFeatureId <= 0) {
 | 
				
			||||||
 | 
					            ContentValues values = new ContentValues(1);
 | 
				
			||||||
 | 
					            values.put(AntiFeatureTable.Cols.NAME, antiFeatureName);
 | 
				
			||||||
 | 
					            antiFeatureId = db().insert(AntiFeatureTable.NAME, null, values);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return antiFeatureId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public int delete(Uri uri, String where, String[] whereArgs) {
 | 
					    public int delete(Uri uri, String where, String[] whereArgs) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -549,6 +644,15 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
            throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
 | 
					            throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        boolean saveAntiFeatures = false;
 | 
				
			||||||
 | 
					        String[] antiFeatures = null;
 | 
				
			||||||
 | 
					        if (values.containsKey(Cols.AntiFeatures.ANTI_FEATURES)) {
 | 
				
			||||||
 | 
					            saveAntiFeatures = true;
 | 
				
			||||||
 | 
					            String antiFeaturesString = values.getAsString(Cols.AntiFeatures.ANTI_FEATURES);
 | 
				
			||||||
 | 
					            antiFeatures = Utils.parseCommaSeparatedString(antiFeaturesString);
 | 
				
			||||||
 | 
					            values.remove(Cols.AntiFeatures.ANTI_FEATURES);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        validateFields(Cols.ALL, values);
 | 
					        validateFields(Cols.ALL, values);
 | 
				
			||||||
        removeFieldsFromOtherTables(values);
 | 
					        removeFieldsFromOtherTables(values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -556,6 +660,19 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
        query = query.add(querySingleWithAppId(uri));
 | 
					        query = query.add(querySingleWithAppId(uri));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs());
 | 
					        int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (saveAntiFeatures) {
 | 
				
			||||||
 | 
					            // Get the database ID of the row we just updated, so that we can join relevant anti features to it.
 | 
				
			||||||
 | 
					            Cursor result = db().query(getTableName(), new String[]{Cols.ROW_ID},
 | 
				
			||||||
 | 
					                    query.getSelection(), query.getArgs(), null, null, null);
 | 
				
			||||||
 | 
					            if (result != null) {
 | 
				
			||||||
 | 
					                result.moveToFirst();
 | 
				
			||||||
 | 
					                long apkId = result.getLong(0);
 | 
				
			||||||
 | 
					                ensureAntiFeatures(antiFeatures, apkId);
 | 
				
			||||||
 | 
					                result.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!isApplyingBatch()) {
 | 
					        if (!isApplyingBatch()) {
 | 
				
			||||||
            getContext().getContentResolver().notifyChange(uri, null);
 | 
					            getContext().getContentResolver().notifyChange(uri, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -82,13 +82,6 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @JsonIgnore
 | 
					    @JsonIgnore
 | 
				
			||||||
    public boolean compatible;
 | 
					    public boolean compatible;
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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.
 | 
					 | 
				
			||||||
     * This is required for getting the full URL to the various graphics and screenshots.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @JsonIgnore
 | 
					    @JsonIgnore
 | 
				
			||||||
    public Apk installedApk; // might be null if not installed
 | 
					    public Apk installedApk; // might be null if not installed
 | 
				
			||||||
    @JsonIgnore
 | 
					    @JsonIgnore
 | 
				
			||||||
@ -107,6 +100,13 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
    @JsonIgnore
 | 
					    @JsonIgnore
 | 
				
			||||||
    public boolean isApk;
 | 
					    public boolean isApk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 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.
 | 
				
			||||||
 | 
					     * This is required for getting the full URL to the various graphics and screenshots.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    @JacksonInject("repoId")
 | 
					    @JacksonInject("repoId")
 | 
				
			||||||
    public long repoId;
 | 
					    public long repoId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -796,6 +796,37 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
        apk.sig = Utils.hashBytes(fdroidSig, "md5");
 | 
					        apk.sig = Utils.hashBytes(fdroidSig, "md5");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Attempts to find the installed {@link Apk} from the database. If not found, will lookup the
 | 
				
			||||||
 | 
					     * {@link InstalledAppProvider} to find the details of the installed app and use that to
 | 
				
			||||||
 | 
					     * instantiate an {@link Apk} to be returned.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * Cases where an {@link Apk} will not be found in the database and for which we fall back to
 | 
				
			||||||
 | 
					     * the {@link InstalledAppProvider} include:
 | 
				
			||||||
 | 
					     *  + System apps which are provided by a repository, but for which the version code bundled
 | 
				
			||||||
 | 
					     *    with the system is not included in the repository.
 | 
				
			||||||
 | 
					     *  + Regular apps from a repository, where the installed version is old enough that it is no
 | 
				
			||||||
 | 
					     *    longer available in the repository.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
 | 
					    public Apk getInstalledApk(Context context) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            PackageInfo pi = context.getPackageManager().getPackageInfo(this.packageName, 0);
 | 
				
			||||||
 | 
					            Apk apk = ApkProvider.Helper.findApkFromAnyRepo(context, pi.packageName, pi.versionCode);
 | 
				
			||||||
 | 
					            if (apk == null) {
 | 
				
			||||||
 | 
					                InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(context, pi.packageName);
 | 
				
			||||||
 | 
					                if (installedApp == null) {
 | 
				
			||||||
 | 
					                    throw new IllegalStateException("No installed app found when trying to uninstall");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                apk = new Apk(installedApp);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return apk;
 | 
				
			||||||
 | 
					        } catch (PackageManager.NameNotFoundException e) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean isValid() {
 | 
					    public boolean isValid() {
 | 
				
			||||||
        if (TextUtils.isEmpty(this.name)
 | 
					        if (TextUtils.isEmpty(this.name)
 | 
				
			||||||
                || TextUtils.isEmpty(this.packageName)) {
 | 
					                || TextUtils.isEmpty(this.packageName)) {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,37 +3,44 @@ package org.fdroid.fdroid.data;
 | 
				
			|||||||
public class AppPrefs extends ValueObject {
 | 
					public class AppPrefs extends ValueObject {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * True if all updates for this app are to be ignored
 | 
					     * True if all updates for this app are to be ignored.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public boolean ignoreAllUpdates;
 | 
					    public boolean ignoreAllUpdates;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * True if the current update for this app is to be ignored
 | 
					     * The version code of the app for which the update should be ignored.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public int ignoreThisUpdate;
 | 
					    public int ignoreThisUpdate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public AppPrefs(int ignoreThis, boolean ignoreAll) {
 | 
					    /**
 | 
				
			||||||
 | 
					     * Don't notify of vulnerabilities in this app.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public boolean ignoreVulnerabilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public AppPrefs(int ignoreThis, boolean ignoreAll, boolean ignoreVulns) {
 | 
				
			||||||
        ignoreThisUpdate = ignoreThis;
 | 
					        ignoreThisUpdate = ignoreThis;
 | 
				
			||||||
        ignoreAllUpdates = ignoreAll;
 | 
					        ignoreAllUpdates = ignoreAll;
 | 
				
			||||||
 | 
					        ignoreVulnerabilities = ignoreVulns;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static AppPrefs createDefault() {
 | 
					    public static AppPrefs createDefault() {
 | 
				
			||||||
        return new AppPrefs(0, false);
 | 
					        return new AppPrefs(0, false, false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean equals(Object o) {
 | 
					    public boolean equals(Object o) {
 | 
				
			||||||
        return o != null && o instanceof AppPrefs &&
 | 
					        return o != null && o instanceof AppPrefs &&
 | 
				
			||||||
                ((AppPrefs) o).ignoreAllUpdates == ignoreAllUpdates &&
 | 
					                ((AppPrefs) o).ignoreAllUpdates == ignoreAllUpdates &&
 | 
				
			||||||
                ((AppPrefs) o).ignoreThisUpdate == ignoreThisUpdate;
 | 
					                ((AppPrefs) o).ignoreThisUpdate == ignoreThisUpdate &&
 | 
				
			||||||
 | 
					                ((AppPrefs) o).ignoreVulnerabilities == ignoreVulnerabilities;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public int hashCode() {
 | 
					    public int hashCode() {
 | 
				
			||||||
        return (ignoreThisUpdate + "-" + ignoreAllUpdates).hashCode();
 | 
					        return (ignoreThisUpdate + "-" + ignoreAllUpdates + "-" + ignoreVulnerabilities).hashCode();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public AppPrefs createClone() {
 | 
					    public AppPrefs createClone() {
 | 
				
			||||||
        return new AppPrefs(ignoreThisUpdate, ignoreAllUpdates);
 | 
					        return new AppPrefs(ignoreThisUpdate, ignoreAllUpdates, ignoreVulnerabilities);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ public class AppPrefsProvider extends FDroidProvider {
 | 
				
			|||||||
            ContentValues values = new ContentValues(3);
 | 
					            ContentValues values = new ContentValues(3);
 | 
				
			||||||
            values.put(Cols.IGNORE_ALL_UPDATES, prefs.ignoreAllUpdates);
 | 
					            values.put(Cols.IGNORE_ALL_UPDATES, prefs.ignoreAllUpdates);
 | 
				
			||||||
            values.put(Cols.IGNORE_THIS_UPDATE, prefs.ignoreThisUpdate);
 | 
					            values.put(Cols.IGNORE_THIS_UPDATE, prefs.ignoreThisUpdate);
 | 
				
			||||||
 | 
					            values.put(Cols.IGNORE_VULNERABILITIES, prefs.ignoreVulnerabilities);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (getPrefsOrNull(context, app) == null) {
 | 
					            if (getPrefsOrNull(context, app) == null) {
 | 
				
			||||||
                values.put(Cols.PACKAGE_NAME, app.packageName);
 | 
					                values.put(Cols.PACKAGE_NAME, app.packageName);
 | 
				
			||||||
@ -51,7 +52,8 @@ public class AppPrefsProvider extends FDroidProvider {
 | 
				
			|||||||
                cursor.moveToFirst();
 | 
					                cursor.moveToFirst();
 | 
				
			||||||
                return new AppPrefs(
 | 
					                return new AppPrefs(
 | 
				
			||||||
                        cursor.getInt(cursor.getColumnIndexOrThrow(Cols.IGNORE_THIS_UPDATE)),
 | 
					                        cursor.getInt(cursor.getColumnIndexOrThrow(Cols.IGNORE_THIS_UPDATE)),
 | 
				
			||||||
                        cursor.getInt(cursor.getColumnIndexOrThrow(Cols.IGNORE_ALL_UPDATES)) > 0);
 | 
					                        cursor.getInt(cursor.getColumnIndexOrThrow(Cols.IGNORE_ALL_UPDATES)) > 0,
 | 
				
			||||||
 | 
					                        cursor.getInt(cursor.getColumnIndexOrThrow(Cols.IGNORE_VULNERABILITIES)) > 0);
 | 
				
			||||||
            } finally {
 | 
					            } finally {
 | 
				
			||||||
                cursor.close();
 | 
					                cursor.close();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import android.text.TextUtils;
 | 
				
			|||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
import org.fdroid.fdroid.Preferences;
 | 
					import org.fdroid.fdroid.Preferences;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
 | 
				
			||||||
@ -133,6 +134,12 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA);
 | 
					            Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA);
 | 
				
			||||||
            context.getContentResolver().query(uri, null, null, null, null);
 | 
					            context.getContentResolver().query(uri, null, null, null, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<App> findInstalledAppsWithKnownVulns(Context context) {
 | 
				
			||||||
 | 
					            Uri uri = getInstalledWithKnownVulnsUri();
 | 
				
			||||||
 | 
					            Cursor cursor = context.getContentResolver().query(uri, Cols.ALL, null, null, null);
 | 
				
			||||||
 | 
					            return cursorToList(cursor);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -150,6 +157,8 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
    protected static class AppQuerySelection extends QuerySelection {
 | 
					    protected static class AppQuerySelection extends QuerySelection {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private boolean naturalJoinToInstalled;
 | 
					        private boolean naturalJoinToInstalled;
 | 
				
			||||||
 | 
					        private boolean naturalJoinApks;
 | 
				
			||||||
 | 
					        private boolean naturalJoinAntiFeatures;
 | 
				
			||||||
        private boolean leftJoinPrefs;
 | 
					        private boolean leftJoinPrefs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AppQuerySelection() {
 | 
					        AppQuerySelection() {
 | 
				
			||||||
@ -170,6 +179,14 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            return naturalJoinToInstalled;
 | 
					            return naturalJoinToInstalled;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public boolean naturalJoinToApks() {
 | 
				
			||||||
 | 
					            return naturalJoinApks;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public boolean naturalJoinAntiFeatures() {
 | 
				
			||||||
 | 
					            return naturalJoinAntiFeatures;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Tells the query selection that it will need to join onto the installed apps table
 | 
					         * Tells the query selection that it will need to join onto the installed apps table
 | 
				
			||||||
         * when used. This should be called when your query makes use of fields from that table
 | 
					         * when used. This should be called when your query makes use of fields from that table
 | 
				
			||||||
@ -182,6 +199,22 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            return this;
 | 
					            return this;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Note that this has large performance implications, so should only be used if you are already limiting
 | 
				
			||||||
 | 
					         * the result set based on other, more drastic conditions first.
 | 
				
			||||||
 | 
					         * See https://gitlab.com/fdroid/fdroidclient/issues/1143 for the investigation which identified these
 | 
				
			||||||
 | 
					         * performance implications.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        public AppQuerySelection requireNaturalJoinApks() {
 | 
				
			||||||
 | 
					            naturalJoinApks = true;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public AppQuerySelection requireNatrualJoinAntiFeatures() {
 | 
				
			||||||
 | 
					            naturalJoinAntiFeatures = true;
 | 
				
			||||||
 | 
					            return this;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public boolean leftJoinToPrefs() {
 | 
					        public boolean leftJoinToPrefs() {
 | 
				
			||||||
            return leftJoinPrefs;
 | 
					            return leftJoinPrefs;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -198,9 +231,18 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                bothWithJoin.requireNaturalInstalledTable();
 | 
					                bothWithJoin.requireNaturalInstalledTable();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.naturalJoinToApks() || query.naturalJoinToApks()) {
 | 
				
			||||||
 | 
					                bothWithJoin.requireNaturalJoinApks();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
 | 
					            if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
 | 
				
			||||||
                bothWithJoin.requireLeftJoinPrefs();
 | 
					                bothWithJoin.requireLeftJoinPrefs();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.naturalJoinAntiFeatures() || query.naturalJoinAntiFeatures()) {
 | 
				
			||||||
 | 
					                bothWithJoin.requireNatrualJoinAntiFeatures();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return bothWithJoin;
 | 
					            return bothWithJoin;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -210,6 +252,8 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private boolean isSuggestedApkTableAdded;
 | 
					        private boolean isSuggestedApkTableAdded;
 | 
				
			||||||
        private boolean requiresInstalledTable;
 | 
					        private boolean requiresInstalledTable;
 | 
				
			||||||
 | 
					        private boolean requiresApkTable;
 | 
				
			||||||
 | 
					        private boolean requiresAntiFeatures;
 | 
				
			||||||
        private boolean requiresLeftJoinToPrefs;
 | 
					        private boolean requiresLeftJoinToPrefs;
 | 
				
			||||||
        private boolean countFieldAppended;
 | 
					        private boolean countFieldAppended;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -240,9 +284,15 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            if (selection.naturalJoinToInstalled()) {
 | 
					            if (selection.naturalJoinToInstalled()) {
 | 
				
			||||||
                naturalJoinToInstalledTable();
 | 
					                naturalJoinToInstalledTable();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if (selection.naturalJoinToApks()) {
 | 
				
			||||||
 | 
					                naturalJoinToApkTable();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (selection.leftJoinToPrefs()) {
 | 
					            if (selection.leftJoinToPrefs()) {
 | 
				
			||||||
                leftJoinToPrefs();
 | 
					                leftJoinToPrefs();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if (selection.naturalJoinAntiFeatures()) {
 | 
				
			||||||
 | 
					                naturalJoinAntiFeatures();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: What if the selection requires a natural join, but we first get a left join
 | 
					        // TODO: What if the selection requires a natural join, but we first get a left join
 | 
				
			||||||
@ -257,6 +307,17 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void naturalJoinToApkTable() {
 | 
				
			||||||
 | 
					            if (!requiresApkTable) {
 | 
				
			||||||
 | 
					                join(
 | 
				
			||||||
 | 
					                        getApkTableName(),
 | 
				
			||||||
 | 
					                        getApkTableName(),
 | 
				
			||||||
 | 
					                        getApkTableName() + "." + ApkTable.Cols.APP_ID + " = " + getTableName() + "." + Cols.ROW_ID
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                requiresApkTable = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void leftJoinToPrefs() {
 | 
					        public void leftJoinToPrefs() {
 | 
				
			||||||
            if (!requiresLeftJoinToPrefs) {
 | 
					            if (!requiresLeftJoinToPrefs) {
 | 
				
			||||||
                leftJoin(
 | 
					                leftJoin(
 | 
				
			||||||
@ -277,6 +338,22 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void naturalJoinAntiFeatures() {
 | 
				
			||||||
 | 
					            if (!requiresAntiFeatures) {
 | 
				
			||||||
 | 
					                join(
 | 
				
			||||||
 | 
					                        getApkAntiFeatureJoinTableName(),
 | 
				
			||||||
 | 
					                        "apkAntiFeature",
 | 
				
			||||||
 | 
					                        "apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.APK_ID + " = " + getApkTableName() + "." + ApkTable.Cols.ROW_ID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                join(
 | 
				
			||||||
 | 
					                        Schema.AntiFeatureTable.NAME,
 | 
				
			||||||
 | 
					                        "antiFeature",
 | 
				
			||||||
 | 
					                        "antiFeature." + Schema.AntiFeatureTable.Cols.ROW_ID + " = " + "apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                requiresAntiFeatures = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void addField(String field) {
 | 
					        public void addField(String field) {
 | 
				
			||||||
            switch (field) {
 | 
					            switch (field) {
 | 
				
			||||||
@ -370,6 +447,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
    private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata";
 | 
					    private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata";
 | 
				
			||||||
    private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
 | 
					    private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
 | 
				
			||||||
    private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory";
 | 
					    private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory";
 | 
				
			||||||
 | 
					    private static final String PATH_INSTALLED_WITH_KNOWN_VULNS = "installedWithKnownVulns";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final int CAN_UPDATE = CODE_SINGLE + 1;
 | 
					    private static final int CAN_UPDATE = CODE_SINGLE + 1;
 | 
				
			||||||
    private static final int INSTALLED = CAN_UPDATE + 1;
 | 
					    private static final int INSTALLED = CAN_UPDATE + 1;
 | 
				
			||||||
@ -383,6 +461,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
    private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1;
 | 
					    private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1;
 | 
				
			||||||
    private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1;
 | 
					    private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1;
 | 
				
			||||||
    private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1;
 | 
					    private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1;
 | 
				
			||||||
 | 
					    private static final int INSTALLED_WITH_KNOWN_VULNS = TOP_FROM_CATEGORY + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static {
 | 
					    static {
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), null, CODE_LIST);
 | 
					        MATCHER.addURI(getAuthority(), null, CODE_LIST);
 | 
				
			||||||
@ -400,6 +479,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
 | 
					        MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
 | 
					        MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY);
 | 
					        MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY);
 | 
				
			||||||
 | 
					        MATCHER.addURI(getAuthority(), PATH_INSTALLED_WITH_KNOWN_VULNS, INSTALLED_WITH_KNOWN_VULNS);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getContentUri() {
 | 
					    public static Uri getContentUri() {
 | 
				
			||||||
@ -421,6 +501,12 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Uri getInstalledWithKnownVulnsUri() {
 | 
				
			||||||
 | 
					        return getContentUri().buildUpon()
 | 
				
			||||||
 | 
					                .appendPath(PATH_INSTALLED_WITH_KNOWN_VULNS)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getTopFromCategoryUri(String category, int limit) {
 | 
					    public static Uri getTopFromCategoryUri(String category, int limit) {
 | 
				
			||||||
        return getContentUri().buildUpon()
 | 
					        return getContentUri().buildUpon()
 | 
				
			||||||
                .appendPath(PATH_TOP_FROM_CATEGORY)
 | 
					                .appendPath(PATH_TOP_FROM_CATEGORY)
 | 
				
			||||||
@ -505,6 +591,10 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        return ApkTable.NAME;
 | 
					        return ApkTable.NAME;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected String getApkAntiFeatureJoinTableName() {
 | 
				
			||||||
 | 
					        return ApkAntiFeatureJoinTable.NAME;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected String getProviderName() {
 | 
					    protected String getProviderName() {
 | 
				
			||||||
        return "AppProvider";
 | 
					        return "AppProvider";
 | 
				
			||||||
@ -652,6 +742,24 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        return new AppQuerySelection(selection, args);
 | 
					        return new AppQuerySelection(selection, args);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private AppQuerySelection queryInstalledWithKnownVulns() {
 | 
				
			||||||
 | 
					        String apk = getApkTableName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Include the hash in this check because otherwise apps with any vulnerable version will
 | 
				
			||||||
 | 
					        // get returned, rather than just the installed version.
 | 
				
			||||||
 | 
					        String compareHash = apk + "." + ApkTable.Cols.HASH + " = installed." + InstalledAppTable.Cols.HASH;
 | 
				
			||||||
 | 
					        String knownVuln = " antiFeature." + Schema.AntiFeatureTable.Cols.NAME + " = 'KnownVuln' ";
 | 
				
			||||||
 | 
					        String notIgnored = " COALESCE(prefs." + AppPrefsTable.Cols.IGNORE_VULNERABILITIES + ", 0) = 0 ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String selection = knownVuln + " AND " + compareHash + " AND " + notIgnored;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new AppQuerySelection(selection)
 | 
				
			||||||
 | 
					                .requireNaturalInstalledTable()
 | 
				
			||||||
 | 
					                .requireNaturalJoinApks()
 | 
				
			||||||
 | 
					                .requireNatrualJoinAntiFeatures()
 | 
				
			||||||
 | 
					                .requireLeftJoinPrefs();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
 | 
					    static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
 | 
				
			||||||
        String[] args = packageNames.split(",");
 | 
					        String[] args = packageNames.split(",");
 | 
				
			||||||
        String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
 | 
					        String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
 | 
				
			||||||
@ -739,6 +847,11 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                includeSwap = false;
 | 
					                includeSwap = false;
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case INSTALLED_WITH_KNOWN_VULNS:
 | 
				
			||||||
 | 
					                selection = selection.add(queryInstalledWithKnownVulns());
 | 
				
			||||||
 | 
					                includeSwap = false;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case RECENTLY_UPDATED:
 | 
					            case RECENTLY_UPDATED:
 | 
				
			||||||
                String table = getTableName();
 | 
					                String table = getTableName();
 | 
				
			||||||
                String isNew = table + "." + Cols.LAST_UPDATED + " <= " + table + "." + Cols.ADDED + " DESC";
 | 
					                String isNew = table + "." + Cols.LAST_UPDATED + " <= " + table + "." + Cols.ADDED + " DESC";
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,8 @@ import android.util.Log;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.fdroid.fdroid.R;
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.AntiFeatureTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.CatJoinTable;
 | 
					import org.fdroid.fdroid.data.Schema.CatJoinTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
				
			||||||
@ -107,8 +109,7 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            + ApkTable.Cols.HASH_TYPE + " string, "
 | 
					            + ApkTable.Cols.HASH_TYPE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.ADDED_DATE + " string, "
 | 
					            + ApkTable.Cols.ADDED_DATE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.IS_COMPATIBLE + " int not null, "
 | 
					            + ApkTable.Cols.IS_COMPATIBLE + " int not null, "
 | 
				
			||||||
            + ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
 | 
					            + ApkTable.Cols.INCOMPATIBLE_REASONS + " text"
 | 
				
			||||||
            + ApkTable.Cols.ANTI_FEATURES + " string"
 | 
					 | 
				
			||||||
            + ");";
 | 
					            + ");";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
					    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
				
			||||||
@ -157,8 +158,9 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
    private static final String CREATE_TABLE_APP_PREFS = "CREATE TABLE " + AppPrefsTable.NAME
 | 
					    private static final String CREATE_TABLE_APP_PREFS = "CREATE TABLE " + AppPrefsTable.NAME
 | 
				
			||||||
            + " ( "
 | 
					            + " ( "
 | 
				
			||||||
            + AppPrefsTable.Cols.PACKAGE_NAME + " TEXT, "
 | 
					            + AppPrefsTable.Cols.PACKAGE_NAME + " TEXT, "
 | 
				
			||||||
            + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + " INT BOOLEAN NOT NULL, "
 | 
					            + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + " INT NOT NULL, "
 | 
				
			||||||
            + AppPrefsTable.Cols.IGNORE_ALL_UPDATES + " INT NOT NULL "
 | 
					            + AppPrefsTable.Cols.IGNORE_ALL_UPDATES + " INT BOOLEAN NOT NULL, "
 | 
				
			||||||
 | 
					            + AppPrefsTable.Cols.IGNORE_VULNERABILITIES + " INT BOOLEAN NOT NULL "
 | 
				
			||||||
            + " );";
 | 
					            + " );";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String CREATE_TABLE_CATEGORY = "CREATE TABLE " + Schema.CategoryTable.NAME
 | 
					    private static final String CREATE_TABLE_CATEGORY = "CREATE TABLE " + Schema.CategoryTable.NAME
 | 
				
			||||||
@ -194,7 +196,19 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            + InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
 | 
					            + InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
 | 
				
			||||||
            + " );";
 | 
					            + " );";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected static final int DB_VERSION = 74;
 | 
					    private static final String CREATE_TABLE_ANTI_FEATURE = "CREATE TABLE " + AntiFeatureTable.NAME
 | 
				
			||||||
 | 
					            + " ( "
 | 
				
			||||||
 | 
					            + AntiFeatureTable.Cols.NAME + " TEXT NOT NULL "
 | 
				
			||||||
 | 
					            + " );";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static final String CREATE_TABLE_APK_ANTI_FEATURE_JOIN = "CREATE TABLE " + ApkAntiFeatureJoinTable.NAME
 | 
				
			||||||
 | 
					            + " ( "
 | 
				
			||||||
 | 
					            + ApkAntiFeatureJoinTable.Cols.APK_ID + " INT NOT NULL, "
 | 
				
			||||||
 | 
					            + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + " INT NOT NULL, "
 | 
				
			||||||
 | 
					            + "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") "
 | 
				
			||||||
 | 
					            + " );";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected static final int DB_VERSION = 75;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Context context;
 | 
					    private final Context context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -214,6 +228,8 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        db.execSQL(CREATE_TABLE_INSTALLED_APP);
 | 
					        db.execSQL(CREATE_TABLE_INSTALLED_APP);
 | 
				
			||||||
        db.execSQL(CREATE_TABLE_REPO);
 | 
					        db.execSQL(CREATE_TABLE_REPO);
 | 
				
			||||||
        db.execSQL(CREATE_TABLE_APP_PREFS);
 | 
					        db.execSQL(CREATE_TABLE_APP_PREFS);
 | 
				
			||||||
 | 
					        db.execSQL(CREATE_TABLE_ANTI_FEATURE);
 | 
				
			||||||
 | 
					        db.execSQL(CREATE_TABLE_APK_ANTI_FEATURE_JOIN);
 | 
				
			||||||
        ensureIndexes(db);
 | 
					        ensureIndexes(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] defaultRepos = context.getResources().getStringArray(R.array.default_repos);
 | 
					        String[] defaultRepos = context.getResources().getStringArray(R.array.default_repos);
 | 
				
			||||||
@ -283,6 +299,28 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        addPreferredSignerToApp(db, oldVersion);
 | 
					        addPreferredSignerToApp(db, oldVersion);
 | 
				
			||||||
        updatePreferredSignerIfEmpty(db, oldVersion);
 | 
					        updatePreferredSignerIfEmpty(db, oldVersion);
 | 
				
			||||||
        addIsAppToApp(db, oldVersion);
 | 
					        addIsAppToApp(db, oldVersion);
 | 
				
			||||||
 | 
					        addApkAntiFeatures(db, oldVersion);
 | 
				
			||||||
 | 
					        addIgnoreVulnPref(db, oldVersion);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void addIgnoreVulnPref(SQLiteDatabase db, int oldVersion) {
 | 
				
			||||||
 | 
					        if (oldVersion >= 74) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!columnExists(db, AppPrefsTable.NAME, AppPrefsTable.Cols.IGNORE_VULNERABILITIES)) {
 | 
				
			||||||
 | 
					            Utils.debugLog(TAG, "Adding " + AppPrefsTable.Cols.IGNORE_VULNERABILITIES + " field to " + AppPrefsTable.NAME + " table in db.");
 | 
				
			||||||
 | 
					            db.execSQL("alter table " + AppPrefsTable.NAME + " add column " + AppPrefsTable.Cols.IGNORE_VULNERABILITIES + " boolean;");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void addApkAntiFeatures(SQLiteDatabase db, int oldVersion) {
 | 
				
			||||||
 | 
					        if (oldVersion >= 74) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Log.i(TAG, "Adding anti features on a per-apk basis.");
 | 
				
			||||||
 | 
					        resetTransient(db);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void addIsAppToApp(SQLiteDatabase db, int oldVersion) {
 | 
					    private void addIsAppToApp(SQLiteDatabase db, int oldVersion) {
 | 
				
			||||||
@ -436,11 +474,6 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            Utils.debugLog(TAG, "Adding " + RepoTable.Cols.MIRRORS + " field to " + RepoTable.NAME + " table in db.");
 | 
					            Utils.debugLog(TAG, "Adding " + RepoTable.Cols.MIRRORS + " field to " + RepoTable.NAME + " table in db.");
 | 
				
			||||||
            db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.MIRRORS + " string;");
 | 
					            db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.MIRRORS + " string;");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!columnExists(db, ApkTable.NAME, ApkTable.Cols.ANTI_FEATURES)) {
 | 
					 | 
				
			||||||
            Utils.debugLog(TAG, "Adding " + ApkTable.Cols.ANTI_FEATURES + " field to " + ApkTable.NAME + " table in db.");
 | 
					 | 
				
			||||||
            db.execSQL("alter table " + ApkTable.NAME + " add column " + ApkTable.Cols.ANTI_FEATURES + " string;");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -1059,6 +1092,14 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
                db.execSQL("DROP TABLE " + PackageTable.NAME);
 | 
					                db.execSQL("DROP TABLE " + PackageTable.NAME);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tableExists(db, AntiFeatureTable.NAME)) {
 | 
				
			||||||
 | 
					                db.execSQL("DROP TABLE " + AntiFeatureTable.NAME);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tableExists(db, ApkAntiFeatureJoinTable.NAME)) {
 | 
				
			||||||
 | 
					                db.execSQL("DROP TABLE " + ApkAntiFeatureJoinTable.NAME);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            db.execSQL("DROP TABLE " + AppMetadataTable.NAME);
 | 
					            db.execSQL("DROP TABLE " + AppMetadataTable.NAME);
 | 
				
			||||||
            db.execSQL("DROP TABLE " + ApkTable.NAME);
 | 
					            db.execSQL("DROP TABLE " + ApkTable.NAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1067,6 +1108,8 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            db.execSQL(CREATE_TABLE_APK);
 | 
					            db.execSQL(CREATE_TABLE_APK);
 | 
				
			||||||
            db.execSQL(CREATE_TABLE_CATEGORY);
 | 
					            db.execSQL(CREATE_TABLE_CATEGORY);
 | 
				
			||||||
            db.execSQL(CREATE_TABLE_CAT_JOIN);
 | 
					            db.execSQL(CREATE_TABLE_CAT_JOIN);
 | 
				
			||||||
 | 
					            db.execSQL(CREATE_TABLE_ANTI_FEATURE);
 | 
				
			||||||
 | 
					            db.execSQL(CREATE_TABLE_APK_ANTI_FEATURE_JOIN);
 | 
				
			||||||
            clearRepoEtags(db);
 | 
					            clearRepoEtags(db);
 | 
				
			||||||
            ensureIndexes(db);
 | 
					            ensureIndexes(db);
 | 
				
			||||||
            db.setTransactionSuccessful();
 | 
					            db.setTransactionSuccessful();
 | 
				
			||||||
 | 
				
			|||||||
@ -54,8 +54,9 @@ public interface Schema {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            String IGNORE_ALL_UPDATES = "ignoreAllUpdates";
 | 
					            String IGNORE_ALL_UPDATES = "ignoreAllUpdates";
 | 
				
			||||||
            String IGNORE_THIS_UPDATE = "ignoreThisUpdate";
 | 
					            String IGNORE_THIS_UPDATE = "ignoreThisUpdate";
 | 
				
			||||||
 | 
					            String IGNORE_VULNERABILITIES = "ignoreVulnerabilities";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            String[] ALL = {PACKAGE_NAME, IGNORE_ALL_UPDATES, IGNORE_THIS_UPDATE};
 | 
					            String[] ALL = {PACKAGE_NAME, IGNORE_ALL_UPDATES, IGNORE_THIS_UPDATE, IGNORE_VULNERABILITIES};
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,6 +107,47 @@ public interface Schema {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface AntiFeatureTable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String NAME = "fdroid_antiFeature";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        interface Cols {
 | 
				
			||||||
 | 
					            String ROW_ID = "rowid";
 | 
				
			||||||
 | 
					            String NAME = "name";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String[] ALL = {ROW_ID, NAME};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * An entry in this table signifies that an apk has a particular anti feature.
 | 
				
			||||||
 | 
					     * @see AntiFeatureTable
 | 
				
			||||||
 | 
					     * @see ApkTable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    interface ApkAntiFeatureJoinTable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String NAME = "fdroid_apkAntiFeatureJoin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        interface Cols {
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * Foreign key to {@link ApkTable}.
 | 
				
			||||||
 | 
					             * @see ApkTable
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            String APK_ID = "apkId";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * Foreign key to {@link AntiFeatureTable}.
 | 
				
			||||||
 | 
					             * @see AntiFeatureTable
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            String ANTI_FEATURE_ID = "antiFeatureId";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * @see AppMetadataTable.Cols#ALL_COLS
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            String[] ALL_COLS = {APK_ID, ANTI_FEATURE_ID};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    interface AppMetadataTable {
 | 
					    interface AppMetadataTable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String NAME = "fdroid_app";
 | 
					        String NAME = "fdroid_app";
 | 
				
			||||||
@ -258,7 +300,6 @@ public interface Schema {
 | 
				
			|||||||
            String ADDED_DATE      = "added";
 | 
					            String ADDED_DATE      = "added";
 | 
				
			||||||
            String IS_COMPATIBLE   = "compatible";
 | 
					            String IS_COMPATIBLE   = "compatible";
 | 
				
			||||||
            String INCOMPATIBLE_REASONS = "incompatibleReasons";
 | 
					            String INCOMPATIBLE_REASONS = "incompatibleReasons";
 | 
				
			||||||
            String ANTI_FEATURES   = "antiFeatures";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            interface Repo {
 | 
					            interface Repo {
 | 
				
			||||||
                String VERSION = "repoVersion";
 | 
					                String VERSION = "repoVersion";
 | 
				
			||||||
@ -269,6 +310,10 @@ public interface Schema {
 | 
				
			|||||||
                String PACKAGE_NAME = "package_packageName";
 | 
					                String PACKAGE_NAME = "package_packageName";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            interface AntiFeatures {
 | 
				
			||||||
 | 
					                String ANTI_FEATURES   = "antiFeatures_commaSeparated";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
             * @see AppMetadataTable.Cols#ALL_COLS
 | 
					             * @see AppMetadataTable.Cols#ALL_COLS
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
@ -277,7 +322,7 @@ public interface Schema {
 | 
				
			|||||||
                    SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
 | 
					                    SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
 | 
				
			||||||
                    OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
 | 
					                    OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
 | 
				
			||||||
                    REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
					                    REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
				
			||||||
                    IS_COMPATIBLE, INCOMPATIBLE_REASONS, ANTI_FEATURES,
 | 
					                    IS_COMPATIBLE, INCOMPATIBLE_REASONS,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
@ -289,7 +334,7 @@ public interface Schema {
 | 
				
			|||||||
                    OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
 | 
					                    OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
 | 
				
			||||||
                    REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
					                    REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
				
			||||||
                    IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
 | 
					                    IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
 | 
				
			||||||
                    ANTI_FEATURES,
 | 
					                    AntiFeatures.ANTI_FEATURES,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,11 @@ public class TempApkProvider extends ApkProvider {
 | 
				
			|||||||
        return TABLE_TEMP_APK;
 | 
					        return TABLE_TEMP_APK;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected String getApkAntiFeatureJoinTableName() {
 | 
				
			||||||
 | 
					        return TempAppProvider.TABLE_TEMP_APK_ANTI_FEATURE_JOIN;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected String getAppTableName() {
 | 
					    protected String getAppTableName() {
 | 
				
			||||||
        return TempAppProvider.TABLE_TEMP_APP;
 | 
					        return TempAppProvider.TABLE_TEMP_APP;
 | 
				
			||||||
@ -93,11 +98,23 @@ public class TempApkProvider extends ApkProvider {
 | 
				
			|||||||
        final SQLiteDatabase db = db();
 | 
					        final SQLiteDatabase db = db();
 | 
				
			||||||
        final String memoryDbName = TempAppProvider.DB;
 | 
					        final String memoryDbName = TempAppProvider.DB;
 | 
				
			||||||
        db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(ApkTable.NAME, memoryDbName + "." + getTableName()));
 | 
					        db.execSQL(DBHelper.CREATE_TABLE_APK.replaceFirst(ApkTable.NAME, memoryDbName + "." + getTableName()));
 | 
				
			||||||
 | 
					        db.execSQL(DBHelper.CREATE_TABLE_APK_ANTI_FEATURE_JOIN.replaceFirst(Schema.ApkAntiFeatureJoinTable.NAME, memoryDbName + "." + getApkAntiFeatureJoinTableName()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String where = ApkTable.NAME + "." + Cols.REPO_ID + " != ?";
 | 
					        String where = ApkTable.NAME + "." + Cols.REPO_ID + " != ?";
 | 
				
			||||||
        String[] whereArgs = new String[]{Long.toString(repoIdBeingUpdated)};
 | 
					        String[] whereArgs = new String[]{Long.toString(repoIdBeingUpdated)};
 | 
				
			||||||
        db.execSQL(TempAppProvider.copyData(Cols.ALL_COLS, ApkTable.NAME, memoryDbName + "." + getTableName(), where), whereArgs);
 | 
					        db.execSQL(TempAppProvider.copyData(Cols.ALL_COLS, ApkTable.NAME, memoryDbName + "." + getTableName(), where), whereArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String antiFeaturesWhere =
 | 
				
			||||||
 | 
					                Schema.ApkAntiFeatureJoinTable.NAME + "." + Schema.ApkAntiFeatureJoinTable.Cols.APK_ID + " IN " +
 | 
				
			||||||
 | 
					                "(SELECT innerApk." + Cols.ROW_ID + " FROM " + ApkTable.NAME + " AS innerApk " +
 | 
				
			||||||
 | 
					                "WHERE innerApk." + Cols.REPO_ID + " != ?)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        db.execSQL(TempAppProvider.copyData(
 | 
				
			||||||
 | 
					                Schema.ApkAntiFeatureJoinTable.Cols.ALL_COLS,
 | 
				
			||||||
 | 
					                Schema.ApkAntiFeatureJoinTable.NAME,
 | 
				
			||||||
 | 
					                memoryDbName + "." + getApkAntiFeatureJoinTableName(),
 | 
				
			||||||
 | 
					                antiFeaturesWhere), whereArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_appId on " + getTableName() + " (" + Cols.APP_ID + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_appId on " + getTableName() + " (" + Cols.APP_ID + ");");
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + Cols.IS_COMPATIBLE + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (" + Cols.IS_COMPATIBLE + ");");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
    private static final String PROVIDER_NAME = "TempAppProvider";
 | 
					    private static final String PROVIDER_NAME = "TempAppProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String TABLE_TEMP_APP = "temp_" + AppMetadataTable.NAME;
 | 
					    static final String TABLE_TEMP_APP = "temp_" + AppMetadataTable.NAME;
 | 
				
			||||||
 | 
					    static final String TABLE_TEMP_APK_ANTI_FEATURE_JOIN = "temp_" + Schema.ApkAntiFeatureJoinTable.NAME;
 | 
				
			||||||
    static final String TABLE_TEMP_CAT_JOIN = "temp_" + CatJoinTable.NAME;
 | 
					    static final String TABLE_TEMP_CAT_JOIN = "temp_" + CatJoinTable.NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String PATH_INIT = "init";
 | 
					    private static final String PATH_INIT = "init";
 | 
				
			||||||
@ -125,6 +126,10 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
        return TempApkProvider.TABLE_TEMP_APK;
 | 
					        return TempApkProvider.TABLE_TEMP_APK;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected String getApkAntiFeatureJoinTableName() {
 | 
				
			||||||
 | 
					        return TempApkProvider.TABLE_TEMP_APK;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Uri insert(Uri uri, ContentValues values) {
 | 
					    public Uri insert(Uri uri, ContentValues values) {
 | 
				
			||||||
        switch (MATCHER.match(uri)) {
 | 
					        switch (MATCHER.match(uri)) {
 | 
				
			||||||
@ -218,6 +223,7 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
            final String tempApp = DB + "." + TABLE_TEMP_APP;
 | 
					            final String tempApp = DB + "." + TABLE_TEMP_APP;
 | 
				
			||||||
            final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK;
 | 
					            final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK;
 | 
				
			||||||
            final String tempCatJoin = DB + "." + TABLE_TEMP_CAT_JOIN;
 | 
					            final String tempCatJoin = DB + "." + TABLE_TEMP_CAT_JOIN;
 | 
				
			||||||
 | 
					            final String tempAntiFeatureJoin = DB + "." + TABLE_TEMP_APK_ANTI_FEATURE_JOIN;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final String[] repoArgs = new String[]{Long.toString(repoIdToCommit)};
 | 
					            final String[] repoArgs = new String[]{Long.toString(repoIdToCommit)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -230,6 +236,16 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
            db.execSQL("DELETE FROM " + CatJoinTable.NAME + " WHERE " + getCatRepoWhere(CatJoinTable.NAME), repoArgs);
 | 
					            db.execSQL("DELETE FROM " + CatJoinTable.NAME + " WHERE " + getCatRepoWhere(CatJoinTable.NAME), repoArgs);
 | 
				
			||||||
            db.execSQL(copyData(CatJoinTable.Cols.ALL_COLS, tempCatJoin, CatJoinTable.NAME, getCatRepoWhere(tempCatJoin)), repoArgs);
 | 
					            db.execSQL(copyData(CatJoinTable.Cols.ALL_COLS, tempCatJoin, CatJoinTable.NAME, getCatRepoWhere(tempCatJoin)), repoArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            db.execSQL(
 | 
				
			||||||
 | 
					                    "DELETE FROM " + Schema.ApkAntiFeatureJoinTable.NAME + " " +
 | 
				
			||||||
 | 
					                    "WHERE " + getAntiFeatureRepoWhere(Schema.ApkAntiFeatureJoinTable.NAME), repoArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            db.execSQL(copyData(
 | 
				
			||||||
 | 
					                    Schema.ApkAntiFeatureJoinTable.Cols.ALL_COLS,
 | 
				
			||||||
 | 
					                    tempAntiFeatureJoin,
 | 
				
			||||||
 | 
					                    Schema.ApkAntiFeatureJoinTable.NAME,
 | 
				
			||||||
 | 
					                    getAntiFeatureRepoWhere(tempAntiFeatureJoin)), repoArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            db.setTransactionSuccessful();
 | 
					            db.setTransactionSuccessful();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
 | 
					            getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
 | 
				
			||||||
@ -250,4 +266,13 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return CatJoinTable.Cols.ROW_ID + " IN (" + catRepoSubquery + ")";
 | 
					        return CatJoinTable.Cols.ROW_ID + " IN (" + catRepoSubquery + ")";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String getAntiFeatureRepoWhere(String antiFeatureTable) {
 | 
				
			||||||
 | 
					        String subquery =
 | 
				
			||||||
 | 
					                "SELECT innerApk." + ApkTable.Cols.ROW_ID + " " +
 | 
				
			||||||
 | 
					                "FROM " + ApkTable.NAME + " AS innerApk " +
 | 
				
			||||||
 | 
					                "WHERE innerApk." + ApkTable.Cols.REPO_ID + " = ?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return antiFeatureTable + "." + Schema.ApkAntiFeatureJoinTable.Cols.APK_ID + " IN (" + subquery + ")";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -93,6 +93,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
 | 
				
			|||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
    private final Button actionButton;
 | 
					    private final Button actionButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable
 | 
				
			||||||
 | 
					    private final Button secondaryButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final DisplayImageOptions displayImageOptions;
 | 
					    private final DisplayImageOptions displayImageOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
@ -137,11 +140,16 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
 | 
				
			|||||||
        progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
 | 
					        progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
 | 
				
			||||||
        cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
 | 
					        cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
 | 
				
			||||||
        actionButton = (Button) itemView.findViewById(R.id.action_button);
 | 
					        actionButton = (Button) itemView.findViewById(R.id.action_button);
 | 
				
			||||||
 | 
					        secondaryButton = (Button) itemView.findViewById(R.id.secondary_button);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (actionButton != null) {
 | 
					        if (actionButton != null) {
 | 
				
			||||||
            actionButton.setOnClickListener(onActionClicked);
 | 
					            actionButton.setOnClickListener(onActionClicked);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (secondaryButton != null) {
 | 
				
			||||||
 | 
					            secondaryButton.setOnClickListener(onSecondaryButtonClicked);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (cancelButton != null) {
 | 
					        if (cancelButton != null) {
 | 
				
			||||||
            cancelButton.setOnClickListener(onCancelDownload);
 | 
					            cancelButton.setOnClickListener(onCancelDownload);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -213,6 +221,15 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (secondaryButton != null) {
 | 
				
			||||||
 | 
					            if (viewState.shouldShowSecondaryButton()) {
 | 
				
			||||||
 | 
					                secondaryButton.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					                secondaryButton.setText(viewState.getSecondaryButtonText());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                secondaryButton.setVisibility(View.GONE);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (progressBar != null) {
 | 
					        if (progressBar != null) {
 | 
				
			||||||
            if (viewState.showProgress()) {
 | 
					            if (viewState.showProgress()) {
 | 
				
			||||||
                progressBar.setVisibility(View.VISIBLE);
 | 
					                progressBar.setVisibility(View.VISIBLE);
 | 
				
			||||||
@ -388,55 +405,74 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // When the button says "Run", then launch the app.
 | 
					            onActionButtonPressed(currentApp);
 | 
				
			||||||
            if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
 | 
					        }
 | 
				
			||||||
                Intent intent = activity.getPackageManager().getLaunchIntentForPackage(currentApp.packageName);
 | 
					    };
 | 
				
			||||||
                if (intent != null) {
 | 
					 | 
				
			||||||
                    activity.startActivity(intent);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // Once it is explicitly launched by the user, then we can pretty much forget about
 | 
					    @SuppressWarnings("FieldCanBeLocal")
 | 
				
			||||||
                    // any sort of notification that the app was successfully installed. It should be
 | 
					    private final View.OnClickListener onSecondaryButtonClicked = new View.OnClickListener() {
 | 
				
			||||||
                    // apparent to the user because they just launched it.
 | 
					        @Override
 | 
				
			||||||
                    AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey());
 | 
					        public void onClick(View v) {
 | 
				
			||||||
                }
 | 
					            if (currentApp == null) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
 | 
					            onSecondaryButtonPressed(currentApp);
 | 
				
			||||||
                Uri apkDownloadUri = Uri.parse(currentStatus.apk.getUrl());
 | 
					 | 
				
			||||||
                File apkFilePath = ApkCache.getApkDownloadPath(activity, apkDownloadUri);
 | 
					 | 
				
			||||||
                Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() +
 | 
					 | 
				
			||||||
                        " to " + apkFilePath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // TODO: This seems like a bit of a hack. Is there a better way to do this by changing
 | 
					 | 
				
			||||||
                // the Installer API so that we can ask it to install without having to get it to fire
 | 
					 | 
				
			||||||
                // off an intent which we then listen for and action?
 | 
					 | 
				
			||||||
                final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity);
 | 
					 | 
				
			||||||
                final BroadcastReceiver receiver = new BroadcastReceiver() {
 | 
					 | 
				
			||||||
                    @Override
 | 
					 | 
				
			||||||
                    public void onReceive(Context context, Intent intent) {
 | 
					 | 
				
			||||||
                        broadcastManager.unregisterReceiver(this);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) {
 | 
					 | 
				
			||||||
                            PendingIntent pendingIntent =
 | 
					 | 
				
			||||||
                                    intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
 | 
					 | 
				
			||||||
                            try {
 | 
					 | 
				
			||||||
                                pendingIntent.send();
 | 
					 | 
				
			||||||
                            } catch (PendingIntent.CanceledException ignored) { }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri));
 | 
					 | 
				
			||||||
                Installer installer = InstallerFactory.create(activity, currentStatus.apk);
 | 
					 | 
				
			||||||
                installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, currentApp);
 | 
					 | 
				
			||||||
                InstallManagerService.queue(activity, currentApp, suggestedApk);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected void onActionButtonPressed(@NonNull App app) {
 | 
				
			||||||
 | 
					        // When the button says "Run", then launch the app.
 | 
				
			||||||
 | 
					        if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
 | 
				
			||||||
 | 
					            Intent intent = activity.getPackageManager().getLaunchIntentForPackage(app.packageName);
 | 
				
			||||||
 | 
					            if (intent != null) {
 | 
				
			||||||
 | 
					                activity.startActivity(intent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Once it is explicitly launched by the user, then we can pretty much forget about
 | 
				
			||||||
 | 
					                // any sort of notification that the app was successfully installed. It should be
 | 
				
			||||||
 | 
					                // apparent to the user because they just launched it.
 | 
				
			||||||
 | 
					                AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
 | 
				
			||||||
 | 
					            Uri apkDownloadUri = Uri.parse(currentStatus.apk.getUrl());
 | 
				
			||||||
 | 
					            File apkFilePath = ApkCache.getApkDownloadPath(activity, apkDownloadUri);
 | 
				
			||||||
 | 
					            Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() +
 | 
				
			||||||
 | 
					                    " to " + apkFilePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // TODO: This seems like a bit of a hack. Is there a better way to do this by changing
 | 
				
			||||||
 | 
					            // the Installer API so that we can ask it to install without having to get it to fire
 | 
				
			||||||
 | 
					            // off an intent which we then listen for and action?
 | 
				
			||||||
 | 
					            final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity);
 | 
				
			||||||
 | 
					            final BroadcastReceiver receiver = new BroadcastReceiver() {
 | 
				
			||||||
 | 
					                @Override
 | 
				
			||||||
 | 
					                public void onReceive(Context context, Intent intent) {
 | 
				
			||||||
 | 
					                    broadcastManager.unregisterReceiver(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) {
 | 
				
			||||||
 | 
					                        PendingIntent pendingIntent =
 | 
				
			||||||
 | 
					                                intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
 | 
					                            pendingIntent.send();
 | 
				
			||||||
 | 
					                        } catch (PendingIntent.CanceledException ignored) { }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri));
 | 
				
			||||||
 | 
					            Installer installer = InstallerFactory.create(activity, currentStatus.apk);
 | 
				
			||||||
 | 
					            installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app);
 | 
				
			||||||
 | 
					            InstallManagerService.queue(activity, app, suggestedApk);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** To be overridden by subclasses if desired */
 | 
				
			||||||
 | 
					    protected void onSecondaryButtonPressed(@NonNull App app) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @SuppressWarnings("FieldCanBeLocal")
 | 
					    @SuppressWarnings("FieldCanBeLocal")
 | 
				
			||||||
    private final View.OnClickListener onCancelDownload = new View.OnClickListener() {
 | 
					    private final View.OnClickListener onCancelDownload = new View.OnClickListener() {
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ public class AppListItemState {
 | 
				
			|||||||
    private final App app;
 | 
					    private final App app;
 | 
				
			||||||
    private CharSequence mainText = null;
 | 
					    private CharSequence mainText = null;
 | 
				
			||||||
    private CharSequence actionButtonText = null;
 | 
					    private CharSequence actionButtonText = null;
 | 
				
			||||||
 | 
					    private CharSequence secondaryButtonText = null;
 | 
				
			||||||
    private CharSequence statusText = null;
 | 
					    private CharSequence statusText = null;
 | 
				
			||||||
    private CharSequence secondaryStatusText = null;
 | 
					    private CharSequence secondaryStatusText = null;
 | 
				
			||||||
    private int progressCurrent = -1;
 | 
					    private int progressCurrent = -1;
 | 
				
			||||||
@ -34,6 +35,11 @@ public class AppListItemState {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public AppListItemState showSecondaryButton(CharSequence label) {
 | 
				
			||||||
 | 
					        secondaryButtonText = label;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public AppListItemState setStatusText(CharSequence text) {
 | 
					    public AppListItemState setStatusText(CharSequence text) {
 | 
				
			||||||
        this.statusText = text;
 | 
					        this.statusText = text;
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
@ -74,6 +80,14 @@ public class AppListItemState {
 | 
				
			|||||||
        return actionButtonText;
 | 
					        return actionButtonText;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean shouldShowSecondaryButton() {
 | 
				
			||||||
 | 
					        return secondaryButtonText != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CharSequence getSecondaryButtonText() {
 | 
				
			||||||
 | 
					        return secondaryButtonText;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean showProgress() {
 | 
					    public boolean showProgress() {
 | 
				
			||||||
        return progressCurrent >= 0;
 | 
					        return progressCurrent >= 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import android.content.Context;
 | 
				
			|||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
import android.content.IntentFilter;
 | 
					import android.content.IntentFilter;
 | 
				
			||||||
import android.database.Cursor;
 | 
					import android.database.Cursor;
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.support.v4.app.LoaderManager;
 | 
					import android.support.v4.app.LoaderManager;
 | 
				
			||||||
import android.support.v4.content.CursorLoader;
 | 
					import android.support.v4.content.CursorLoader;
 | 
				
			||||||
@ -20,6 +21,7 @@ import org.fdroid.fdroid.data.AppProvider;
 | 
				
			|||||||
import org.fdroid.fdroid.data.Schema;
 | 
					import org.fdroid.fdroid.data.Schema;
 | 
				
			||||||
import org.fdroid.fdroid.views.updates.items.AppStatus;
 | 
					import org.fdroid.fdroid.views.updates.items.AppStatus;
 | 
				
			||||||
import org.fdroid.fdroid.views.updates.items.AppUpdateData;
 | 
					import org.fdroid.fdroid.views.updates.items.AppUpdateData;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.views.updates.items.KnownVulnApp;
 | 
				
			||||||
import org.fdroid.fdroid.views.updates.items.UpdateableApp;
 | 
					import org.fdroid.fdroid.views.updates.items.UpdateableApp;
 | 
				
			||||||
import org.fdroid.fdroid.views.updates.items.UpdateableAppsHeader;
 | 
					import org.fdroid.fdroid.views.updates.items.UpdateableAppsHeader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,6 +67,9 @@ import java.util.Set;
 | 
				
			|||||||
public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
 | 
					public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
 | 
				
			||||||
        implements LoaderManager.LoaderCallbacks<Cursor> {
 | 
					        implements LoaderManager.LoaderCallbacks<Cursor> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final int LOADER_CAN_UPDATE = 289753982;
 | 
				
			||||||
 | 
					    private static final int LOADER_KNOWN_VULN = 520389740;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final AdapterDelegatesManager<List<AppUpdateData>> delegatesManager = new AdapterDelegatesManager<>();
 | 
					    private final AdapterDelegatesManager<List<AppUpdateData>> delegatesManager = new AdapterDelegatesManager<>();
 | 
				
			||||||
    private final List<AppUpdateData> items = new ArrayList<>();
 | 
					    private final List<AppUpdateData> items = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -72,6 +77,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final List<AppStatus> appsToShowStatus = new ArrayList<>();
 | 
					    private final List<AppStatus> appsToShowStatus = new ArrayList<>();
 | 
				
			||||||
    private final List<UpdateableApp> updateableApps = new ArrayList<>();
 | 
					    private final List<UpdateableApp> updateableApps = new ArrayList<>();
 | 
				
			||||||
 | 
					    private final List<KnownVulnApp> knownVulnApps = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean showAllUpdateableApps = false;
 | 
					    private boolean showAllUpdateableApps = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,9 +86,11 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        delegatesManager.addDelegate(new AppStatus.Delegate(activity))
 | 
					        delegatesManager.addDelegate(new AppStatus.Delegate(activity))
 | 
				
			||||||
                .addDelegate(new UpdateableApp.Delegate(activity))
 | 
					                .addDelegate(new UpdateableApp.Delegate(activity))
 | 
				
			||||||
                .addDelegate(new UpdateableAppsHeader.Delegate(activity));
 | 
					                .addDelegate(new UpdateableAppsHeader.Delegate(activity))
 | 
				
			||||||
 | 
					                .addDelegate(new KnownVulnApp.Delegate(activity));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        activity.getSupportLoaderManager().initLoader(0, null, this);
 | 
					        activity.getSupportLoaderManager().initLoader(LOADER_CAN_UPDATE, null, this);
 | 
				
			||||||
 | 
					        activity.getSupportLoaderManager().initLoader(LOADER_KNOWN_VULN, null, this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -162,6 +170,10 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (KnownVulnApp app : knownVulnApps) {
 | 
				
			||||||
 | 
					            items.add(app);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -186,33 +198,41 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
 | 
					    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
 | 
				
			||||||
 | 
					        Uri uri;
 | 
				
			||||||
 | 
					        switch (id) {
 | 
				
			||||||
 | 
					            case LOADER_CAN_UPDATE:
 | 
				
			||||||
 | 
					                uri = AppProvider.getCanUpdateUri();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case LOADER_KNOWN_VULN:
 | 
				
			||||||
 | 
					                uri = AppProvider.getInstalledWithKnownVulnsUri();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                throw new IllegalStateException("Unknown loader requested: " + id);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new CursorLoader(
 | 
					        return new CursorLoader(
 | 
				
			||||||
                activity,
 | 
					                activity, uri, Schema.AppMetadataTable.Cols.ALL, null, null, Schema.AppMetadataTable.Cols.NAME);
 | 
				
			||||||
                AppProvider.getCanUpdateUri(),
 | 
					 | 
				
			||||||
                new String[]{
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols._ID, // Required for cursor loader to work.
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.NAME,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.SUMMARY,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.IS_COMPATIBLE,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.LICENSE,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.ICON,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.ICON_URL,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.InstalledApp.VERSION_CODE,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.InstalledApp.VERSION_NAME,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.SuggestedApk.VERSION_NAME,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.SUGGESTED_VERSION_CODE,
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.REQUIREMENTS, // Needed for filtering apps that require root.
 | 
					 | 
				
			||||||
                        Schema.AppMetadataTable.Cols.ANTI_FEATURES, // Needed for filtering apps that require anti-features.
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                null,
 | 
					 | 
				
			||||||
                null,
 | 
					 | 
				
			||||||
                Schema.AppMetadataTable.Cols.NAME
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
 | 
					    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
 | 
				
			||||||
 | 
					        switch (loader.getId()) {
 | 
				
			||||||
 | 
					            case LOADER_CAN_UPDATE:
 | 
				
			||||||
 | 
					                onCanUpdateLoadFinished(cursor);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case LOADER_KNOWN_VULN:
 | 
				
			||||||
 | 
					                onKnownVulnLoadFinished(cursor);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        populateItems();
 | 
				
			||||||
 | 
					        notifyDataSetChanged();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void onCanUpdateLoadFinished(Cursor cursor) {
 | 
				
			||||||
        updateableApps.clear();
 | 
					        updateableApps.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cursor.moveToFirst();
 | 
					        cursor.moveToFirst();
 | 
				
			||||||
@ -220,9 +240,16 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
 | 
				
			|||||||
            updateableApps.add(new UpdateableApp(activity, new App(cursor)));
 | 
					            updateableApps.add(new UpdateableApp(activity, new App(cursor)));
 | 
				
			||||||
            cursor.moveToNext();
 | 
					            cursor.moveToNext();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        populateItems();
 | 
					    private void onKnownVulnLoadFinished(Cursor cursor) {
 | 
				
			||||||
        notifyDataSetChanged();
 | 
					        knownVulnApps.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cursor.moveToFirst();
 | 
				
			||||||
 | 
					        while (!cursor.isAfterLast()) {
 | 
				
			||||||
 | 
					            knownVulnApps.add(new KnownVulnApp(activity, new App(cursor)));
 | 
				
			||||||
 | 
					            cursor.moveToNext();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.views.updates.items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Activity;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.v7.widget.RecyclerView;
 | 
				
			||||||
 | 
					import android.view.ViewGroup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * List of all apps which can be updated, but have not yet been downloaded.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see KnownVulnApp The data that is bound to this view.
 | 
				
			||||||
 | 
					 * @see R.layout#known_vuln_app_list_item The view that this binds to.
 | 
				
			||||||
 | 
					 * @see KnownVulnAppListItemController Used for binding the {@link App} to
 | 
				
			||||||
 | 
					 *      the {@link R.layout#known_vuln_app_list_item}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class KnownVulnApp extends AppUpdateData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public final App app;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public KnownVulnApp(Activity activity, App app) {
 | 
				
			||||||
 | 
					        super(activity);
 | 
				
			||||||
 | 
					        this.app = app;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static class Delegate extends AdapterDelegate<List<AppUpdateData>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final Activity activity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Delegate(Activity activity) {
 | 
				
			||||||
 | 
					            this.activity = activity;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected boolean isForViewType(@NonNull List<AppUpdateData> items, int position) {
 | 
				
			||||||
 | 
					            return items.get(position) instanceof KnownVulnApp;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @NonNull
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
 | 
				
			||||||
 | 
					            return new KnownVulnAppListItemController(activity, activity.getLayoutInflater()
 | 
				
			||||||
 | 
					                    .inflate(R.layout.known_vuln_app_list_item, parent, false));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position,
 | 
				
			||||||
 | 
					                                        @NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
 | 
				
			||||||
 | 
					            KnownVulnApp app = (KnownVulnApp) items.get(position);
 | 
				
			||||||
 | 
					            ((KnownVulnAppListItemController) holder).bindModel(app.app);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.views.updates.items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Activity;
 | 
				
			||||||
 | 
					import android.app.PendingIntent;
 | 
				
			||||||
 | 
					import android.content.BroadcastReceiver;
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.content.Intent;
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
 | 
					import android.support.v4.content.LocalBroadcastManager;
 | 
				
			||||||
 | 
					import android.view.View;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.AppUpdateStatusManager;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Apk;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.ApkProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppPrefs;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppPrefsProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.installer.InstallManagerService;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.installer.Installer;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.installer.InstallerService;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.views.apps.AppListItemController;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.views.apps.AppListItemState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Tell the user that an app they have installed has a known vulnerability.
 | 
				
			||||||
 | 
					 * The role of this controller is to prompt the user what it is that should be done in response to this
 | 
				
			||||||
 | 
					 * (e.g. uninstall, update, disable).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class KnownVulnAppListItemController extends AppListItemController {
 | 
				
			||||||
 | 
					    public KnownVulnAppListItemController(Activity activity, View itemView) {
 | 
				
			||||||
 | 
					        super(activity, itemView);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected AppListItemState getCurrentViewState(
 | 
				
			||||||
 | 
					            @NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
 | 
				
			||||||
 | 
					        String mainText;
 | 
				
			||||||
 | 
					        String actionButtonText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app);
 | 
				
			||||||
 | 
					        if (shouldUpgradeInsteadOfUninstall(app, suggestedApk)) {
 | 
				
			||||||
 | 
					            mainText = activity.getString(R.string.updates__app_with_known_vulnerability__prompt_upgrade, app.name);
 | 
				
			||||||
 | 
					            actionButtonText = activity.getString(R.string.menu_upgrade);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            mainText = activity.getString(R.string.updates__app_with_known_vulnerability__prompt_uninstall, app.name);
 | 
				
			||||||
 | 
					            actionButtonText = activity.getString(R.string.menu_uninstall);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new AppListItemState(app)
 | 
				
			||||||
 | 
					                .setMainText(mainText)
 | 
				
			||||||
 | 
					                .showActionButton(actionButtonText)
 | 
				
			||||||
 | 
					                .showSecondaryButton(activity.getString(R.string.updates__app_with_known_vulnerability__ignore));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private boolean shouldUpgradeInsteadOfUninstall(@NonNull App app, @Nullable Apk suggestedApk) {
 | 
				
			||||||
 | 
					        return suggestedApk != null && app.installedVersionCode < suggestedApk.versionCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected void onActionButtonPressed(@NonNull App app) {
 | 
				
			||||||
 | 
					        Apk installedApk = app.getInstalledApk(activity);
 | 
				
			||||||
 | 
					        if (installedApk == null) {
 | 
				
			||||||
 | 
					            throw new IllegalStateException(
 | 
				
			||||||
 | 
					                    "Tried to upgrade or uninstall app with known vulnerability but it doesn't seem to be installed");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app);
 | 
				
			||||||
 | 
					        if (shouldUpgradeInsteadOfUninstall(app, suggestedApk)) {
 | 
				
			||||||
 | 
					            LocalBroadcastManager manager = LocalBroadcastManager.getInstance(activity);
 | 
				
			||||||
 | 
					            Uri uri = Uri.parse(suggestedApk.getUrl());
 | 
				
			||||||
 | 
					            manager.registerReceiver(installReceiver, Installer.getInstallIntentFilter(uri));
 | 
				
			||||||
 | 
					            InstallManagerService.queue(activity, app, suggestedApk);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            LocalBroadcastManager manager = LocalBroadcastManager.getInstance(activity);
 | 
				
			||||||
 | 
					            manager.registerReceiver(installReceiver, Installer.getUninstallIntentFilter(app.packageName));
 | 
				
			||||||
 | 
					            InstallerService.uninstall(activity, installedApk);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected void onSecondaryButtonPressed(@NonNull App app) {
 | 
				
			||||||
 | 
					        AppPrefs prefs = app.getPrefs(activity);
 | 
				
			||||||
 | 
					        prefs.ignoreVulnerabilities = true;
 | 
				
			||||||
 | 
					        AppPrefsProvider.Helper.update(activity, app, prefs);
 | 
				
			||||||
 | 
					        refreshUpdatesList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void unregisterInstallReceiver() {
 | 
				
			||||||
 | 
					        LocalBroadcastManager.getInstance(activity).unregisterReceiver(installReceiver);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Trigger the LoaderManager in UpdatesAdapter to automatically requery for the list of
 | 
				
			||||||
 | 
					     * apps with known vulnerabilities (i.e. this app should no longer be in that list).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void refreshUpdatesList() {
 | 
				
			||||||
 | 
					        activity.getContentResolver().notifyChange(AppProvider.getInstalledWithKnownVulnsUri(), null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final BroadcastReceiver installReceiver = new BroadcastReceiver() {
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void onReceive(Context context, Intent intent) {
 | 
				
			||||||
 | 
					            switch (intent.getAction()) {
 | 
				
			||||||
 | 
					                case Installer.ACTION_INSTALL_COMPLETE:
 | 
				
			||||||
 | 
					                case Installer.ACTION_UNINSTALL_COMPLETE:
 | 
				
			||||||
 | 
					                    refreshUpdatesList();
 | 
				
			||||||
 | 
					                    unregisterInstallReceiver();
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                case Installer.ACTION_INSTALL_INTERRUPTED:
 | 
				
			||||||
 | 
					                case Installer.ACTION_UNINSTALL_INTERRUPTED:
 | 
				
			||||||
 | 
					                    unregisterInstallReceiver();
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                case Installer.ACTION_INSTALL_USER_INTERACTION:
 | 
				
			||||||
 | 
					                case Installer.ACTION_UNINSTALL_USER_INTERACTION:
 | 
				
			||||||
 | 
					                    PendingIntent uninstallPendingIntent =
 | 
				
			||||||
 | 
					                            intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        uninstallPendingIntent.send();
 | 
				
			||||||
 | 
					                    } catch (PendingIntent.CanceledException ignored) { }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								app/src/main/res/drawable/ic_known_vuln_overlay.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/src/main/res/drawable/ic_known_vuln_overlay.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<vector android:height="24dp" android:viewportHeight="12.7"
 | 
				
			||||||
 | 
					    android:viewportWidth="12.7" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
 | 
				
			||||||
 | 
					    <path android:fillAlpha="1" android:fillColor="#ffffff"
 | 
				
			||||||
 | 
					        android:pathData="M6.35,6.35m-6.35,0a6.35,6.35 0,1 1,12.7 0a6.35,6.35 0,1 1,-12.7 0"
 | 
				
			||||||
 | 
					        android:strokeAlpha="1" android:strokeColor="#00000000"
 | 
				
			||||||
 | 
					        android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
 | 
				
			||||||
 | 
					    <path android:fillAlpha="1" android:fillColor="#ff3600"
 | 
				
			||||||
 | 
					        android:pathData="M6.35,6.35m-5.33,0a5.33,5.33 0,1 1,10.661 0a5.33,5.33 0,1 1,-10.661 0"
 | 
				
			||||||
 | 
					        android:strokeAlpha="1" android:strokeColor="#00000000"
 | 
				
			||||||
 | 
					        android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
 | 
				
			||||||
 | 
					    <path android:fillAlpha="1" android:fillColor="#ffffff"
 | 
				
			||||||
 | 
					        android:pathData="M5.863,3.778h0.904v3.082h-0.904z"
 | 
				
			||||||
 | 
					        android:strokeAlpha="1" android:strokeColor="#00000000"
 | 
				
			||||||
 | 
					        android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
 | 
				
			||||||
 | 
					    <path android:fillAlpha="1" android:fillColor="#ffffff"
 | 
				
			||||||
 | 
					        android:pathData="M5.863,8.899l0.904,0l0,-0.973l-0.904,0z"
 | 
				
			||||||
 | 
					        android:strokeAlpha="1" android:strokeColor="#00000000"
 | 
				
			||||||
 | 
					        android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
 | 
				
			||||||
 | 
					</vector>
 | 
				
			||||||
							
								
								
									
										78
									
								
								app/src/main/res/layout/known_vuln_app_list_item.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/src/main/res/layout/known_vuln_app_list_item.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<android.support.constraint.ConstraintLayout
 | 
				
			||||||
 | 
					    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
 | 
					    xmlns:tools="http://schemas.android.com/tools"
 | 
				
			||||||
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
 | 
					    android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					    android:paddingBottom="8dp"
 | 
				
			||||||
 | 
					    android:clipToPadding="false">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Ignore ContentDescription because it is kind of meaningless to have TTS read out "App icon"
 | 
				
			||||||
 | 
					         when it will inevitably read out the name of the app straight after. -->
 | 
				
			||||||
 | 
					    <ImageView
 | 
				
			||||||
 | 
					        android:id="@+id/icon"
 | 
				
			||||||
 | 
					        app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					        android:layout_width="48dp"
 | 
				
			||||||
 | 
					        android:layout_height="48dp"
 | 
				
			||||||
 | 
					        tools:src="@drawable/ic_launcher"
 | 
				
			||||||
 | 
					        android:scaleType="fitCenter"
 | 
				
			||||||
 | 
					        android:layout_marginStart="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginLeft="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        tools:ignore="ContentDescription" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ImageView
 | 
				
			||||||
 | 
					        app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					        android:layout_width="24dp"
 | 
				
			||||||
 | 
					        android:layout_height="24dp"
 | 
				
			||||||
 | 
					        android:layout_marginStart="48dp"
 | 
				
			||||||
 | 
					        android:layout_marginLeft="48dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="40dp"
 | 
				
			||||||
 | 
					        android:src="@drawable/ic_known_vuln_overlay"
 | 
				
			||||||
 | 
					        android:scaleType="fitCenter"
 | 
				
			||||||
 | 
					        tools:ignore="ContentDescription"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <TextView
 | 
				
			||||||
 | 
					        android:id="@+id/app_name"
 | 
				
			||||||
 | 
					        android:layout_width="0dp"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        tools:text="We found a vulnerability with VulnApp. We recommend uninstalling this app immediately."
 | 
				
			||||||
 | 
					        android:textSize="16sp"
 | 
				
			||||||
 | 
					        android:textColor="?attr/installedApps"
 | 
				
			||||||
 | 
					        android:ellipsize="end"
 | 
				
			||||||
 | 
					        app:layout_constraintStart_toEndOf="@+id/icon"
 | 
				
			||||||
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
 | 
					        android:layout_marginStart="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginEnd="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        android:layout_marginRight="8dp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					        android:id="@+id/action_button"
 | 
				
			||||||
 | 
					        style="@style/DetailsPrimaryButtonStyleSmall"
 | 
				
			||||||
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_marginEnd="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginRight="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        app:layout_constraintEnd_toEndOf="parent"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toBottomOf="@+id/app_name"
 | 
				
			||||||
 | 
					        tools:text="Uninstall"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					        android:id="@+id/secondary_button"
 | 
				
			||||||
 | 
					        style="@style/DetailsSecondaryButtonStyleSmall"
 | 
				
			||||||
 | 
					        android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					        android:layout_marginEnd="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginRight="16dp"
 | 
				
			||||||
 | 
					        android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					        app:layout_constraintEnd_toStartOf="@+id/action_button"
 | 
				
			||||||
 | 
					        app:layout_constraintTop_toBottomOf="@+id/app_name"
 | 
				
			||||||
 | 
					        tools:text="Ignore"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</android.support.constraint.ConstraintLayout>
 | 
				
			||||||
@ -97,9 +97,12 @@ This often occurs with apps installed via Google Play or other sources, if they
 | 
				
			|||||||
    <string name="updates__tts__download_app">Download</string>
 | 
					    <string name="updates__tts__download_app">Download</string>
 | 
				
			||||||
    <string name="updates__tts__download_updates_for_all_apps">Download all updates</string>
 | 
					    <string name="updates__tts__download_updates_for_all_apps">Download all updates</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="updates__app_with_known_vulnerability__prompt_uninstall">We found a vulnerability with %1$s. We recommend uninstalling this app immediately.</string>
 | 
				
			||||||
 | 
					    <string name="updates__app_with_known_vulnerability__prompt_upgrade">We found a vulnerability with %1$s. We recommend upgrading to the newest version immediately.</string>
 | 
				
			||||||
 | 
					    <string name="updates__app_with_known_vulnerability__ignore">Ignore</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="updates__hide_updateable_apps">Hide apps</string>
 | 
					    <string name="updates__hide_updateable_apps">Hide apps</string>
 | 
				
			||||||
    <string name="updates__show_updateable_apps">Show apps</string>
 | 
					    <string name="updates__show_updateable_apps">Show apps</string>
 | 
				
			||||||
 | 
					 | 
				
			||||||
    <plurals name="updates__download_updates_for_apps">
 | 
					    <plurals name="updates__download_updates_for_apps">
 | 
				
			||||||
        <item quantity="one">Download update for %1$d app.</item>
 | 
					        <item quantity="one">Download update for %1$d app.</item>
 | 
				
			||||||
        <item quantity="other">Download updates for %1$d apps.</item>
 | 
					        <item quantity="other">Download updates for %1$d apps.</item>
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,10 @@
 | 
				
			|||||||
        <item name="android:background">@drawable/button_secondary_background_selector</item>
 | 
					        <item name="android:background">@drawable/button_secondary_background_selector</item>
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style name="DetailsSecondaryButtonStyleSmall" parent="DetailsSecondaryButtonStyle">
 | 
				
			||||||
 | 
					        <item name="android:padding">8dp</item>
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <style name="DetailsMoreButtonStyle">
 | 
					    <style name="DetailsMoreButtonStyle">
 | 
				
			||||||
        <item name="android:padding">5dp</item>
 | 
					        <item name="android:padding">5dp</item>
 | 
				
			||||||
        <item name="android:textSize">15sp</item>
 | 
					        <item name="android:textSize">15sp</item>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										149
									
								
								app/src/test/java/org/fdroid/fdroid/AntiFeaturesTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								app/src/test/java/org/fdroid/fdroid/AntiFeaturesTest.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Application;
 | 
				
			||||||
 | 
					import android.content.ContentValues;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Apk;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.ApkProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppPrefs;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppPrefsProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.AppProvider;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.FDroidProviderTest;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.InstalledAppTestUtils;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema;
 | 
				
			||||||
 | 
					import org.junit.After;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.junit.runner.RunWith;
 | 
				
			||||||
 | 
					import org.robolectric.RobolectricTestRunner;
 | 
				
			||||||
 | 
					import org.robolectric.annotation.Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertNull;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertArrayEquals;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
 | 
				
			||||||
 | 
					@RunWith(RobolectricTestRunner.class)
 | 
				
			||||||
 | 
					public class AntiFeaturesTest extends FDroidProviderTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private App notVuln;
 | 
				
			||||||
 | 
					    private App allVuln;
 | 
				
			||||||
 | 
					    private App vulnAtV2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Before
 | 
				
			||||||
 | 
					    public void setup() {
 | 
				
			||||||
 | 
					        Preferences.setup(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ContentValues vulnValues = new ContentValues(1);
 | 
				
			||||||
 | 
					        vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
 | 
				
			||||||
 | 
					        insertApk(vulnAtV2, 1, false);
 | 
				
			||||||
 | 
					        insertApk(vulnAtV2, 2, true);
 | 
				
			||||||
 | 
					        insertApk(vulnAtV2, 3, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
 | 
				
			||||||
 | 
					        insertApk(notVuln, 5, false);
 | 
				
			||||||
 | 
					        insertApk(notVuln, 10, false);
 | 
				
			||||||
 | 
					        insertApk(notVuln, 15, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
 | 
				
			||||||
 | 
					        insertApk(allVuln, 100, true);
 | 
				
			||||||
 | 
					        insertApk(allVuln, 101, true);
 | 
				
			||||||
 | 
					        insertApk(allVuln, 105, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        AppProvider.Helper.recalculatePreferredMetadata(context);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @After
 | 
				
			||||||
 | 
					    public void tearDown() {
 | 
				
			||||||
 | 
					        Preferences.clearSingletonForTesting();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static String generateHash(String packageName, int versionCode) {
 | 
				
			||||||
 | 
					        return packageName + "-" + versionCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void insertApk(App app, int versionCode, boolean isVuln) {
 | 
				
			||||||
 | 
					        ContentValues values = new ContentValues();
 | 
				
			||||||
 | 
					        values.put(Schema.ApkTable.Cols.HASH, generateHash(app.packageName, versionCode));
 | 
				
			||||||
 | 
					        if (isVuln) {
 | 
				
			||||||
 | 
					            values.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Assert.insertApk(context, app, versionCode, values);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void install(App app, int versionCode) {
 | 
				
			||||||
 | 
					        String hash = generateHash(app.packageName, versionCode);
 | 
				
			||||||
 | 
					        InstalledAppTestUtils.install(context, app.packageName, versionCode, "v" + versionCode, null, hash);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void noVulnerableApps() {
 | 
				
			||||||
 | 
					        List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(0, installed.size());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void futureVersionIsVulnerable() {
 | 
				
			||||||
 | 
					        install(vulnAtV2, 1);
 | 
				
			||||||
 | 
					        List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(0, installed.size());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void vulnerableAndAbleToBeUpdated() {
 | 
				
			||||||
 | 
					        install(vulnAtV2, 2);
 | 
				
			||||||
 | 
					        List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(1, installed.size());
 | 
				
			||||||
 | 
					        assertEquals(vulnAtV2.packageName, installed.get(0).packageName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void vulnerableButUpToDate() {
 | 
				
			||||||
 | 
					        install(vulnAtV2, 3);
 | 
				
			||||||
 | 
					        List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(0, installed.size());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void allVulnerableButIgnored() {
 | 
				
			||||||
 | 
					        install(allVuln, 101);
 | 
				
			||||||
 | 
					        List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(1, installed.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        App app = installed.get(0);
 | 
				
			||||||
 | 
					        AppPrefs prefs = app.getPrefs(context);
 | 
				
			||||||
 | 
					        prefs.ignoreVulnerabilities = true;
 | 
				
			||||||
 | 
					        AppPrefsProvider.Helper.update(context, app, prefs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<App> installedButIgnored = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
 | 
				
			||||||
 | 
					        assertEquals(0, installedButIgnored.size());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void antiFeaturesSaveCorrectly() {
 | 
				
			||||||
 | 
					        List<Apk> notVulnApks = ApkProvider.Helper.findByPackageName(context, notVuln.packageName);
 | 
				
			||||||
 | 
					        assertEquals(3, notVulnApks.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<Apk> allVulnApks = ApkProvider.Helper.findByPackageName(context, allVuln.packageName);
 | 
				
			||||||
 | 
					        assertEquals(3, allVulnApks.size());
 | 
				
			||||||
 | 
					        for (Apk apk : allVulnApks) {
 | 
				
			||||||
 | 
					            assertArrayEquals(new String[]{"KnownVuln", "ContainsGreenButtons"}, apk.antiFeatures);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<Apk> vulnAtV2Apks = ApkProvider.Helper.findByPackageName(context, vulnAtV2.packageName);
 | 
				
			||||||
 | 
					        assertEquals(3, vulnAtV2Apks.size());
 | 
				
			||||||
 | 
					        for (Apk apk : vulnAtV2Apks) {
 | 
				
			||||||
 | 
					            if (apk.versionCode == 2) {
 | 
				
			||||||
 | 
					                assertArrayEquals(new String[]{"KnownVuln", "ContainsGreenButtons"}, apk.antiFeatures);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                assertNull(apk.antiFeatures);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,6 +22,7 @@ import java.util.List;
 | 
				
			|||||||
import static org.fdroid.fdroid.Assert.assertCantDelete;
 | 
					import static org.fdroid.fdroid.Assert.assertCantDelete;
 | 
				
			||||||
import static org.fdroid.fdroid.Assert.assertResultCount;
 | 
					import static org.fdroid.fdroid.Assert.assertResultCount;
 | 
				
			||||||
import static org.fdroid.fdroid.Assert.insertApp;
 | 
					import static org.fdroid.fdroid.Assert.insertApp;
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertArrayEquals;
 | 
				
			||||||
import static org.junit.Assert.assertEquals;
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
import static org.junit.Assert.assertNotNull;
 | 
					import static org.junit.Assert.assertNotNull;
 | 
				
			||||||
import static org.junit.Assert.assertNull;
 | 
					import static org.junit.Assert.assertNull;
 | 
				
			||||||
@ -309,10 +310,12 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertEquals("com.example", apk.packageName);
 | 
					        assertEquals("com.example", apk.packageName);
 | 
				
			||||||
        assertEquals(10, apk.versionCode);
 | 
					        assertEquals(10, apk.versionCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assertNull(apk.antiFeatures);
 | 
				
			||||||
        assertNull(apk.features);
 | 
					        assertNull(apk.features);
 | 
				
			||||||
        assertNull(apk.added);
 | 
					        assertNull(apk.added);
 | 
				
			||||||
        assertNull(apk.hashType);
 | 
					        assertNull(apk.hashType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        apk.antiFeatures = new String[] {"KnownVuln", "Other anti feature"};
 | 
				
			||||||
        apk.features = new String[] {"one", "two", "three" };
 | 
					        apk.features = new String[] {"one", "two", "three" };
 | 
				
			||||||
        long dateTimestamp = System.currentTimeMillis();
 | 
					        long dateTimestamp = System.currentTimeMillis();
 | 
				
			||||||
        apk.added = new Date(dateTimestamp);
 | 
					        apk.added = new Date(dateTimestamp);
 | 
				
			||||||
@ -335,14 +338,8 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertEquals("com.example", updatedApk.packageName);
 | 
					        assertEquals("com.example", updatedApk.packageName);
 | 
				
			||||||
        assertEquals(10, updatedApk.versionCode);
 | 
					        assertEquals(10, updatedApk.versionCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertNotNull(updatedApk.features);
 | 
					        assertArrayEquals(new String[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
 | 
				
			||||||
        assertNotNull(updatedApk.added);
 | 
					        assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
 | 
				
			||||||
        assertNotNull(updatedApk.hashType);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        assertEquals(3, updatedApk.features.length);
 | 
					 | 
				
			||||||
        assertEquals("one", updatedApk.features[0]);
 | 
					 | 
				
			||||||
        assertEquals("two", updatedApk.features[1]);
 | 
					 | 
				
			||||||
        assertEquals("three", updatedApk.features[2]);
 | 
					 | 
				
			||||||
        assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
 | 
					        assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
 | 
				
			||||||
        assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
 | 
					        assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
 | 
				
			||||||
        assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
 | 
					        assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
 | 
				
			||||||
 | 
				
			|||||||
@ -28,16 +28,19 @@ public class AppPrefsProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
    @SuppressWarnings({"PMD.EqualsNull", "EqualsWithItself", "EqualsBetweenInconvertibleTypes", "ObjectEqualsNull"})
 | 
					    @SuppressWarnings({"PMD.EqualsNull", "EqualsWithItself", "EqualsBetweenInconvertibleTypes", "ObjectEqualsNull"})
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void prefEquality() {
 | 
					    public void prefEquality() {
 | 
				
			||||||
        AppPrefs original = new AppPrefs(101, true);
 | 
					        AppPrefs original = new AppPrefs(101, true, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertTrue(original.equals(new AppPrefs(101, true)));
 | 
					        assertTrue(original.equals(new AppPrefs(101, true, true)));
 | 
				
			||||||
        assertTrue(original.equals(original));
 | 
					        assertTrue(original.equals(original));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertFalse(original.equals(null));
 | 
					        assertFalse(original.equals(null));
 | 
				
			||||||
        assertFalse(original.equals("String"));
 | 
					        assertFalse(original.equals("String"));
 | 
				
			||||||
        assertFalse(original.equals(new AppPrefs(102, true)));
 | 
					        assertFalse(original.equals(new AppPrefs(102, true, true)));
 | 
				
			||||||
        assertFalse(original.equals(new AppPrefs(101, false)));
 | 
					        assertFalse(original.equals(new AppPrefs(101, false, true)));
 | 
				
			||||||
        assertFalse(original.equals(new AppPrefs(100, false)));
 | 
					        assertFalse(original.equals(new AppPrefs(100, false, true)));
 | 
				
			||||||
 | 
					        assertFalse(original.equals(new AppPrefs(102, true, false)));
 | 
				
			||||||
 | 
					        assertFalse(original.equals(new AppPrefs(101, false, false)));
 | 
				
			||||||
 | 
					        assertFalse(original.equals(new AppPrefs(100, false, false)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
@ -51,16 +54,19 @@ public class AppPrefsProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        AppPrefs defaultPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
					        AppPrefs defaultPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
				
			||||||
        assertEquals(0, defaultPrefs.ignoreThisUpdate);
 | 
					        assertEquals(0, defaultPrefs.ignoreThisUpdate);
 | 
				
			||||||
        assertFalse(defaultPrefs.ignoreAllUpdates);
 | 
					        assertFalse(defaultPrefs.ignoreAllUpdates);
 | 
				
			||||||
 | 
					        assertFalse(defaultPrefs.ignoreVulnerabilities);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(12, false));
 | 
					        AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(12, false, false));
 | 
				
			||||||
        AppPrefs newPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
					        AppPrefs newPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
				
			||||||
        assertEquals(12, newPrefs.ignoreThisUpdate);
 | 
					        assertEquals(12, newPrefs.ignoreThisUpdate);
 | 
				
			||||||
        assertFalse(newPrefs.ignoreAllUpdates);
 | 
					        assertFalse(newPrefs.ignoreAllUpdates);
 | 
				
			||||||
 | 
					        assertFalse(newPrefs.ignoreVulnerabilities);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(14, true));
 | 
					        AppPrefsProvider.Helper.update(context, withPrefs, new AppPrefs(14, true, true));
 | 
				
			||||||
        AppPrefs evenNewerPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
					        AppPrefs evenNewerPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
 | 
				
			||||||
        assertEquals(14, evenNewerPrefs.ignoreThisUpdate);
 | 
					        assertEquals(14, evenNewerPrefs.ignoreThisUpdate);
 | 
				
			||||||
        assertTrue(evenNewerPrefs.ignoreAllUpdates);
 | 
					        assertTrue(evenNewerPrefs.ignoreAllUpdates);
 | 
				
			||||||
 | 
					        assertTrue(evenNewerPrefs.ignoreVulnerabilities);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withoutPrefs));
 | 
					        assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withoutPrefs));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -97,7 +97,7 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
            String packageName, int installedVercode, int suggestedVercode,
 | 
					            String packageName, int installedVercode, int suggestedVercode,
 | 
				
			||||||
            boolean ignoreAll, int ignoreVercode) {
 | 
					            boolean ignoreAll, int ignoreVercode) {
 | 
				
			||||||
        App app = insertApp(contentResolver, context, packageName, "App: " + packageName, new ContentValues());
 | 
					        App app = insertApp(contentResolver, context, packageName, "App: " + packageName, new ContentValues());
 | 
				
			||||||
        AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll));
 | 
					        AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll, false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ContentValues certValue = new ContentValues(1);
 | 
					        ContentValues certValue = new ContentValues(1);
 | 
				
			||||||
        certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG);
 | 
					        certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG);
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,14 @@ public class InstalledAppTestUtils {
 | 
				
			|||||||
                               String packageName,
 | 
					                               String packageName,
 | 
				
			||||||
                               int versionCode, String versionName,
 | 
					                               int versionCode, String versionName,
 | 
				
			||||||
                               @Nullable String signingCert) {
 | 
					                               @Nullable String signingCert) {
 | 
				
			||||||
 | 
					        install(context, packageName, versionCode, versionName, signingCert, null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void install(Context context,
 | 
				
			||||||
 | 
					                               String packageName,
 | 
				
			||||||
 | 
					                               int versionCode, String versionName,
 | 
				
			||||||
 | 
					                               @Nullable String signingCert,
 | 
				
			||||||
 | 
					                               @Nullable String hash) {
 | 
				
			||||||
        PackageInfo info = new PackageInfo();
 | 
					        PackageInfo info = new PackageInfo();
 | 
				
			||||||
        info.packageName = packageName;
 | 
					        info.packageName = packageName;
 | 
				
			||||||
        info.versionCode = versionCode;
 | 
					        info.versionCode = versionCode;
 | 
				
			||||||
@ -31,8 +39,12 @@ public class InstalledAppTestUtils {
 | 
				
			|||||||
        if (signingCert != null) {
 | 
					        if (signingCert != null) {
 | 
				
			||||||
            info.signatures = new Signature[]{new Signature(signingCert)};
 | 
					            info.signatures = new Signature[]{new Signature(signingCert)};
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String hashType = "sha256";
 | 
					        String hashType = "sha256";
 | 
				
			||||||
        String hash = "00112233445566778899aabbccddeeff";
 | 
					        if (hash == null) {
 | 
				
			||||||
 | 
					            hash = "00112233445566778899aabbccddeeff";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
 | 
					        InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ import org.fdroid.fdroid.data.ApkProvider;
 | 
				
			|||||||
import org.fdroid.fdroid.data.App;
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
import org.fdroid.fdroid.data.AppProvider;
 | 
					import org.fdroid.fdroid.data.AppProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.FDroidProviderTest;
 | 
					import org.fdroid.fdroid.data.FDroidProviderTest;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.InstalledAppTestUtils;
 | 
				
			||||||
import org.fdroid.fdroid.data.Repo;
 | 
					import org.fdroid.fdroid.data.Repo;
 | 
				
			||||||
import org.fdroid.fdroid.data.RepoProvider;
 | 
					import org.fdroid.fdroid.data.RepoProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.RepoPushRequest;
 | 
					import org.fdroid.fdroid.data.RepoPushRequest;
 | 
				
			||||||
@ -91,7 +92,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
 | 
				
			|||||||
        updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
 | 
					        updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
 | 
				
			||||||
        IOUtils.closeQuietly(indexInputStream);
 | 
					        IOUtils.closeQuietly(indexInputStream);
 | 
				
			||||||
        List<App> apps = AppProvider.Helper.all(context.getContentResolver());
 | 
					        List<App> apps = AppProvider.Helper.all(context.getContentResolver());
 | 
				
			||||||
        assertEquals("53 apps present", 53, apps.size());
 | 
					        assertEquals("63 apps present", 63, apps.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] packages = {
 | 
					        String[] packages = {
 | 
				
			||||||
                "fake.app.one",
 | 
					                "fake.app.one",
 | 
				
			||||||
@ -110,7 +111,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
 | 
				
			|||||||
        repos = RepoProvider.Helper.all(context);
 | 
					        repos = RepoProvider.Helper.all(context);
 | 
				
			||||||
        assertEquals("One repo", 1, repos.size());
 | 
					        assertEquals("One repo", 1, repos.size());
 | 
				
			||||||
        Repo repoFromDb = repos.get(0);
 | 
					        Repo repoFromDb = repos.get(0);
 | 
				
			||||||
        assertEquals("repo.timestamp should be set", 1481222111, repoFromDb.timestamp);
 | 
					        assertEquals("repo.timestamp should be set", 1497639511, repoFromDb.timestamp);
 | 
				
			||||||
        assertEquals("repo.address should be the same", repo.address, repoFromDb.address);
 | 
					        assertEquals("repo.address should be the same", repo.address, repoFromDb.address);
 | 
				
			||||||
        assertEquals("repo.name should be set", "non-public test repo", repoFromDb.name);
 | 
					        assertEquals("repo.name should be set", "non-public test repo", repoFromDb.name);
 | 
				
			||||||
        assertEquals("repo.maxage should be set", 0, repoFromDb.maxage);
 | 
					        assertEquals("repo.maxage should be set", 0, repoFromDb.maxage);
 | 
				
			||||||
@ -121,6 +122,12 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertEquals("repo.mirrors should have items", 2, repo.mirrors.length);
 | 
					        assertEquals("repo.mirrors should have items", 2, repo.mirrors.length);
 | 
				
			||||||
        assertEquals("repo.mirrors first URL", "http://frkcchxlcvnb4m5a.onion/fdroid/repo", repo.mirrors[0]);
 | 
					        assertEquals("repo.mirrors first URL", "http://frkcchxlcvnb4m5a.onion/fdroid/repo", repo.mirrors[0]);
 | 
				
			||||||
        assertEquals("repo.mirrors second URL", "http://testy.at.or.at/fdroid/repo", repo.mirrors[1]);
 | 
					        assertEquals("repo.mirrors second URL", "http://testy.at.or.at/fdroid/repo", repo.mirrors[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make sure the per-apk anti features which are new in index v1 get added correctly.
 | 
				
			||||||
 | 
					        assertEquals(0, AppProvider.Helper.findInstalledAppsWithKnownVulns(context).size());
 | 
				
			||||||
 | 
					        InstalledAppTestUtils.install(context, "com.waze", 1019841, "v3.9.5.4", "362488e7be5ea0689b4e97d989ae1404",
 | 
				
			||||||
 | 
					                "cbbdb8c5dafeccd7dd7b642dde0477d3489e18ac366e3c8473d5c07e5f735a95");
 | 
				
			||||||
 | 
					        assertEquals(1, AppProvider.Helper.findInstalledAppsWithKnownVulns(context).size());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test(expected = RepoUpdater.SigningException.class)
 | 
					    @Test(expected = RepoUpdater.SigningException.class)
 | 
				
			||||||
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user