Merge remote-tracking branch 'upstream/master' into improvement/16/manage-repos

Conflicts:
	res/values/strings.xml
	src/org/fdroid/fdroid/DB.java
	src/org/fdroid/fdroid/ManageRepo.java
	src/org/fdroid/fdroid/RepoXMLHandler.java
	src/org/fdroid/fdroid/Utils.java
This commit is contained in:
Peter Serwylo 2014-01-04 20:45:52 +11:00
commit 3731ed8f23
14 changed files with 251 additions and 156 deletions

2
.gitignore vendored
View File

@ -1,11 +1,9 @@
/local.properties /local.properties
/build.properties
.classpath .classpath
/bin/ /bin/
/gen/ /gen/
/build /build
/.gradle /.gradle
/proguard.cfg
/build.xml /build.xml
*~ *~
.idea .idea

View File

@ -18,6 +18,14 @@
android:layout_marginRight="4dp" android:layout_marginRight="4dp"
android:orientation="vertical" > android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:textSize="18sp"
android:textStyle="bold" />
<RelativeLayout <RelativeLayout
android:id="@+id/header" android:id="@+id/header"
android:layout_width="fill_parent" android:layout_width="fill_parent"
@ -25,41 +33,48 @@
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:orientation="horizontal" > android:orientation="horizontal" >
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginBottom="4dp"
android:singleLine="false"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView <ImageView
android:id="@+id/icon" android:id="@+id/icon"
android:layout_width="48dp" android:layout_width="56dp"
android:layout_height="48dp" android:layout_height="56dp"
android:layout_below="@id/title" android:padding="4dp"
android:layout_marginRight="6dp"
android:scaleType="fitCenter" /> android:scaleType="fitCenter" />
<TextView <RelativeLayout
android:id="@+id/license" android:layout_width="fill_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:padding="4dp"
android:layout_below="@id/title" android:layout_toRightOf="@id/icon"
android:layout_toRightOf="@id/icon" android:orientation="vertical" >
android:textSize="13sp" />
<TextView
android:id="@+id/license"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textSize="13sp" />
<TextView
android:id="@+id/categories"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/license"
android:layout_above="@id/status"
android:layout_centerVertical="true"
android:textSize="13sp" />
<TextView
android:id="@+id/status"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:textSize="13sp" />
</RelativeLayout>
<TextView
android:id="@+id/status"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/license"
android:layout_toRightOf="@id/icon"
android:textSize="13sp" />
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<ListView <ListView

View File

@ -15,7 +15,7 @@
android:textSize="18sp" /> android:textSize="18sp" />
<TextView android:id="@+id/status" <TextView android:id="@+id/status"
android:textSize="12sp" android:textSize="13sp"
android:maxLines="2" android:maxLines="2"
android:ellipsize="end" android:ellipsize="end"
android:layout_below="@id/version" android:layout_below="@id/version"
@ -23,28 +23,36 @@
android:layout_width="wrap_content" /> android:layout_width="wrap_content" />
<TextView android:id="@+id/added" <TextView android:id="@+id/added"
android:textSize="12sp" android:textSize="13sp"
android:layout_below="@id/status" android:layout_below="@id/status"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" /> android:layout_width="wrap_content" />
<TextView android:id="@+id/buildtype" <TextView android:id="@+id/buildtype"
android:textSize="12sp" android:textSize="13sp"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginBottom="4sp" /> android:layout_marginBottom="4sp" />
<TextView android:id="@+id/size" <TextView android:id="@+id/size"
android:textSize="12sp" android:textSize="13sp"
android:layout_below="@id/buildtype" android:layout_below="@id/buildtype"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginBottom="4sp" /> android:layout_marginBottom="4sp" />
<TextView android:id="@+id/api"
android:textSize="13sp"
android:layout_below="@id/buildtype"
android:layout_toLeftOf="@id/size"
android:layout_marginRight="16sp"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
<TextView android:id="@+id/nativecode" <TextView android:id="@+id/nativecode"
android:textSize="12sp" android:textSize="13sp"
android:layout_below="@id/size" android:layout_below="@id/size"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -22,9 +22,8 @@
<RelativeLayout <RelativeLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="56dp"
android:padding="6dp" android:padding="6dp"
android:layout_centerVertical="true"
android:orientation="vertical" > android:orientation="vertical" >
<TextView <TextView
@ -33,9 +32,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:paddingTop="2sp" android:paddingTop="3sp"
android:paddingBottom="2sp" android:paddingBottom="3sp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8sp"
android:textSize="13sp" /> android:textSize="13sp" />
<TextView <TextView
@ -44,7 +43,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_marginLeft="8dp" android:layout_marginLeft="8sp"
android:textSize="13sp" /> android:textSize="13sp" />
<TextView <TextView

View File

@ -27,11 +27,11 @@
<TextView android:id="@+id/status" <TextView android:id="@+id/status"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end" android:ellipsize="end"
android:paddingTop="2sp" android:paddingTop="3sp"
android:paddingBottom="2sp" android:paddingBottom="3sp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="10sp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentRight="true" /> android:layout_alignParentRight="true" />
@ -41,7 +41,7 @@
android:ellipsize="end" android:ellipsize="end"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="10sp"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" /> android:layout_alignParentRight="true" />

View File

@ -194,5 +194,6 @@
<string name="repo_disabled_notification">Disabled "%1$s".\n\nYou will <string name="repo_disabled_notification">Disabled "%1$s".\n\nYou will
need to re-enable this repository to install apps from it. need to re-enable this repository to install apps from it.
</string> </string>
<string name="minsdk_or_later">Android %s or later</string>
</resources> </resources>

View File

@ -76,6 +76,9 @@ import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.utils.StorageUtils;
import android.os.Environment;
public class AppDetails extends ListActivity { public class AppDetails extends ListActivity {
@ -140,10 +143,11 @@ public class AppDetails extends ListActivity {
tv = (TextView) v.findViewById(R.id.status); tv = (TextView) v.findViewById(R.id.status);
if (apk.vercode == app.installedVerCode if (apk.vercode == app.installedVerCode
&& apk.sig.equals(mInstalledSigID)) && apk.sig.equals(mInstalledSigID)) {
tv.setText(getString(R.string.inst)); tv.setText(getString(R.string.inst));
else } else {
tv.setText(getString(R.string.not_inst)); tv.setText(getString(R.string.not_inst));
}
tv.setEnabled(apk.compatible); tv.setEnabled(apk.compatible);
tv = (TextView) v.findViewById(R.id.size); tv = (TextView) v.findViewById(R.id.size);
@ -153,6 +157,16 @@ public class AppDetails extends ListActivity {
tv.setText(Utils.getFriendlySize(apk.detail_size)); tv.setText(Utils.getFriendlySize(apk.detail_size));
tv.setEnabled(apk.compatible); tv.setEnabled(apk.compatible);
} }
tv = (TextView) v.findViewById(R.id.api);
if (apk.minSdkVersion == 0) {
tv.setText("");
} else {
tv.setText(getString(R.string.minsdk_or_later,
Utils.getAndroidVersionName(apk.minSdkVersion)));
tv.setEnabled(apk.compatible);
}
tv = (TextView) v.findViewById(R.id.buildtype); tv = (TextView) v.findViewById(R.id.buildtype);
if (apk.srcname != null) { if (apk.srcname != null) {
tv.setText("source"); tv.setText("source");
@ -160,6 +174,7 @@ public class AppDetails extends ListActivity {
tv.setText("bin"); tv.setText("bin");
} }
tv.setEnabled(apk.compatible); tv.setEnabled(apk.compatible);
tv = (TextView) v.findViewById(R.id.added); tv = (TextView) v.findViewById(R.id.added);
if (apk.added != null) { if (apk.added != null) {
tv.setVisibility(View.VISIBLE); tv.setVisibility(View.VISIBLE);
@ -168,6 +183,7 @@ public class AppDetails extends ListActivity {
} else { } else {
tv.setVisibility(View.GONE); tv.setVisibility(View.GONE);
} }
tv = (TextView) v.findViewById(R.id.nativecode); tv = (TextView) v.findViewById(R.id.nativecode);
if (pref_expert && apk.nativecode != null) { if (pref_expert && apk.nativecode != null) {
tv.setVisibility(View.VISIBLE); tv.setVisibility(View.VISIBLE);
@ -457,26 +473,6 @@ public class AppDetails extends ListActivity {
tv = (TextView) infoView.findViewById(R.id.description); tv = (TextView) infoView.findViewById(R.id.description);
/*
The following is a quick solution to enable both text selection and
links. Causes glitches and crashes:
java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
class CustomMovementMethod extends LinkMovementMethod {
@Override
public boolean canSelectArbitrarily () {
return true;
}
}
if (Utils.hasApi(11)) {
tv.setTextIsSelectable(true);
tv.setMovementMethod(new CustomMovementMethod());
} else {
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
*/
tv.setMovementMethod(LinkMovementMethod.getInstance()); tv.setMovementMethod(LinkMovementMethod.getInstance());
// Need this to add the unimplemented support for ordered and unordered // Need this to add the unimplemented support for ordered and unordered
@ -848,8 +844,8 @@ public class AppDetails extends ListActivity {
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int whichButton) { int whichButton) {
downloadHandler = new DownloadHandler(app.curApk, downloadHandler = new DownloadHandler(app.curApk,
repoaddress, DB repoaddress, Utils
.getDataPath(getBaseContext())); .getApkCacheDir(getBaseContext()));
} }
}); });
ask_alrt.setNegativeButton(getString(R.string.no), ask_alrt.setNegativeButton(getString(R.string.no),
@ -879,7 +875,7 @@ public class AppDetails extends ListActivity {
return; return;
} }
downloadHandler = new DownloadHandler(app.curApk, repoaddress, downloadHandler = new DownloadHandler(app.curApk, repoaddress,
DB.getDataPath(this)); Utils.getApkCacheDir(getBaseContext()));
} }
private void removeApk(String id) { private void removeApk(String id) {

View File

@ -108,7 +108,7 @@ public class DB {
+ "lastUpdated string," + "compatible int not null," + "lastUpdated string," + "compatible int not null,"
+ "ignoreAllUpdates int not null," + "ignoreAllUpdates int not null,"
+ "ignoreThisUpdate int not null," + "ignoreThisUpdate int not null,"
+ "provides string," + "primary key(id));"; + "primary key(id));";
public static class App implements Comparable<App> { public static class App implements Comparable<App> {
@ -126,7 +126,6 @@ public class DB {
detail_dogecoinAddr = null; detail_dogecoinAddr = null;
detail_webURL = null; detail_webURL = null;
categories = null; categories = null;
provides = null;
antiFeatures = null; antiFeatures = null;
requirements = null; requirements = null;
hasUpdates = false; hasUpdates = false;
@ -201,9 +200,6 @@ public class DB {
public int installedVerCode; public int installedVerCode;
public boolean userInstalled; public boolean userInstalled;
// List of app IDs that this app provides or null if there aren't any.
public CommaSeparatedList provides;
// List of categories (as defined in the metadata // List of categories (as defined in the metadata
// documentation) or null if there aren't any. // documentation) or null if there aren't any.
public CommaSeparatedList categories; public CommaSeparatedList categories;
@ -377,10 +373,11 @@ public class DB {
} }
} }
cpuAbis = new ArrayList<String>(); cpuAbis = new ArrayList<String>(2);
if (hasApi(8))
cpuAbis.add(android.os.Build.CPU_ABI2);
cpuAbis.add(android.os.Build.CPU_ABI); cpuAbis.add(android.os.Build.CPU_ABI);
if (hasApi(8)) {
cpuAbis.add(android.os.Build.CPU_ABI2);
}
Log.d("FDroid", logMsg.toString()); Log.d("FDroid", logMsg.toString());
} }
@ -413,8 +410,7 @@ public class DB {
} }
if (!compatibleApi(apk.nativecode)) { if (!compatibleApi(apk.nativecode)) {
Log.d("FDroid", apk.id + " vercode " + apk.vercode Log.d("FDroid", apk.id + " vercode " + apk.vercode
+ " makes use of incompatible native code: " + " only supports " + CommaSeparatedList.str(apk.nativecode)
+ CommaSeparatedList.str(apk.nativecode)
+ " while your architecture is " + cpuAbis.get(0)); + " while your architecture is " + cpuAbis.get(0));
return false; return false;
} }
@ -437,6 +433,7 @@ public class DB {
public String address; public String address;
public String name; public String name;
public String description; public String description;
public int version; // index version, i.e. what fdroidserver built it - 0 if not specified
public boolean inuse; public boolean inuse;
public int priority; public int priority;
public String pubkey; // null for an unsigned repo public String pubkey; // null for an unsigned repo
@ -530,7 +527,7 @@ public class DB {
} }
} }
private final int DBVersion = 33; private final int DBVersion = 35;
private int countAppsForRepo(int id) { private int countAppsForRepo(int id) {
String[] selection = { "COUNT(distinct id)" }; String[] selection = { "COUNT(distinct id)" };
@ -609,6 +606,7 @@ public class DB {
mContext.getString(R.string.default_repo_name)); mContext.getString(R.string.default_repo_name));
values.put("description", values.put("description",
mContext.getString(R.string.default_repo_description)); mContext.getString(R.string.default_repo_description));
values.put("version", 0);
String pubkey = mContext.getString(R.string.default_repo_pubkey); String pubkey = mContext.getString(R.string.default_repo_pubkey);
String fingerprint = DB.calcFingerprint(pubkey); String fingerprint = DB.calcFingerprint(pubkey);
values.put("pubkey", pubkey); values.put("pubkey", pubkey);
@ -626,9 +624,11 @@ public class DB {
mContext.getString(R.string.default_repo_name2)); mContext.getString(R.string.default_repo_name2));
values.put("description", values.put("description",
mContext.getString(R.string.default_repo_description2)); mContext.getString(R.string.default_repo_description2));
values.put("version", 0);
// default #2 is /archive which has the same key as /repo // default #2 is /archive which has the same key as /repo
values.put("pubkey", pubkey); values.put("pubkey", pubkey);
values.put("fingerprint", fingerprint); values.put("fingerprint", fingerprint);
values.put("maxage", 0);
values.put("inuse", 0); values.put("inuse", 0);
values.put("priority", 20); values.put("priority", 20);
values.put("lastetag", (String) null); values.put("lastetag", (String) null);
@ -720,23 +720,14 @@ public class DB {
db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0"); db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0");
} }
if (oldVersion < 33) { if (oldVersion < 35) {
if (!columnExists(db, TABLE_REPO, "lastUpdated")) if (!columnExists(db, TABLE_REPO, "lastUpdated"))
db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string"); db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string");
} }
} }
} }
/**
* Get the local storage (cache) path. This will also create it if
* it doesn't exist. It can return null if it's currently unavailable.
*/
public static File getDataPath(Context ctx) {
return ContextCompat.create(ctx).getExternalCacheDir();
}
private Context mContext; private Context mContext;
private Apk.CompatibilityChecker compatChecker = null; private Apk.CompatibilityChecker compatChecker = null;
@ -924,16 +915,23 @@ public class DB {
} }
} }
Map<String, App> apps = new HashMap<String, App>(); // Start the map at the actual number of apps we will have
Cursor c = null; Cursor c = db.rawQuery("select count(*) from "+TABLE_APP, null);
c.moveToFirst();
int count = c.getInt(0);
c.close();
c = null;
Log.d("FDroid", "Will be fetching " + count + " apps, and this took us ");
Map<String, App> apps = new HashMap<String, App>(count);
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
try { try {
String cols[] = new String[] { "antiFeatures", "requirements", String cols[] = new String[] { "antiFeatures", "requirements",
"categories", "id", "name", "summary", "icon", "license", "categories", "id", "name", "summary", "icon", "license",
"curVersion", "curVercode", "added", "lastUpdated", "curVersion", "curVercode", "added", "lastUpdated",
"compatible", "ignoreAllUpdates", "ignoreThisUpdate", "compatible", "ignoreAllUpdates", "ignoreThisUpdate" };
"provides" };
c = db.query(TABLE_APP, cols, null, null, null, null, null); c = db.query(TABLE_APP, cols, null, null, null, null, null);
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
@ -959,7 +957,6 @@ public class DB {
app.compatible = c.getInt(12) == 1; app.compatible = c.getInt(12) == 1;
app.ignoreAllUpdates = c.getInt(13) == 1; app.ignoreAllUpdates = c.getInt(13) == 1;
app.ignoreThisUpdate = c.getInt(14); app.ignoreThisUpdate = c.getInt(14);
app.provides = DB.CommaSeparatedList.make(c.getString(15));
app.hasUpdates = false; app.hasUpdates = false;
if (getinstalledinfo && systemApks.containsKey(app.id)) { if (getinstalledinfo && systemApks.containsKey(app.id)) {
@ -979,18 +976,13 @@ public class DB {
} }
apps.put(app.id, app); apps.put(app.id, app);
if (app.provides != null) {
for (String id : app.provides) {
apps.put(id, app);
}
}
c.moveToNext(); c.moveToNext();
} }
c.close(); c.close();
c = null; c = null;
Log.d("FDroid", "Read app data from database " + " (took " Log.d("FDroid", "Read app data from database (took "
+ (System.currentTimeMillis() - startTime) + " ms)"); + (System.currentTimeMillis() - startTime) + " ms)");
List<Repo> repos = getRepos(); List<Repo> repos = getRepos();
@ -1049,7 +1041,7 @@ public class DB {
c.close(); c.close();
} }
Log.d("FDroid", "Read app and apk data from database " + " (took " Log.d("FDroid", "Read app and apk data from database (took "
+ (System.currentTimeMillis() - startTime) + " ms)"); + (System.currentTimeMillis() - startTime) + " ms)");
} }
@ -1141,7 +1133,7 @@ public class DB {
try { try {
String filter = "%" + query + "%"; String filter = "%" + query + "%";
c = db.query(TABLE_APP, new String[] { "id" }, c = db.query(TABLE_APP, new String[] { "id" },
"id like ? or provides like ? or name like ? or summary like ? or description like ?", "id like ? or name like ? or summary like ? or description like ?",
new String[] { filter, filter, filter, filter }, null, null, null); new String[] { filter, filter, filter, filter }, null, null, null);
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
@ -1412,8 +1404,8 @@ public class DB {
Cursor c = null; Cursor c = null;
try { try {
c = db.query(TABLE_REPO, new String[] { "address", "name", c = db.query(TABLE_REPO, new String[] { "address", "name",
"description", "inuse", "priority", "pubkey", "fingerprint", "description", "version", "inuse", "priority", "pubkey",
"maxage", "lastetag", "lastUpdated" }, "fingerprint", "maxage", "lastetag", "lastUpdated" },
"id = ?", new String[] { Integer.toString(id) }, null, null, null); "id = ?", new String[] { Integer.toString(id) }, null, null, null);
if (!c.moveToFirst()) if (!c.moveToFirst())
return null; return null;
@ -1422,19 +1414,19 @@ public class DB {
repo.address = c.getString(0); repo.address = c.getString(0);
repo.name = c.getString(1); repo.name = c.getString(1);
repo.description = c.getString(2); repo.description = c.getString(2);
repo.inuse = (c.getInt(3) == 1); repo.version = c.getInt(3);
repo.priority = c.getInt(4); repo.inuse = (c.getInt(4) == 1);
repo.pubkey = c.getString(5); repo.priority = c.getInt(5);
repo.fingerprint = c.getString(6); repo.pubkey = c.getString(6);
repo.maxage = c.getInt(7); repo.fingerprint = c.getString(7);
repo.lastetag = c.getString(8); repo.maxage = c.getInt(8);
repo.lastetag = c.getString(9);
try { try {
repo.lastUpdated = c.getString(9) != null ? repo.lastUpdated = c.getString(10) != null ?
mDateFormat.parse( c.getString(9)) : mDateFormat.parse( c.getString(10)) :
null; null;
} catch (ParseException e) { } catch (ParseException e) {
Log.e("FDroid", "Error parsing date " + c.getString(9)); Log.e("FDroid", "Error parsing date " + c.getString(10));
} }
return repo; return repo;
} finally { } finally {
@ -1449,8 +1441,8 @@ public class DB {
Cursor c = null; Cursor c = null;
try { try {
c = db.query(TABLE_REPO, new String[] { "id", "address", "name", c = db.query(TABLE_REPO, new String[] { "id", "address", "name",
"description", "inuse", "priority", "pubkey", "fingerprint", "description", "version", "inuse", "priority", "pubkey",
"maxage", "lastetag" }, "fingerprint", "maxage", "lastetag" },
null, null, null, null, "priority"); null, null, null, null, "priority");
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
@ -1459,12 +1451,13 @@ public class DB {
repo.address = c.getString(1); repo.address = c.getString(1);
repo.name = c.getString(2); repo.name = c.getString(2);
repo.description = c.getString(3); repo.description = c.getString(3);
repo.inuse = (c.getInt(4) == 1); repo.version = c.getInt(4);
repo.priority = c.getInt(5); repo.inuse = (c.getInt(5) == 1);
repo.pubkey = c.getString(6); repo.priority = c.getInt(6);
repo.fingerprint = c.getString(7); repo.pubkey = c.getString(7);
repo.maxage = c.getInt(8); repo.fingerprint = c.getString(8);
repo.lastetag = c.getString(9); repo.maxage = c.getInt(9);
repo.lastetag = c.getString(10);
repos.add(repo); repos.add(repo);
c.moveToNext(); c.moveToNext();
} }
@ -1524,6 +1517,7 @@ public class DB {
values.put("name", repo.name); values.put("name", repo.name);
values.put("address", repo.address); values.put("address", repo.address);
values.put("description", repo.description); values.put("description", repo.description);
values.put("version", repo.version);
values.put("inuse", repo.inuse); values.put("inuse", repo.inuse);
values.put("priority", repo.priority); values.put("priority", repo.priority);
values.put("pubkey", repo.pubkey); values.put("pubkey", repo.pubkey);
@ -1558,12 +1552,14 @@ public class DB {
} }
public void addRepo(String address, String name, String description, public void addRepo(String address, String name, String description,
int priority, String pubkey, String fingerprint, int maxage, boolean inuse) int version, int priority, String pubkey, String fingerprint,
int maxage, boolean inuse)
throws SecurityException { throws SecurityException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("address", address); values.put("address", address);
values.put("name", name); values.put("name", name);
values.put("description", description); values.put("description", description);
values.put("version", version);
values.put("inuse", inuse ? 1 : 0); values.put("inuse", inuse ? 1 : 0);
values.put("priority", priority); values.put("priority", priority);
values.put("pubkey", pubkey); values.put("pubkey", pubkey);

View File

@ -127,8 +127,11 @@ public class FDroid extends FragmentActivity {
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon( MenuItem update = menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon(
android.R.drawable.ic_menu_rotate); android.R.drawable.ic_menu_rotate);
MenuItemCompat.setShowAsAction(update,
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon( menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
android.R.drawable.ic_menu_agenda); android.R.drawable.ic_menu_agenda);
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon( MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(

View File

@ -31,11 +31,13 @@ import android.util.Log;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import com.nostra13.universalimageloader.utils.StorageUtils; import org.fdroid.fdroid.Utils;
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache; import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.utils.StorageUtils;
public class FDroidApp extends Application { public class FDroidApp extends Application {
@ -78,14 +80,14 @@ public class FDroidApp extends Application {
curTheme = Theme.valueOf(prefs.getString("theme", "dark")); curTheme = Theme.valueOf(prefs.getString("theme", "dark"));
if (!prefs.getBoolean("cacheDownloaded", false)) { if (!prefs.getBoolean("cacheDownloaded", false)) {
File local_path = DB.getDataPath(this); File local_path = Utils.getApkCacheDir(this);
// Things can be null if the SD card is not ready - we'll just // Things can be null if the SD card is not ready - we'll just
// ignore that and do it next time. // ignore that and do it next time.
if(local_path != null) { if (local_path != null) {
File[] files = local_path.listFiles(); File[] files = local_path.listFiles();
if(files != null) { if (files != null) {
for(File f : files) { for (File f : files) {
if(f.getName().endsWith(".apk")) { if (f.getName().endsWith(".apk")) {
f.delete(); f.delete();
} }
} }
@ -101,7 +103,8 @@ public class FDroidApp extends Application {
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ctx) ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ctx)
.discCache(new LimitedAgeDiscCache( .discCache(new LimitedAgeDiscCache(
new File(StorageUtils.getCacheDirectory(ctx), "icons"), new File(StorageUtils.getCacheDirectory(ctx, true),
"icons"),
new FileNameGenerator() { new FileNameGenerator() {
@Override @Override
public String generate(String imageUri) { public String generate(String imageUri) {

View File

@ -64,8 +64,9 @@ public class RepoXMLHandler extends DefaultHandler {
private DB.Apk curapk = null; private DB.Apk curapk = null;
private StringBuilder curchars = new StringBuilder(); private StringBuilder curchars = new StringBuilder();
// After processing the XML, this will be -1 if the index didn't specify // After processing the XML, these will be -1 if the index didn't specify
// a maximum age - otherwise it will be the value specified. // them - otherwise it will be the value specified.
private int version = -1;
private int maxage = -1; private int maxage = -1;
// After processing the XML, this will be null if the index specified a // After processing the XML, this will be null if the index specified a
@ -242,8 +243,6 @@ public class RepoXMLHandler extends DefaultHandler {
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
curapp.curVercode = -1; curapp.curVercode = -1;
} }
} else if (curel.equals("provides")) {
curapp.provides = DB.CommaSeparatedList.make(str);
} else if (curel.equals("categories")) { } else if (curel.equals("categories")) {
curapp.categories = DB.CommaSeparatedList.make(str); curapp.categories = DB.CommaSeparatedList.make(str);
} else if (curel.equals("antifeatures")) { } else if (curel.equals("antifeatures")) {
@ -266,10 +265,12 @@ public class RepoXMLHandler extends DefaultHandler {
public void startElement(String uri, String localName, String qName, public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException { Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes); super.startElement(uri, localName, qName, attributes);
if (localName.equals("repo")) { if (localName.equals("repo")) {
String pk = attributes.getValue("", "pubkey"); String pk = attributes.getValue("", "pubkey");
if (pk != null) if (pk != null)
pubkey = pk; pubkey = pk;
String maxAgeAttr = attributes.getValue("", "maxage"); String maxAgeAttr = attributes.getValue("", "maxage");
if (maxAgeAttr != null) { if (maxAgeAttr != null) {
try { try {
@ -277,12 +278,20 @@ public class RepoXMLHandler extends DefaultHandler {
} catch (NumberFormatException nfe) {} } catch (NumberFormatException nfe) {}
} }
String versionAttr = attributes.getValue("", "version");
if (versionAttr != null) {
try {
version = Integer.parseInt(versionAttr);
} catch (NumberFormatException nfe) {}
}
String nm = attributes.getValue("", "name"); String nm = attributes.getValue("", "name");
if (nm != null) if (nm != null)
name = nm; name = nm;
String dc = attributes.getValue("", "description"); String dc = attributes.getValue("", "description");
if (dc != null) if (dc != null)
description = dc; description = dc;
} else if (localName.equals("application") && curapp == null) { } else if (localName.equals("application") && curapp == null) {
curapp = new DB.App(); curapp = new DB.App();
curapp.detail_Populated = true; curapp.detail_Populated = true;
@ -293,11 +302,13 @@ public class RepoXMLHandler extends DefaultHandler {
new ProgressListener.Event( new ProgressListener.Event(
RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter, RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter,
totalAppCount, progressData)); totalAppCount, progressData));
} else if (localName.equals("package") && curapp != null && curapk == null) { } else if (localName.equals("package") && curapp != null && curapk == null) {
curapk = new DB.Apk(); curapk = new DB.Apk();
curapk.id = curapp.id; curapk.id = curapp.id;
curapk.repo = repo.id; curapk.repo = repo.id;
hashType = null; hashType = null;
} else if (localName.equals("hash") && curapk != null) { } else if (localName.equals("hash") && curapk != null) {
hashType = attributes.getValue("", "type"); hashType = attributes.getValue("", "type");
} }
@ -511,6 +522,13 @@ public class RepoXMLHandler extends DefaultHandler {
repoChanged = true; repoChanged = true;
} }
if (version != -1 && version != repo.version) {
Log.d("FDroid", "Repo specified a new version: from "
+ repo.version + " to " + version);
repo.version = version;
repoChanged = true;
}
if (maxage != -1 && maxage != repo.maxage) { if (maxage != -1 && maxage != repo.maxage) {
Log.d("FDroid", Log.d("FDroid",
"Repo specified a new maximum age - updated"); "Repo specified a new maximum age - updated");

View File

@ -31,6 +31,10 @@ import java.io.OutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Formatter; import java.util.Formatter;
import android.content.Context;
import com.nostra13.universalimageloader.utils.StorageUtils;
public final class Utils { public final class Utils {
public static final int BUFFER_SIZE = 4096; public static final int BUFFER_SIZE = 4096;
@ -86,6 +90,30 @@ public final class Utils {
return String.format(FRIENDLY_SIZE_FORMAT[i], s); return String.format(FRIENDLY_SIZE_FORMAT[i], s);
} }
public static String getAndroidVersionName(int sdkLevel) {
switch (sdkLevel) {
case 19: return "4.4";
case 18: return "4.3";
case 17: return "4.2";
case 16: return "4.1";
case 15: return "4.0.3";
case 14: return "4.0";
case 13: return "3.2";
case 12: return "3.1";
case 11: return "3.0";
case 10: return "2.3.3";
case 9: return "2.3";
case 8: return "2.2";
case 7: return "2.1";
case 6: return "2.0.1";
case 5: return "2.0";
case 4: return "1.6";
case 3: return "1.5";
case 2: return "1.1";
default: return "1.0";
}
}
public static int countSubstringOccurrence(File file, String substring) throws IOException { public static int countSubstringOccurrence(File file, String substring) throws IOException {
int count = 0; int count = 0;
BufferedReader reader = null; BufferedReader reader = null;
@ -152,4 +180,14 @@ public final class Utils {
} }
return fingerprintString; return fingerprintString;
} }
public static File getApkCacheDir(Context context) {
File apkCacheDir = new File(
StorageUtils.getCacheDirectory(context, true), "apks");
if (!apkCacheDir.exists()) {
apkCacheDir.mkdir();
}
return apkCacheDir;
}
} }

View File

@ -39,7 +39,7 @@ class OldContextCompatImpl extends ContextCompat {
public File getExternalCacheDir() { public File getExternalCacheDir() {
File file = new File(Environment.getExternalStorageDirectory(), File file = new File(Environment.getExternalStorageDirectory(),
"Android/data/org.fdroid.fdroid/cache"); "Android/data/org.fdroid.fdroid/cache");
if(!file.exists()) if (!file.exists())
file.mkdirs(); file.mkdirs();
return file; return file;
} }

View File

@ -27,10 +27,13 @@ abstract public class AppListAdapter extends BaseAdapter {
private List<DB.App> items = new ArrayList<DB.App>(); private List<DB.App> items = new ArrayList<DB.App>();
private Context mContext; private Context mContext;
private LayoutInflater mInflater;
private DisplayImageOptions displayImageOptions; private DisplayImageOptions displayImageOptions;
public AppListAdapter(Context context) { public AppListAdapter(Context context) {
mContext = context; mContext = context;
mInflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
displayImageOptions = new DisplayImageOptions.Builder() displayImageOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true) .cacheInMemory(true)
@ -76,38 +79,55 @@ abstract public class AppListAdapter extends BaseAdapter {
return position; return position;
} }
static class ViewHolder {
TextView name;
TextView summary;
TextView status;
TextView license;
ImageView icon;
}
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
boolean compact = Preferences.get().hasCompactLayout(); boolean compact = Preferences.get().hasCompactLayout();
DB.App app = items.get(position); DB.App app = items.get(position);
ViewHolder holder;
if (convertView == null) { if (convertView == null) {
convertView = ((LayoutInflater) mContext.getSystemService( convertView = mInflater.inflate(R.layout.applistitem, null);
Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.applistitem, null);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.summary = (TextView) convertView.findViewById(R.id.summary);
holder.status = (TextView) convertView.findViewById(R.id.status);
holder.license = (TextView) convertView.findViewById(R.id.license);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
} }
TextView name = (TextView) convertView.findViewById(R.id.name); holder.name.setText(app.name);
TextView summary = (TextView) convertView.findViewById(R.id.summary); holder.summary.setText(app.summary);
TextView status = (TextView) convertView.findViewById(R.id.status);
TextView license = (TextView) convertView.findViewById(R.id.license);
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
name.setText(app.name); layoutIcon(holder.icon, compact);
summary.setText(app.summary); ImageLoader.getInstance().displayImage(app.iconUrl, holder.icon,
int visibleOnCompact = compact ? View.VISIBLE : View.GONE;
int notVisibleOnCompact = compact ? View.GONE : View.VISIBLE;
layoutIcon(icon, compact);
ImageLoader.getInstance().displayImage(app.iconUrl, icon,
displayImageOptions); displayImageOptions);
status.setText(getVersionInfo(app)); holder.status.setText(getVersionInfo(app));
license.setText(app.license); holder.license.setText(app.license);
// Disable it all if it isn't compatible... // Disable it all if it isn't compatible...
View[] views = { convertView, status, summary, license, name }; View[] views = {
convertView,
holder.status,
holder.summary,
holder.license,
holder.name
};
for (View view : views) { for (View view : views) {
view.setEnabled(app.compatible && !app.filtered); view.setEnabled(app.compatible && !app.filtered);
} }