Refactoring Apk references into content provider.
Removed DB.Apk in favour of stand-alone Apk class. Conflicts: src/org/fdroid/fdroid/DB.java
This commit is contained in:
parent
2a8c570a00
commit
b3773a1561
@ -36,10 +36,15 @@
|
||||
android:supportsRtl="false" >
|
||||
|
||||
<provider
|
||||
android:authorities="org.fdroid.fdroid.data"
|
||||
android:authorities="org.fdroid.fdroid.data.RepoProvider"
|
||||
android:name="org.fdroid.fdroid.data.RepoProvider"
|
||||
android:exported="false"/>
|
||||
|
||||
<provider
|
||||
android:authorities="org.fdroid.fdroid.data.ApkProvider"
|
||||
android:name="org.fdroid.fdroid.data.ApkProvider"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:name=".FDroid"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize" >
|
||||
|
3
TODO
Normal file
3
TODO
Normal file
@ -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.
|
@ -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<DB.Apk> items;
|
||||
private List<Apk> items;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public ApkListAdapter(Context context, List<DB.Apk> items) {
|
||||
this.items = new ArrayList<DB.Apk>();
|
||||
public ApkListAdapter(Context context, List<Apk> items) {
|
||||
this.items = new ArrayList<Apk>();
|
||||
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<DB.Apk> getItems() {
|
||||
public List<Apk> 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();
|
||||
|
@ -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<String> features;
|
||||
private Set<String> 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<String>();
|
||||
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<Apk> 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<App> apps = getApps(false);
|
||||
for (App app : apps) {
|
||||
if (app.apks.isEmpty()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<DB.App> 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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
216
src/org/fdroid/fdroid/data/Apk.java
Normal file
216
src/org/fdroid/fdroid/data/Apk.java
Normal file
@ -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<String> features;
|
||||
private Set<String> 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<String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
370
src/org/fdroid/fdroid/data/ApkProvider.java
Normal file
370
src/org/fdroid/fdroid/data/ApkProvider.java
Normal file
@ -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<Apk> all(Context context) {
|
||||
return all(context, DataColumns.ALL);
|
||||
}
|
||||
|
||||
public static List<Apk> 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<Apk> cursorToList(Cursor cursor) {
|
||||
List<Apk> apks = new ArrayList<Apk>();
|
||||
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<String,String> REPO_FIELDS = new HashMap<String,String>();
|
||||
|
||||
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<String> args = new ArrayList<String>(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<String,String> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
23
src/org/fdroid/fdroid/data/ValueObject.java
Normal file
23
src/org/fdroid/fdroid/data/ValueObject.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user