diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c8b373eb7..248240f6c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -36,10 +36,15 @@
android:supportsRtl="false" >
+
+
diff --git a/TODO b/TODO
new file mode 100644
index 000000000..63f46645f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,3 @@
+incompatible_reasons needs to be implemented correctly for the data.Apk class (rather than DB.Apk).
+Currently, it is set during a CompatabilityChecker call to isCompatible(), which means we don't really
+know whether the field has been set or not.
diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java
index c01eccdb5..d39486d8a 100644
--- a/src/org/fdroid/fdroid/AppDetails.java
+++ b/src/org/fdroid/fdroid/AppDetails.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.xml.sax.XMLReader;
@@ -74,13 +75,9 @@ import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.compat.MenuManager;
import org.fdroid.fdroid.DB.CommaSeparatedList;
-import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
-import com.nostra13.universalimageloader.utils.StorageUtils;
-
-import android.os.Environment;
public class AppDetails extends ListActivity {
@@ -99,13 +96,13 @@ public class AppDetails extends ListActivity {
private class ApkListAdapter extends BaseAdapter {
- private List items;
+ private List items;
private LayoutInflater mInflater;
- public ApkListAdapter(Context context, List items) {
- this.items = new ArrayList();
+ public ApkListAdapter(Context context, List items) {
+ this.items = new ArrayList();
if (items != null) {
- for (DB.Apk apk : items) {
+ for (Apk apk : items) {
this.addItem(apk);
}
}
@@ -113,13 +110,13 @@ public class AppDetails extends ListActivity {
Context.LAYOUT_INFLATER_SERVICE);
}
- public void addItem(DB.Apk apk) {
+ public void addItem(Apk apk) {
if (apk.compatible || pref_incompatibleVersions) {
items.add(apk);
}
}
- public List getItems() {
+ public List getItems() {
return items;
}
@@ -142,7 +139,7 @@ public class AppDetails extends ListActivity {
public View getView(int position, View convertView, ViewGroup parent) {
java.text.DateFormat df = DateFormat.getDateFormat(mctx);
- DB.Apk apk = items.get(position);
+ Apk apk = items.get(position);
ViewHolder holder;
if (convertView == null) {
@@ -983,7 +980,7 @@ public class AppDetails extends ListActivity {
private boolean updating;
private String id;
- public DownloadHandler(DB.Apk apk, String repoaddress, File destdir) {
+ public DownloadHandler(Apk apk, String repoaddress, File destdir) {
id = apk.id;
download = new Downloader(apk, repoaddress, destdir);
download.start();
diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java
index e94374bab..b101cad14 100644
--- a/src/org/fdroid/fdroid/DB.java
+++ b/src/org/fdroid/fdroid/DB.java
@@ -21,16 +21,17 @@ package org.fdroid.fdroid;
import java.io.File;
import java.security.MessageDigest;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
-import java.util.Set;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -55,8 +56,7 @@ import android.util.Log;
import org.fdroid.fdroid.compat.Compatibility;
import org.fdroid.fdroid.compat.ContextCompat;
import org.fdroid.fdroid.compat.SupportedArchitectures;
-import org.fdroid.fdroid.data.DBHelper;
-import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.*;
public class DB {
@@ -270,155 +270,6 @@ public class DB {
}
- // The TABLE_APK table stores details of all the application versions we
- // know about. Each relates directly back to an entry in TABLE_APP.
- // This information is retrieved from the repositories.
- public static final String TABLE_APK = "fdroid_apk";
-
- public static class Apk {
-
- public Apk() {
- updated = false;
- detail_size = 0;
- added = null;
- repo = 0;
- detail_hash = null;
- detail_hashType = null;
- detail_permissions = null;
- compatible = false;
- }
-
- public String id;
- public String version;
- public int vercode;
- public int detail_size; // Size in bytes - 0 means we don't know!
- public long repo; // ID of the repo it comes from
- public String detail_hash;
- public String detail_hashType;
- public int minSdkVersion; // 0 if unknown
- public Date added;
- public CommaSeparatedList detail_permissions; // null if empty or
- // unknown
- public CommaSeparatedList features; // null if empty or unknown
-
- public CommaSeparatedList nativecode; // null if empty or unknown
-
- public CommaSeparatedList incompatible_reasons; // null if empty or
- // unknown
- // ID (md5 sum of public key) of signature. Might be null, in the
- // transition to this field existing.
- public String sig;
-
- // True if compatible with the device.
- public boolean compatible;
-
- public String apkName;
-
- // If not null, this is the name of the source tarball for the
- // application. Null indicates that it's a developer's binary
- // build - otherwise it's built from source.
- public String srcname;
-
- // Used internally for tracking during repo updates.
- public boolean updated;
-
- // Call isCompatible(apk) on an instance of this class to
- // check if an APK is compatible with the user's device.
- private static class CompatibilityChecker extends Compatibility {
-
- private Set features;
- private Set cpuAbis;
- private String cpuAbisDesc;
- private boolean ignoreTouchscreen;
-
- public CompatibilityChecker(Context ctx) {
-
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(ctx);
- ignoreTouchscreen = prefs
- .getBoolean(Preferences.PREF_IGN_TOUCH, false);
-
- PackageManager pm = ctx.getPackageManager();
- StringBuilder logMsg = new StringBuilder();
- logMsg.append("Available device features:");
- features = new HashSet();
- if (pm != null) {
- for (FeatureInfo fi : pm.getSystemAvailableFeatures()) {
- features.add(fi.name);
- logMsg.append('\n');
- logMsg.append(fi.name);
- }
- }
-
- cpuAbis = SupportedArchitectures.getAbis();
-
- StringBuilder builder = new StringBuilder();
- boolean first = true;
- for (String abi : cpuAbis) {
- if (first) first = false;
- else builder.append(", ");
- builder.append(abi);
- }
- cpuAbisDesc = builder.toString();
- builder = null;
-
- Log.d("FDroid", logMsg.toString());
- }
-
- private boolean compatibleApi(CommaSeparatedList nativecode) {
- if (nativecode == null) return true;
- for (String abi : nativecode) {
- if (cpuAbis.contains(abi)) {
- return true;
- }
- }
- return false;
- }
-
- public boolean isCompatible(Apk apk) {
- if (!hasApi(apk.minSdkVersion)) {
- apk.incompatible_reasons = CommaSeparatedList.make(String.valueOf(apk.minSdkVersion));
- return false;
- }
- if (apk.features != null) {
- for (String feat : apk.features) {
- if (ignoreTouchscreen
- && feat.equals("android.hardware.touchscreen")) {
- // Don't check it!
- } else if (!features.contains(feat)) {
- apk.incompatible_reasons = CommaSeparatedList.make(feat);
- Log.d("FDroid", apk.id + " vercode " + apk.vercode
- + " is incompatible based on lack of "
- + feat);
- return false;
- }
- }
- }
- if (!compatibleApi(apk.nativecode)) {
- apk.incompatible_reasons = apk.nativecode;
- Log.d("FDroid", apk.id + " vercode " + apk.vercode
- + " only supports " + CommaSeparatedList.str(apk.nativecode)
- + " while your architectures are " + cpuAbisDesc);
- return false;
- }
- return true;
- }
- }
- }
-
- public int countAppsForRepo(long id) {
- String[] selection = { "COUNT(distinct id)" };
- String[] selectionArgs = { Long.toString(id) };
- Cursor result = db.query(
- TABLE_APK, selection, "repo = ?", selectionArgs, "repo", null, null);
- if (result.getCount() > 0) {
- result.moveToFirst();
- return result.getInt(0);
- } else {
- return 0;
- }
- }
-
public static String calcFingerprint(String keyHexString) {
if (TextUtils.isEmpty(keyHexString))
return null;
@@ -569,33 +420,21 @@ public class DB {
}
}
- private static final String[] POPULATE_APK_COLS = new String[] { "hash", "hashType", "size", "permissions" };
+ private static final String[] POPULATE_APK_COLS = new String[] {
+ ApkProvider.DataColumns.HASH,
+ ApkProvider.DataColumns.HASH_TYPE,
+ ApkProvider.DataColumns.SIZE,
+ ApkProvider.DataColumns.PERMISSIONS
+ };
private void populateApkDetails(Apk apk, long repo) {
if (repo == 0 || repo == apk.repo) {
- Cursor cursor = null;
- try {
- cursor = db.query(
- TABLE_APK,
- POPULATE_APK_COLS,
- "id = ? and vercode = ?",
- new String[] { apk.id,
- Integer.toString(apk.vercode) }, null,
- null, null, null);
- cursor.moveToFirst();
- apk.detail_hash = cursor.getString(0);
- apk.detail_hashType = cursor.getString(1);
- apk.detail_size = cursor.getInt(2);
- apk.detail_permissions = CommaSeparatedList.make(cursor
- .getString(3));
- } catch (Exception e) {
- Log.d("FDroid", "Error populating apk details for " + apk.id + " (version " + apk.version + ")");
- Log.d("FDroid", e.getMessage());
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
+ Apk loadedApk = ApkProvider.Helper.find(
+ mContext, apk.id, apk.vercode, POPULATE_APK_COLS);
+ apk.detail_hash = loadedApk.detail_hash;
+ apk.detail_hashType = loadedApk.detail_hashType;
+ apk.detail_size = loadedApk.detail_size;
+ apk.detail_permissions = loadedApk.detail_permissions;
} else {
Log.d("FDroid", "Not setting details for apk '" + apk.id + "' (version " + apk.version +") because it belongs to a different repo.");
}
@@ -695,18 +534,6 @@ public class DB {
Log.d("FDroid", "Read app data from database " + " (took "
+ (System.currentTimeMillis() - startTime) + " ms)");
- String query = "SELECT apk.id, apk.version, apk.vercode, apk.sig,"
- + " apk.srcname, apk.apkName, apk.minSdkVersion, "
- + " apk.added, apk.features, apk.nativecode, "
- + " apk.compatible, apk.repo, repo.version, repo.address "
- + " FROM " + TABLE_APK + " as apk "
- + " LEFT JOIN " + DBHelper.TABLE_REPO + " as repo "
- + " ON repo._id = apk.repo "
- + " ORDER BY apk.vercode DESC";
-
- c = db.rawQuery(query, null);
- c.moveToFirst();
-
DisplayMetrics metrics = mContext.getResources()
.getDisplayMetrics();
String iconsDir = null;
@@ -726,43 +553,21 @@ public class DB {
metrics = null;
Log.d("FDroid", "Density-specific icons dir is " + iconsDir);
- while (!c.isAfterLast()) {
- String id = c.getString(0);
- App app = apps.get(id);
+ List apks = ApkProvider.Helper.all(mContext);
+ for (Apk apk : apks) {
+ App app = apps.get(apk.id);
if (app == null) {
- c.moveToNext();
continue;
}
- boolean compatible = c.getInt(10) == 1;
- int repoid = c.getInt(11);
- Apk apk = new Apk();
- apk.id = id;
- apk.version = c.getString(1);
- apk.vercode = c.getInt(2);
- apk.sig = c.getString(3);
- apk.srcname = c.getString(4);
- apk.apkName = c.getString(5);
- apk.minSdkVersion = c.getInt(6);
- String sApkAdded = c.getString(7);
- apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null
- : DATE_FORMAT.parse(sApkAdded);
- apk.features = CommaSeparatedList.make(c.getString(8));
- apk.nativecode = CommaSeparatedList.make(c.getString(9));
- apk.compatible = compatible;
- apk.repo = repoid;
app.apks.add(apk);
if (app.iconUrl == null && app.icon != null) {
- int repoVersion = c.getInt(12);
- String repoAddress = c.getString(13);
- if (repoVersion >= 11) {
- app.iconUrl = repoAddress + iconsDir + app.icon;
+ if (apk.repoVersion >= 11) {
+ app.iconUrl = apk.repoAddress + iconsDir + app.icon;
} else {
- app.iconUrl = repoAddress + "/icons/" + app.icon;
+ app.iconUrl = apk.repoAddress + "/icons/" + app.icon;
}
}
- c.moveToNext();
}
- c.close();
} catch (Exception e) {
Log.e("FDroid",
@@ -948,8 +753,8 @@ public class DB {
// in the repos.
Log.d("FDroid", "AppUpdate: " + app.name
+ " is no longer in any repository - removing");
- db.delete(TABLE_APP, "id = ?", new String[] { app.id });
- db.delete(TABLE_APK, "id = ?", new String[] { app.id });
+ db.delete(TABLE_APP, "id = ?", new String[]{app.id});
+ ApkProvider.Helper.deleteApksByApp(mContext, app);
} else {
for (Apk apk : app.apks) {
if (!apk.updated) {
@@ -958,8 +763,7 @@ public class DB {
Log.d("FDroid", "AppUpdate: Package " + apk.id + "/"
+ apk.version
+ " is no longer in any repository - removing");
- db.delete(TABLE_APK, "id = ? and version = ?",
- new String[] { app.id, apk.version });
+ ApkProvider.Helper.delete(mContext, app.id, apk.vercode);
}
}
}
@@ -1014,9 +818,13 @@ public class DB {
boolean afound = false;
for (Apk apk : app.apks) {
if (apk.vercode == upapk.vercode) {
- // Log.d("FDroid", "AppUpdate: " + apk.version
- // + " is a known version.");
- updateApkIfDifferent(apk, upapk);
+
+ ApkProvider.Helper.update(
+ mContext,
+ upapk,
+ apk.id,
+ apk.vercode);
+
apk.updated = true;
afound = true;
break;
@@ -1024,7 +832,7 @@ public class DB {
}
if (!afound) {
// A new version of this application.
- updateApkIfDifferent(null, upapk);
+ ApkProvider.Helper.insert(mContext, upapk);
upapk.updated = true;
app.apks.add(upapk);
}
@@ -1036,7 +844,7 @@ public class DB {
// It's a brand new application...
updateApp(null, upapp);
for (Apk upapk : upapp.apks) {
- updateApkIfDifferent(null, upapk);
+ ApkProvider.Helper.insert(mContext, upapk);
upapk.updated = true;
}
upapp.updated = true;
@@ -1095,41 +903,6 @@ public class DB {
}
}
- // Update apk details in the database, if different to the
- // previous ones.
- // 'oldapk' - previous details - i.e. what's in the database.
- // If null, this apk is not in the database at all and
- // should be added.
- // 'upapk' - updated details
- private void updateApkIfDifferent(Apk oldapk, Apk upapk) {
- ContentValues values = new ContentValues();
- values.put("id", upapk.id);
- values.put("version", upapk.version);
- values.put("vercode", upapk.vercode);
- values.put("repo", upapk.repo);
- values.put("hash", upapk.detail_hash);
- values.put("hashType", upapk.detail_hashType);
- values.put("sig", upapk.sig);
- values.put("srcname", upapk.srcname);
- values.put("size", upapk.detail_size);
- values.put("apkName", upapk.apkName);
- values.put("minSdkVersion", upapk.minSdkVersion);
- values.put("added",
- upapk.added == null ? "" : DATE_FORMAT.format(upapk.added));
- values.put("permissions",
- CommaSeparatedList.str(upapk.detail_permissions));
- values.put("features", CommaSeparatedList.str(upapk.features));
- values.put("nativecode", CommaSeparatedList.str(upapk.nativecode));
- values.put("compatible", upapk.compatible ? 1 : 0);
- if (oldapk != null) {
- db.update(TABLE_APK, values,
- "id = ? and vercode = ?",
- new String[] { oldapk.id, Integer.toString(oldapk.vercode) });
- } else {
- db.insert(TABLE_APK, null, values);
- }
- }
-
public void setIgnoreUpdates(String appid, boolean All, int This) {
db.execSQL("update " + TABLE_APP + " set"
+ " ignoreAllUpdates=" + (All ? '1' : '0')
@@ -1141,7 +914,7 @@ public class DB {
db.beginTransaction();
try {
- db.delete(TABLE_APK, "repo = ?", new String[] { Long.toString(repo.getId()) });
+ ApkProvider.Helper.deleteApksByRepo(mContext, repo);
List apps = getApps(false);
for (App app : apps) {
if (app.apks.isEmpty()) {
diff --git a/src/org/fdroid/fdroid/Downloader.java b/src/org/fdroid/fdroid/Downloader.java
index e0cb96ad6..ebd45271c 100644
--- a/src/org/fdroid/fdroid/Downloader.java
+++ b/src/org/fdroid/fdroid/Downloader.java
@@ -27,10 +27,11 @@ import java.io.OutputStream;
import java.net.URL;
import android.util.Log;
+import org.fdroid.fdroid.data.Apk;
public class Downloader extends Thread {
- private DB.Apk curapk;
+ private Apk curapk;
private String repoaddress;
private String filename;
private File destdir;
@@ -38,11 +39,11 @@ public class Downloader extends Thread {
public static enum Status {
STARTING, RUNNING, ERROR, DONE, CANCELLED
- };
+ }
public static enum Error {
CORRUPT, UNKNOWN
- };
+ }
private Status status = Status.STARTING;
private Error error;
@@ -52,7 +53,7 @@ public class Downloader extends Thread {
// Constructor - creates a Downloader to download the given Apk,
// which must have its detail populated.
- Downloader(DB.Apk apk, String repoaddress, File destdir) {
+ Downloader(Apk apk, String repoaddress, File destdir) {
curapk = apk;
this.repoaddress = repoaddress;
this.destdir = destdir;
@@ -91,7 +92,7 @@ public class Downloader extends Thread {
}
// The APK being downloaded
- public synchronized DB.Apk getApk() {
+ public synchronized Apk getApk() {
return curapk;
}
diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java
index 59a109b7a..a22c98c36 100644
--- a/src/org/fdroid/fdroid/RepoXMLHandler.java
+++ b/src/org/fdroid/fdroid/RepoXMLHandler.java
@@ -20,6 +20,7 @@
package org.fdroid.fdroid;
import android.os.Bundle;
+import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.updater.RepoUpdater;
import org.xml.sax.Attributes;
@@ -40,7 +41,7 @@ public class RepoXMLHandler extends DefaultHandler {
private List appsList;
private DB.App curapp = null;
- private DB.Apk curapk = null;
+ private Apk curapk = null;
private StringBuilder curchars = new StringBuilder();
// After processing the XML, these will be -1 if the index didn't specify
@@ -280,7 +281,7 @@ public class RepoXMLHandler extends DefaultHandler {
totalAppCount, progressData));
} else if (localName.equals("package") && curapp != null && curapk == null) {
- curapk = new DB.Apk();
+ curapk = new Apk();
curapk.id = curapp.id;
curapk.repo = repo.getId();
hashType = null;
diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java
index d161cc5ae..7d4063a88 100644
--- a/src/org/fdroid/fdroid/UpdateService.java
+++ b/src/org/fdroid/fdroid/UpdateService.java
@@ -49,6 +49,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
+import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.updater.RepoUpdater;
@@ -351,7 +352,7 @@ public class UpdateService extends IntentService implements ProgressListener {
for (long keep : keeprepos) {
for (DB.App app : apps) {
boolean keepapp = false;
- for (DB.Apk apk : app.apks) {
+ for (Apk apk : app.apks) {
if (apk.repo == keep) {
keepapp = true;
break;
@@ -371,7 +372,7 @@ public class UpdateService extends IntentService implements ProgressListener {
}
app_k.updated = true;
db.populateDetails(app_k, keep);
- for (DB.Apk apk : app.apks)
+ for (Apk apk : app.apks)
if (apk.repo == keep)
apk.updated = true;
}
diff --git a/src/org/fdroid/fdroid/data/Apk.java b/src/org/fdroid/fdroid/data/Apk.java
new file mode 100644
index 000000000..cf0c0a0f7
--- /dev/null
+++ b/src/org/fdroid/fdroid/data/Apk.java
@@ -0,0 +1,216 @@
+package org.fdroid.fdroid.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import org.fdroid.fdroid.DB;
+import org.fdroid.fdroid.compat.Compatibility;
+import org.fdroid.fdroid.compat.SupportedArchitectures;
+
+import java.util.Date;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Apk {
+
+ public String id;
+ public String version;
+ public int vercode;
+ public int detail_size; // Size in bytes - 0 means we don't know!
+ public long repo; // ID of the repo it comes from
+ public String detail_hash;
+ public String detail_hashType;
+ public int minSdkVersion; // 0 if unknown
+ public Date added;
+ public DB.CommaSeparatedList detail_permissions; // null if empty or
+ // unknown
+ public DB.CommaSeparatedList features; // null if empty or unknown
+
+ public DB.CommaSeparatedList nativecode; // null if empty or unknown
+
+ // ID (md5 sum of public key) of signature. Might be null, in the
+ // transition to this field existing.
+ public String sig;
+
+ // True if compatible with the device.
+ public boolean compatible;
+
+ public String apkName;
+
+ // If not null, this is the name of the source tarball for the
+ // application. Null indicates that it's a developer's binary
+ // build - otherwise it's built from source.
+ public String srcname;
+
+ // Used internally for tracking during repo updates.
+ public boolean updated;
+
+ public int repoVersion;
+ public String repoAddress;
+ public DB.CommaSeparatedList incompatible_reasons;
+
+ public Apk() {
+ updated = false;
+ detail_size = 0;
+ added = null;
+ repo = 0;
+ detail_hash = null;
+ detail_hashType = null;
+ detail_permissions = null;
+ compatible = false;
+ }
+
+ public Apk(Cursor cursor) {
+ for(int i = 0; i < cursor.getColumnCount(); i ++ ) {
+ String column = cursor.getColumnName(i);
+ if (column.equals(ApkProvider.DataColumns.HASH)) {
+ detail_hash = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.HASH_TYPE)) {
+ detail_hashType = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.ADDED_DATE)) {
+ added = ValueObject.toDate(cursor.getString(i));
+ } else if (column.equals(ApkProvider.DataColumns.FEATURES)) {
+ features = DB.CommaSeparatedList.make(cursor.getString(i));
+ } else if (column.equals(ApkProvider.DataColumns.APK_ID)) {
+ id = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.IS_COMPATIBLE)) {
+ compatible = cursor.getInt(i) == 1;
+ } else if (column.equals(ApkProvider.DataColumns.MIN_SDK_VERSION)) {
+ minSdkVersion = cursor.getInt(i);
+ } else if (column.equals(ApkProvider.DataColumns.NAME)) {
+ apkName = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.PERMISSIONS)) {
+ detail_permissions = DB.CommaSeparatedList.make(cursor.getString(i));
+ } else if (column.equals(ApkProvider.DataColumns.NATIVE_CODE)) {
+ nativecode = DB.CommaSeparatedList.make(cursor.getString(i));
+ } else if (column.equals(ApkProvider.DataColumns.REPO_ID)) {
+ repo = cursor.getInt(i);
+ } else if (column.equals(ApkProvider.DataColumns.SIGNATURE)) {
+ sig = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.SIZE)) {
+ detail_size = cursor.getInt(i);
+ } else if (column.equals(ApkProvider.DataColumns.SOURCE_NAME)) {
+ srcname = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.VERSION)) {
+ version = cursor.getString(i);
+ } else if (column.equals(ApkProvider.DataColumns.VERSION_CODE)) {
+ vercode = cursor.getInt(i);
+ } else if (column.equals(ApkProvider.DataColumns.REPO_VERSION)) {
+ repoVersion = cursor.getInt(i);
+ } else if (column.equals(ApkProvider.DataColumns.REPO_ADDRESS)) {
+ repoAddress = cursor.getString(i);
+ }
+ }
+ }
+
+ public ContentValues toContentValues() {
+ ContentValues values = new ContentValues();
+ values.put(ApkProvider.DataColumns.APK_ID, id);
+ values.put(ApkProvider.DataColumns.VERSION, version);
+ values.put(ApkProvider.DataColumns.VERSION_CODE, vercode);
+ values.put(ApkProvider.DataColumns.REPO_ID, repo);
+ values.put(ApkProvider.DataColumns.HASH, detail_hash);
+ values.put(ApkProvider.DataColumns.HASH_TYPE, detail_hashType);
+ values.put(ApkProvider.DataColumns.SIGNATURE, sig);
+ values.put(ApkProvider.DataColumns.SOURCE_NAME, srcname);
+ values.put(ApkProvider.DataColumns.SIZE, detail_size);
+ values.put(ApkProvider.DataColumns.NAME, apkName);
+ values.put(ApkProvider.DataColumns.MIN_SDK_VERSION, minSdkVersion);
+ values.put(ApkProvider.DataColumns.ADDED_DATE,
+ added == null ? "" : DB.DATE_FORMAT.format(added));
+ values.put(ApkProvider.DataColumns.PERMISSIONS,
+ DB.CommaSeparatedList.str(detail_permissions));
+ values.put(ApkProvider.DataColumns.FEATURES, DB.CommaSeparatedList.str(features));
+ values.put(ApkProvider.DataColumns.NATIVE_CODE, DB.CommaSeparatedList.str(nativecode));
+ values.put(ApkProvider.DataColumns.IS_COMPATIBLE, compatible ? 1 : 0);
+ return values;
+ }
+
+ // Call isCompatible(apk) on an instance of this class to
+ // check if an APK is compatible with the user's device.
+ public static class CompatibilityChecker extends Compatibility {
+
+ private Set features;
+ private Set cpuAbis;
+ private String cpuAbisDesc;
+ private boolean ignoreTouchscreen;
+
+ public CompatibilityChecker(Context ctx) {
+
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(ctx);
+ ignoreTouchscreen = prefs
+ .getBoolean("ignoreTouchscreen", false);
+
+ PackageManager pm = ctx.getPackageManager();
+ StringBuilder logMsg = new StringBuilder();
+ logMsg.append("Available device features:");
+ features = new HashSet();
+ if (pm != null) {
+ for (FeatureInfo fi : pm.getSystemAvailableFeatures()) {
+ features.add(fi.name);
+ logMsg.append('\n');
+ logMsg.append(fi.name);
+ }
+ }
+
+ cpuAbis = SupportedArchitectures.getAbis();
+
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String abi : cpuAbis) {
+ if (first) first = false;
+ else builder.append(", ");
+ builder.append(abi);
+ }
+ cpuAbisDesc = builder.toString();
+ builder = null;
+
+ Log.d("FDroid", logMsg.toString());
+ }
+
+ private boolean compatibleApi(DB.CommaSeparatedList nativecode) {
+ if (nativecode == null) return true;
+ for (String abi : nativecode) {
+ if (cpuAbis.contains(abi)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isCompatible(Apk apk) {
+ if (!hasApi(apk.minSdkVersion)) {
+ apk.incompatible_reasons = DB.CommaSeparatedList.make(String.valueOf(apk.minSdkVersion));
+ return false;
+ }
+ if (apk.features != null) {
+ for (String feat : apk.features) {
+ if (ignoreTouchscreen
+ && feat.equals("android.hardware.touchscreen")) {
+ // Don't check it!
+ } else if (!features.contains(feat)) {
+ apk.incompatible_reasons = DB.CommaSeparatedList.make(feat);
+ Log.d("FDroid", apk.id + " vercode " + apk.vercode
+ + " is incompatible based on lack of "
+ + feat);
+ return false;
+ }
+ }
+ }
+ if (!compatibleApi(apk.nativecode)) {
+ apk.incompatible_reasons = apk.nativecode;
+ Log.d("FDroid", apk.id + " vercode " + apk.vercode
+ + " only supports " + DB.CommaSeparatedList.str(apk.nativecode)
+ + " while your architectures are " + cpuAbisDesc);
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/org/fdroid/fdroid/data/ApkProvider.java b/src/org/fdroid/fdroid/data/ApkProvider.java
new file mode 100644
index 000000000..b1fc75f30
--- /dev/null
+++ b/src/org/fdroid/fdroid/data/ApkProvider.java
@@ -0,0 +1,370 @@
+package org.fdroid.fdroid.data;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.util.Log;
+import org.fdroid.fdroid.DB;
+
+import java.util.*;
+
+public class ApkProvider extends FDroidProvider {
+
+ public static final class Helper {
+
+ private Helper() {}
+
+ public static void update(Context context, Apk apk,
+ String id, int versionCode) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri(id, versionCode);
+ resolver.update(uri, apk.toContentValues(), null, null);
+ }
+
+ public static void update(Context context, Apk apk) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri(apk.id, apk.vercode);
+ resolver.update(uri, apk.toContentValues(), null, null);
+ }
+
+ /**
+ * This doesn't do anything other than call "insert" on the content
+ * resolver, but I thought I'd put it here in the interests of having
+ * each of the CRUD methods available in the helper class.
+ */
+ public static void insert(Context context, ContentValues values) {
+ ContentResolver resolver = context.getContentResolver();
+ resolver.insert(getContentUri(), values);
+ }
+
+ public static void insert(Context context, Apk apk) {
+ insert(context, apk.toContentValues());
+ }
+
+ public static List all(Context context) {
+ return all(context, DataColumns.ALL);
+ }
+
+ public static List all(Context context, String[] projection) {
+
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = ApkProvider.getContentUri();
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ return cursorToList(cursor);
+ }
+
+ private static List cursorToList(Cursor cursor) {
+ List apks = new ArrayList();
+ if (cursor != null) {
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast()) {
+ apks.add(new Apk(cursor));
+ cursor.moveToNext();
+ }
+ cursor.close();
+ }
+ return apks;
+ }
+
+ public static void deleteApksByRepo(Context context, Repo repo) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri();
+ String[] args = { Long.toString(repo.getId()) };
+ String selection = DataColumns.REPO_ID + " = ?";
+ resolver.delete(uri, selection + " = ?", args);
+ }
+
+ public static void deleteApksByApp(Context context, DB.App app) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri();
+ String[] args = { app.id };
+ String selection = DataColumns.APK_ID + " = ?";
+ resolver.delete(uri, selection, args);
+ }
+
+ public static Apk find(Context context, String id, int versionCode) {
+ return find(context, id, versionCode, DataColumns.ALL);
+ }
+
+ public static Apk find(Context context, String id, int versionCode, String[] projection) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri(id, versionCode);
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ if (cursor != null && cursor.getCount() > 0) {
+ return new Apk(cursor);
+ } else {
+ return null;
+ }
+ }
+
+ public static void delete(Context context, String id, int versionCode) {
+ ContentResolver resolver = context.getContentResolver();
+ Uri uri = getContentUri(id, versionCode);
+ resolver.delete(uri, null, null);
+ }
+ }
+
+ public interface DataColumns extends BaseColumns {
+
+ public static String APK_ID = "id";
+ public static String VERSION = "version";
+ public static String REPO_ID = "repo";
+ public static String HASH = "hash";
+ public static String VERSION_CODE = "vercode";
+ public static String NAME = "apkName";
+ public static String SIZE = "size";
+ public static String SIGNATURE = "sig";
+ public static String SOURCE_NAME = "srcname";
+ public static String MIN_SDK_VERSION = "minSdkVersion";
+ public static String PERMISSIONS = "permissions";
+ public static String FEATURES = "features";
+ public static String NATIVE_CODE = "nativecode";
+ public static String HASH_TYPE = "hashType";
+ public static String ADDED_DATE = "added";
+ public static String IS_COMPATIBLE = "compatible";
+ public static String REPO_VERSION = "repoVersion";
+ public static String REPO_ADDRESS = "repoAddress";
+
+ public static String[] ALL = {
+ _ID, APK_ID, VERSION, REPO_ID, HASH, VERSION_CODE, NAME, SIZE,
+ SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, PERMISSIONS, FEATURES,
+ NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE,
+
+ REPO_VERSION, REPO_ADDRESS
+ };
+ }
+
+ private static final String PROVIDER_NAME = "ApkProvider";
+
+ private static final UriMatcher matcher = new UriMatcher(-1);
+
+ public static Map REPO_FIELDS = new HashMap();
+
+ static {
+ REPO_FIELDS.put(DataColumns.REPO_VERSION, RepoProvider.DataColumns.VERSION);
+ REPO_FIELDS.put(DataColumns.REPO_ADDRESS, RepoProvider.DataColumns.ADDRESS);
+
+ matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
+ matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "/*/#", CODE_SINGLE);
+ }
+
+ public static Uri getContentUri() {
+ return Uri.parse("content://" + AUTHORITY + "." + PROVIDER_NAME);
+ }
+
+ public static Uri getContentUri(String id, int versionCode) {
+ return getContentUri()
+ .buildUpon()
+ .appendPath(Integer.toString(versionCode))
+ .appendPath(id)
+ .build();
+ }
+
+ @Override
+ protected String getTableName() {
+ return DBHelper.TABLE_APK;
+ }
+
+ @Override
+ protected String getProviderName() {
+ return PROVIDER_NAME;
+ }
+
+ protected UriMatcher getMatcher() {
+ return matcher;
+ }
+
+ private static class QueryBuilder {
+
+ private StringBuilder fields = new StringBuilder();
+ private StringBuilder tables = new StringBuilder(DBHelper.TABLE_APK + " AS apk");
+ private String selection = null;
+ private String orderBy = null;
+
+ private boolean repoTableRequired = false;
+
+ public void addField(String field) {
+ if (REPO_FIELDS.containsKey(field)) {
+ addRepoField(REPO_FIELDS.get(field), field);
+ } else if (field.equals(DataColumns._ID)) {
+ appendField("rowid", "apk", "_id");
+ } else if (field.startsWith("COUNT")) {
+ appendField(field);
+ } else {
+ appendField(field, "apk");
+ }
+ }
+
+ public void addRepoField(String field, String alias) {
+ if (!repoTableRequired) {
+ repoTableRequired = true;
+ tables.append(" LEFT JOIN ");
+ tables.append(DBHelper.TABLE_REPO);
+ tables.append(" AS repo ON (apk.repo = repo._id) ");
+ }
+ appendField(field, "repo", alias);
+ }
+
+ private void appendField(String field) {
+ appendField(field, null, null);
+ }
+
+ private void appendField(String field, String tableAlias) {
+ appendField(field, tableAlias, null);
+ }
+
+ private void appendField(String field, String tableAlias,
+ String fieldAlias) {
+ if (fields.length() != 0) {
+ fields.append(',');
+ }
+
+ if (tableAlias != null) {
+ fields.append(tableAlias).append('.');
+ }
+
+ fields.append(field);
+
+ if (fieldAlias != null) {
+ fields.append(" AS ").append(fieldAlias);
+ }
+ }
+
+ public void addSelection(String selection) {
+ this.selection = selection;
+ }
+
+ public void addOrderBy(String orderBy) {
+ this.orderBy = orderBy;
+ }
+
+ public String toString() {
+
+ StringBuilder suffix = new StringBuilder();
+ if (selection != null) {
+ suffix.append(" WHERE ").append(selection);
+ }
+
+ if (orderBy != null) {
+ suffix.append(" ORDER BY ").append(orderBy);
+ }
+
+ return "SELECT " + fields + " FROM " + tables + suffix;
+ }
+ }
+
+ private String appendPrimaryKeyToSelection(String selection) {
+ return (selection == null ? "" : selection + " AND ") + " id = ? and vercode = ?";
+ }
+
+ private String[] appendPrimaryKeyToArgs(Uri uri, String[] selectionArgs) {
+ List args = new ArrayList(selectionArgs.length + 2);
+ for (String arg : args) {
+ args.add(arg);
+ }
+ args.addAll(uri.getPathSegments());
+ return (String[])args.toArray();
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+
+ switch (matcher.match(uri)) {
+ case CODE_LIST:
+ break;
+
+ case CODE_SINGLE:
+ selection = appendPrimaryKeyToSelection(selection);
+ selectionArgs = appendPrimaryKeyToArgs(uri, selectionArgs);
+ break;
+
+ default:
+ Log.e("FDroid", "Invalid URI for apk content provider: " + uri);
+ throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri);
+ }
+
+ QueryBuilder query = new QueryBuilder();
+ for (String field : projection) {
+ query.addField(field);
+ }
+ query.addSelection(selection);
+ query.addOrderBy(sortOrder);
+
+ Cursor cursor = read().rawQuery(query.toString(), selectionArgs);
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+ return cursor;
+ }
+
+ private static void removeRepoFields(ContentValues values) {
+ for (Map.Entry repoField : REPO_FIELDS.entrySet()) {
+ String field = repoField.getKey();
+ if (values.containsKey(field)) {
+ Log.i("FDroid", "Cannot insert/update '" + field + "' field " +
+ "on apk table, as it belongs to the repo table. " +
+ "This field will be ignored.");
+ values.remove(field);
+ }
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+
+ removeRepoFields(values);
+ long id = write().insertOrThrow(getTableName(), null, values);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return getContentUri(
+ values.getAsString(DataColumns.APK_ID),
+ values.getAsInteger(DataColumns.VERSION_CODE));
+
+ }
+
+ @Override
+ public int delete(Uri uri, String where, String[] whereArgs) {
+
+ switch (matcher.match(uri)) {
+ case CODE_LIST:
+ // Don't support deleting of multiple apks yet.
+ return 0;
+
+ case CODE_SINGLE:
+ where = appendPrimaryKeyToSelection(where);
+ whereArgs = appendPrimaryKeyToArgs(uri, whereArgs);
+ break;
+
+ default:
+ Log.e("FDroid", "Invalid URI for apk content provider: " + uri);
+ throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri);
+ }
+
+ int rowsAffected = write().delete(getTableName(), where, whereArgs);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return rowsAffected;
+
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
+
+ switch (matcher.match(uri)) {
+ case CODE_LIST:
+ return 0;
+
+ case CODE_SINGLE:
+ where = appendPrimaryKeyToSelection(where);
+ whereArgs = appendPrimaryKeyToArgs(uri, whereArgs);
+ break;
+ }
+
+ removeRepoFields(values);
+ int numRows = write().update(getTableName(), values, where, whereArgs);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return numRows;
+
+ }
+
+}
diff --git a/src/org/fdroid/fdroid/data/DBHelper.java b/src/org/fdroid/fdroid/data/DBHelper.java
index 26c1b3582..58807514c 100644
--- a/src/org/fdroid/fdroid/data/DBHelper.java
+++ b/src/org/fdroid/fdroid/data/DBHelper.java
@@ -18,6 +18,11 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE_REPO = "fdroid_repo";
+ // The TABLE_APK table stores details of all the application versions we
+ // know about. Each relates directly back to an entry in TABLE_APP.
+ // This information is retrieved from the repositories.
+ public static final String TABLE_APK = "fdroid_apk";
+
private static final String CREATE_TABLE_REPO = "create table "
+ TABLE_REPO + " (_id integer primary key, "
+ "address text not null, "
@@ -27,15 +32,26 @@ public class DBHelper extends SQLiteOpenHelper {
+ "version integer not null default 0, "
+ "lastetag text, lastUpdated string);";
- private static final String CREATE_TABLE_APK = "create table " + DB.TABLE_APK
- + " ( " + "id text not null, " + "version text not null, "
- + "repo integer not null, " + "hash text not null, "
- + "vercode int not null," + "apkName text not null, "
- + "size int not null," + "sig string," + "srcname string,"
- + "minSdkVersion integer," + "permissions string,"
- + "features string," + "nativecode string,"
- + "hashType string," + "added string,"
- + "compatible int not null," + "primary key(id,vercode));";
+ private static final String CREATE_TABLE_APK =
+ "CREATE TABLE " + TABLE_APK + " ( "
+ + "id text not null, "
+ + "version text not null, "
+ + "repo integer not null, "
+ + "hash text not null, "
+ + "vercode int not null,"
+ + "apkName text not null, "
+ + "size int not null, "
+ + "sig string, "
+ + "srcname string, "
+ + "minSdkVersion integer, "
+ + "permissions string, "
+ + "features string, "
+ + "nativecode string, "
+ + "hashType string, "
+ + "added string, "
+ + "compatible int not null, "
+ + "primary key(id, vercode)"
+ + ");";
private static final String CREATE_TABLE_APP = "create table " + DB.TABLE_APP
+ " ( " + "id text not null, " + "name text not null, "
@@ -308,7 +324,7 @@ public class DBHelper extends SQLiteOpenHelper {
context.getSharedPreferences("FDroid", Context.MODE_PRIVATE).edit()
.putBoolean("triedEmptyUpdate", false).commit();
db.execSQL("drop table " + DB.TABLE_APP);
- db.execSQL("drop table " + DB.TABLE_APK);
+ db.execSQL("drop table " + TABLE_APK);
db.execSQL("update " + TABLE_REPO + " set lastetag = NULL");
createAppApk(db);
}
@@ -317,8 +333,8 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL(CREATE_TABLE_APP);
db.execSQL("create index app_id on " + DB.TABLE_APP + " (id);");
db.execSQL(CREATE_TABLE_APK);
- db.execSQL("create index apk_vercode on " + DB.TABLE_APK + " (vercode);");
- db.execSQL("create index apk_id on " + DB.TABLE_APK + " (id);");
+ db.execSQL("create index apk_vercode on " + TABLE_APK + " (vercode);");
+ db.execSQL("create index apk_id on " + TABLE_APK + " (id);");
}
private static boolean columnExists(SQLiteDatabase db,
diff --git a/src/org/fdroid/fdroid/data/Repo.java b/src/org/fdroid/fdroid/data/Repo.java
index ca01bde09..051c634ff 100644
--- a/src/org/fdroid/fdroid/data/Repo.java
+++ b/src/org/fdroid/fdroid/data/Repo.java
@@ -10,7 +10,7 @@ import java.net.URL;
import java.text.ParseException;
import java.util.Date;
-public class Repo {
+public class Repo extends ValueObject{
private long id;
@@ -46,14 +46,7 @@ public class Repo {
} else if (column.equals(RepoProvider.DataColumns.IN_USE)) {
inuse = cursor.getInt(i) == 1;
} else if (column.equals(RepoProvider.DataColumns.LAST_UPDATED)) {
- String dateString = cursor.getString(i);
- if (dateString != null) {
- try {
- lastUpdated = DB.DATE_FORMAT.parse(dateString);
- } catch (ParseException e) {
- Log.e("FDroid", "Error parsing date " + dateString);
- }
- }
+ lastUpdated = toDate(cursor.getString(i));
} else if (column.equals(RepoProvider.DataColumns.MAX_AGE)) {
maxage = cursor.getInt(i);
} else if (column.equals(RepoProvider.DataColumns.VERSION)) {
@@ -78,13 +71,6 @@ public class Repo {
return address;
}
- public int getNumberOfApps() {
- DB db = DB.getDB();
- int count = db.countAppsForRepo(id);
- DB.releaseDB();
- return count;
- }
-
public boolean isSigned() {
return this.pubkey != null && this.pubkey.length() > 0;
}
diff --git a/src/org/fdroid/fdroid/data/RepoProvider.java b/src/org/fdroid/fdroid/data/RepoProvider.java
index 3f955fd37..6466af81f 100644
--- a/src/org/fdroid/fdroid/data/RepoProvider.java
+++ b/src/org/fdroid/fdroid/data/RepoProvider.java
@@ -76,6 +76,7 @@ public class RepoProvider extends FDroidProvider {
repos.add(new Repo(cursor));
cursor.moveToNext();
}
+ cursor.close();
}
return repos;
}
@@ -163,6 +164,20 @@ public class RepoProvider extends FDroidProvider {
}
}
+ public static int countAppsForRepo(ContentResolver resolver,
+ long repoId) {
+ String[] projection = { "COUNT(distinct id)" };
+ String selection = "repo = ?";
+ String[] args = { Long.toString(repoId) };
+ Uri apkUri = ApkProvider.getContentUri();
+ Cursor result = resolver.query(apkUri, projection, selection, args, null);
+ if (result != null && result.getCount() > 0) {
+ result.moveToFirst();
+ return result.getInt(0);
+ } else {
+ return 0;
+ }
+ }
}
public interface DataColumns extends BaseColumns {
@@ -189,12 +204,12 @@ public class RepoProvider extends FDroidProvider {
private static final UriMatcher matcher = new UriMatcher(-1);
static {
- matcher.addURI(AUTHORITY, PROVIDER_NAME, CODE_LIST);
- matcher.addURI(AUTHORITY, PROVIDER_NAME + "/#", CODE_SINGLE);
+ matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
+ matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE);
}
public static Uri getContentUri() {
- return Uri.parse("content://" + AUTHORITY + "/" + PROVIDER_NAME);
+ return Uri.parse("content://" + AUTHORITY + "." + PROVIDER_NAME);
}
public static Uri getContentUri(long repoId) {
@@ -226,8 +241,8 @@ public class RepoProvider extends FDroidProvider {
break;
case CODE_SINGLE:
- selection = ( selection == null ? "" : selection ) +
- "_ID = " + uri.getLastPathSegment();
+ selection = ( selection == null ? "" : selection + " AND " ) +
+ DataColumns._ID + " = " + uri.getLastPathSegment();
break;
default:
@@ -287,7 +302,7 @@ public class RepoProvider extends FDroidProvider {
return 0;
case CODE_SINGLE:
- where = ( where == null ? "" : where ) +
+ where = ( where == null ? "" : where + " AND " ) +
"_ID = " + uri.getLastPathSegment();
break;
diff --git a/src/org/fdroid/fdroid/data/ValueObject.java b/src/org/fdroid/fdroid/data/ValueObject.java
new file mode 100644
index 000000000..fb936cc77
--- /dev/null
+++ b/src/org/fdroid/fdroid/data/ValueObject.java
@@ -0,0 +1,23 @@
+package org.fdroid.fdroid.data;
+
+import android.util.Log;
+import org.fdroid.fdroid.DB;
+
+import java.text.ParseException;
+import java.util.Date;
+
+abstract class ValueObject {
+
+ static Date toDate(String string) {
+ Date date = null;
+ if (string != null) {
+ try {
+ date = DB.DATE_FORMAT.parse(string);
+ } catch (ParseException e) {
+ Log.e("FDroid", "Error parsing date " + string);
+ }
+ }
+ return date;
+ }
+
+}
diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
index 1e23b034e..d86807c0d 100644
--- a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
@@ -148,7 +148,10 @@ public class RepoDetailsFragment extends Fragment {
TextView lastUpdated = (TextView)repoView.findViewById(R.id.text_last_update);
name.setText(repo.getName());
- numApps.setText(Integer.toString(repo.getNumberOfApps()));
+
+ int appCount = RepoProvider.Helper.countAppsForRepo(
+ getActivity().getContentResolver(), repo.getId());
+ numApps.setText(Integer.toString(appCount));
setupDescription(repoView, repo);
setupRepoFingerprint(repoView, repo);