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.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
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.AppPrefsProvider;
|
||||
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.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
@ -740,40 +735,6 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
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
|
||||
public void uninstallApk() {
|
||||
Apk apk = app.installedApk;
|
||||
@ -783,7 +744,10 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
apk = app.getMediaApkifInstalled(getApplicationContext());
|
||||
if (apk == null) {
|
||||
// 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;
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
||||
case Cols.Repo.ADDRESS:
|
||||
repoAddress = cursor.getString(i);
|
||||
break;
|
||||
case Cols.ANTI_FEATURES:
|
||||
case Cols.AntiFeatures.ANTI_FEATURES:
|
||||
antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
|
||||
break;
|
||||
}
|
||||
@ -348,7 +348,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
||||
values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features));
|
||||
values.put(Cols.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode));
|
||||
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);
|
||||
return values;
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
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.Cols;
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("LineLength")
|
||||
public class ApkProvider extends FDroidProvider {
|
||||
@ -266,6 +272,10 @@ public class ApkProvider extends FDroidProvider {
|
||||
return ApkTable.NAME;
|
||||
}
|
||||
|
||||
protected String getApkAntiFeatureJoinTableName() {
|
||||
return ApkAntiFeatureJoinTable.NAME;
|
||||
}
|
||||
|
||||
protected String getAppTableName() {
|
||||
return AppMetadataTable.NAME;
|
||||
}
|
||||
@ -283,6 +293,18 @@ public class ApkProvider extends FDroidProvider {
|
||||
private class Query extends QueryBuilder {
|
||||
|
||||
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
|
||||
protected String getRequiredTables() {
|
||||
@ -301,6 +323,9 @@ public class ApkProvider extends FDroidProvider {
|
||||
addPackageField(PACKAGE_FIELDS.get(field), field);
|
||||
} else if (REPO_FIELDS.containsKey(field)) {
|
||||
addRepoField(REPO_FIELDS.get(field), field);
|
||||
} else if (Cols.AntiFeatures.ANTI_FEATURES.equals(field)) {
|
||||
antiFeaturesRequested = true;
|
||||
addAntiFeatures();
|
||||
} else if (field.equals(Cols._ID)) {
|
||||
appendField("rowid", "apk", "_id");
|
||||
} else if (field.equals(Cols._COUNT)) {
|
||||
@ -324,6 +349,18 @@ public class ApkProvider extends FDroidProvider {
|
||||
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) {
|
||||
@ -508,15 +545,73 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
@Override
|
||||
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);
|
||||
validateFields(Cols.ALL, values);
|
||||
long newId = db().insertOrThrow(getTableName(), null, values);
|
||||
|
||||
if (saveAntiFeatures) {
|
||||
ensureAntiFeatures(antiFeatures, newId);
|
||||
}
|
||||
|
||||
if (!isApplyingBatch()) {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
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
|
||||
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.");
|
||||
}
|
||||
|
||||
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);
|
||||
removeFieldsFromOtherTables(values);
|
||||
|
||||
@ -556,6 +660,19 @@ public class ApkProvider extends FDroidProvider {
|
||||
query = query.add(querySingleWithAppId(uri));
|
||||
|
||||
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()) {
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
@ -82,13 +82,6 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
*/
|
||||
@JsonIgnore
|
||||
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
|
||||
public Apk installedApk; // might be null if not installed
|
||||
@JsonIgnore
|
||||
@ -107,6 +100,13 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
@JsonIgnore
|
||||
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")
|
||||
public long repoId;
|
||||
|
||||
@ -796,6 +796,37 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
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() {
|
||||
if (TextUtils.isEmpty(this.name)
|
||||
|| TextUtils.isEmpty(this.packageName)) {
|
||||
|
@ -3,37 +3,44 @@ package org.fdroid.fdroid.data;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
ignoreAllUpdates = ignoreAll;
|
||||
ignoreVulnerabilities = ignoreVulns;
|
||||
}
|
||||
|
||||
public static AppPrefs createDefault() {
|
||||
return new AppPrefs(0, false);
|
||||
return new AppPrefs(0, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o != null && o instanceof AppPrefs &&
|
||||
((AppPrefs) o).ignoreAllUpdates == ignoreAllUpdates &&
|
||||
((AppPrefs) o).ignoreThisUpdate == ignoreThisUpdate;
|
||||
((AppPrefs) o).ignoreThisUpdate == ignoreThisUpdate &&
|
||||
((AppPrefs) o).ignoreVulnerabilities == ignoreVulnerabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (ignoreThisUpdate + "-" + ignoreAllUpdates).hashCode();
|
||||
return (ignoreThisUpdate + "-" + ignoreAllUpdates + "-" + ignoreVulnerabilities).hashCode();
|
||||
}
|
||||
|
||||
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);
|
||||
values.put(Cols.IGNORE_ALL_UPDATES, prefs.ignoreAllUpdates);
|
||||
values.put(Cols.IGNORE_THIS_UPDATE, prefs.ignoreThisUpdate);
|
||||
values.put(Cols.IGNORE_VULNERABILITIES, prefs.ignoreVulnerabilities);
|
||||
|
||||
if (getPrefsOrNull(context, app) == null) {
|
||||
values.put(Cols.PACKAGE_NAME, app.packageName);
|
||||
@ -51,7 +52,8 @@ public class AppPrefsProvider extends FDroidProvider {
|
||||
cursor.moveToFirst();
|
||||
return new AppPrefs(
|
||||
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 {
|
||||
cursor.close();
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
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.AppMetadataTable;
|
||||
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);
|
||||
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 {
|
||||
|
||||
private boolean naturalJoinToInstalled;
|
||||
private boolean naturalJoinApks;
|
||||
private boolean naturalJoinAntiFeatures;
|
||||
private boolean leftJoinPrefs;
|
||||
|
||||
AppQuerySelection() {
|
||||
@ -170,6 +179,14 @@ public class AppProvider extends FDroidProvider {
|
||||
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
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return leftJoinPrefs;
|
||||
}
|
||||
@ -198,9 +231,18 @@ public class AppProvider extends FDroidProvider {
|
||||
bothWithJoin.requireNaturalInstalledTable();
|
||||
}
|
||||
|
||||
if (this.naturalJoinToApks() || query.naturalJoinToApks()) {
|
||||
bothWithJoin.requireNaturalJoinApks();
|
||||
}
|
||||
|
||||
if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
|
||||
bothWithJoin.requireLeftJoinPrefs();
|
||||
}
|
||||
|
||||
if (this.naturalJoinAntiFeatures() || query.naturalJoinAntiFeatures()) {
|
||||
bothWithJoin.requireNatrualJoinAntiFeatures();
|
||||
}
|
||||
|
||||
return bothWithJoin;
|
||||
}
|
||||
|
||||
@ -210,6 +252,8 @@ public class AppProvider extends FDroidProvider {
|
||||
|
||||
private boolean isSuggestedApkTableAdded;
|
||||
private boolean requiresInstalledTable;
|
||||
private boolean requiresApkTable;
|
||||
private boolean requiresAntiFeatures;
|
||||
private boolean requiresLeftJoinToPrefs;
|
||||
private boolean countFieldAppended;
|
||||
|
||||
@ -240,9 +284,15 @@ public class AppProvider extends FDroidProvider {
|
||||
if (selection.naturalJoinToInstalled()) {
|
||||
naturalJoinToInstalledTable();
|
||||
}
|
||||
if (selection.naturalJoinToApks()) {
|
||||
naturalJoinToApkTable();
|
||||
}
|
||||
if (selection.leftJoinToPrefs()) {
|
||||
leftJoinToPrefs();
|
||||
}
|
||||
if (selection.naturalJoinAntiFeatures()) {
|
||||
naturalJoinAntiFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if (!requiresLeftJoinToPrefs) {
|
||||
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
|
||||
public void addField(String 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_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
|
||||
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 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 CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 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 {
|
||||
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_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
|
||||
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() {
|
||||
@ -421,6 +501,12 @@ public class AppProvider extends FDroidProvider {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getInstalledWithKnownVulnsUri() {
|
||||
return getContentUri().buildUpon()
|
||||
.appendPath(PATH_INSTALLED_WITH_KNOWN_VULNS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getTopFromCategoryUri(String category, int limit) {
|
||||
return getContentUri().buildUpon()
|
||||
.appendPath(PATH_TOP_FROM_CATEGORY)
|
||||
@ -505,6 +591,10 @@ public class AppProvider extends FDroidProvider {
|
||||
return ApkTable.NAME;
|
||||
}
|
||||
|
||||
protected String getApkAntiFeatureJoinTableName() {
|
||||
return ApkAntiFeatureJoinTable.NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProviderName() {
|
||||
return "AppProvider";
|
||||
@ -652,6 +742,24 @@ public class AppProvider extends FDroidProvider {
|
||||
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) {
|
||||
String[] args = packageNames.split(",");
|
||||
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
|
||||
@ -739,6 +847,11 @@ public class AppProvider extends FDroidProvider {
|
||||
includeSwap = false;
|
||||
break;
|
||||
|
||||
case INSTALLED_WITH_KNOWN_VULNS:
|
||||
selection = selection.add(queryInstalledWithKnownVulns());
|
||||
includeSwap = false;
|
||||
break;
|
||||
|
||||
case RECENTLY_UPDATED:
|
||||
String table = getTableName();
|
||||
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.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.CatJoinTable;
|
||||
import org.fdroid.fdroid.data.Schema.PackageTable;
|
||||
@ -107,8 +109,7 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
+ ApkTable.Cols.HASH_TYPE + " string, "
|
||||
+ ApkTable.Cols.ADDED_DATE + " string, "
|
||||
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
|
||||
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
|
||||
+ ApkTable.Cols.ANTI_FEATURES + " string"
|
||||
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text"
|
||||
+ ");";
|
||||
|
||||
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
|
||||
+ " ( "
|
||||
+ AppPrefsTable.Cols.PACKAGE_NAME + " TEXT, "
|
||||
+ AppPrefsTable.Cols.IGNORE_THIS_UPDATE + " INT BOOLEAN NOT NULL, "
|
||||
+ AppPrefsTable.Cols.IGNORE_ALL_UPDATES + " INT NOT NULL "
|
||||
+ AppPrefsTable.Cols.IGNORE_THIS_UPDATE + " 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
|
||||
@ -194,7 +196,19 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
+ 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;
|
||||
|
||||
@ -214,6 +228,8 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(CREATE_TABLE_INSTALLED_APP);
|
||||
db.execSQL(CREATE_TABLE_REPO);
|
||||
db.execSQL(CREATE_TABLE_APP_PREFS);
|
||||
db.execSQL(CREATE_TABLE_ANTI_FEATURE);
|
||||
db.execSQL(CREATE_TABLE_APK_ANTI_FEATURE_JOIN);
|
||||
ensureIndexes(db);
|
||||
|
||||
String[] defaultRepos = context.getResources().getStringArray(R.array.default_repos);
|
||||
@ -283,6 +299,28 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
addPreferredSignerToApp(db, oldVersion);
|
||||
updatePreferredSignerIfEmpty(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) {
|
||||
@ -436,11 +474,6 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
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;");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 " + ApkTable.NAME);
|
||||
|
||||
@ -1067,6 +1108,8 @@ class DBHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(CREATE_TABLE_APK);
|
||||
db.execSQL(CREATE_TABLE_CATEGORY);
|
||||
db.execSQL(CREATE_TABLE_CAT_JOIN);
|
||||
db.execSQL(CREATE_TABLE_ANTI_FEATURE);
|
||||
db.execSQL(CREATE_TABLE_APK_ANTI_FEATURE_JOIN);
|
||||
clearRepoEtags(db);
|
||||
ensureIndexes(db);
|
||||
db.setTransactionSuccessful();
|
||||
|
@ -54,8 +54,9 @@ public interface Schema {
|
||||
|
||||
String IGNORE_ALL_UPDATES = "ignoreAllUpdates";
|
||||
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 {
|
||||
|
||||
String NAME = "fdroid_app";
|
||||
@ -258,7 +300,6 @@ public interface Schema {
|
||||
String ADDED_DATE = "added";
|
||||
String IS_COMPATIBLE = "compatible";
|
||||
String INCOMPATIBLE_REASONS = "incompatibleReasons";
|
||||
String ANTI_FEATURES = "antiFeatures";
|
||||
|
||||
interface Repo {
|
||||
String VERSION = "repoVersion";
|
||||
@ -269,6 +310,10 @@ public interface Schema {
|
||||
String PACKAGE_NAME = "package_packageName";
|
||||
}
|
||||
|
||||
interface AntiFeatures {
|
||||
String ANTI_FEATURES = "antiFeatures_commaSeparated";
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256,
|
||||
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,
|
||||
REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApkAntiFeatureJoinTableName() {
|
||||
return TempAppProvider.TABLE_TEMP_APK_ANTI_FEATURE_JOIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAppTableName() {
|
||||
return TempAppProvider.TABLE_TEMP_APP;
|
||||
@ -93,11 +98,23 @@ public class TempApkProvider extends ApkProvider {
|
||||
final SQLiteDatabase db = db();
|
||||
final String memoryDbName = TempAppProvider.DB;
|
||||
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[] whereArgs = new String[]{Long.toString(repoIdBeingUpdated)};
|
||||
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_compatible ON " + getTableName() + " (" + Cols.IS_COMPATIBLE + ");");
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public class TempAppProvider extends AppProvider {
|
||||
private static final String PROVIDER_NAME = "TempAppProvider";
|
||||
|
||||
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;
|
||||
|
||||
private static final String PATH_INIT = "init";
|
||||
@ -125,6 +126,10 @@ public class TempAppProvider extends AppProvider {
|
||||
return TempApkProvider.TABLE_TEMP_APK;
|
||||
}
|
||||
|
||||
protected String getApkAntiFeatureJoinTableName() {
|
||||
return TempApkProvider.TABLE_TEMP_APK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
switch (MATCHER.match(uri)) {
|
||||
@ -218,6 +223,7 @@ public class TempAppProvider extends AppProvider {
|
||||
final String tempApp = DB + "." + TABLE_TEMP_APP;
|
||||
final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK;
|
||||
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)};
|
||||
|
||||
@ -230,6 +236,16 @@ public class TempAppProvider extends AppProvider {
|
||||
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(
|
||||
"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();
|
||||
|
||||
getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
||||
@ -250,4 +266,13 @@ public class TempAppProvider extends AppProvider {
|
||||
|
||||
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
|
||||
private final Button actionButton;
|
||||
|
||||
@Nullable
|
||||
private final Button secondaryButton;
|
||||
|
||||
private final DisplayImageOptions displayImageOptions;
|
||||
|
||||
@Nullable
|
||||
@ -137,11 +140,16 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
|
||||
cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
|
||||
actionButton = (Button) itemView.findViewById(R.id.action_button);
|
||||
secondaryButton = (Button) itemView.findViewById(R.id.secondary_button);
|
||||
|
||||
if (actionButton != null) {
|
||||
actionButton.setOnClickListener(onActionClicked);
|
||||
}
|
||||
|
||||
if (secondaryButton != null) {
|
||||
secondaryButton.setOnClickListener(onSecondaryButtonClicked);
|
||||
}
|
||||
|
||||
if (cancelButton != null) {
|
||||
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 (viewState.showProgress()) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
@ -388,9 +405,26 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
return;
|
||||
}
|
||||
|
||||
onActionButtonPressed(currentApp);
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final View.OnClickListener onSecondaryButtonClicked = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentApp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSecondaryButtonPressed(currentApp);
|
||||
}
|
||||
};
|
||||
|
||||
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(currentApp.packageName);
|
||||
Intent intent = activity.getPackageManager().getLaunchIntentForPackage(app.packageName);
|
||||
if (intent != null) {
|
||||
activity.startActivity(intent);
|
||||
|
||||
@ -431,11 +465,13 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
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);
|
||||
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")
|
||||
private final View.OnClickListener onCancelDownload = new View.OnClickListener() {
|
||||
|
@ -14,6 +14,7 @@ public class AppListItemState {
|
||||
private final App app;
|
||||
private CharSequence mainText = null;
|
||||
private CharSequence actionButtonText = null;
|
||||
private CharSequence secondaryButtonText = null;
|
||||
private CharSequence statusText = null;
|
||||
private CharSequence secondaryStatusText = null;
|
||||
private int progressCurrent = -1;
|
||||
@ -34,6 +35,11 @@ public class AppListItemState {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AppListItemState showSecondaryButton(CharSequence label) {
|
||||
secondaryButtonText = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AppListItemState setStatusText(CharSequence text) {
|
||||
this.statusText = text;
|
||||
return this;
|
||||
@ -74,6 +80,14 @@ public class AppListItemState {
|
||||
return actionButtonText;
|
||||
}
|
||||
|
||||
public boolean shouldShowSecondaryButton() {
|
||||
return secondaryButtonText != null;
|
||||
}
|
||||
|
||||
public CharSequence getSecondaryButtonText() {
|
||||
return secondaryButtonText;
|
||||
}
|
||||
|
||||
public boolean showProgress() {
|
||||
return progressCurrent >= 0;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
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.views.updates.items.AppStatus;
|
||||
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.UpdateableAppsHeader;
|
||||
|
||||
@ -65,6 +67,9 @@ import java.util.Set;
|
||||
public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
||||
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 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<UpdateableApp> updateableApps = new ArrayList<>();
|
||||
private final List<KnownVulnApp> knownVulnApps = new ArrayList<>();
|
||||
|
||||
private boolean showAllUpdateableApps = false;
|
||||
|
||||
@ -80,9 +86,11 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
delegatesManager.addDelegate(new AppStatus.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
|
||||
@ -186,33 +198,41 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
@Override
|
||||
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(
|
||||
activity,
|
||||
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
|
||||
);
|
||||
activity, uri, Schema.AppMetadataTable.Cols.ALL, null, null, Schema.AppMetadataTable.Cols.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
cursor.moveToFirst();
|
||||
@ -220,9 +240,16 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
updateableApps.add(new UpdateableApp(activity, new App(cursor)));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
populateItems();
|
||||
notifyDataSetChanged();
|
||||
private void onKnownVulnLoadFinished(Cursor cursor) {
|
||||
knownVulnApps.clear();
|
||||
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
knownVulnApps.add(new KnownVulnApp(activity, new App(cursor)));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
@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_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__show_updateable_apps">Show apps</string>
|
||||
|
||||
<plurals name="updates__download_updates_for_apps">
|
||||
<item quantity="one">Download update for %1$d app.</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>
|
||||
</style>
|
||||
|
||||
<style name="DetailsSecondaryButtonStyleSmall" parent="DetailsSecondaryButtonStyle">
|
||||
<item name="android:padding">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="DetailsMoreButtonStyle">
|
||||
<item name="android:padding">5dp</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.assertResultCount;
|
||||
import static org.fdroid.fdroid.Assert.insertApp;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@ -309,10 +310,12 @@ public class ApkProviderTest extends FDroidProviderTest {
|
||||
assertEquals("com.example", apk.packageName);
|
||||
assertEquals(10, apk.versionCode);
|
||||
|
||||
assertNull(apk.antiFeatures);
|
||||
assertNull(apk.features);
|
||||
assertNull(apk.added);
|
||||
assertNull(apk.hashType);
|
||||
|
||||
apk.antiFeatures = new String[] {"KnownVuln", "Other anti feature"};
|
||||
apk.features = new String[] {"one", "two", "three" };
|
||||
long dateTimestamp = System.currentTimeMillis();
|
||||
apk.added = new Date(dateTimestamp);
|
||||
@ -335,14 +338,8 @@ public class ApkProviderTest extends FDroidProviderTest {
|
||||
assertEquals("com.example", updatedApk.packageName);
|
||||
assertEquals(10, updatedApk.versionCode);
|
||||
|
||||
assertNotNull(updatedApk.features);
|
||||
assertNotNull(updatedApk.added);
|
||||
assertNotNull(updatedApk.hashType);
|
||||
|
||||
assertEquals(3, updatedApk.features.length);
|
||||
assertEquals("one", updatedApk.features[0]);
|
||||
assertEquals("two", updatedApk.features[1]);
|
||||
assertEquals("three", updatedApk.features[2]);
|
||||
assertArrayEquals(new String[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
|
||||
assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
|
||||
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
|
||||
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
|
||||
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
|
||||
|
@ -28,16 +28,19 @@ public class AppPrefsProviderTest extends FDroidProviderTest {
|
||||
@SuppressWarnings({"PMD.EqualsNull", "EqualsWithItself", "EqualsBetweenInconvertibleTypes", "ObjectEqualsNull"})
|
||||
@Test
|
||||
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));
|
||||
|
||||
assertFalse(original.equals(null));
|
||||
assertFalse(original.equals("String"));
|
||||
assertFalse(original.equals(new AppPrefs(102, true)));
|
||||
assertFalse(original.equals(new AppPrefs(101, false)));
|
||||
assertFalse(original.equals(new AppPrefs(100, false)));
|
||||
assertFalse(original.equals(new AppPrefs(102, true, true)));
|
||||
assertFalse(original.equals(new AppPrefs(101, false, true)));
|
||||
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
|
||||
@ -51,16 +54,19 @@ public class AppPrefsProviderTest extends FDroidProviderTest {
|
||||
AppPrefs defaultPrefs = AppPrefsProvider.Helper.getPrefsOrDefault(context, withPrefs);
|
||||
assertEquals(0, defaultPrefs.ignoreThisUpdate);
|
||||
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);
|
||||
assertEquals(12, newPrefs.ignoreThisUpdate);
|
||||
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);
|
||||
assertEquals(14, evenNewerPrefs.ignoreThisUpdate);
|
||||
assertTrue(evenNewerPrefs.ignoreAllUpdates);
|
||||
assertTrue(evenNewerPrefs.ignoreVulnerabilities);
|
||||
|
||||
assertNull(AppPrefsProvider.Helper.getPrefsOrNull(context, withoutPrefs));
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public class AppProviderTest extends FDroidProviderTest {
|
||||
String packageName, int installedVercode, int suggestedVercode,
|
||||
boolean ignoreAll, int ignoreVercode) {
|
||||
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);
|
||||
certValue.put(Schema.ApkTable.Cols.SIGNATURE, TestUtils.FDROID_SIG);
|
||||
|
@ -22,6 +22,14 @@ public class InstalledAppTestUtils {
|
||||
String packageName,
|
||||
int versionCode, String versionName,
|
||||
@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();
|
||||
info.packageName = packageName;
|
||||
info.versionCode = versionCode;
|
||||
@ -31,8 +39,12 @@ public class InstalledAppTestUtils {
|
||||
if (signingCert != null) {
|
||||
info.signatures = new Signature[]{new Signature(signingCert)};
|
||||
}
|
||||
|
||||
String hashType = "sha256";
|
||||
String hash = "00112233445566778899aabbccddeeff";
|
||||
if (hash == null) {
|
||||
hash = "00112233445566778899aabbccddeeff";
|
||||
}
|
||||
|
||||
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.AppProvider;
|
||||
import org.fdroid.fdroid.data.FDroidProviderTest;
|
||||
import org.fdroid.fdroid.data.InstalledAppTestUtils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.RepoPushRequest;
|
||||
@ -91,7 +92,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
||||
updater.processIndexV1(indexInputStream, indexEntry, "fakeEtag");
|
||||
IOUtils.closeQuietly(indexInputStream);
|
||||
List<App> apps = AppProvider.Helper.all(context.getContentResolver());
|
||||
assertEquals("53 apps present", 53, apps.size());
|
||||
assertEquals("63 apps present", 63, apps.size());
|
||||
|
||||
String[] packages = {
|
||||
"fake.app.one",
|
||||
@ -110,7 +111,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
||||
repos = RepoProvider.Helper.all(context);
|
||||
assertEquals("One repo", 1, repos.size());
|
||||
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.name should be set", "non-public test repo", repoFromDb.name);
|
||||
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 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]);
|
||||
|
||||
// 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)
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user