Add "What's New" and "Recently Updated" categories

This commit is contained in:
Ciaran Gultnieks 2012-08-26 09:31:59 +01:00
parent 6c838afe0b
commit 8323aacc7e
6 changed files with 176 additions and 99 deletions

1
gen/.gitignore vendored

@ -1 +0,0 @@
org/

@ -140,4 +140,9 @@
<string name="showincompat_long">Show apps written for newer Android versions or different hardware</string>
<string name="rooted">Root</string>
<string name="rooted_long">Show apps that require root privileges</string>
<string name="category_all">All</string>
<string name="category_whatsnew">What\'s New</string>
<string name="category_recentlyupdated">Recently Updated</string>
</resources>

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010-11 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -44,6 +44,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@ -90,6 +91,9 @@ public class AppDetails extends ListActivity {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
java.text.DateFormat df = DateFormat.getDateFormat(mctx);
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@ -112,15 +116,15 @@ public class AppDetails extends ListActivity {
size.setText(getFriendlySize(apk.size));
}
TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
if(apk.srcname!=null) {
if (apk.srcname != null) {
buildtype.setText("source");
} else {
} else {
buildtype.setText("bin");
}
TextView added = (TextView) v.findViewById(R.id.added);
if (apk.added != null && !apk.added.equals("")) {
added.setVisibility(View.VISIBLE);
added.setText(apk.added);
added.setText(df.format(apk.added));
} else {
added.setVisibility(View.GONE);
}
@ -202,7 +206,7 @@ public class AppDetails extends ListActivity {
.getDefaultSharedPreferences(getBaseContext());
pref_cacheDownloaded = prefs.getBoolean("cacheDownloaded", false);
pref_expert = prefs.getBoolean("expert", false);
AppDetails old = (AppDetails)getLastNonConfigurationInstance();
AppDetails old = (AppDetails) getLastNonConfigurationInstance();
if (old != null) {
copyState(old);
} else {
@ -258,7 +262,7 @@ public class AppDetails extends ListActivity {
// place of reset(), so it must initialize all fields normally set
// there.
private void copyState(AppDetails old) {
ApkListAdapter oldAdapter = (ApkListAdapter)old.getListAdapter();
ApkListAdapter oldAdapter = (ApkListAdapter) old.getListAdapter();
setListAdapter(new ApkListAdapter(this, oldAdapter.getItems()));
if (old.downloadHandler != null)
downloadHandler = new DownloadHandler(old.downloadHandler);
@ -287,7 +291,7 @@ public class AppDetails extends ListActivity {
PackageManager.GET_SIGNATURES);
mInstalledSignature = pi.signatures[0];
Hasher hash = new Hasher("MD5", mInstalledSignature
.toCharsString().getBytes());
.toCharsString().getBytes());
mInstalledSigID = hash.getHash();
} catch (NameNotFoundException e) {
Log.d("FDroid", "Failed to get installed signature");
@ -342,7 +346,7 @@ public class AppDetails extends ListActivity {
protected void onListItemClick(ListView l, View v, int position, long id) {
curapk = app.apks.get(position);
if (app.installedVersion != null
&& app.installedVersion.equals(curapk.version)) {
&& app.installedVersion.equals(curapk.version)) {
removeApk(app.id);
} else if (compatChecker.isCompatible(curapk)) {
install();
@ -412,23 +416,23 @@ public class AppDetails extends ListActivity {
return true;
case ISSUES:
startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse(app.trackerURL)));
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.trackerURL)));
return true;
case SOURCE:
startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse(app.sourceURL)));
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.sourceURL)));
return true;
case MARKET:
startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse("http://market.android.com/details?id=" + app.id)));
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://market.android.com/details?id=" + app.id)));
return true;
case DONATE:
startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse(app.donateURL)));
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.donateURL)));
return true;
}
@ -482,18 +486,17 @@ public class AppDetails extends ListActivity {
pd.setMax(max);
pd.setProgress(p);
pd.setCancelable(true);
pd.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
downloadHandler.cancel();
}
});
pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
downloadHandler.cancel();
}
});
pd.setButton(getString(R.string.cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
pd.cancel();
}
});
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
pd.cancel();
}
});
pd.show();
return pd;
}
@ -525,14 +528,14 @@ public class AppDetails extends ListActivity {
case RUNNING:
if (pd == null) {
pd = createProgressDialog(download.remoteFile(),
download.getProgress(),
download.getMax());
download.getProgress(), download.getMax());
} else {
pd.setProgress(download.getProgress());
}
break;
case ERROR:
if (pd != null) pd.dismiss();
if (pd != null)
pd.dismiss();
String text;
if (download.getErrorType() == Downloader.Error.CORRUPT)
text = getString(R.string.corrupt_download);
@ -542,14 +545,15 @@ public class AppDetails extends ListActivity {
finished = true;
break;
case DONE:
if (pd != null) pd.dismiss();
if (pd != null)
pd.dismiss();
installApk(localFile = download.localFile());
finished = true;
break;
case CANCELLED:
Toast.makeText(AppDetails.this,
getString(R.string.download_cancelled),
Toast.LENGTH_SHORT).show();
getString(R.string.download_cancelled),
Toast.LENGTH_SHORT).show();
finished = true;
break;
}
@ -569,7 +573,8 @@ public class AppDetails extends ListActivity {
}
public void cancel() {
if (download != null) download.interrupt();
if (download != null)
download.interrupt();
}
public void cleanUp() {
@ -603,7 +608,8 @@ public class AppDetails extends ListActivity {
// Repeatedly run updateProgress() until it's finished.
@Override
public void handleMessage(Message msg) {
if (download == null) return;
if (download == null)
return;
boolean finished = updateProgress();
if (finished)
download = null;
@ -613,9 +619,8 @@ public class AppDetails extends ListActivity {
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
switch(requestCode) {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_INSTALL:
if (downloadHandler != null) {
downloadHandler.cleanUp();

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010-11 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
@ -19,6 +19,8 @@
package org.fdroid.fdroid;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -79,8 +81,8 @@ public class DB {
requirements = null;
hasUpdates = false;
updated = false;
added = "";
lastUpdated = "";
added = null;
lastUpdated = null;
apks = new Vector<Apk>();
}
@ -100,8 +102,8 @@ public class DB {
public int installedVerCode;
public String marketVersion;
public int marketVercode;
public String added;
public String lastUpdated;
public Date added;
public Date lastUpdated;
// List of anti-features (as defined in the metadata
// documentation) or null if there aren't any.
@ -170,7 +172,7 @@ public class DB {
updated = false;
size = 0;
apkSource = null;
added = "";
added = null;
}
public String id;
@ -181,7 +183,7 @@ public class DB {
public String hash;
public String hashType;
public int minSdkVersion; // 0 if unknown
public String added;
public Date added;
public CommaSeparatedList permissions; // null if empty or unknown
public CommaSeparatedList features; // null if empty or unknown
@ -306,7 +308,7 @@ public class DB {
//
private static final String[][] DB_UPGRADES = {
// Version 2...
// Version 2...
{ "alter table " + TABLE_APP + " add marketVersion text",
"alter table " + TABLE_APP + " add marketVercode integer" },
@ -348,8 +350,8 @@ public class DB {
// Version 14...
{ "alter table " + TABLE_APK + " add added string",
"alter table " + TABLE_APP + " add added string",
"alter table " + TABLE_APP + " add lastUpdated string"} };
"alter table " + TABLE_APP + " add added string",
"alter table " + TABLE_APP + " add lastUpdated string" } };
private class DBHelper extends SQLiteOpenHelper {
@ -364,10 +366,10 @@ public class DB {
db.execSQL(CREATE_TABLE_APK);
onUpgrade(db, 1, DB_UPGRADES.length + 1);
ContentValues values = new ContentValues();
values.put("address", mContext
.getString(R.string.default_repo_address));
values.put("pubkey", mContext
.getString(R.string.default_repo_pubkey));
values.put("address",
mContext.getString(R.string.default_repo_address));
values.put("pubkey",
mContext.getString(R.string.default_repo_pubkey));
values.put("inuse", 1);
values.put("priority", 10);
db.insert(TABLE_REPO, null, values);
@ -390,6 +392,10 @@ public class DB {
private Context mContext;
private Apk.CompatibilityChecker compatChecker;
// The date format used for storing dates (e.g. lastupdated, added) in the
// database.
private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public DB(Context ctx) {
mContext = ctx;
@ -425,8 +431,8 @@ public class DB {
// Also try and delete the old one, from versions 0.13 and earlier.
ctx.deleteDatabase("fdroid_db");
} catch (Exception ex) {
Log.e("FDroid", "Exception in DB.delete:\n"
+ Log.getStackTraceString(ex));
Log.e("FDroid",
"Exception in DB.delete:\n" + Log.getStackTraceString(ex));
}
}
@ -443,7 +449,6 @@ public class DB {
public Vector<String> getCategories() {
Vector<String> result = new Vector<String>();
result.add("All");
Cursor c = null;
try {
c = db.rawQuery("select distinct category from " + TABLE_APP
@ -459,8 +464,9 @@ public class DB {
c.moveToNext();
}
} catch (Exception e) {
Log.e("FDroid", "Exception during database reading:\n"
+ Log.getStackTraceString(e));
Log.e("FDroid",
"Exception during database reading:\n"
+ Log.getStackTraceString(e));
} finally {
if (c != null) {
c.close();
@ -561,10 +567,13 @@ public class DB {
.getColumnIndex("marketVersion"));
app.marketVercode = c.getInt(c
.getColumnIndex("marketVercode"));
app.added = c.getString(c
.getColumnIndex("added"));
app.lastUpdated = c.getString(c
String sAdded = c.getString(c.getColumnIndex("added"));
app.added = sAdded.length() == 0 ? null : mDateFormat
.parse(sAdded);
String sLastUpdated = c.getString(c
.getColumnIndex("lastUpdated"));
app.lastUpdated = sLastUpdated.length() == 0 ? null
: mDateFormat.parse(sLastUpdated);
app.hasUpdates = false;
c2 = db.rawQuery("select * from " + TABLE_APK
@ -592,8 +601,10 @@ public class DB {
.getColumnIndex("apkSource"));
apk.minSdkVersion = c2.getInt(c2
.getColumnIndex("minSdkVersion"));
apk.added = c2.getString(c2
String sApkAdded = c2.getString(c2
.getColumnIndex("added"));
apk.added = sApkAdded.length() == 0 ? null
: mDateFormat.parse(sApkAdded);
apk.permissions = CommaSeparatedList.make(c2
.getString(c2.getColumnIndex("permissions")));
apk.features = CommaSeparatedList.make(c2.getString(c2
@ -619,8 +630,9 @@ public class DB {
}
} catch (Exception e) {
Log.e("FDroid", "Exception during database reading:\n"
+ Log.getStackTraceString(e));
Log.e("FDroid",
"Exception during database reading:\n"
+ Log.getStackTraceString(e));
} finally {
if (c != null) {
c.close();
@ -636,8 +648,9 @@ public class DB {
getUpdates(result);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.e("FDroid", "Exception while getting updates: "
+ Log.getStackTraceString(e));
Log.e("FDroid",
"Exception while getting updates: "
+ Log.getStackTraceString(e));
} finally {
db.endTransaction();
}
@ -860,8 +873,8 @@ public class DB {
values.put("trackerURL", upapp.trackerURL);
values.put("sourceURL", upapp.sourceURL);
values.put("donateURL", upapp.donateURL);
values.put("added", upapp.added);
values.put("lastUpdated", upapp.lastUpdated);
values.put("added", mDateFormat.format(upapp.added));
values.put("lastUpdated", mDateFormat.format(upapp.lastUpdated));
values.put("marketVersion", upapp.marketVersion);
values.put("marketVercode", upapp.marketVercode);
values.put("antiFeatures", CommaSeparatedList.str(upapp.antiFeatures));
@ -894,7 +907,7 @@ public class DB {
values.put("apkName", upapk.apkName);
values.put("apkSource", upapk.apkSource);
values.put("minSdkVersion", upapk.minSdkVersion);
values.put("added", upapk.added);
values.put("added", mDateFormat.format(upapk.added));
values.put("permissions", CommaSeparatedList.str(upapk.permissions));
values.put("features", CommaSeparatedList.str(upapk.features));
if (oldapk != null) {

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
@ -20,6 +20,8 @@
package org.fdroid.fdroid;
import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.Vector;
import org.fdroid.fdroid.R;
@ -44,7 +46,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TabHost;
@ -54,7 +55,8 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TabHost.TabSpec;
public class FDroid extends TabActivity implements OnItemClickListener, OnItemSelectedListener {
public class FDroid extends TabActivity implements OnItemClickListener,
OnItemSelectedListener {
private String LOCAL_PATH = "/sdcard/.fdroid";
@ -81,7 +83,7 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
// Category list
private ArrayAdapter<String> categories;
private String currentCategory = "All";
private String currentCategory = null;
private ProgressDialog pd;
@ -112,9 +114,9 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
Spinner spinner = (Spinner) findViewById(R.id.category);
categories = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item,
new Vector<String>());
categories.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
android.R.layout.simple_spinner_item, new Vector<String>());
categories
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(categories);
spinner.setOnItemSelectedListener(FDroid.this);
@ -194,8 +196,8 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
TextView tv = (TextView) view.findViewById(R.id.version);
PackageManager pm = getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo(
getApplicationContext().getPackageName(), 0);
PackageInfo pi = pm.getPackageInfo(getApplicationContext()
.getPackageName(), 0);
tv.setText(pi.versionName);
} catch (Exception e) {
}
@ -328,11 +330,21 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
long startTime = System.currentTimeMillis();
// Make sure we show at least "All" category even for empty DB
for (String s: db.getCategories()) {
// Populate the category list with the real categories, and the locally
// generated meta-categories for "All", "What's New" and "Recently
// Updated"...
String cat_all = getString(R.string.category_all);
String cat_whatsnew = getString(R.string.category_whatsnew);
String cat_recentlyupdated = getString(R.string.category_recentlyupdated);
categories.add(cat_all);
for (String s : db.getCategories()) {
Log.d("FDroid", "s: " + s);
categories.add(s);
}
categories.add(cat_whatsnew);
categories.add(cat_recentlyupdated);
if (currentCategory == null)
currentCategory = cat_all;
Vector<DB.App> apps = db.getApps(null, null, update, true);
if (apps.isEmpty()) {
@ -348,12 +360,35 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
return;
}
Log.d("FDroid", "Updating lists - " + apps.size() + " apps in total"
+ " (update took " + (System.currentTimeMillis() - startTime)
+ " ms)");
+ " (update took " + (System.currentTimeMillis() - startTime)
+ " ms)");
// Calculate the cutoff date we'll use for What's New and Recently
// Updated...
Calendar recent = Calendar.getInstance();
recent.add(Calendar.DAY_OF_YEAR, -14);
Date recentDate = recent.getTime();
for (DB.App app : apps) {
if (!"All".equals(currentCategory) && !currentCategory.equals(app.category)) {
continue;
if (currentCategory.equals(cat_all)) {
// Let everything through!
} else if (currentCategory.equals(cat_whatsnew)) {
if (app.added == null)
continue;
if (app.added.compareTo(recentDate) < 0)
continue;
} else if (currentCategory.equals(cat_recentlyupdated)) {
if (app.lastUpdated == null)
continue;
// Don't include in the recently updated category if the
// 'update' was actually it being added.
if (app.lastUpdated.compareTo(app.added) == 0)
continue;
if (app.lastUpdated.compareTo(recentDate) < 0)
continue;
} else {
if (!currentCategory.equals(app.category))
continue;
}
if (app.installedVersion == null) {
apps_av.addItem(app);
@ -411,7 +446,8 @@ public class FDroid extends TabActivity implements OnItemClickListener, OnItemSe
}
};
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
public void onItemSelected(AdapterView<?> parent, View view, int pos,
long id) {
currentCategory = parent.getItemAtPosition(pos).toString();
populateLists(false);
}

@ -31,6 +31,8 @@ import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@ -62,6 +64,9 @@ public class RepoXMLHandler extends DefaultHandler {
private String pubkey;
private String hashType;
// The date format used in the repo XML file.
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public RepoXMLHandler(String srv, DB db) {
mserver = srv;
this.db = db;
@ -141,7 +146,12 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.minSdkVersion = 0;
}
} else if (curel.equals("added")) {
curapk.added = str;
try {
curapk.added = str.length() == 0 ? null : mXMLDateFormat
.parse(str);
} catch (ParseException e) {
curapk.added = null;
}
} else if (curel.equals("permissions")) {
curapk.permissions = DB.CommaSeparatedList.make(str);
} else if (curel.equals("features")) {
@ -172,9 +182,19 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("tracker")) {
curapp.trackerURL = str;
} else if (curel.equals("added")) {
curapp.added = str;
try {
curapp.added = str.length() == 0 ? null : mXMLDateFormat
.parse(str);
} catch (ParseException e) {
curapp.added = null;
}
} else if (curel.equals("lastupdated")) {
curapp.lastUpdated = str;
try {
curapp.lastUpdated = str.length() == 0 ? null
: mXMLDateFormat.parse(str);
} catch (ParseException e) {
curapp.lastUpdated = null;
}
} else if (curel.equals("marketversion")) {
curapp.marketVersion = str;
} else if (curel.equals("marketvercode")) {
@ -247,8 +267,8 @@ public class RepoXMLHandler extends DefaultHandler {
throws MalformedURLException, IOException {
FileOutputStream f = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
BufferedInputStream getit = new BufferedInputStream(new URL(url)
.openStream());
BufferedInputStream getit = new BufferedInputStream(
new URL(url).openStream());
BufferedOutputStream bout = new BufferedOutputStream(f, 1024);
byte data[] = new byte[1024];
@ -281,7 +301,8 @@ public class RepoXMLHandler extends DefaultHandler {
String address = repo.address + "/index.jar";
PackageManager pm = ctx.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0);
PackageInfo pi = pm.getPackageInfo(
ctx.getPackageName(), 0);
address += "?" + pi.versionName;
} catch (Exception e) {
}
@ -294,8 +315,8 @@ public class RepoXMLHandler extends DefaultHandler {
je = (JarEntry) jar.getEntry("index.xml");
File efile = new File(ctx.getFilesDir(),
"/tempindex.xml");
InputStream in = new BufferedInputStream(jar
.getInputStream(je), 8192);
InputStream in = new BufferedInputStream(
jar.getInputStream(je), 8192);
OutputStream out = new BufferedOutputStream(
new FileOutputStream(efile), 8192);
byte[] buffer = new byte[8192];
@ -352,18 +373,16 @@ public class RepoXMLHandler extends DefaultHandler {
db);
xr.setContentHandler(handler);
InputStreamReader isr = new FileReader(new File(ctx
.getFilesDir()
+ "/tempindex.xml"));
InputStreamReader isr = new FileReader(new File(
ctx.getFilesDir() + "/tempindex.xml"));
InputSource is = new InputSource(isr);
xr.parse(is);
if (handler.pubkey != null && repo.pubkey == null) {
// We read an unsigned index, but that indicates that
// a signed version is now available...
Log
.d("FDroid",
"Public key found - switching to signed repo for future updates");
Log.d("FDroid",
"Public key found - switching to signed repo for future updates");
repo.pubkey = handler.pubkey;
db.updateRepoByAddress(repo);
}