diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71f62b25f..6dfd02709 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,10 +10,16 @@ before_script: test: script: + - cd app + - ./tools/langs-list-check.py + - ./tools/check-string-format.py + - cd .. - ./gradlew assemble -PdisablePreDex # always report on lint errors to the build log - sed -i -e 's,textReport .*,textReport true,' app/build.gradle - ./gradlew lint -PdisablePreDex + - ./gradlew pmd -PdisablePreDex + - ./gradlew checkstyle -PdisablePreDex - ./gradlew test -PdisablePreDex || { for log in app/build/reports/*ests/*/*ml; do echo "read $log here:"; @@ -64,24 +70,6 @@ connected24: done - exit $EXITVALUE -pmd: - script: - - ./gradlew pmd -PdisablePreDex - -checkstyle: - script: - - ./gradlew checkstyle -PdisablePreDex - -tools: - before_script: - - echo "ignored, no gradle needed" - script: - - cd app - - ./tools/langs-list-check.py - - ./tools/check-string-format.py - after_script: - - echo "ignored, no gradle needed" - after_script: # this file changes every time but should not be cached - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index 0452e6e3e..8ff6ee17b 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -1163,23 +1163,23 @@ public class AppDetails extends AppCompatActivity { App app = appDetails.getApp(); switch (v.getId()) { case R.id.website: - url = app.webURL; + url = app.webSite; break; case R.id.email: final String subject = Uri.encode(getString(R.string.app_details_subject, app.name)); - url = "mailto:" + app.email + "?subject=" + subject; + url = "mailto:" + app.authorEmail + "?subject=" + subject; break; case R.id.source: - url = app.sourceURL; + url = app.sourceCode; break; case R.id.issues: - url = app.trackerURL; + url = app.issueTracker; break; case R.id.changelog: - url = app.changelogURL; + url = app.changelog; break; case R.id.donate: - url = app.donateURL; + url = app.donate; break; case R.id.bitcoin: url = app.getBitcoinUri(); @@ -1267,7 +1267,7 @@ public class AppDetails extends AppCompatActivity { // Website button View tv = view.findViewById(R.id.website); - if (!TextUtils.isEmpty(app.webURL)) { + if (!TextUtils.isEmpty(app.webSite)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1275,7 +1275,7 @@ public class AppDetails extends AppCompatActivity { // Email button tv = view.findViewById(R.id.email); - if (!TextUtils.isEmpty(app.email)) { + if (!TextUtils.isEmpty(app.authorEmail)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1283,7 +1283,7 @@ public class AppDetails extends AppCompatActivity { // Source button tv = view.findViewById(R.id.source); - if (!TextUtils.isEmpty(app.sourceURL)) { + if (!TextUtils.isEmpty(app.sourceCode)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1291,7 +1291,7 @@ public class AppDetails extends AppCompatActivity { // Issues button tv = view.findViewById(R.id.issues); - if (!TextUtils.isEmpty(app.trackerURL)) { + if (!TextUtils.isEmpty(app.issueTracker)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1299,7 +1299,7 @@ public class AppDetails extends AppCompatActivity { // Changelog button tv = view.findViewById(R.id.changelog); - if (!TextUtils.isEmpty(app.changelogURL)) { + if (!TextUtils.isEmpty(app.changelog)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1307,7 +1307,7 @@ public class AppDetails extends AppCompatActivity { // Donate button tv = view.findViewById(R.id.donate); - if (!TextUtils.isEmpty(app.donateURL)) { + if (!TextUtils.isEmpty(app.donate)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1315,7 +1315,7 @@ public class AppDetails extends AppCompatActivity { // Bitcoin tv = view.findViewById(R.id.bitcoin); - if (!TextUtils.isEmpty(app.bitcoinAddr)) { + if (!TextUtils.isEmpty(app.bitcoin)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1323,7 +1323,7 @@ public class AppDetails extends AppCompatActivity { // Litecoin tv = view.findViewById(R.id.litecoin); - if (!TextUtils.isEmpty(app.litecoinAddr)) { + if (!TextUtils.isEmpty(app.litecoin)) { tv.setOnClickListener(onClickListener); } else { tv.setVisibility(View.GONE); @@ -1642,8 +1642,8 @@ public class AppDetails extends AppCompatActivity { btMain.setEnabled(true); } TextView author = (TextView) view.findViewById(R.id.author); - if (!TextUtils.isEmpty(app.author)) { - author.setText(getString(R.string.by_author) + " " + app.author); + if (!TextUtils.isEmpty(app.authorName)) { + author.setText(getString(R.string.by_author) + " " + app.authorName); author.setVisibility(View.VISIBLE); } TextView currentVersion = (TextView) view.findViewById(R.id.current_version); diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index 3c47c485f..a29899c89 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -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; } diff --git a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java index 09a883591..345b0d890 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java @@ -59,6 +59,8 @@ public class RepoXMLHandler extends DefaultHandler { private long repoTimestamp; private String repoDescription; private String repoName; + private String repoIcon; + private final ArrayList 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 packages); @@ -205,34 +208,34 @@ public class RepoXMLHandler extends DefaultHandler { curapp.license = str; break; case "author": - curapp.author = str; + curapp.authorName = str; break; case "email": - curapp.email = str; + curapp.authorEmail = str; break; case "source": - curapp.sourceURL = str; + curapp.sourceCode = str; break; case "changelog": - curapp.changelogURL = str; + curapp.changelog = str; break; case "donate": - curapp.donateURL = str; + curapp.donate = str; break; case "bitcoin": - curapp.bitcoinAddr = str; + curapp.bitcoin = str; break; case "litecoin": - curapp.litecoinAddr = str; + curapp.litecoin = str; break; case "flattr": curapp.flattrID = str; break; case "web": - curapp.webURL = str; + curapp.webSite = str; break; case "tracker": - curapp.trackerURL = str; + curapp.issueTracker = str; break; case "added": curapp.added = Utils.parseDate(str, null); @@ -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) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index d00250966..d1f75ff89 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -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, 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, 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, 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, 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, Parcelable { this.repoVersion = in.readInt(); this.repoAddress = in.readString(); this.incompatibleReasons = in.createStringArray(); + this.antiFeatures = in.createStringArray(); this.appId = in.readLong(); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 60e5cfd44..e301bd74c 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -65,22 +65,22 @@ public class App extends ValueObject implements Comparable, Parcelable { public String license = "Unknown"; - public String author; - public String email; + public String authorName; + public String authorEmail; - public String webURL; + public String webSite; - public String trackerURL; + public String issueTracker; - public String sourceURL; + public String sourceCode; - public String changelogURL; + public String changelog; - public String donateURL; + public String donate; - public String bitcoinAddr; + public String bitcoin; - public String litecoinAddr; + public String litecoin; public String flattrID; @@ -113,8 +113,9 @@ public class App extends ValueObject implements Comparable, Parcelable { public String[] antiFeatures; /** - * List of special requirements (such as root privileges) or null if there aren't any. + * Requires root access (only ever used for root) */ + @Deprecated public String[] requirements; private AppPrefs prefs; @@ -185,32 +186,32 @@ public class App extends ValueObject implements Comparable, Parcelable { case Cols.LICENSE: license = cursor.getString(i); break; - case Cols.AUTHOR: - author = cursor.getString(i); + case Cols.AUTHOR_NAME: + authorName = cursor.getString(i); break; - case Cols.EMAIL: - email = cursor.getString(i); + case Cols.AUTHOR_EMAIL: + authorEmail = cursor.getString(i); break; - case Cols.WEB_URL: - webURL = cursor.getString(i); + case Cols.WEBSITE: + webSite = cursor.getString(i); break; - case Cols.TRACKER_URL: - trackerURL = cursor.getString(i); + case Cols.ISSUE_TRACKER: + issueTracker = cursor.getString(i); break; - case Cols.SOURCE_URL: - sourceURL = cursor.getString(i); + case Cols.SOURCE_CODE: + sourceCode = cursor.getString(i); break; - case Cols.CHANGELOG_URL: - changelogURL = cursor.getString(i); + case Cols.CHANGELOG: + changelog = cursor.getString(i); break; - case Cols.DONATE_URL: - donateURL = cursor.getString(i); + case Cols.DONATE: + donate = cursor.getString(i); break; - case Cols.BITCOIN_ADDR: - bitcoinAddr = cursor.getString(i); + case Cols.BITCOIN: + bitcoin = cursor.getString(i); break; - case Cols.LITECOIN_ADDR: - litecoinAddr = cursor.getString(i); + case Cols.LITECOIN: + litecoin = cursor.getString(i); break; case Cols.FLATTR_ID: flattrID = cursor.getString(i); @@ -489,15 +490,15 @@ public class App extends ValueObject implements Comparable, Parcelable { values.put(Cols.ICON_URL_LARGE, iconUrlLarge); values.put(Cols.DESCRIPTION, description); values.put(Cols.LICENSE, license); - values.put(Cols.AUTHOR, author); - values.put(Cols.EMAIL, email); - values.put(Cols.WEB_URL, webURL); - values.put(Cols.TRACKER_URL, trackerURL); - values.put(Cols.SOURCE_URL, sourceURL); - values.put(Cols.CHANGELOG_URL, changelogURL); - values.put(Cols.DONATE_URL, donateURL); - values.put(Cols.BITCOIN_ADDR, bitcoinAddr); - values.put(Cols.LITECOIN_ADDR, litecoinAddr); + values.put(Cols.AUTHOR_NAME, authorName); + values.put(Cols.AUTHOR_EMAIL, authorEmail); + values.put(Cols.WEBSITE, webSite); + values.put(Cols.ISSUE_TRACKER, issueTracker); + values.put(Cols.SOURCE_CODE, sourceCode); + values.put(Cols.CHANGELOG, changelog); + values.put(Cols.DONATE, donate); + values.put(Cols.BITCOIN, bitcoin); + values.put(Cols.LITECOIN, litecoin); values.put(Cols.FLATTR_ID, flattrID); values.put(Cols.ADDED, Utils.formatDate(added, "")); values.put(Cols.LAST_UPDATED, Utils.formatDate(lastUpdated, "")); @@ -555,12 +556,12 @@ public class App extends ValueObject implements Comparable, Parcelable { @Nullable public String getBitcoinUri() { - return TextUtils.isEmpty(bitcoinAddr) ? null : "bitcoin:" + bitcoinAddr; + return TextUtils.isEmpty(bitcoin) ? null : "bitcoin:" + bitcoin; } @Nullable public String getLitecoinUri() { - return TextUtils.isEmpty(litecoinAddr) ? null : "litecoin:" + litecoinAddr; + return TextUtils.isEmpty(bitcoin) ? null : "litecoin:" + bitcoin; } @Nullable @@ -631,15 +632,15 @@ public class App extends ValueObject implements Comparable, Parcelable { dest.writeString(this.icon); dest.writeString(this.description); dest.writeString(this.license); - dest.writeString(this.author); - dest.writeString(this.email); - dest.writeString(this.webURL); - dest.writeString(this.trackerURL); - dest.writeString(this.sourceURL); - dest.writeString(this.changelogURL); - dest.writeString(this.donateURL); - dest.writeString(this.bitcoinAddr); - dest.writeString(this.litecoinAddr); + dest.writeString(this.authorName); + dest.writeString(this.authorEmail); + dest.writeString(this.webSite); + dest.writeString(this.issueTracker); + dest.writeString(this.sourceCode); + dest.writeString(this.changelog); + dest.writeString(this.donate); + dest.writeString(this.bitcoin); + dest.writeString(this.litecoin); dest.writeString(this.flattrID); dest.writeString(this.upstreamVersionName); dest.writeInt(this.upstreamVersionCode); @@ -668,15 +669,15 @@ public class App extends ValueObject implements Comparable, Parcelable { this.icon = in.readString(); this.description = in.readString(); this.license = in.readString(); - this.author = in.readString(); - this.email = in.readString(); - this.webURL = in.readString(); - this.trackerURL = in.readString(); - this.sourceURL = in.readString(); - this.changelogURL = in.readString(); - this.donateURL = in.readString(); - this.bitcoinAddr = in.readString(); - this.litecoinAddr = in.readString(); + this.authorName = in.readString(); + this.authorEmail = in.readString(); + this.webSite = in.readString(); + this.issueTracker = in.readString(); + this.sourceCode = in.readString(); + this.changelog = in.readString(); + this.donate = in.readString(); + this.bitcoin = in.readString(); + this.litecoin = in.readString(); this.flattrID = in.readString(); this.upstreamVersionName = in.readString(); this.upstreamVersionCode = in.readInt(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 3022fdfcb..3b6972915 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -12,9 +12,9 @@ import android.util.Log; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Schema.ApkTable; -import org.fdroid.fdroid.data.Schema.AppPrefsTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; +import org.fdroid.fdroid.data.Schema.AppPrefsTable; import org.fdroid.fdroid.data.Schema.CatJoinTable; import org.fdroid.fdroid.data.Schema.CategoryTable; import org.fdroid.fdroid.data.Schema.InstalledAppTable; @@ -837,6 +837,11 @@ public class AppProvider extends FDroidProvider { values.remove(Cols.Package.PACKAGE_NAME); values.put(Cols.PACKAGE_ID, packageId); + if (!values.containsKey(Cols.DESCRIPTION) || values.getAsString(Cols.DESCRIPTION) == null) { + // the current structure assumes that description is always present and non-null + values.put(Cols.DESCRIPTION, ""); + } + String[] categories = null; boolean saveCategories = false; if (values.containsKey(Cols.ForWriting.Categories.CATEGORIES)) { diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 4803e78b3..76a4f9312 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -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 + ")" + ");"; @@ -116,19 +119,19 @@ class DBHelper extends SQLiteOpenHelper { + AppMetadataTable.Cols.ICON + " text, " + AppMetadataTable.Cols.DESCRIPTION + " text not null, " + AppMetadataTable.Cols.LICENSE + " text not null, " - + AppMetadataTable.Cols.AUTHOR + " text, " - + AppMetadataTable.Cols.EMAIL + " text, " - + AppMetadataTable.Cols.WEB_URL + " text, " - + AppMetadataTable.Cols.TRACKER_URL + " text, " - + AppMetadataTable.Cols.SOURCE_URL + " text, " - + AppMetadataTable.Cols.CHANGELOG_URL + " text, " + + AppMetadataTable.Cols.AUTHOR_NAME + " text, " + + AppMetadataTable.Cols.AUTHOR_EMAIL + " text, " + + AppMetadataTable.Cols.WEBSITE + " text, " + + AppMetadataTable.Cols.ISSUE_TRACKER + " text, " + + AppMetadataTable.Cols.SOURCE_CODE + " text, " + + AppMetadataTable.Cols.CHANGELOG + " text, " + AppMetadataTable.Cols.SUGGESTED_VERSION_CODE + " text," + AppMetadataTable.Cols.UPSTREAM_VERSION_NAME + " text," + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + " integer," + AppMetadataTable.Cols.ANTI_FEATURES + " string," - + AppMetadataTable.Cols.DONATE_URL + " string," - + AppMetadataTable.Cols.BITCOIN_ADDR + " string," - + AppMetadataTable.Cols.LITECOIN_ADDR + " string," + + AppMetadataTable.Cols.DONATE + " string," + + AppMetadataTable.Cols.BITCOIN + " string," + + AppMetadataTable.Cols.LITECOIN + " string," + AppMetadataTable.Cols.FLATTR_ID + " string," + AppMetadataTable.Cols.REQUIREMENTS + " string," + AppMetadataTable.Cols.ADDED + " string," @@ -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;"); + } } /** @@ -756,11 +780,11 @@ class DBHelper extends SQLiteOpenHelper { } private void addChangelogToApp(SQLiteDatabase db, int oldVersion) { - if (oldVersion >= 48 || columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.CHANGELOG_URL)) { + if (oldVersion >= 48 || columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.CHANGELOG)) { return; } - Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.CHANGELOG_URL + " column to " + AppMetadataTable.NAME); - db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.CHANGELOG_URL + " text"); + Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.CHANGELOG + " column to " + AppMetadataTable.NAME); + db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.CHANGELOG + " text"); } private void addIconUrlLargeToApp(SQLiteDatabase db, int oldVersion) { @@ -809,13 +833,13 @@ class DBHelper extends SQLiteOpenHelper { if (oldVersion >= 53) { return; } - if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR)) { - Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR + " column to " + AppMetadataTable.NAME); - db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR + " text"); + if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR_NAME)) { + Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR_NAME + " column to " + AppMetadataTable.NAME); + db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR_NAME + " text"); } - if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.EMAIL)) { - Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.EMAIL + " column to " + AppMetadataTable.NAME); - db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.EMAIL + " text"); + if (!columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.AUTHOR_EMAIL)) { + Utils.debugLog(TAG, "Adding " + AppMetadataTable.Cols.AUTHOR_EMAIL + " column to " + AppMetadataTable.NAME); + db.execSQL("alter table " + AppMetadataTable.NAME + " add column " + AppMetadataTable.Cols.AUTHOR_EMAIL + " text"); } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java index 34616230a..6f0dee7fb 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -124,13 +124,21 @@ public class InstalledAppProviderService extends IntentService { * is in sync with what the {@link PackageManager} tells us is installed. Once * completed, the relevant {@link android.content.ContentProvider}s will be * notified of any changes to installed statuses. - *

+ *

* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or * ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}. * This method returns immediately, and will continue to work in an * {@link IntentService}. It doesn't really matter where we put this in the * bootstrap process, because it runs in its own thread, at the lowest priority: * {@link Process#THREAD_PRIORITY_LOWEST}. + *

+ * APKs installed in {@code /system} will often have zeroed out timestamps, like + * 2008-01-01 (ziptime) or 2009-01-01. So instead anything older than 2010 every + * time since we have no way to know whether an APK wasn't changed as part of an + * OTA update. An OTA update could change the APK without changing the + * {@link PackageInfo#versionCode} or {@link PackageInfo#lastUpdateTime}. + * + * @see "; + String author = "" + app.authorName + ""; donateHeading.setText(Html.fromHtml(context.getString(R.string.app_details_donate_prompt, app.name, author))); } donationOptionsLayout.removeAllViews(); // Donate button - if (uriIsSetAndCanBeOpened(app.donateURL)) { - addDonateOption(R.layout.donate_generic, app.donateURL); + if (uriIsSetAndCanBeOpened(app.donate)) { + addDonateOption(R.layout.donate_generic, app.donate); } // Bitcoin @@ -639,28 +639,28 @@ public class AppDetailsRecyclerViewAdapter contentView.removeAllViews(); // Source button - if (uriIsSetAndCanBeOpened(app.sourceURL)) { - addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceURL); + if (uriIsSetAndCanBeOpened(app.sourceCode)) { + addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceCode); } // Issues button - if (uriIsSetAndCanBeOpened(app.trackerURL)) { - addLinkItemView(contentView, R.string.menu_issues, R.drawable.ic_issues, app.trackerURL); + if (uriIsSetAndCanBeOpened(app.issueTracker)) { + addLinkItemView(contentView, R.string.menu_issues, R.drawable.ic_issues, app.issueTracker); } // Changelog button - if (uriIsSetAndCanBeOpened(app.changelogURL)) { - addLinkItemView(contentView, R.string.menu_changelog, R.drawable.ic_changelog, app.changelogURL); + if (uriIsSetAndCanBeOpened(app.changelog)) { + addLinkItemView(contentView, R.string.menu_changelog, R.drawable.ic_changelog, app.changelog); } // Website button - if (uriIsSetAndCanBeOpened(app.webURL)) { - addLinkItemView(contentView, R.string.menu_website, R.drawable.ic_website, app.webURL); + if (uriIsSetAndCanBeOpened(app.webSite)) { + addLinkItemView(contentView, R.string.menu_website, R.drawable.ic_website, app.webSite); } // Email button final String subject = Uri.encode(context.getString(R.string.app_details_subject, app.name)); - String emailUrl = app.email == null ? null : ("mailto:" + app.email + "?subject=" + subject); + String emailUrl = app.authorEmail == null ? null : ("mailto:" + app.authorEmail + "?subject=" + subject); if (uriIsSetAndCanBeOpened(emailUrl)) { addLinkItemView(contentView, R.string.menu_email, R.drawable.ic_email, emailUrl); } diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java index 19dea49bb..e978ec5ac 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiRepoUpdaterTest.java @@ -274,9 +274,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals("2048", a2048.name); assertEquals(String.format("

2048 from %s repo.

", id), a2048.description); assertEquals(String.format("Puzzle game (%s)", id), a2048.summary); - assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webURL); - assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceURL); - assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.trackerURL); + assertEquals(String.format("https://github.com/uberspot/2048-android?%s", id), a2048.webSite); + assertEquals(String.format("https://github.com/uberspot/2048-android?code&%s", id), a2048.sourceCode); + assertEquals(String.format("https://github.com/uberspot/2048-android/issues?%s", id), a2048.issueTracker); } private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) { @@ -290,11 +290,11 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals(String.format("AdAway", id), adaway.name); assertEquals(String.format("

AdAway from %s repo.

", id), adaway.description); assertEquals(String.format("Block advertisements (%s)", id), adaway.summary); - assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webURL); - assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceURL); - assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.trackerURL); - assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelogURL); - assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donateURL); + assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webSite); + assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceCode); + assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.issueTracker); + assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelog); + assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donate); assertEquals(String.format("369138", id), adaway.flattrID); } @@ -309,9 +309,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals("adbWireless", adb.name); assertEquals(String.format("

adbWireless from %s repo.

", id), adb.description); assertEquals(String.format("Wireless adb (%s)", id), adb.summary); - assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webURL); - assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceURL); - assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.trackerURL); + assertEquals(String.format("https://adbwireless.example.com?%s", id), adb.webSite); + assertEquals(String.format("https://adbwireless.example.com/source?%s", id), adb.sourceCode); + assertEquals(String.format("https://adbwireless.example.com/issues?%s", id), adb.issueTracker); } private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) { @@ -325,9 +325,9 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { assertEquals("Add to calendar", calendar.name); assertEquals(String.format("

Add to calendar from %s repo.

", id), calendar.description); assertEquals(String.format("Import .ics files into calendar (%s)", id), calendar.summary); - assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webURL); - assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceURL); - assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.trackerURL); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webSite); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceCode); + assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.issueTracker); assertEquals("2225390", calendar.flattrID); } diff --git a/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java b/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java index 4371bbaa4..5d8428a15 100644 --- a/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java +++ b/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java @@ -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 apks = new ArrayList<>(); public List apps = new ArrayList<>(); public List 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