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:ems="20"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/repo_add_http"/>
|
||||
android:text="https://"/>
|
||||
</LinearLayout>
|
||||
<!--
|
||||
* Copyright (C) 2009 Roberto Jacinto
|
||||
|
@ -74,7 +74,6 @@
|
||||
<string name="download_server">Getting application from</string>
|
||||
|
||||
<string name="repo_add_url">Repository address</string>
|
||||
<string name="repo_add_http">http://</string>
|
||||
|
||||
<string name="isinst">Installed: </string>
|
||||
|
||||
@ -153,4 +152,16 @@
|
||||
<string name="category_whatsnew">What\'s New</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>
|
||||
|
@ -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<DB.App> apps = ((FDroidApp) getApplication()).getApps();
|
||||
List<DB.App> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import java.util.*;
|
||||
*/
|
||||
public class AppListManager {
|
||||
|
||||
private Vector<DB.App> allApps = null;
|
||||
private List<DB.App> 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<String>(activity,
|
||||
android.R.layout.simple_spinner_item, new Vector<String>());
|
||||
android.R.layout.simple_spinner_item, new ArrayList<String>());
|
||||
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<DB.App> availApps = new Vector<DB.App>();
|
||||
List<DB.App> availApps = new ArrayList<DB.App>();
|
||||
for (DB.App app : allApps) {
|
||||
|
||||
boolean isInCategory = isInCategory(app, currentCategory, recentDate);
|
||||
@ -215,7 +215,10 @@ public class AppListManager {
|
||||
}
|
||||
|
||||
public void setCurrentCategory(String currentCategory) {
|
||||
if (!this.currentCategory.equals(currentCategory)){
|
||||
this.currentCategory = currentCategory;
|
||||
repopulateLists();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* 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<Apk>();
|
||||
apks = new ArrayList<Apk>();
|
||||
detail_Populated = false;
|
||||
compatible = false;
|
||||
}
|
||||
@ -181,7 +180,7 @@ public class DB {
|
||||
public boolean updated;
|
||||
|
||||
// List of apks.
|
||||
public Vector<Apk> apks;
|
||||
public List<Apk> 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<Repo> oldrepos = new Vector<Repo>();
|
||||
List<Repo> oldrepos = new ArrayList<Repo>();
|
||||
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<App> apps = getApps(true);
|
||||
List<App> apps = getApps(true);
|
||||
int count = 0;
|
||||
for (App app : apps) {
|
||||
if (app.hasUpdates)
|
||||
@ -512,8 +507,8 @@ public class DB {
|
||||
return count;
|
||||
}
|
||||
|
||||
public Vector<String> getCategories() {
|
||||
Vector<String> result = new Vector<String>();
|
||||
public List<String> getCategories() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
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<App> getApps(boolean getinstalledinfo) {
|
||||
public List<App> getApps(boolean getinstalledinfo) {
|
||||
|
||||
// If we're going to need it, get info in what's currently installed
|
||||
Map<String, PackageInfo> systemApks = null;
|
||||
if (getinstalledinfo) {
|
||||
Log.d("FDroid", "Reading installed packages");
|
||||
systemApks = new HashMap<String, PackageInfo>();
|
||||
List<PackageInfo> installedPackages = mContext.getPackageManager().getInstalledPackages(0);
|
||||
List<PackageInfo> 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<App> result = new Vector<App>(apps.values());
|
||||
List<App> result = new ArrayList<App>(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<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;
|
||||
try {
|
||||
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
|
||||
// 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
|
||||
// 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<Apk> compatibleapks = new Vector<Apk>();
|
||||
List<Apk> compatibleapks = new ArrayList<Apk>();
|
||||
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<Repo> getRepos() {
|
||||
Vector<Repo> repos = new Vector<Repo>();
|
||||
public List<Repo> getRepos() {
|
||||
List<Repo> repos = new ArrayList<Repo>();
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = db.rawQuery(
|
||||
@ -1078,10 +1074,27 @@ public class DB {
|
||||
db.insert(TABLE_REPO, null, values);
|
||||
}
|
||||
|
||||
public void removeServers(Vector<String> addresses) {
|
||||
public void removeRepos(List<String> 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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<DB.App> apps;
|
||||
private List<DB.App> 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<DB.App> getApps() {
|
||||
public List<DB.App> getApps() {
|
||||
|
||||
boolean invalid = false;
|
||||
try {
|
||||
@ -95,7 +96,7 @@ public class FDroidApp extends Application {
|
||||
}
|
||||
}
|
||||
if (apps == null)
|
||||
return new Vector<DB.App>();
|
||||
return new ArrayList<DB.App>();
|
||||
return apps;
|
||||
}
|
||||
|
||||
|
@ -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<DB.Repo> repos;
|
||||
private List<DB.Repo> repos;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -201,7 +200,7 @@ public class ManageRepo extends ListActivity {
|
||||
return true;
|
||||
|
||||
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()];
|
||||
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();
|
||||
}
|
||||
|
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.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<DB.App> apps;
|
||||
private List<DB.App> 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<DB.App> apps) {
|
||||
public RepoXMLHandler(DB.Repo repo, List<DB.App> 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<DB.App> apps, StringBuilder newetag, Vector<Integer> keeprepos) {
|
||||
List<DB.App> apps, StringBuilder newetag, List<Integer> 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 = "<application";
|
||||
handler.setTotalAppCount(Utils.countSubstringOccurrence(tempIndex, APPLICATION));
|
||||
|
||||
InputSource is = new InputSource(r);
|
||||
xr.parse(is);
|
||||
|
||||
@ -427,4 +476,7 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setTotalAppCount(int totalAppCount) {
|
||||
this.totalAppCount = totalAppCount;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.app.SearchManager;
|
||||
@ -68,7 +69,7 @@ public class SearchResults extends ListActivity {
|
||||
|
||||
private void updateView() {
|
||||
|
||||
Vector<String> matchingids = new Vector<String>();
|
||||
List<String> matchingids = new ArrayList<String>();
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
matchingids = db.doSearch(mQuery);
|
||||
@ -78,9 +79,9 @@ public class SearchResults extends ListActivity {
|
||||
DB.releaseDB();
|
||||
}
|
||||
|
||||
Vector<DB.App> apps = new Vector<DB.App>();
|
||||
List<DB.App> apps = new ArrayList<DB.App>();
|
||||
AppFilter appfilter = new AppFilter(this);
|
||||
Vector<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
|
||||
List<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
|
||||
for (DB.App tapp : tapps) {
|
||||
boolean include = false;
|
||||
for (String tid : matchingids) {
|
||||
|
@ -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<DB.Repo> repos;
|
||||
List<DB.Repo> repos;
|
||||
try {
|
||||
DB db = DB.getDB();
|
||||
repos = db.getRepos();
|
||||
@ -119,14 +144,17 @@ public class UpdateService extends IntentService {
|
||||
}
|
||||
|
||||
// Process each repo...
|
||||
Vector<DB.App> apps = new Vector<DB.App>();
|
||||
Vector<Integer> keeprepos = new Vector<Integer>();
|
||||
List<DB.App> apps = new ArrayList<DB.App>();
|
||||
List<Integer> keeprepos = new ArrayList<Integer>();
|
||||
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<DB.App> acceptedapps = new Vector<DB.App>();
|
||||
Vector<DB.App> prevapps = ((FDroidApp) getApplication())
|
||||
.getApps();
|
||||
sendStatus(STATUS_INFO, getString(R.string.status_checking_compatibility));
|
||||
List<DB.App> acceptedapps = new ArrayList<DB.App>();
|
||||
List<DB.App> 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);
|
||||
sendStatus(STATUS_ERROR, errmsg);
|
||||
} else {
|
||||
receiver.send(0, resultData);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
sendStatus(STATUS_ERROR, errmsg);
|
||||
} finally {
|
||||
Log.d("FDroid", "Update took "
|
||||
+ ((System.currentTimeMillis() - startTime) / 1000)
|
||||
+ " seconds.");
|
||||
receiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void getIcon(DB.App app, Vector<DB.Repo> repos) {
|
||||
private void getIcon(DB.App app, List<DB.Repo> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
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;
|
||||
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user