fields as instance vars: icon, mirrors for Repo; antiFeatures for Apk

This adds support for the index fields: icon, mirrors, and antiFeatures.
icon and mirrors are for Repo, they've been around a while on the server
side, but just never used on the client side.

For Apk, this adds a new per-APK antiFeatures field so that each APK can
be individually marked.  For example, when tracking is added or removed,
vulnerabilities are discovered and fixed, etc.

These fields will be ignored when using the v0 index.xml format, they
will be used by the upcoming index-v1 format: !422
This commit is contained in:
Hans-Christoph Steiner 2017-02-27 14:23:14 +01:00
parent bc0db92c50
commit 601c85103e
7 changed files with 89 additions and 9 deletions

View File

@ -58,6 +58,7 @@ import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.jar.JarEntry;
@ -195,9 +196,11 @@ public class RepoUpdater {
private RepoXMLHandler.IndexReceiver createIndexReceiver() {
return new RepoXMLHandler.IndexReceiver() {
@Override
public void receiveRepo(String name, String description, String signingCert, int maxAge, int version, long timestamp) {
public void receiveRepo(String name, String description, String signingCert, int maxAge,
int version, long timestamp, String icon, String[] mirrors) {
signingCertFromIndexXml = signingCert;
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version, timestamp);
repoDetailsToSave = prepareRepoDetailsForSaving(name, description, maxAge, version,
timestamp, icon, mirrors);
}
@Override
@ -294,7 +297,9 @@ public class RepoUpdater {
* Update tracking data for the repo represented by this instance (index version, etag,
* description, human-readable name, etc.
*/
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge, int version, long timestamp) {
private ContentValues prepareRepoDetailsForSaving(String name, String description, int maxAge,
int version, long timestamp, String icon,
String[] mirrors) {
ContentValues values = new ContentValues();
values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
@ -329,6 +334,14 @@ public class RepoUpdater {
// timestamp.
values.put(RepoTable.Cols.TIMESTAMP, timestamp);
if (icon != null && !icon.equals(repo.icon)) {
values.put(RepoTable.Cols.ICON, icon);
}
if (mirrors != null && mirrors.length > 0 && !Arrays.equals(mirrors, repo.mirrors)) {
values.put(RepoTable.Cols.MIRRORS, Utils.serializeCommaSeparatedString(mirrors));
}
return values;
}

View File

@ -59,6 +59,8 @@ public class RepoXMLHandler extends DefaultHandler {
private long repoTimestamp;
private String repoDescription;
private String repoName;
private String repoIcon;
private final ArrayList<String> repoMirrors = new ArrayList<>();
/**
* Set of requested permissions per package/APK
@ -73,7 +75,8 @@ public class RepoXMLHandler extends DefaultHandler {
private final StringBuilder curchars = new StringBuilder();
public interface IndexReceiver {
void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp);
void receiveRepo(String name, String description, String signingCert, int maxage, int version,
long timestamp, String icon, String[] mirrors);
void receiveApp(App app, List<Apk> packages);
@ -258,6 +261,8 @@ public class RepoXMLHandler extends DefaultHandler {
}
} else if ("description".equals(localName)) {
repoDescription = cleanWhiteSpace(str);
} else if ("mirror".equals(localName)) {
repoMirrors.add(str);
}
}
@ -312,7 +317,8 @@ public class RepoXMLHandler extends DefaultHandler {
}
private void onRepoParsed() {
receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion, repoTimestamp);
receiver.receiveRepo(repoName, repoDescription, repoSigningCert, repoMaxAge, repoVersion,
repoTimestamp, repoIcon, repoMirrors.toArray(new String[repoMirrors.size()]));
}
private void onRepoPushRequestParsed(RepoPushRequest repoPushRequest) {
@ -331,6 +337,7 @@ public class RepoXMLHandler extends DefaultHandler {
repoName = cleanWhiteSpace(attributes.getValue("", "name"));
repoDescription = cleanWhiteSpace(attributes.getValue("", "description"));
repoTimestamp = parseLong(attributes.getValue("", "timestamp"), 0);
repoIcon = attributes.getValue("", "icon");
} else if (RepoPushRequest.INSTALL.equals(localName)
|| RepoPushRequest.UNINSTALL.equals(localName)) {
if (repo.pushRequests == Repo.PUSH_REQUEST_ACCEPT_ALWAYS) {

View File

@ -2,6 +2,7 @@ package org.fdroid.fdroid.data;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.os.Build;
import android.os.Parcel;
@ -73,6 +74,8 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
public String repoAddress;
public String[] incompatibleReasons;
public String[] antiFeatures;
/**
* The numeric primary key of the Metadata table, which is used to join apks.
*/
@ -203,6 +206,9 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
case Cols.Repo.ADDRESS:
repoAddress = cursor.getString(i);
break;
case Cols.ANTI_FEATURES:
antiFeatures = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
}
}
}
@ -303,6 +309,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.IS_COMPATIBLE, compatible ? 1 : 0);
return values;
}
@ -349,6 +356,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
dest.writeInt(this.repoVersion);
dest.writeString(this.repoAddress);
dest.writeStringArray(this.incompatibleReasons);
dest.writeStringArray(this.antiFeatures);
dest.writeLong(this.appId);
}
@ -380,6 +388,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
this.repoVersion = in.readInt();
this.repoAddress = in.readString();
this.incompatibleReasons = in.createStringArray();
this.antiFeatures = in.createStringArray();
this.appId = in.readLong();
}

View File

@ -76,6 +76,8 @@ class DBHelper extends SQLiteOpenHelper {
+ RepoTable.Cols.USERNAME + " string, "
+ RepoTable.Cols.PASSWORD + " string,"
+ RepoTable.Cols.TIMESTAMP + " integer not null default 0, "
+ RepoTable.Cols.ICON + " string, "
+ RepoTable.Cols.MIRRORS + " string, "
+ RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
+ ");";
@ -104,6 +106,7 @@ class DBHelper extends SQLiteOpenHelper {
+ ApkTable.Cols.ADDED_DATE + " string, "
+ ApkTable.Cols.IS_COMPATIBLE + " int not null, "
+ ApkTable.Cols.INCOMPATIBLE_REASONS + " text, "
+ ApkTable.Cols.ANTI_FEATURES + " string, "
+ "PRIMARY KEY (" + ApkTable.Cols.APP_ID + ", " + ApkTable.Cols.VERSION_CODE + ", " + ApkTable.Cols.REPO_ID + ")"
+ ");";
@ -178,7 +181,7 @@ class DBHelper extends SQLiteOpenHelper {
+ InstalledAppTable.Cols.HASH + " TEXT NOT NULL"
+ " );";
protected static final int DB_VERSION = 65;
protected static final int DB_VERSION = 66;
private final Context context;
@ -258,6 +261,27 @@ class DBHelper extends SQLiteOpenHelper {
migrateToPackageTable(db, oldVersion);
addObbFiles(db, oldVersion);
addCategoryTables(db, oldVersion);
addIndexV1Fields(db, oldVersion);
}
private void addIndexV1Fields(SQLiteDatabase db, int oldVersion) {
if (oldVersion >= 66) {
return;
}
if (!columnExists(db, Schema.RepoTable.NAME, RepoTable.Cols.ICON)) {
Utils.debugLog(TAG, "Adding " + RepoTable.Cols.ICON + " field to " + RepoTable.NAME + " table in db.");
db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.ICON + " string;");
}
if (!columnExists(db, RepoTable.NAME, RepoTable.Cols.MIRRORS)) {
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;");
}
}
/**

View File

@ -47,6 +47,7 @@ public class Repo extends ValueObject {
public String address;
public String name;
public String description;
public String icon;
/** index version, i.e. what fdroidserver built it - 0 if not specified */
public int version;
public boolean inuse;
@ -71,6 +72,9 @@ public class Repo extends ValueObject {
/** When the signed repo index was generated, used to protect against replay attacks */
public long timestamp;
/** Official mirrors of this repo, considered automatically interchangeable */
public String[] mirrors;
/** How to treat push requests included in this repo's index XML */
public int pushRequests = PUSH_REQUEST_IGNORE;
@ -131,6 +135,12 @@ public class Repo extends ValueObject {
case Cols.TIMESTAMP:
timestamp = cursor.getLong(i);
break;
case Cols.ICON:
icon = cursor.getString(i);
break;
case Cols.MIRRORS:
mirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
break;
case Cols.PUSH_REQUESTS:
pushRequests = cursor.getInt(i);
break;
@ -257,6 +267,14 @@ public class Repo extends ValueObject {
timestamp = toInt(values.getAsInteger(Cols.TIMESTAMP));
}
if (values.containsKey(Cols.ICON)) {
icon = values.getAsString(Cols.ICON);
}
if (values.containsKey(Cols.MIRRORS)) {
mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS));
}
if (values.containsKey(Cols.PUSH_REQUESTS)) {
pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
}

View File

@ -240,6 +240,7 @@ public interface Schema {
String ADDED_DATE = "added";
String IS_COMPATIBLE = "compatible";
String INCOMPATIBLE_REASONS = "incompatibleReasons";
String ANTI_FEATURES = "antiFeatures";
interface Repo {
String VERSION = "repoVersion";
@ -258,7 +259,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,
IS_COMPATIBLE, INCOMPATIBLE_REASONS, ANTI_FEATURES,
};
/**
@ -270,6 +271,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,
};
}
}
@ -295,12 +297,14 @@ public interface Schema {
String USERNAME = "username";
String PASSWORD = "password";
String TIMESTAMP = "timestamp";
String ICON = "icon";
String MIRRORS = "mirrors";
String PUSH_REQUESTS = "pushRequests";
String[] ALL = {
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
USERNAME, PASSWORD, TIMESTAMP, PUSH_REQUESTS,
USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, PUSH_REQUESTS,
};
}
}

View File

@ -31,19 +31,24 @@ public class RepoDetails implements RepoXMLHandler.IndexReceiver {
public int maxAge;
public int version;
public long timestamp;
public String icon;
public String[] mirrors;
public List<Apk> apks = new ArrayList<>();
public List<App> apps = new ArrayList<>();
public List<RepoPushRequest> repoPushRequestList = new ArrayList<>();
@Override
public void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp) {
public void receiveRepo(String name, String description, String signingCert, int maxage,
int version, long timestamp, String icon, String[] mirrors) {
this.name = name;
this.description = description;
this.signingCert = signingCert;
this.maxAge = maxage;
this.version = version;
this.timestamp = timestamp;
this.icon = icon;
this.mirrors = mirrors;
}
@Override