diff --git a/res/layout/addrepo.xml b/res/layout/addrepo.xml
index 0f0845155..c67ee5d7b 100644
--- a/res/layout/addrepo.xml
+++ b/res/layout/addrepo.xml
@@ -15,7 +15,7 @@
android:layout_width="wrap_content"
android:ems="20"
android:layout_height="wrap_content"
- android:text="@string/repo_add_http"/>
+ android:text="https://"/>
+ Downloading\n%2$s / %3$s (%4$d%%) from\n%1$s
+ Processing application\n%2$d of %3$d from\n%1$s
+ Connecting to\n%1$s
+ Checking all apps compatibility with your deviceā¦
+
diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java
index 5e4f75917..624a32cc6 100644
--- a/src/org/fdroid/fdroid/AppDetails.java
+++ b/src/org/fdroid/fdroid/AppDetails.java
@@ -22,9 +22,9 @@ import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
import android.support.v4.view.MenuItemCompat;
+import org.fdroid.fdroid.compat.MenuManager;
import org.xml.sax.XMLReader;
import android.app.AlertDialog;
@@ -127,7 +127,7 @@ public class AppDetails extends ListActivity {
if (apk.detail_size == 0) {
size.setText("");
} else {
- size.setText(getFriendlySize(apk.detail_size));
+ size.setText(Utils.getFriendlySize(apk.detail_size));
}
TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
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 UNINSTALL = Menu.FIRST + 1;
private static final int WEBSITE = Menu.FIRST + 2;
@@ -249,7 +236,9 @@ public class AppDetails extends ListActivity {
resetRequired = false;
}
resetViews();
- invalidateOptionsMenu();
+
+ MenuManager.create(this).invalidateOptionsMenu();
+
if (downloadHandler != null) {
downloadHandler.startUpdates();
}
@@ -302,7 +291,7 @@ public class AppDetails extends ListActivity {
Log.d("FDroid", "Getting application details for " + appid);
app = null;
- Vector apps = ((FDroidApp) getApplication()).getApps();
+ List apps = ((FDroidApp) getApplication()).getApps();
for (DB.App tapp : apps) {
if (tapp.id.equals(appid)) {
app = tapp;
@@ -797,4 +786,5 @@ public class AppDetails extends ListActivity {
break;
}
}
+
}
diff --git a/src/org/fdroid/fdroid/AppListManager.java b/src/org/fdroid/fdroid/AppListManager.java
index 59f66f161..99541eeb8 100644
--- a/src/org/fdroid/fdroid/AppListManager.java
+++ b/src/org/fdroid/fdroid/AppListManager.java
@@ -15,7 +15,7 @@ import java.util.*;
*/
public class AppListManager {
- private Vector allApps = null;
+ private List allApps = null;
private FDroid fdroidActivity;
@@ -55,7 +55,7 @@ public class AppListManager {
// Needs to be created before createViews(), because that will use the
// getCategoriesAdapter() accessor which expects this object...
categories = new ArrayAdapter(activity,
- android.R.layout.simple_spinner_item, new Vector());
+ android.R.layout.simple_spinner_item, new ArrayList());
categories
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}
@@ -185,7 +185,7 @@ public class AppListManager {
Date recentDate = calcMaxHistory();
AppFilter appFilter = new AppFilter(fdroidActivity);
- Vector availApps = new Vector();
+ List availApps = new ArrayList();
for (DB.App app : allApps) {
boolean isInCategory = isInCategory(app, currentCategory, recentDate);
@@ -215,7 +215,10 @@ public class AppListManager {
}
public void setCurrentCategory(String currentCategory) {
- this.currentCategory = currentCategory;
+ if (!this.currentCategory.equals(currentCategory)){
+ this.currentCategory = currentCategory;
+ repopulateLists();
+ }
}
static class WhatsNewComparator implements Comparator {
diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java
index 7a15eb142..2f55a8e94 100644
--- a/src/org/fdroid/fdroid/DB.java
+++ b/src/org/fdroid/fdroid/DB.java
@@ -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
*
* 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.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -28,7 +29,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Vector;
import java.util.concurrent.Semaphore;
import android.annotation.TargetApi;
@@ -41,7 +41,6 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.text.TextUtils.SimpleStringSplitter;
@@ -116,7 +115,7 @@ public class DB {
updated = false;
added = null;
lastUpdated = null;
- apks = new Vector();
+ apks = new ArrayList();
detail_Populated = false;
compatible = false;
}
@@ -181,7 +180,7 @@ public class DB {
public boolean updated;
// List of apks.
- public Vector apks;
+ public List apks;
// Get the current version - this will be one of the Apks from 'apks'.
// 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.
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 static CompatibilityChecker getChecker(Context ctx) {
CompatibilityChecker checker;
- if (SDK_INT >= 5)
+ if (Utils.hasApi(5))
checker = new EclairChecker(ctx);
else
checker = new BasicChecker();
Log.d("FDroid", "Compatibility checker for API level "
- + SDK_INT + ": " + checker.getClass().getName());
+ + Utils.getApi() + ": " + checker.getClass().getName());
return checker;
}
}
private static class BasicChecker extends CompatibilityChecker {
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) {
- if (apk.minSdkVersion > SDK_INT)
+ if (apk.minSdkVersion > Utils.getApi())
return false;
if (apk.features != null) {
for (String feat : apk.features) {
@@ -379,6 +373,7 @@ public class DB {
public static void resetTransient(SQLiteDatabase db) {
db.execSQL("drop table " + TABLE_APP);
db.execSQL("drop table " + TABLE_APK);
+ db.execSQL("update " + TABLE_REPO + " set lastetag = NULL");
createAppApk(db);
}
@@ -412,7 +407,7 @@ public class DB {
// Migrate repo list to new structure. (No way to change primary
// key in sqlite - table must be recreated)
if (oldVersion < 20) {
- Vector oldrepos = new Vector();
+ List oldrepos = new ArrayList();
Cursor c = db.rawQuery("select address, inuse, pubkey from "
+ TABLE_REPO, null);
c.moveToFirst();
@@ -503,7 +498,7 @@ public class DB {
// Get the number of apps that have updates available. This can be a
// time consuming operation.
public int getNumUpdates() {
- Vector apps = getApps(true);
+ List apps = getApps(true);
int count = 0;
for (App app : apps) {
if (app.hasUpdates)
@@ -512,8 +507,8 @@ public class DB {
return count;
}
- public Vector getCategories() {
- Vector result = new Vector();
+ public List getCategories() {
+ List result = new ArrayList();
Cursor c = null;
try {
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
// also done based on compatibility and anti-features according to
// the user's current preferences.
- public Vector getApps(boolean getinstalledinfo) {
+ public List getApps(boolean getinstalledinfo) {
// If we're going to need it, get info in what's currently installed
Map systemApks = null;
if (getinstalledinfo) {
Log.d("FDroid", "Reading installed packages");
systemApks = new HashMap();
- List installedPackages = mContext.getPackageManager().getInstalledPackages(0);
+ List installedPackages = mContext.getPackageManager()
+ .getInstalledPackages(0);
for (PackageInfo appInfo : installedPackages) {
systemApks.put(appInfo.packageName, appInfo);
}
@@ -696,7 +692,7 @@ public class DB {
+ (System.currentTimeMillis() - startTime) + " ms)");
}
- Vector result = new Vector(apps.values());
+ List result = new ArrayList(apps.values());
Collections.sort(result);
// Fill in the hasUpdates fields if we have the necessary information...
@@ -720,9 +716,9 @@ public class DB {
return result;
}
- public Vector doSearch(String query) {
+ public List doSearch(String query) {
- Vector ids = new Vector();
+ List ids = new ArrayList();
Cursor c = null;
try {
String filter = "%" + query + "%";
@@ -770,11 +766,11 @@ public class DB {
}
}
- private Vector updateApps = null;
+ private List updateApps = null;
// Called before a repo update starts. Returns the number of updates
// available beforehand.
- public int beginUpdate(Vector apps) {
+ public int beginUpdate(List apps) {
// 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
// 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)
// Also keep a list of which were compatible, because they're the
// only ones we'll add, unless the showIncompatible preference is set.
- Vector compatibleapks = new Vector();
+ List compatibleapks = new ArrayList();
for (Apk apk : upapp.apks) {
if (compatChecker.isCompatible(apk)) {
apk.compatible = true;
@@ -1016,8 +1012,8 @@ public class DB {
}
// Get a list of the configured repositories.
- public Vector getRepos() {
- Vector repos = new Vector();
+ public List getRepos() {
+ List repos = new ArrayList();
Cursor c = null;
try {
c = db.rawQuery(
@@ -1078,10 +1074,27 @@ public class DB {
db.insert(TABLE_REPO, null, values);
}
- public void removeServers(Vector addresses) {
+ public void removeRepos(List addresses) {
db.beginTransaction();
try {
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.setTransactionSuccessful();
diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java
index c14703eb5..9278bc7d8 100644
--- a/src/org/fdroid/fdroid/FDroid.java
+++ b/src/org/fdroid/fdroid/FDroid.java
@@ -19,28 +19,17 @@
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 org.fdroid.fdroid.DB.App;
import org.fdroid.fdroid.R;
-import android.R.drawable;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
-import android.app.FragmentTransaction;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
@@ -52,7 +41,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
-import android.widget.TabHost.TabSpec;
+import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
public class FDroid extends FragmentActivity {
@@ -75,8 +64,7 @@ public class FDroid extends FragmentActivity {
private AppListManager manager = null;
- // Used by pre 3.0 devices which don't have an ActionBar...
- private TabHost tabHost;
+ private TabManager tabManager = null;
public AppListManager getManager() {
return manager;
@@ -89,7 +77,7 @@ public class FDroid extends FragmentActivity {
manager = new AppListManager(this);
setContentView(R.layout.fdroid);
createViews();
- createTabs();
+ getTabManager().createTabs();
// Must be done *after* createViews, because it will involve a
// 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)) {
boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
if (showUpdateTab) {
- selectTab(2);
+ getTabManager().selectTab(2);
}
}
}
@@ -250,127 +238,7 @@ public class FDroid extends FragmentActivity {
viewPager.setAdapter(viewPageAdapter);
viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
public void onPageSelected(int position) {
- 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());
+ getTabManager().selectTab(position);
}
});
}
@@ -384,13 +252,19 @@ public class FDroid extends FragmentActivity {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode == 1) {
- Toast.makeText(FDroid.this, resultData.getString("errmsg"),
- Toast.LENGTH_LONG).show();
- } else {
+ String message = resultData.getString(UpdateService.RESULT_MESSAGE);
+ boolean finished = false;
+ if (resultCode == UpdateService.STATUS_ERROR) {
+ Toast.makeText(FDroid.this, message, Toast.LENGTH_LONG).show();
+ finished = true;
+ } else if (resultCode == UpdateService.STATUS_COMPLETE) {
repopulateViews();
+ finished = true;
+ } else if (resultCode == UpdateService.STATUS_INFO) {
+ pd.setMessage(message);
}
- if (pd.isShowing())
+
+ if (finished && pd.isShowing())
pd.dismiss();
}
}
@@ -409,7 +283,7 @@ public class FDroid extends FragmentActivity {
boolean hasTriedEmptyUpdate = getPreferences(MODE_PRIVATE).getBoolean(TRIED_EMPTY_UPDATE, false);
if (!hasTriedEmptyUpdate) {
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();
return true;
} else {
@@ -434,4 +308,15 @@ public class FDroid extends FragmentActivity {
startService(intent);
}
+ private TabManager getTabManager() {
+ if (tabManager == null) {
+ tabManager = TabManager.create(this, viewPager);
+ }
+ return tabManager;
+ }
+
+ public void refreshUpdateTabLabel() {
+ getTabManager().refreshTabLabel(TabManager.INDEX_CAN_UPDATE);
+ }
+
}
diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java
index c4b765cd3..eb8ead6b9 100644
--- a/src/org/fdroid/fdroid/FDroidApp.java
+++ b/src/org/fdroid/fdroid/FDroidApp.java
@@ -19,7 +19,8 @@
package org.fdroid.fdroid;
import java.io.File;
-import java.util.Vector;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Semaphore;
import android.app.Application;
@@ -47,7 +48,7 @@ public class FDroidApp extends Application {
}
// Global list of all known applications.
- private Vector apps;
+ private List apps;
// Set when something has changed (database or installed apps) so we know
// 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
// database is locked (i.e. between DB.getDB() and db.releaseDB(). The
// contents should never be modified, it's for reading only.
- public Vector getApps() {
+ public List getApps() {
boolean invalid = false;
try {
@@ -95,7 +96,7 @@ public class FDroidApp extends Application {
}
}
if (apps == null)
- return new Vector();
+ return new ArrayList();
return apps;
}
diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java
index 5953f3f61..d15fc98e6 100644
--- a/src/org/fdroid/fdroid/ManageRepo.java
+++ b/src/org/fdroid/fdroid/ManageRepo.java
@@ -26,7 +26,6 @@ import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Vector;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
@@ -55,7 +54,7 @@ public class ManageRepo extends ListActivity {
private boolean changed = false;
- private Vector repos;
+ private List repos;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -201,7 +200,7 @@ public class ManageRepo extends ListActivity {
return true;
case REM_REPO:
- final Vector rem_lst = new Vector();
+ final List rem_lst = new ArrayList();
CharSequence[] b = new CharSequence[repos.size()];
for (int i = 0; i < repos.size(); i++) {
b[i] = repos.get(i).address;
@@ -215,9 +214,9 @@ public class ManageRepo extends ListActivity {
public void onClick(DialogInterface dialog,
int whichButton, boolean isChecked) {
if (isChecked) {
- rem_lst.addElement(repos.get(whichButton).address);
+ rem_lst.add(repos.get(whichButton).address);
} 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) {
try {
DB db = DB.getDB();
- db.removeServers(rem_lst);
+ db.removeRepos(rem_lst);
} finally {
DB.releaseDB();
}
diff --git a/src/org/fdroid/fdroid/ProgressListener.java b/src/org/fdroid/fdroid/ProgressListener.java
new file mode 100644
index 000000000..d38e342cd
--- /dev/null
+++ b/src/org/fdroid/fdroid/ProgressListener.java
@@ -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;
+ }
+ }
+
+}
diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java
index 2ab7360a5..6ad04fd7c 100644
--- a/src/org/fdroid/fdroid/RepoXMLHandler.java
+++ b/src/org/fdroid/fdroid/RepoXMLHandler.java
@@ -26,14 +26,13 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.Reader;
import java.net.HttpURLConnection;
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.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -41,6 +40,7 @@ import javax.net.ssl.SSLHandshakeException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
+import android.os.Bundle;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -54,10 +54,10 @@ import android.util.Log;
public class RepoXMLHandler extends DefaultHandler {
- // The ID of the repo we're processing.
- private int repo;
+ // The repo we're processing.
+ private DB.Repo repo;
- private Vector apps;
+ private List apps;
private DB.App curapp = null;
private DB.Apk curapk = null;
@@ -66,13 +66,23 @@ public class RepoXMLHandler extends DefaultHandler {
private String pubkey;
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.
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ private int totalAppCount;
- public RepoXMLHandler(int repo, Vector apps) {
+ public RepoXMLHandler(DB.Repo repo, List apps, ProgressListener listener) {
this.repo = repo;
this.apps = apps;
pubkey = null;
+ progressListener = listener;
}
@Override
@@ -219,27 +229,37 @@ public class RepoXMLHandler extends DefaultHandler {
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
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
-
super.startElement(uri, localName, qName, attributes);
- if (localName == "repo") {
+ if (localName.equals("repo")) {
String pk = attributes.getValue("", "pubkey");
if (pk != null)
pubkey = pk;
- } else if (localName == "application" && curapp == null) {
+ } else if (localName.equals("application") && curapp == null) {
curapp = new DB.App();
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.id = curapp.id;
- curapk.repo = repo;
+ curapk.repo = repo.id;
hashType = null;
- } else if (localName == "hash" && curapk != null) {
+ } else if (localName.equals("hash") && curapk != null) {
hashType = attributes.getValue("", "type");
}
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 if none was available.
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 {
long startTime = System.currentTimeMillis();
URL u = new URL(url);
- HttpURLConnection uc = (HttpURLConnection) u.openConnection();
+ HttpURLConnection connection = (HttpURLConnection) u.openConnection();
if (etag != null)
- uc.setRequestProperty("If-None-Match", etag);
+ connection.setRequestProperty("If-None-Match", etag);
int totalBytes = 0;
- int code = uc.getResponseCode();
+ int code = connection.getResponseCode();
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;
OutputStream output = null;
try {
- input = new URL(url).openStream();
+ input = connection.getInputStream();
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
- Utils.copy(input, output);
+ Utils.copy(input, output, progressListener, progressEvent);
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
}
- String et = uc.getHeaderField("ETag");
+ String et = connection.getHeaderField("ETag");
if (et != null)
retag.append(et);
}
@@ -293,7 +323,8 @@ public class RepoXMLHandler extends DefaultHandler {
// value for the index that was successfully processed, or it may contain
// null if none was available.
public static String doUpdate(Context ctx, DB.Repo repo,
- Vector apps, StringBuilder newetag, Vector keeprepos) {
+ List apps, StringBuilder newetag, List keeprepos,
+ ProgressListener progressListener) {
try {
int code = 0;
@@ -309,8 +340,11 @@ public class RepoXMLHandler extends DefaultHandler {
address += "?" + pi.versionName;
} 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",
- repo.lastetag, newetag);
+ repo.lastetag, newetag, progressListener, event );
if (code == 200) {
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
JarFile jar = null;
@@ -365,8 +399,12 @@ public class RepoXMLHandler extends DefaultHandler {
// It's an old-fashioned unsigned repo...
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",
- "tempindex.xml", repo.lastetag, newetag);
+ "tempindex.xml", repo.lastetag, newetag,
+ progressListener, event);
}
if (code == 200) {
@@ -374,11 +412,22 @@ public class RepoXMLHandler extends DefaultHandler {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
- RepoXMLHandler handler = new RepoXMLHandler(repo.id, apps);
+ RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
xr.setContentHandler(handler);
- Reader r = new BufferedReader(new FileReader(new File(
- ctx.getFilesDir() + "/tempindex.xml")));
+ File tempIndex = new File(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 = " matchingids = new Vector();
+ List matchingids = new ArrayList();
try {
DB db = DB.getDB();
matchingids = db.doSearch(mQuery);
@@ -78,9 +79,9 @@ public class SearchResults extends ListActivity {
DB.releaseDB();
}
- Vector apps = new Vector();
+ List apps = new ArrayList();
AppFilter appfilter = new AppFilter(this);
- Vector tapps = ((FDroidApp) getApplication()).getApps();
+ List tapps = ((FDroidApp) getApplication()).getApps();
for (DB.App tapp : tapps) {
boolean include = false;
for (String tid : matchingids) {
diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java
index bfd66bf99..0233f77c9 100644
--- a/src/org/fdroid/fdroid/UpdateService.java
+++ b/src/org/fdroid/fdroid/UpdateService.java
@@ -24,7 +24,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
-import java.util.Vector;
+import java.util.ArrayList;
+import java.util.List;
import android.app.AlarmManager;
import android.app.IntentService;
@@ -41,7 +42,14 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
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() {
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) {
- // 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 get this receiver,
- // it's
- // the latter...
- ResultReceiver receiver = intent.getParcelableExtra("receiver");
+ receiver = intent.getParcelableExtra("receiver");
long startTime = System.currentTimeMillis();
String errmsg = "";
-
try {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
// See if it's time to actually do anything yet...
- if (receiver == null) {
+ if (isScheduledRun()) {
long lastUpdate = prefs.getLong("lastUpdateCheck", 0);
String sint = prefs.getString("updateInterval", "0");
int interval = Integer.parseInt(sint);
@@ -110,7 +135,7 @@ public class UpdateService extends IntentService {
// database while we do all the downloading, etc...
int prevUpdates = 0;
int newUpdates = 0;
- Vector repos;
+ List repos;
try {
DB db = DB.getDB();
repos = db.getRepos();
@@ -119,14 +144,17 @@ public class UpdateService extends IntentService {
}
// Process each repo...
- Vector apps = new Vector();
- Vector keeprepos = new Vector();
+ List apps = new ArrayList();
+ List keeprepos = new ArrayList();
boolean success = true;
for (DB.Repo repo : repos) {
if (repo.inuse) {
+
+ sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
+
StringBuilder newetag = new StringBuilder();
String err = RepoXMLHandler.doUpdate(getBaseContext(),
- repo, apps, newetag, keeprepos);
+ repo, apps, newetag, keeprepos, this);
if (err == null) {
repo.lastetag = newetag.toString();
} else {
@@ -142,9 +170,9 @@ public class UpdateService extends IntentService {
}
if (success) {
- Vector acceptedapps = new Vector();
- Vector prevapps = ((FDroidApp) getApplication())
- .getApps();
+ sendStatus(STATUS_INFO, getString(R.string.status_checking_compatibility));
+ List acceptedapps = new ArrayList();
+ List prevapps = ((FDroidApp) getApplication()).getApps();
DB db = DB.getDB();
try {
@@ -234,17 +262,12 @@ public class UpdateService extends IntentService {
}
}
- if (receiver != null) {
- Bundle resultData = new Bundle();
- if (!success) {
- if (errmsg.length() == 0)
- errmsg = "Unknown error";
- resultData.putString("errmsg", errmsg);
- receiver.send(1, resultData);
- } else {
- receiver.send(0, resultData);
- }
-
+ if (!success) {
+ if (errmsg.length() == 0)
+ errmsg = "Unknown error";
+ sendStatus(STATUS_ERROR, errmsg);
+ } else {
+ sendStatus(STATUS_COMPLETE);
}
if(success) {
@@ -257,22 +280,18 @@ public class UpdateService extends IntentService {
Log.e("FDroid",
"Exception during update processing:\n"
+ Log.getStackTraceString(e));
- if (receiver != null) {
- Bundle resultData = new Bundle();
- if (errmsg.length() == 0)
- errmsg = "Unknown error";
- resultData.putString("errmsg", errmsg);
- receiver.send(1, resultData);
- }
+ if (errmsg.length() == 0)
+ errmsg = "Unknown error";
+ sendStatus(STATUS_ERROR, errmsg);
} finally {
Log.d("FDroid", "Update took "
+ ((System.currentTimeMillis() - startTime) / 1000)
+ " seconds.");
+ receiver = null;
}
-
}
- private void getIcon(DB.App app, Vector repos) {
+ private void getIcon(DB.App app, List repos) {
try {
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);
+ }
}
diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java
index 14dad6be0..d1679cbce 100644
--- a/src/org/fdroid/fdroid/Utils.java
+++ b/src/org/fdroid/fdroid/Utils.java
@@ -18,25 +18,45 @@
package org.fdroid.fdroid;
+import android.os.Build;
+
+import java.io.BufferedReader;
import java.io.Closeable;
+import java.io.File;
+import java.io.FileReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
public final class Utils {
- private Utils() {
- }
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)
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];
+ int bytesRead = 0;
while (true) {
int count = input.read(buffer);
if (count == -1) {
break;
}
+ if (progressListener != null) {
+ bytesRead += count;
+ templateProgressEvent.progress = bytesRead;
+ progressListener.onProgress(templateProgressEvent);
+ }
output.write(buffer, 0, count);
}
output.flush();
@@ -52,4 +72,60 @@ public final class Utils {
// 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;
+ }
+
}
diff --git a/src/org/fdroid/fdroid/compat/MenuManager.java b/src/org/fdroid/fdroid/compat/MenuManager.java
new file mode 100644
index 000000000..9a60b595e
--- /dev/null
+++ b/src/org/fdroid/fdroid/compat/MenuManager.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/org/fdroid/fdroid/compat/TabManager.java b/src/org/fdroid/fdroid/compat/TabManager.java
new file mode 100644
index 000000000..b8863bedb
--- /dev/null
+++ b/src/org/fdroid/fdroid/compat/TabManager.java
@@ -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);
+ }
+}
diff --git a/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java
index e817ebd29..069fc37fc 100644
--- a/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java
+++ b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java
@@ -1,14 +1,7 @@
package org.fdroid.fdroid.views;
-import android.os.Bundle;
-import android.os.Parcelable;
import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
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.R;
import org.fdroid.fdroid.views.fragments.AvailableAppsFragment;
diff --git a/src/org/fdroid/fdroid/views/AppListView.java b/src/org/fdroid/fdroid/views/AppListView.java
index 4fbe716ed..b65f50cc3 100644
--- a/src/org/fdroid/fdroid/views/AppListView.java
+++ b/src/org/fdroid/fdroid/views/AppListView.java
@@ -27,10 +27,6 @@ public class AppListView extends LinearLayout {
super(context, attrs);
}
- public AppListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
public void setAppList(ListView appList) {
this.appList = appList;
}
diff --git a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
index 6ad8f9588..e5728221e 100644
--- a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
@@ -3,7 +3,6 @@ package org.fdroid.fdroid.views.fragments;
import android.app.Activity;
import android.content.Intent;
import android.support.v4.app.Fragment;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
@@ -13,7 +12,6 @@ import org.fdroid.fdroid.views.AppListView;
abstract class AppListFragment extends Fragment implements AdapterView.OnItemClickListener {
- private AppListManager appListManager;
private FDroid parent;
protected abstract AppListAdapter getAppListAdapter();
diff --git a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java
index 517476e79..c5955efc4 100644
--- a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java
@@ -1,13 +1,12 @@
package org.fdroid.fdroid.views.fragments;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
+
import org.fdroid.fdroid.AppListAdapter;
-import org.fdroid.fdroid.AppListManager;
import org.fdroid.fdroid.R;
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,
long id) {
- getAppListManager().setCurrentCategory(parent.getItemAtPosition(pos).toString());
- getAppListManager().repopulateLists();
+ String category = parent.getItemAtPosition(pos).toString();
+ getAppListManager().setCurrentCategory(category);
}
@Override
diff --git a/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java
index e6b48f35f..628ddf9b4 100644
--- a/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java
@@ -4,7 +4,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
import org.fdroid.fdroid.AppListAdapter;
public class InstalledAppsFragment extends AppListFragment {