Merge branch 'master' of git://gitorious.org/f-droid/fdroidclient
This commit is contained in:
commit
299596ae58
@ -15,7 +15,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:ems="20"
|
android:ems="20"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/repo_add_http"/>
|
android:text="https://"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<!--
|
<!--
|
||||||
* Copyright (C) 2009 Roberto Jacinto
|
* Copyright (C) 2009 Roberto Jacinto
|
||||||
|
@ -74,7 +74,6 @@
|
|||||||
<string name="download_server">Getting application from</string>
|
<string name="download_server">Getting application from</string>
|
||||||
|
|
||||||
<string name="repo_add_url">Repository address</string>
|
<string name="repo_add_url">Repository address</string>
|
||||||
<string name="repo_add_http">http://</string>
|
|
||||||
|
|
||||||
<string name="isinst">Installed: </string>
|
<string name="isinst">Installed: </string>
|
||||||
|
|
||||||
@ -153,4 +152,16 @@
|
|||||||
<string name="category_whatsnew">What\'s New</string>
|
<string name="category_whatsnew">What\'s New</string>
|
||||||
<string name="category_recentlyupdated">Recently Updated</string>
|
<string name="category_recentlyupdated">Recently Updated</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
status_download takes four parameters:
|
||||||
|
- Repository (url)
|
||||||
|
- Downloaded size (human readable)
|
||||||
|
- Total size (human readable)
|
||||||
|
- Percentage complete (int between 0-100)
|
||||||
|
-->
|
||||||
|
<string name="status_download">Downloading\n%2$s / %3$s (%4$d%%) from\n%1$s</string>
|
||||||
|
<string name="status_processing_xml">Processing application\n%2$d of %3$d from\n%1$s</string>
|
||||||
|
<string name="status_connecting_to_repo">Connecting to\n%1$s</string>
|
||||||
|
<string name="status_checking_compatibility">Checking all apps compatibility with your device…</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -22,9 +22,9 @@ import java.io.File;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
|
import org.fdroid.fdroid.compat.MenuManager;
|
||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
@ -127,7 +127,7 @@ public class AppDetails extends ListActivity {
|
|||||||
if (apk.detail_size == 0) {
|
if (apk.detail_size == 0) {
|
||||||
size.setText("");
|
size.setText("");
|
||||||
} else {
|
} else {
|
||||||
size.setText(getFriendlySize(apk.detail_size));
|
size.setText(Utils.getFriendlySize(apk.detail_size));
|
||||||
}
|
}
|
||||||
TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
|
TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
|
||||||
if (apk.srcname != null) {
|
if (apk.srcname != null) {
|
||||||
@ -153,19 +153,6 @@ public class AppDetails extends ListActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] FRIENDLY_SIZE_FORMAT = {
|
|
||||||
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" };
|
|
||||||
|
|
||||||
private static String getFriendlySize(int size) {
|
|
||||||
double s = size;
|
|
||||||
int i = 0;
|
|
||||||
while (i < FRIENDLY_SIZE_FORMAT.length - 1 && s >= 1024) {
|
|
||||||
s = (100 * s / 1024) / 100.0;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return String.format(FRIENDLY_SIZE_FORMAT[i], s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int INSTALL = Menu.FIRST;
|
private static final int INSTALL = Menu.FIRST;
|
||||||
private static final int UNINSTALL = Menu.FIRST + 1;
|
private static final int UNINSTALL = Menu.FIRST + 1;
|
||||||
private static final int WEBSITE = Menu.FIRST + 2;
|
private static final int WEBSITE = Menu.FIRST + 2;
|
||||||
@ -249,7 +236,9 @@ public class AppDetails extends ListActivity {
|
|||||||
resetRequired = false;
|
resetRequired = false;
|
||||||
}
|
}
|
||||||
resetViews();
|
resetViews();
|
||||||
invalidateOptionsMenu();
|
|
||||||
|
MenuManager.create(this).invalidateOptionsMenu();
|
||||||
|
|
||||||
if (downloadHandler != null) {
|
if (downloadHandler != null) {
|
||||||
downloadHandler.startUpdates();
|
downloadHandler.startUpdates();
|
||||||
}
|
}
|
||||||
@ -302,7 +291,7 @@ public class AppDetails extends ListActivity {
|
|||||||
|
|
||||||
Log.d("FDroid", "Getting application details for " + appid);
|
Log.d("FDroid", "Getting application details for " + appid);
|
||||||
app = null;
|
app = null;
|
||||||
Vector<DB.App> apps = ((FDroidApp) getApplication()).getApps();
|
List<DB.App> apps = ((FDroidApp) getApplication()).getApps();
|
||||||
for (DB.App tapp : apps) {
|
for (DB.App tapp : apps) {
|
||||||
if (tapp.id.equals(appid)) {
|
if (tapp.id.equals(appid)) {
|
||||||
app = tapp;
|
app = tapp;
|
||||||
@ -797,4 +786,5 @@ public class AppDetails extends ListActivity {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class AppListManager {
|
public class AppListManager {
|
||||||
|
|
||||||
private Vector<DB.App> allApps = null;
|
private List<DB.App> allApps = null;
|
||||||
|
|
||||||
private FDroid fdroidActivity;
|
private FDroid fdroidActivity;
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ public class AppListManager {
|
|||||||
// Needs to be created before createViews(), because that will use the
|
// Needs to be created before createViews(), because that will use the
|
||||||
// getCategoriesAdapter() accessor which expects this object...
|
// getCategoriesAdapter() accessor which expects this object...
|
||||||
categories = new ArrayAdapter<String>(activity,
|
categories = new ArrayAdapter<String>(activity,
|
||||||
android.R.layout.simple_spinner_item, new Vector<String>());
|
android.R.layout.simple_spinner_item, new ArrayList<String>());
|
||||||
categories
|
categories
|
||||||
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ public class AppListManager {
|
|||||||
Date recentDate = calcMaxHistory();
|
Date recentDate = calcMaxHistory();
|
||||||
AppFilter appFilter = new AppFilter(fdroidActivity);
|
AppFilter appFilter = new AppFilter(fdroidActivity);
|
||||||
|
|
||||||
Vector<DB.App> availApps = new Vector<DB.App>();
|
List<DB.App> availApps = new ArrayList<DB.App>();
|
||||||
for (DB.App app : allApps) {
|
for (DB.App app : allApps) {
|
||||||
|
|
||||||
boolean isInCategory = isInCategory(app, currentCategory, recentDate);
|
boolean isInCategory = isInCategory(app, currentCategory, recentDate);
|
||||||
@ -215,7 +215,10 @@ public class AppListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentCategory(String currentCategory) {
|
public void setCurrentCategory(String currentCategory) {
|
||||||
this.currentCategory = currentCategory;
|
if (!this.currentCategory.equals(currentCategory)){
|
||||||
|
this.currentCategory = currentCategory;
|
||||||
|
repopulateLists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class WhatsNewComparator implements Comparator<DB.App> {
|
static class WhatsNewComparator implements Comparator<DB.App> {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
|
* Copyright (C) 2010-13 Ciaran Gultnieks, ciaran@ciarang.com
|
||||||
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@ -21,6 +21,7 @@ package org.fdroid.fdroid;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -28,7 +29,6 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
@ -41,7 +41,6 @@ import android.content.pm.PackageManager;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.text.TextUtils.SimpleStringSplitter;
|
import android.text.TextUtils.SimpleStringSplitter;
|
||||||
@ -116,7 +115,7 @@ public class DB {
|
|||||||
updated = false;
|
updated = false;
|
||||||
added = null;
|
added = null;
|
||||||
lastUpdated = null;
|
lastUpdated = null;
|
||||||
apks = new Vector<Apk>();
|
apks = new ArrayList<Apk>();
|
||||||
detail_Populated = false;
|
detail_Populated = false;
|
||||||
compatible = false;
|
compatible = false;
|
||||||
}
|
}
|
||||||
@ -181,7 +180,7 @@ public class DB {
|
|||||||
public boolean updated;
|
public boolean updated;
|
||||||
|
|
||||||
// List of apks.
|
// List of apks.
|
||||||
public Vector<Apk> apks;
|
public List<Apk> apks;
|
||||||
|
|
||||||
// Get the current version - this will be one of the Apks from 'apks'.
|
// Get the current version - this will be one of the Apks from 'apks'.
|
||||||
// Can return null if there are no available versions.
|
// Can return null if there are no available versions.
|
||||||
@ -278,28 +277,23 @@ public class DB {
|
|||||||
// check if an APK is compatible with the user's device.
|
// check if an APK is compatible with the user's device.
|
||||||
public static abstract class CompatibilityChecker {
|
public static abstract class CompatibilityChecker {
|
||||||
|
|
||||||
// Because Build.VERSION.SDK_INT requires API level 5
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
protected final static int SDK_INT = Integer
|
|
||||||
.parseInt(Build.VERSION.SDK);
|
|
||||||
|
|
||||||
public abstract boolean isCompatible(Apk apk);
|
public abstract boolean isCompatible(Apk apk);
|
||||||
|
|
||||||
public static CompatibilityChecker getChecker(Context ctx) {
|
public static CompatibilityChecker getChecker(Context ctx) {
|
||||||
CompatibilityChecker checker;
|
CompatibilityChecker checker;
|
||||||
if (SDK_INT >= 5)
|
if (Utils.hasApi(5))
|
||||||
checker = new EclairChecker(ctx);
|
checker = new EclairChecker(ctx);
|
||||||
else
|
else
|
||||||
checker = new BasicChecker();
|
checker = new BasicChecker();
|
||||||
Log.d("FDroid", "Compatibility checker for API level "
|
Log.d("FDroid", "Compatibility checker for API level "
|
||||||
+ SDK_INT + ": " + checker.getClass().getName());
|
+ Utils.getApi() + ": " + checker.getClass().getName());
|
||||||
return checker;
|
return checker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BasicChecker extends CompatibilityChecker {
|
private static class BasicChecker extends CompatibilityChecker {
|
||||||
public boolean isCompatible(Apk apk) {
|
public boolean isCompatible(Apk apk) {
|
||||||
return (apk.minSdkVersion <= SDK_INT);
|
return (apk.minSdkVersion <= Utils.getApi());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +323,7 @@ public class DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCompatible(Apk apk) {
|
public boolean isCompatible(Apk apk) {
|
||||||
if (apk.minSdkVersion > SDK_INT)
|
if (apk.minSdkVersion > Utils.getApi())
|
||||||
return false;
|
return false;
|
||||||
if (apk.features != null) {
|
if (apk.features != null) {
|
||||||
for (String feat : apk.features) {
|
for (String feat : apk.features) {
|
||||||
@ -379,6 +373,7 @@ public class DB {
|
|||||||
public static void resetTransient(SQLiteDatabase db) {
|
public static void resetTransient(SQLiteDatabase db) {
|
||||||
db.execSQL("drop table " + TABLE_APP);
|
db.execSQL("drop table " + TABLE_APP);
|
||||||
db.execSQL("drop table " + TABLE_APK);
|
db.execSQL("drop table " + TABLE_APK);
|
||||||
|
db.execSQL("update " + TABLE_REPO + " set lastetag = NULL");
|
||||||
createAppApk(db);
|
createAppApk(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +407,7 @@ public class DB {
|
|||||||
// Migrate repo list to new structure. (No way to change primary
|
// Migrate repo list to new structure. (No way to change primary
|
||||||
// key in sqlite - table must be recreated)
|
// key in sqlite - table must be recreated)
|
||||||
if (oldVersion < 20) {
|
if (oldVersion < 20) {
|
||||||
Vector<Repo> oldrepos = new Vector<Repo>();
|
List<Repo> oldrepos = new ArrayList<Repo>();
|
||||||
Cursor c = db.rawQuery("select address, inuse, pubkey from "
|
Cursor c = db.rawQuery("select address, inuse, pubkey from "
|
||||||
+ TABLE_REPO, null);
|
+ TABLE_REPO, null);
|
||||||
c.moveToFirst();
|
c.moveToFirst();
|
||||||
@ -503,7 +498,7 @@ public class DB {
|
|||||||
// Get the number of apps that have updates available. This can be a
|
// Get the number of apps that have updates available. This can be a
|
||||||
// time consuming operation.
|
// time consuming operation.
|
||||||
public int getNumUpdates() {
|
public int getNumUpdates() {
|
||||||
Vector<App> apps = getApps(true);
|
List<App> apps = getApps(true);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (App app : apps) {
|
for (App app : apps) {
|
||||||
if (app.hasUpdates)
|
if (app.hasUpdates)
|
||||||
@ -512,8 +507,8 @@ public class DB {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector<String> getCategories() {
|
public List<String> getCategories() {
|
||||||
Vector<String> result = new Vector<String>();
|
List<String> result = new ArrayList<String>();
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
try {
|
try {
|
||||||
c = db.rawQuery("select distinct category from " + TABLE_APP
|
c = db.rawQuery("select distinct category from " + TABLE_APP
|
||||||
@ -591,14 +586,15 @@ public class DB {
|
|||||||
// Return a list of apps matching the given criteria. Filtering is
|
// Return a list of apps matching the given criteria. Filtering is
|
||||||
// also done based on compatibility and anti-features according to
|
// also done based on compatibility and anti-features according to
|
||||||
// the user's current preferences.
|
// the user's current preferences.
|
||||||
public Vector<App> getApps(boolean getinstalledinfo) {
|
public List<App> getApps(boolean getinstalledinfo) {
|
||||||
|
|
||||||
// If we're going to need it, get info in what's currently installed
|
// If we're going to need it, get info in what's currently installed
|
||||||
Map<String, PackageInfo> systemApks = null;
|
Map<String, PackageInfo> systemApks = null;
|
||||||
if (getinstalledinfo) {
|
if (getinstalledinfo) {
|
||||||
Log.d("FDroid", "Reading installed packages");
|
Log.d("FDroid", "Reading installed packages");
|
||||||
systemApks = new HashMap<String, PackageInfo>();
|
systemApks = new HashMap<String, PackageInfo>();
|
||||||
List<PackageInfo> installedPackages = mContext.getPackageManager().getInstalledPackages(0);
|
List<PackageInfo> installedPackages = mContext.getPackageManager()
|
||||||
|
.getInstalledPackages(0);
|
||||||
for (PackageInfo appInfo : installedPackages) {
|
for (PackageInfo appInfo : installedPackages) {
|
||||||
systemApks.put(appInfo.packageName, appInfo);
|
systemApks.put(appInfo.packageName, appInfo);
|
||||||
}
|
}
|
||||||
@ -696,7 +692,7 @@ public class DB {
|
|||||||
+ (System.currentTimeMillis() - startTime) + " ms)");
|
+ (System.currentTimeMillis() - startTime) + " ms)");
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<App> result = new Vector<App>(apps.values());
|
List<App> result = new ArrayList<App>(apps.values());
|
||||||
Collections.sort(result);
|
Collections.sort(result);
|
||||||
|
|
||||||
// Fill in the hasUpdates fields if we have the necessary information...
|
// Fill in the hasUpdates fields if we have the necessary information...
|
||||||
@ -720,9 +716,9 @@ public class DB {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector<String> doSearch(String query) {
|
public List<String> doSearch(String query) {
|
||||||
|
|
||||||
Vector<String> ids = new Vector<String>();
|
List<String> ids = new ArrayList<String>();
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
try {
|
try {
|
||||||
String filter = "%" + query + "%";
|
String filter = "%" + query + "%";
|
||||||
@ -770,11 +766,11 @@ public class DB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector<App> updateApps = null;
|
private List<App> updateApps = null;
|
||||||
|
|
||||||
// Called before a repo update starts. Returns the number of updates
|
// Called before a repo update starts. Returns the number of updates
|
||||||
// available beforehand.
|
// available beforehand.
|
||||||
public int beginUpdate(Vector<DB.App> apps) {
|
public int beginUpdate(List<DB.App> apps) {
|
||||||
// Get a list of all apps. All the apps and apks in this list will
|
// Get a list of all apps. All the apps and apks in this list will
|
||||||
// have 'updated' set to false at this point, and we will only set
|
// have 'updated' set to false at this point, and we will only set
|
||||||
// it to true when we see the app/apk in a repository. Thus, at the
|
// it to true when we see the app/apk in a repository. Thus, at the
|
||||||
@ -865,7 +861,7 @@ public class DB {
|
|||||||
// compatible apk - if it's not, leave it out)
|
// compatible apk - if it's not, leave it out)
|
||||||
// Also keep a list of which were compatible, because they're the
|
// Also keep a list of which were compatible, because they're the
|
||||||
// only ones we'll add, unless the showIncompatible preference is set.
|
// only ones we'll add, unless the showIncompatible preference is set.
|
||||||
Vector<Apk> compatibleapks = new Vector<Apk>();
|
List<Apk> compatibleapks = new ArrayList<Apk>();
|
||||||
for (Apk apk : upapp.apks) {
|
for (Apk apk : upapp.apks) {
|
||||||
if (compatChecker.isCompatible(apk)) {
|
if (compatChecker.isCompatible(apk)) {
|
||||||
apk.compatible = true;
|
apk.compatible = true;
|
||||||
@ -1016,8 +1012,8 @@ public class DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of the configured repositories.
|
// Get a list of the configured repositories.
|
||||||
public Vector<Repo> getRepos() {
|
public List<Repo> getRepos() {
|
||||||
Vector<Repo> repos = new Vector<Repo>();
|
List<Repo> repos = new ArrayList<Repo>();
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
try {
|
try {
|
||||||
c = db.rawQuery(
|
c = db.rawQuery(
|
||||||
@ -1078,10 +1074,27 @@ public class DB {
|
|||||||
db.insert(TABLE_REPO, null, values);
|
db.insert(TABLE_REPO, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeServers(Vector<String> addresses) {
|
public void removeRepos(List<String> addresses) {
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try {
|
try {
|
||||||
for (String address : addresses) {
|
for (String address : addresses) {
|
||||||
|
|
||||||
|
// Before removing the repo, remove any apks that are
|
||||||
|
// connected to it...
|
||||||
|
Cursor c = null;
|
||||||
|
try {
|
||||||
|
c = db.rawQuery("select id from " + TABLE_REPO
|
||||||
|
+ " where address = '" + address + "'", null);
|
||||||
|
c.moveToFirst();
|
||||||
|
if (!c.isAfterLast()) {
|
||||||
|
db.delete(TABLE_APK, "repo = ?",
|
||||||
|
new String[] { Integer.toString(c.getInt(0)) });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (c != null) {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
db.delete(TABLE_REPO, "address = ?", new String[] { address });
|
db.delete(TABLE_REPO, "address = ?", new String[] { address });
|
||||||
}
|
}
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -19,28 +19,17 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.app.ActionBar;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import org.fdroid.fdroid.DB.App;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
|
||||||
import android.R.drawable;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.AlertDialog.Builder;
|
import android.app.AlertDialog.Builder;
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
@ -52,7 +41,7 @@ import android.view.Menu;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import android.widget.TabHost.TabSpec;
|
import org.fdroid.fdroid.compat.TabManager;
|
||||||
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
|
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
|
||||||
|
|
||||||
public class FDroid extends FragmentActivity {
|
public class FDroid extends FragmentActivity {
|
||||||
@ -75,8 +64,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
|
|
||||||
private AppListManager manager = null;
|
private AppListManager manager = null;
|
||||||
|
|
||||||
// Used by pre 3.0 devices which don't have an ActionBar...
|
private TabManager tabManager = null;
|
||||||
private TabHost tabHost;
|
|
||||||
|
|
||||||
public AppListManager getManager() {
|
public AppListManager getManager() {
|
||||||
return manager;
|
return manager;
|
||||||
@ -89,7 +77,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
manager = new AppListManager(this);
|
manager = new AppListManager(this);
|
||||||
setContentView(R.layout.fdroid);
|
setContentView(R.layout.fdroid);
|
||||||
createViews();
|
createViews();
|
||||||
createTabs();
|
getTabManager().createTabs();
|
||||||
|
|
||||||
// Must be done *after* createViews, because it will involve a
|
// Must be done *after* createViews, because it will involve a
|
||||||
// callback to update the tab label for the "update" tab. This
|
// callback to update the tab label for the "update" tab. This
|
||||||
@ -104,7 +92,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
} else if (i.hasExtra(EXTRA_TAB_UPDATE)) {
|
} else if (i.hasExtra(EXTRA_TAB_UPDATE)) {
|
||||||
boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
|
boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
|
||||||
if (showUpdateTab) {
|
if (showUpdateTab) {
|
||||||
selectTab(2);
|
getTabManager().selectTab(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,127 +238,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
viewPager.setAdapter(viewPageAdapter);
|
viewPager.setAdapter(viewPageAdapter);
|
||||||
viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
|
viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
selectTab(position);
|
getTabManager().selectTab(position);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTabs() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
createActionBarTabs();
|
|
||||||
} else {
|
|
||||||
createOldTabs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectTab(int index) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
getActionBar().setSelectedNavigationItem(index);
|
|
||||||
} else {
|
|
||||||
tabHost.setCurrentTab(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshUpdateTabLabel() {
|
|
||||||
final int INDEX = 2;
|
|
||||||
CharSequence text = viewPager.getAdapter().getPageTitle(INDEX);
|
|
||||||
if ( Build.VERSION.SDK_INT >= 11) {
|
|
||||||
getActionBar().getTabAt(INDEX).setText(text);
|
|
||||||
} else {
|
|
||||||
// Update the count on the 'Updates' tab to show the number available.
|
|
||||||
// This is quite unpleasant, but seems to be the only way to do it.
|
|
||||||
TextView textView = (TextView) tabHost.getTabWidget().getChildAt(2)
|
|
||||||
.findViewById(android.R.id.title);
|
|
||||||
textView.setText(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createActionBarTabs() {
|
|
||||||
final ActionBar actionBar = getActionBar();
|
|
||||||
final ViewPager pager = viewPager;
|
|
||||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
|
||||||
for (int i = 0; i < viewPager.getAdapter().getCount(); i ++) {
|
|
||||||
CharSequence label = viewPager.getAdapter().getPageTitle(i);
|
|
||||||
actionBar.addTab(
|
|
||||||
actionBar.newTab()
|
|
||||||
.setText(label)
|
|
||||||
.setTabListener(new ActionBar.TabListener() {
|
|
||||||
public void onTabSelected(ActionBar.Tab tab,
|
|
||||||
FragmentTransaction ft) {
|
|
||||||
pager.setCurrentItem(tab.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* There is a bit of boiler-plate code required to get a TabWidget showing,
|
|
||||||
* which includes creating a TabHost, populating it with the TabWidget,
|
|
||||||
* and giving it a FrameLayout as a child. This will make the tabs have
|
|
||||||
* dummy empty contents and then hook them up to our ViewPager.
|
|
||||||
*/
|
|
||||||
private void createOldTabs() {
|
|
||||||
tabHost = new TabHost(this);
|
|
||||||
tabHost.setLayoutParams(new TabHost.LayoutParams(
|
|
||||||
TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
TabWidget tabWidget = new TabWidget(this);
|
|
||||||
tabWidget.setId(android.R.id.tabs);
|
|
||||||
tabHost.setLayoutParams(new TabHost.LayoutParams(
|
|
||||||
TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
FrameLayout layout = new FrameLayout(this);
|
|
||||||
layout.setId(android.R.id.tabcontent);
|
|
||||||
layout.setLayoutParams(new TabWidget.LayoutParams(0, 0));
|
|
||||||
|
|
||||||
tabHost.addView(tabWidget);
|
|
||||||
tabHost.addView(layout);
|
|
||||||
tabHost.setup();
|
|
||||||
|
|
||||||
TabHost.TabContentFactory factory = new TabHost.TabContentFactory() {
|
|
||||||
@Override
|
|
||||||
public View createTabContent(String tag) {
|
|
||||||
return new View(FDroid.this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TabSpec availableTabSpec = tabHost.newTabSpec("available")
|
|
||||||
.setIndicator(
|
|
||||||
getString(R.string.tab_noninstalled),
|
|
||||||
getResources().getDrawable(android.R.drawable.ic_input_add))
|
|
||||||
.setContent(factory);
|
|
||||||
|
|
||||||
TabSpec installedTabSpec = tabHost.newTabSpec("installed")
|
|
||||||
.setIndicator(
|
|
||||||
getString(R.string.tab_installed),
|
|
||||||
getResources().getDrawable(android.R.drawable.star_off))
|
|
||||||
.setContent(factory);
|
|
||||||
|
|
||||||
TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate")
|
|
||||||
.setIndicator(
|
|
||||||
getString(R.string.tab_updates),
|
|
||||||
getResources().getDrawable(android.R.drawable.star_on))
|
|
||||||
.setContent(factory);
|
|
||||||
|
|
||||||
tabHost.addTab(availableTabSpec);
|
|
||||||
tabHost.addTab(installedTabSpec);
|
|
||||||
tabHost.addTab(canUpdateTabSpec);
|
|
||||||
|
|
||||||
LinearLayout contentView = (LinearLayout)findViewById(R.id.fdroid_layout);
|
|
||||||
contentView.addView(tabHost, 0);
|
|
||||||
|
|
||||||
tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onTabChanged(String tabId) {
|
|
||||||
viewPager.setCurrentItem(tabHost.getCurrentTab());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -384,13 +252,19 @@ public class FDroid extends FragmentActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||||
if (resultCode == 1) {
|
String message = resultData.getString(UpdateService.RESULT_MESSAGE);
|
||||||
Toast.makeText(FDroid.this, resultData.getString("errmsg"),
|
boolean finished = false;
|
||||||
Toast.LENGTH_LONG).show();
|
if (resultCode == UpdateService.STATUS_ERROR) {
|
||||||
} else {
|
Toast.makeText(FDroid.this, message, Toast.LENGTH_LONG).show();
|
||||||
|
finished = true;
|
||||||
|
} else if (resultCode == UpdateService.STATUS_COMPLETE) {
|
||||||
repopulateViews();
|
repopulateViews();
|
||||||
|
finished = true;
|
||||||
|
} else if (resultCode == UpdateService.STATUS_INFO) {
|
||||||
|
pd.setMessage(message);
|
||||||
}
|
}
|
||||||
if (pd.isShowing())
|
|
||||||
|
if (finished && pd.isShowing())
|
||||||
pd.dismiss();
|
pd.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,7 +283,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
boolean hasTriedEmptyUpdate = getPreferences(MODE_PRIVATE).getBoolean(TRIED_EMPTY_UPDATE, false);
|
boolean hasTriedEmptyUpdate = getPreferences(MODE_PRIVATE).getBoolean(TRIED_EMPTY_UPDATE, false);
|
||||||
if (!hasTriedEmptyUpdate) {
|
if (!hasTriedEmptyUpdate) {
|
||||||
Log.d("FDroid", "Empty app list, and we haven't done an update yet. Forcing repo update.");
|
Log.d("FDroid", "Empty app list, and we haven't done an update yet. Forcing repo update.");
|
||||||
getPreferences(MODE_PRIVATE).edit().putBoolean(TRIED_EMPTY_UPDATE, true).apply();
|
getPreferences(MODE_PRIVATE).edit().putBoolean(TRIED_EMPTY_UPDATE, true).commit();
|
||||||
updateRepos();
|
updateRepos();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -434,4 +308,15 @@ public class FDroid extends FragmentActivity {
|
|||||||
startService(intent);
|
startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TabManager getTabManager() {
|
||||||
|
if (tabManager == null) {
|
||||||
|
tabManager = TabManager.create(this, viewPager);
|
||||||
|
}
|
||||||
|
return tabManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshUpdateTabLabel() {
|
||||||
|
getTabManager().refreshTabLabel(TabManager.INDEX_CAN_UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Vector;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
@ -47,7 +48,7 @@ public class FDroidApp extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Global list of all known applications.
|
// Global list of all known applications.
|
||||||
private Vector<DB.App> apps;
|
private List<DB.App> apps;
|
||||||
|
|
||||||
// Set when something has changed (database or installed apps) so we know
|
// Set when something has changed (database or installed apps) so we know
|
||||||
// we should invalidate the apps.
|
// we should invalidate the apps.
|
||||||
@ -70,7 +71,7 @@ public class FDroidApp extends Application {
|
|||||||
// Get a list of all known applications. Should not be called when the
|
// Get a list of all known applications. Should not be called when the
|
||||||
// database is locked (i.e. between DB.getDB() and db.releaseDB(). The
|
// database is locked (i.e. between DB.getDB() and db.releaseDB(). The
|
||||||
// contents should never be modified, it's for reading only.
|
// contents should never be modified, it's for reading only.
|
||||||
public Vector<DB.App> getApps() {
|
public List<DB.App> getApps() {
|
||||||
|
|
||||||
boolean invalid = false;
|
boolean invalid = false;
|
||||||
try {
|
try {
|
||||||
@ -95,7 +96,7 @@ public class FDroidApp extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (apps == null)
|
if (apps == null)
|
||||||
return new Vector<DB.App>();
|
return new ArrayList<DB.App>();
|
||||||
return apps;
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import java.util.Formatter;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.AlertDialog.Builder;
|
import android.app.AlertDialog.Builder;
|
||||||
@ -55,7 +54,7 @@ public class ManageRepo extends ListActivity {
|
|||||||
|
|
||||||
private boolean changed = false;
|
private boolean changed = false;
|
||||||
|
|
||||||
private Vector<DB.Repo> repos;
|
private List<DB.Repo> repos;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -201,7 +200,7 @@ public class ManageRepo extends ListActivity {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case REM_REPO:
|
case REM_REPO:
|
||||||
final Vector<String> rem_lst = new Vector<String>();
|
final List<String> rem_lst = new ArrayList<String>();
|
||||||
CharSequence[] b = new CharSequence[repos.size()];
|
CharSequence[] b = new CharSequence[repos.size()];
|
||||||
for (int i = 0; i < repos.size(); i++) {
|
for (int i = 0; i < repos.size(); i++) {
|
||||||
b[i] = repos.get(i).address;
|
b[i] = repos.get(i).address;
|
||||||
@ -215,9 +214,9 @@ public class ManageRepo extends ListActivity {
|
|||||||
public void onClick(DialogInterface dialog,
|
public void onClick(DialogInterface dialog,
|
||||||
int whichButton, boolean isChecked) {
|
int whichButton, boolean isChecked) {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
rem_lst.addElement(repos.get(whichButton).address);
|
rem_lst.add(repos.get(whichButton).address);
|
||||||
} else {
|
} else {
|
||||||
rem_lst.removeElement(repos.get(whichButton).address);
|
rem_lst.remove(repos.get(whichButton).address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -227,7 +226,7 @@ public class ManageRepo extends ListActivity {
|
|||||||
int whichButton) {
|
int whichButton) {
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
db.removeServers(rem_lst);
|
db.removeRepos(rem_lst);
|
||||||
} finally {
|
} finally {
|
||||||
DB.releaseDB();
|
DB.releaseDB();
|
||||||
}
|
}
|
||||||
|
54
src/org/fdroid/fdroid/ProgressListener.java
Normal file
54
src/org/fdroid/fdroid/ProgressListener.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public interface ProgressListener {
|
||||||
|
|
||||||
|
public void onProgress(Event event);
|
||||||
|
|
||||||
|
// I went a bit overboard with the overloaded constructors, but they all
|
||||||
|
// seemed potentially useful and unambiguous, so I just put them in there
|
||||||
|
// while I'm here.
|
||||||
|
public static class Event {
|
||||||
|
|
||||||
|
public static final int NO_VALUE = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
public final int type;
|
||||||
|
public final Bundle data;
|
||||||
|
|
||||||
|
// These two are not final, so that you can create a template Event,
|
||||||
|
// pass it into a function which performs something over time, and
|
||||||
|
// that function can initialize "total" and progressively
|
||||||
|
// update "progress"
|
||||||
|
public int progress;
|
||||||
|
public int total;
|
||||||
|
|
||||||
|
public Event(int type) {
|
||||||
|
this(type, NO_VALUE, NO_VALUE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type, Bundle data) {
|
||||||
|
this(type, NO_VALUE, NO_VALUE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type, int progress) {
|
||||||
|
this(type, progress, NO_VALUE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type, int progress, Bundle data) {
|
||||||
|
this(type, NO_VALUE, NO_VALUE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type, int progress, int total) {
|
||||||
|
this(type, progress, total, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int type, int progress, int total, Bundle data) {
|
||||||
|
this.type = type;
|
||||||
|
this.progress = progress;
|
||||||
|
this.total = total;
|
||||||
|
this.data = data == null ? new Bundle() : data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,14 +26,13 @@ import java.io.FileReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Vector;
|
import java.util.List;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
@ -41,6 +40,7 @@ import javax.net.ssl.SSLHandshakeException;
|
|||||||
import javax.xml.parsers.SAXParser;
|
import javax.xml.parsers.SAXParser;
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
@ -54,10 +54,10 @@ import android.util.Log;
|
|||||||
|
|
||||||
public class RepoXMLHandler extends DefaultHandler {
|
public class RepoXMLHandler extends DefaultHandler {
|
||||||
|
|
||||||
// The ID of the repo we're processing.
|
// The repo we're processing.
|
||||||
private int repo;
|
private DB.Repo repo;
|
||||||
|
|
||||||
private Vector<DB.App> apps;
|
private List<DB.App> apps;
|
||||||
|
|
||||||
private DB.App curapp = null;
|
private DB.App curapp = null;
|
||||||
private DB.Apk curapk = null;
|
private DB.Apk curapk = null;
|
||||||
@ -66,13 +66,23 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
private String pubkey;
|
private String pubkey;
|
||||||
private String hashType;
|
private String hashType;
|
||||||
|
|
||||||
|
private int progressCounter = 0;
|
||||||
|
private ProgressListener progressListener;
|
||||||
|
|
||||||
|
public static final int PROGRESS_TYPE_DOWNLOAD = 1;
|
||||||
|
public static final int PROGRESS_TYPE_PROCESS_XML = 2;
|
||||||
|
|
||||||
|
public static final String PROGRESS_DATA_REPO = "repo";
|
||||||
|
|
||||||
// The date format used in the repo XML file.
|
// The date format used in the repo XML file.
|
||||||
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
private int totalAppCount;
|
||||||
|
|
||||||
public RepoXMLHandler(int repo, Vector<DB.App> apps) {
|
public RepoXMLHandler(DB.Repo repo, List<DB.App> apps, ProgressListener listener) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
this.apps = apps;
|
this.apps = apps;
|
||||||
pubkey = null;
|
pubkey = null;
|
||||||
|
progressListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -219,27 +229,37 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
curapp.requirements = DB.CommaSeparatedList.make(str);
|
curapp.requirements = DB.CommaSeparatedList.make(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Bundle createProgressData(String repoAddress) {
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putString(PROGRESS_DATA_REPO, repoAddress);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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 == "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;
|
||||||
} else if (localName == "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;
|
||||||
} else if (localName == "package" && curapp != null && curapk == null) {
|
Bundle progressData = createProgressData(repo.address);
|
||||||
|
progressCounter ++;
|
||||||
|
progressListener.onProgress(
|
||||||
|
new ProgressListener.Event(
|
||||||
|
RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter,
|
||||||
|
totalAppCount, progressData));
|
||||||
|
} 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;
|
curapk.repo = repo.id;
|
||||||
hashType = null;
|
hashType = null;
|
||||||
} else if (localName == "hash" && curapk != null) {
|
} else if (localName.equals("hash") && curapk != null) {
|
||||||
hashType = attributes.getValue("", "type");
|
hashType = attributes.getValue("", "type");
|
||||||
}
|
}
|
||||||
curchars.setLength(0);
|
curchars.setLength(0);
|
||||||
@ -252,29 +272,39 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
// empty) may contain an etag value for the response, or it may be left
|
// empty) may contain an etag value for the response, or it may be left
|
||||||
// empty if none was available.
|
// empty if none was available.
|
||||||
private static int getRemoteFile(Context ctx, String url, String dest,
|
private static int getRemoteFile(Context ctx, String url, String dest,
|
||||||
String etag, StringBuilder retag) throws MalformedURLException,
|
String etag, StringBuilder retag,
|
||||||
|
ProgressListener progressListener,
|
||||||
|
ProgressListener.Event progressEvent) throws MalformedURLException,
|
||||||
IOException {
|
IOException {
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
URL u = new URL(url);
|
URL u = new URL(url);
|
||||||
HttpURLConnection uc = (HttpURLConnection) u.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) u.openConnection();
|
||||||
if (etag != null)
|
if (etag != null)
|
||||||
uc.setRequestProperty("If-None-Match", etag);
|
connection.setRequestProperty("If-None-Match", etag);
|
||||||
int totalBytes = 0;
|
int totalBytes = 0;
|
||||||
int code = uc.getResponseCode();
|
int code = connection.getResponseCode();
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
|
// Testing in the emulator for me, showed that figuring out the filesize took about 1 to 1.5 seconds.
|
||||||
|
// To put this in context, downloading a repo of:
|
||||||
|
// - 400k takes ~6 seconds
|
||||||
|
// - 5k takes ~3 seconds
|
||||||
|
// on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will
|
||||||
|
// become more worth it.
|
||||||
|
progressEvent.total = connection.getContentLength();
|
||||||
|
Log.d("FDroid", "Downloading " + progressEvent.total + " bytes from " + url);
|
||||||
InputStream input = null;
|
InputStream input = null;
|
||||||
OutputStream output = null;
|
OutputStream output = null;
|
||||||
try {
|
try {
|
||||||
input = new URL(url).openStream();
|
input = connection.getInputStream();
|
||||||
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
|
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
|
||||||
Utils.copy(input, output);
|
Utils.copy(input, output, progressListener, progressEvent);
|
||||||
} finally {
|
} finally {
|
||||||
Utils.closeQuietly(output);
|
Utils.closeQuietly(output);
|
||||||
Utils.closeQuietly(input);
|
Utils.closeQuietly(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
String et = uc.getHeaderField("ETag");
|
String et = connection.getHeaderField("ETag");
|
||||||
if (et != null)
|
if (et != null)
|
||||||
retag.append(et);
|
retag.append(et);
|
||||||
}
|
}
|
||||||
@ -293,7 +323,8 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
// value for the index that was successfully processed, or it may contain
|
// value for the index that was successfully processed, or it may contain
|
||||||
// null if none was available.
|
// null if none was available.
|
||||||
public static String doUpdate(Context ctx, DB.Repo repo,
|
public static String doUpdate(Context ctx, DB.Repo repo,
|
||||||
Vector<DB.App> apps, StringBuilder newetag, Vector<Integer> keeprepos) {
|
List<DB.App> apps, StringBuilder newetag, List<Integer> keeprepos,
|
||||||
|
ProgressListener progressListener) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
int code = 0;
|
int code = 0;
|
||||||
@ -309,8 +340,11 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
address += "?" + pi.versionName;
|
address += "?" + pi.versionName;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
Bundle progressData = createProgressData(repo.address);
|
||||||
|
ProgressListener.Event event = new ProgressListener.Event(
|
||||||
|
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, progressData);
|
||||||
code = getRemoteFile(ctx, address, "tempindex.jar",
|
code = getRemoteFile(ctx, address, "tempindex.jar",
|
||||||
repo.lastetag, newetag);
|
repo.lastetag, newetag, progressListener, event );
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
|
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
|
||||||
JarFile jar = null;
|
JarFile jar = null;
|
||||||
@ -365,8 +399,12 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
|
|
||||||
// It's an old-fashioned unsigned repo...
|
// It's an old-fashioned unsigned repo...
|
||||||
Log.d("FDroid", "Getting unsigned index from " + repo.address);
|
Log.d("FDroid", "Getting unsigned index from " + repo.address);
|
||||||
|
Bundle eventData = createProgressData(repo.address);
|
||||||
|
ProgressListener.Event event = new ProgressListener.Event(
|
||||||
|
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, eventData);
|
||||||
code = getRemoteFile(ctx, repo.address + "/index.xml",
|
code = getRemoteFile(ctx, repo.address + "/index.xml",
|
||||||
"tempindex.xml", repo.lastetag, newetag);
|
"tempindex.xml", repo.lastetag, newetag,
|
||||||
|
progressListener, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
@ -374,11 +412,22 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||||
SAXParser sp = spf.newSAXParser();
|
SAXParser sp = spf.newSAXParser();
|
||||||
XMLReader xr = sp.getXMLReader();
|
XMLReader xr = sp.getXMLReader();
|
||||||
RepoXMLHandler handler = new RepoXMLHandler(repo.id, apps);
|
RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
|
||||||
xr.setContentHandler(handler);
|
xr.setContentHandler(handler);
|
||||||
|
|
||||||
Reader r = new BufferedReader(new FileReader(new File(
|
File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml");
|
||||||
ctx.getFilesDir() + "/tempindex.xml")));
|
BufferedReader r = new BufferedReader(new FileReader(tempIndex));
|
||||||
|
|
||||||
|
// A bit of a hack, this might return false positives if an apps description
|
||||||
|
// or some other part of the XML file contains this, but it is a pretty good
|
||||||
|
// estimate and makes the progress counter more informative.
|
||||||
|
// As with asking the server about the size of the index before downloading,
|
||||||
|
// this also has a time tradeoff. It takes about three seconds to iterate
|
||||||
|
// through the file and count 600 apps on a slow emulator (v17), but if it is
|
||||||
|
// taking two minutes to update, the three second wait may be worth it.
|
||||||
|
final String APPLICATION = "<application";
|
||||||
|
handler.setTotalAppCount(Utils.countSubstringOccurrence(tempIndex, APPLICATION));
|
||||||
|
|
||||||
InputSource is = new InputSource(r);
|
InputSource is = new InputSource(r);
|
||||||
xr.parse(is);
|
xr.parse(is);
|
||||||
|
|
||||||
@ -427,4 +476,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTotalAppCount(int totalAppCount) {
|
||||||
|
this.totalAppCount = totalAppCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import java.util.Vector;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.ListActivity;
|
import android.app.ListActivity;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
@ -68,7 +69,7 @@ public class SearchResults extends ListActivity {
|
|||||||
|
|
||||||
private void updateView() {
|
private void updateView() {
|
||||||
|
|
||||||
Vector<String> matchingids = new Vector<String>();
|
List<String> matchingids = new ArrayList<String>();
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
matchingids = db.doSearch(mQuery);
|
matchingids = db.doSearch(mQuery);
|
||||||
@ -78,9 +79,9 @@ public class SearchResults extends ListActivity {
|
|||||||
DB.releaseDB();
|
DB.releaseDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<DB.App> apps = new Vector<DB.App>();
|
List<DB.App> apps = new ArrayList<DB.App>();
|
||||||
AppFilter appfilter = new AppFilter(this);
|
AppFilter appfilter = new AppFilter(this);
|
||||||
Vector<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
|
List<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
|
||||||
for (DB.App tapp : tapps) {
|
for (DB.App tapp : tapps) {
|
||||||
boolean include = false;
|
boolean include = false;
|
||||||
for (String tid : matchingids) {
|
for (String tid : matchingids) {
|
||||||
|
@ -24,7 +24,8 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Vector;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
@ -41,7 +42,14 @@ import android.os.SystemClock;
|
|||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
public class UpdateService extends IntentService {
|
public class UpdateService extends IntentService implements ProgressListener {
|
||||||
|
|
||||||
|
public static final String RESULT_MESSAGE = "msg";
|
||||||
|
public static final int STATUS_COMPLETE = 0;
|
||||||
|
public static final int STATUS_ERROR = 1;
|
||||||
|
public static final int STATUS_INFO = 2;
|
||||||
|
|
||||||
|
private ResultReceiver receiver = null;
|
||||||
|
|
||||||
public UpdateService() {
|
public UpdateService() {
|
||||||
super("UpdateService");
|
super("UpdateService");
|
||||||
@ -71,24 +79,41 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void sendStatus(int statusCode ) {
|
||||||
|
sendStatus(statusCode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendStatus(int statusCode, String message ) {
|
||||||
|
if (receiver != null) {
|
||||||
|
Bundle resultData = new Bundle();
|
||||||
|
if (message != null && message.length() > 0)
|
||||||
|
resultData.putString(RESULT_MESSAGE, message);
|
||||||
|
receiver.send( statusCode, resultData );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We might be doing a scheduled run, or we might have been launched by
|
||||||
|
* the app in response to a user's request. If we have a receiver, it's
|
||||||
|
* the latter...
|
||||||
|
*/
|
||||||
|
private boolean isScheduledRun() {
|
||||||
|
return receiver == null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void onHandleIntent(Intent intent) {
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
|
||||||
// We might be doing a scheduled run, or we might have been launched by
|
receiver = intent.getParcelableExtra("receiver");
|
||||||
// the app in response to a user's request. If we get this receiver,
|
|
||||||
// it's
|
|
||||||
// the latter...
|
|
||||||
ResultReceiver receiver = intent.getParcelableExtra("receiver");
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
String errmsg = "";
|
String errmsg = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
SharedPreferences prefs = PreferenceManager
|
SharedPreferences prefs = PreferenceManager
|
||||||
.getDefaultSharedPreferences(getBaseContext());
|
.getDefaultSharedPreferences(getBaseContext());
|
||||||
|
|
||||||
// See if it's time to actually do anything yet...
|
// See if it's time to actually do anything yet...
|
||||||
if (receiver == null) {
|
if (isScheduledRun()) {
|
||||||
long lastUpdate = prefs.getLong("lastUpdateCheck", 0);
|
long lastUpdate = prefs.getLong("lastUpdateCheck", 0);
|
||||||
String sint = prefs.getString("updateInterval", "0");
|
String sint = prefs.getString("updateInterval", "0");
|
||||||
int interval = Integer.parseInt(sint);
|
int interval = Integer.parseInt(sint);
|
||||||
@ -110,7 +135,7 @@ public class UpdateService extends IntentService {
|
|||||||
// database while we do all the downloading, etc...
|
// database while we do all the downloading, etc...
|
||||||
int prevUpdates = 0;
|
int prevUpdates = 0;
|
||||||
int newUpdates = 0;
|
int newUpdates = 0;
|
||||||
Vector<DB.Repo> repos;
|
List<DB.Repo> repos;
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
repos = db.getRepos();
|
repos = db.getRepos();
|
||||||
@ -119,14 +144,17 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process each repo...
|
// Process each repo...
|
||||||
Vector<DB.App> apps = new Vector<DB.App>();
|
List<DB.App> apps = new ArrayList<DB.App>();
|
||||||
Vector<Integer> keeprepos = new Vector<Integer>();
|
List<Integer> keeprepos = new ArrayList<Integer>();
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
for (DB.Repo repo : repos) {
|
for (DB.Repo repo : repos) {
|
||||||
if (repo.inuse) {
|
if (repo.inuse) {
|
||||||
|
|
||||||
|
sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
||||||
|
|
||||||
StringBuilder newetag = new StringBuilder();
|
StringBuilder newetag = new StringBuilder();
|
||||||
String err = RepoXMLHandler.doUpdate(getBaseContext(),
|
String err = RepoXMLHandler.doUpdate(getBaseContext(),
|
||||||
repo, apps, newetag, keeprepos);
|
repo, apps, newetag, keeprepos, this);
|
||||||
if (err == null) {
|
if (err == null) {
|
||||||
repo.lastetag = newetag.toString();
|
repo.lastetag = newetag.toString();
|
||||||
} else {
|
} else {
|
||||||
@ -142,9 +170,9 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Vector<DB.App> acceptedapps = new Vector<DB.App>();
|
sendStatus(STATUS_INFO, getString(R.string.status_checking_compatibility));
|
||||||
Vector<DB.App> prevapps = ((FDroidApp) getApplication())
|
List<DB.App> acceptedapps = new ArrayList<DB.App>();
|
||||||
.getApps();
|
List<DB.App> prevapps = ((FDroidApp) getApplication()).getApps();
|
||||||
|
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
try {
|
try {
|
||||||
@ -234,17 +262,12 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receiver != null) {
|
if (!success) {
|
||||||
Bundle resultData = new Bundle();
|
if (errmsg.length() == 0)
|
||||||
if (!success) {
|
errmsg = "Unknown error";
|
||||||
if (errmsg.length() == 0)
|
sendStatus(STATUS_ERROR, errmsg);
|
||||||
errmsg = "Unknown error";
|
} else {
|
||||||
resultData.putString("errmsg", errmsg);
|
sendStatus(STATUS_COMPLETE);
|
||||||
receiver.send(1, resultData);
|
|
||||||
} else {
|
|
||||||
receiver.send(0, resultData);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(success) {
|
if(success) {
|
||||||
@ -257,22 +280,18 @@ public class UpdateService extends IntentService {
|
|||||||
Log.e("FDroid",
|
Log.e("FDroid",
|
||||||
"Exception during update processing:\n"
|
"Exception during update processing:\n"
|
||||||
+ Log.getStackTraceString(e));
|
+ Log.getStackTraceString(e));
|
||||||
if (receiver != null) {
|
if (errmsg.length() == 0)
|
||||||
Bundle resultData = new Bundle();
|
errmsg = "Unknown error";
|
||||||
if (errmsg.length() == 0)
|
sendStatus(STATUS_ERROR, errmsg);
|
||||||
errmsg = "Unknown error";
|
|
||||||
resultData.putString("errmsg", errmsg);
|
|
||||||
receiver.send(1, resultData);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
Log.d("FDroid", "Update took "
|
Log.d("FDroid", "Update took "
|
||||||
+ ((System.currentTimeMillis() - startTime) / 1000)
|
+ ((System.currentTimeMillis() - startTime) / 1000)
|
||||||
+ " seconds.");
|
+ " seconds.");
|
||||||
|
receiver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getIcon(DB.App app, Vector<DB.Repo> repos) {
|
private void getIcon(DB.App app, List<DB.Repo> repos) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
File f = new File(DB.getIconsPath(), app.icon);
|
File f = new File(DB.getIconsPath(), app.icon);
|
||||||
@ -306,4 +325,25 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Received progress event from the RepoXMLHandler.
|
||||||
|
* It could be progress downloading from the repo, or perhaps processing the info from the repo.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onProgress(ProgressListener.Event event) {
|
||||||
|
|
||||||
|
String message = "";
|
||||||
|
if (event.type == RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD) {
|
||||||
|
String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO);
|
||||||
|
String downloadedSize = Utils.getFriendlySize( event.progress );
|
||||||
|
String totalSize = Utils.getFriendlySize( event.total );
|
||||||
|
int percent = (int)((double)event.progress/event.total * 100);
|
||||||
|
message = getString(R.string.status_download, repoAddress, downloadedSize, totalSize, percent);
|
||||||
|
} else if (event.type == RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML) {
|
||||||
|
String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO);
|
||||||
|
message = getString(R.string.status_processing_xml, repoAddress, event.progress, event.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStatus(STATUS_INFO, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,25 +18,45 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public final class Utils {
|
public final class Utils {
|
||||||
private Utils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final int BUFFER_SIZE = 4096;
|
public static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
private static final String[] FRIENDLY_SIZE_FORMAT = {
|
||||||
|
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" };
|
||||||
|
|
||||||
|
|
||||||
public static void copy(InputStream input, OutputStream output)
|
public static void copy(InputStream input, OutputStream output)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
copy(input, output, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(InputStream input, OutputStream output,
|
||||||
|
ProgressListener progressListener,
|
||||||
|
ProgressListener.Event templateProgressEvent)
|
||||||
|
throws IOException {
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
int bytesRead = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
int count = input.read(buffer);
|
int count = input.read(buffer);
|
||||||
if (count == -1) {
|
if (count == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (progressListener != null) {
|
||||||
|
bytesRead += count;
|
||||||
|
templateProgressEvent.progress = bytesRead;
|
||||||
|
progressListener.onProgress(templateProgressEvent);
|
||||||
|
}
|
||||||
output.write(buffer, 0, count);
|
output.write(buffer, 0, count);
|
||||||
}
|
}
|
||||||
output.flush();
|
output.flush();
|
||||||
@ -52,4 +72,60 @@ public final class Utils {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasApi(int apiLevel) {
|
||||||
|
return Build.VERSION.SDK_INT >= apiLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getApi() {
|
||||||
|
return Build.VERSION.SDK_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFriendlySize(int size) {
|
||||||
|
double s = size;
|
||||||
|
int i = 0;
|
||||||
|
while (i < FRIENDLY_SIZE_FORMAT.length - 1 && s >= 1024) {
|
||||||
|
s = (100 * s / 1024) / 100.0;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return String.format(FRIENDLY_SIZE_FORMAT[i], s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int countSubstringOccurrence(File file, String substring) throws IOException {
|
||||||
|
int count = 0;
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
reader = new BufferedReader(new FileReader(file));
|
||||||
|
while(true) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count += countSubstringOccurrence(line, substring);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
closeQuietly(reader);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thanks to http://stackoverflow.com/a/767910
|
||||||
|
*/
|
||||||
|
public static int countSubstringOccurrence(String toSearch, String substring) {
|
||||||
|
int count = 0;
|
||||||
|
int index = 0;
|
||||||
|
while (true) {
|
||||||
|
index = toSearch.indexOf(substring, index);
|
||||||
|
if (index == -1){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count ++;
|
||||||
|
index += substring.length();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
48
src/org/fdroid/fdroid/compat/MenuManager.java
Normal file
48
src/org/fdroid/fdroid/compat/MenuManager.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package org.fdroid.fdroid.compat;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
|
abstract public class MenuManager {
|
||||||
|
|
||||||
|
public static MenuManager create(Activity activity) {
|
||||||
|
if (Utils.hasApi(11)) {
|
||||||
|
return new HoneycombMenuManagerImpl(activity);
|
||||||
|
} else {
|
||||||
|
return new OldMenuManagerImpl(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Activity activity;
|
||||||
|
|
||||||
|
protected MenuManager(Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public void invalidateOptionsMenu();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class OldMenuManagerImpl extends MenuManager {
|
||||||
|
|
||||||
|
protected OldMenuManagerImpl(Activity activity) {
|
||||||
|
super(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateOptionsMenu() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HoneycombMenuManagerImpl extends MenuManager {
|
||||||
|
|
||||||
|
protected HoneycombMenuManagerImpl(Activity activity) {
|
||||||
|
super(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateOptionsMenu() {
|
||||||
|
activity.invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
}
|
173
src/org/fdroid/fdroid/compat/TabManager.java
Normal file
173
src/org/fdroid/fdroid/compat/TabManager.java
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package org.fdroid.fdroid.compat;
|
||||||
|
|
||||||
|
import android.app.ActionBar;
|
||||||
|
import android.app.FragmentTransaction;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.*;
|
||||||
|
import org.fdroid.fdroid.FDroid;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
|
public abstract class TabManager {
|
||||||
|
|
||||||
|
public static final int INDEX_AVAILABLE = 0;
|
||||||
|
public static final int INDEX_INSTALLED = 1;
|
||||||
|
public static final int INDEX_CAN_UPDATE = 2;
|
||||||
|
|
||||||
|
public static TabManager create(FDroid parent, ViewPager pager) {
|
||||||
|
if (Utils.hasApi(11)) {
|
||||||
|
return new HoneycombTabManagerImpl(parent, pager);
|
||||||
|
} else {
|
||||||
|
return new OldTabManagerImpl(parent, pager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ViewPager pager;
|
||||||
|
protected final FDroid parent;
|
||||||
|
|
||||||
|
protected TabManager(FDroid parent, ViewPager pager) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.pager = pager;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public void createTabs();
|
||||||
|
abstract public void selectTab(int index);
|
||||||
|
abstract public void refreshTabLabel(int index);
|
||||||
|
|
||||||
|
protected CharSequence getLabel(int index) {
|
||||||
|
return pager.getAdapter().getPageTitle(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OldTabManagerImpl extends TabManager {
|
||||||
|
|
||||||
|
private TabHost tabHost;
|
||||||
|
|
||||||
|
public OldTabManagerImpl(FDroid parent, ViewPager pager) {
|
||||||
|
super(parent, pager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There is a bit of boiler-plate code required to get a TabWidget showing,
|
||||||
|
* which includes creating a TabHost, populating it with the TabWidget,
|
||||||
|
* and giving it a FrameLayout as a child. This will make the tabs have
|
||||||
|
* dummy empty contents and then hook them up to our ViewPager.
|
||||||
|
*/
|
||||||
|
public void createTabs() {
|
||||||
|
tabHost = new TabHost(parent);
|
||||||
|
tabHost.setLayoutParams(new TabHost.LayoutParams(
|
||||||
|
TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
TabWidget tabWidget = new TabWidget(parent);
|
||||||
|
tabWidget.setId(android.R.id.tabs);
|
||||||
|
tabHost.setLayoutParams(new TabHost.LayoutParams(
|
||||||
|
TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
FrameLayout layout = new FrameLayout(parent);
|
||||||
|
layout.setId(android.R.id.tabcontent);
|
||||||
|
layout.setLayoutParams(new TabWidget.LayoutParams(0, 0));
|
||||||
|
|
||||||
|
tabHost.addView(tabWidget);
|
||||||
|
tabHost.addView(layout);
|
||||||
|
tabHost.setup();
|
||||||
|
|
||||||
|
TabHost.TabContentFactory factory = new TabHost.TabContentFactory() {
|
||||||
|
@Override
|
||||||
|
public View createTabContent(String tag) {
|
||||||
|
return new View(parent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TabHost.TabSpec availableTabSpec = tabHost.newTabSpec("available")
|
||||||
|
.setIndicator(
|
||||||
|
parent.getString(R.string.tab_noninstalled),
|
||||||
|
parent.getResources().getDrawable(android.R.drawable.ic_input_add))
|
||||||
|
.setContent(factory);
|
||||||
|
|
||||||
|
TabHost.TabSpec installedTabSpec = tabHost.newTabSpec("installed")
|
||||||
|
.setIndicator(
|
||||||
|
parent.getString(R.string.tab_installed),
|
||||||
|
parent.getResources().getDrawable(android.R.drawable.star_off))
|
||||||
|
.setContent(factory);
|
||||||
|
|
||||||
|
TabHost.TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate")
|
||||||
|
.setIndicator(
|
||||||
|
parent.getString(R.string.tab_updates),
|
||||||
|
parent.getResources().getDrawable(android.R.drawable.star_on))
|
||||||
|
.setContent(factory);
|
||||||
|
|
||||||
|
tabHost.addTab(availableTabSpec);
|
||||||
|
tabHost.addTab(installedTabSpec);
|
||||||
|
tabHost.addTab(canUpdateTabSpec);
|
||||||
|
|
||||||
|
LinearLayout contentView = (LinearLayout)parent.findViewById(R.id.fdroid_layout);
|
||||||
|
contentView.addView(tabHost, 0);
|
||||||
|
|
||||||
|
tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onTabChanged(String tabId) {
|
||||||
|
pager.setCurrentItem(tabHost.getCurrentTab());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void selectTab(int index) {
|
||||||
|
tabHost.setCurrentTab(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTabLabel(int index) {
|
||||||
|
CharSequence text = getLabel(index);
|
||||||
|
|
||||||
|
// Update the count on the 'Updates' tab to show the number available.
|
||||||
|
// This is quite unpleasant, but seems to be the only way to do it.
|
||||||
|
TextView textView = (TextView) tabHost.getTabWidget().getChildAt(index)
|
||||||
|
.findViewById(android.R.id.title);
|
||||||
|
textView.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HoneycombTabManagerImpl extends TabManager {
|
||||||
|
|
||||||
|
protected final ActionBar actionBar;
|
||||||
|
|
||||||
|
public HoneycombTabManagerImpl(FDroid parent, ViewPager pager) {
|
||||||
|
super(parent, pager);
|
||||||
|
actionBar = parent.getActionBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTabs() {
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||||
|
for (int i = 0; i < pager.getAdapter().getCount(); i ++) {
|
||||||
|
CharSequence label = pager.getAdapter().getPageTitle(i);
|
||||||
|
actionBar.addTab(
|
||||||
|
actionBar.newTab()
|
||||||
|
.setText(label)
|
||||||
|
.setTabListener(new ActionBar.TabListener() {
|
||||||
|
public void onTabSelected(ActionBar.Tab tab,
|
||||||
|
FragmentTransaction ft) {
|
||||||
|
pager.setCurrentItem(tab.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectTab(int index) {
|
||||||
|
actionBar.setSelectedNavigationItem(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTabLabel(int index) {
|
||||||
|
CharSequence text = getLabel(index);
|
||||||
|
actionBar.getTabAt(index).setText(text);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,7 @@
|
|||||||
package org.fdroid.fdroid.views;
|
package org.fdroid.fdroid.views;
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import org.fdroid.fdroid.AppListManager;
|
|
||||||
import org.fdroid.fdroid.FDroid;
|
import org.fdroid.fdroid.FDroid;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.views.fragments.AvailableAppsFragment;
|
import org.fdroid.fdroid.views.fragments.AvailableAppsFragment;
|
||||||
|
@ -27,10 +27,6 @@ public class AppListView extends LinearLayout {
|
|||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppListView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAppList(ListView appList) {
|
public void setAppList(ListView appList) {
|
||||||
this.appList = appList;
|
this.appList = appList;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package org.fdroid.fdroid.views.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
@ -13,7 +12,6 @@ import org.fdroid.fdroid.views.AppListView;
|
|||||||
|
|
||||||
abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener {
|
abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
private AppListManager appListManager;
|
|
||||||
private FDroid parent;
|
private FDroid parent;
|
||||||
|
|
||||||
protected abstract AppListAdapter getAppListAdapter();
|
protected abstract AppListAdapter getAppListAdapter();
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package org.fdroid.fdroid.views.fragments;
|
package org.fdroid.fdroid.views.fragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.fdroid.fdroid.AppListAdapter;
|
import org.fdroid.fdroid.AppListAdapter;
|
||||||
import org.fdroid.fdroid.AppListManager;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.views.AppListView;
|
import org.fdroid.fdroid.views.AppListView;
|
||||||
|
|
||||||
@ -51,8 +50,8 @@ public class AvailableAppsFragment extends AppListFragment implements AdapterVie
|
|||||||
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int pos,
|
public void onItemSelected(AdapterView<?> parent, View view, int pos,
|
||||||
long id) {
|
long id) {
|
||||||
getAppListManager().setCurrentCategory(parent.getItemAtPosition(pos).toString());
|
String category = parent.getItemAtPosition(pos).toString();
|
||||||
getAppListManager().repopulateLists();
|
getAppListManager().setCurrentCategory(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,7 +4,6 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import org.fdroid.fdroid.AppListAdapter;
|
import org.fdroid.fdroid.AppListAdapter;
|
||||||
|
|
||||||
public class InstalledAppsFragment extends AppListFragment {
|
public class InstalledAppsFragment extends AppListFragment {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user