Merge branch 'master' into development

Conflicts:
	src/org/fdroid/fdroid/DB.java
	src/org/fdroid/fdroid/FDroid.java
	src/org/fdroid/fdroid/RepoXMLHandler.java
This commit is contained in:
Peter Serwylo 2014-01-06 15:05:37 +11:00
commit c1ccdcfca8
9 changed files with 2404 additions and 21 deletions

10
.gitignore vendored
View File

@ -1,11 +1,11 @@
/local.properties /local.properties
.classpath /.classpath
/bin/ /bin/
/gen/ /gen/
/build /build/
/.gradle /.gradle/
/build.xml /build.xml
*~ *~
.idea /.idea/
*.iml /*.iml
out out

View File

@ -3,7 +3,7 @@
package="org.fdroid.fdroid" package="org.fdroid.fdroid"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="560" android:versionCode="560"
android:versionName="@string/version_name" > android:versionName="0.56-test" >
<uses-sdk <uses-sdk
android:minSdkVersion="5" android:minSdkVersion="5"

View File

@ -1,3 +1,36 @@
### Upcoming release
* Tweaked some layouts, especially the app lists and their compact layout
* App lists now show more useful version information: current version names,
rather than number of versions available
* Reduce scroll lag in app lists by caching views in a ViewHolder
* Slightly increase performance in repo index XML handling by mapping apps
with a HashMap, as opposed to doing linear searches
* More info on App Details: The category in which the app was found, all the
categories the app is in and the Android version required to run each one of
its versions available.
* The preferences screen now uses descriptive summaries, which means that you
can see what the checkbox preferences actually mean and what the edit and
list preferences are set at.
* Support for dogecoin donation method added (wow)
* Don't keep app icons older than 30 days on disc cache
* Always include incompatible apks in memory to avoid issues with apps
seemingly not having any apks available
* Fixed a crash when trying to access a non-existing app
* Other minor bug fixes
* Lots of translation updates
### 0.55 (2013-11-11) ### 0.55 (2013-11-11)
* Fixed problems with category selection and permission lists on Android 2.X devices. * Fixed problems with category selection and permission lists on Android 2.X devices.
@ -7,35 +40,57 @@
* New options on the App Details screen to ignore all future updates for that * New options on the App Details screen to ignore all future updates for that
particular app, or ignore just the current update. particular app, or ignore just the current update.
* Apps with Anti-features are no longer hidden, and the corresponding * Apps with Anti-features are no longer hidden, and the corresponding
preferences to unhide them are removed. Instead they are clearly marked on the preferences to unhide them are removed. Instead they are clearly marked on the
App Details screen. App Details screen.
* Apps with incompatible native code architecture requirements are now correctly * Apps with incompatible native code architecture requirements are now correctly
filtered. filtered.
* A bug that prevented update notifications from appearing has been fixed. * A bug that prevented update notifications from appearing has been fixed.
* Theming support, with Light and Dark themes. * Theming support, with Light and Dark themes.
* New launcher and notification icons, and new default/loading app icon. Icons * New launcher and notification icons, and new default/loading app icon. Icons
are now retrieved dynamically, drastically improving startup time on first are now retrieved dynamically, drastically improving startup time on first
installation. installation.
* All app donation options have been grouped into a submenu, and Litecoin * All app donation options have been grouped into a submenu, and Litecoin
donation support has been added. donation support has been added.
* App filter settings now take effect immediately. * App filter settings now take effect immediately.
* Apk native code ABIs are now shown in expert mode. * Apk native code ABIs are now shown in expert mode.
* Search uris for market://search and fdroid.search: are now handled. * Search uris for market://search and fdroid.search: are now handled.
* A problem with ActionBar Up navigation on some devices has been fixed. * A problem with ActionBar Up navigation on some devices has been fixed.
* Other minor bug fixes, and adjustments to spacings and layouts. * Other minor bug fixes, and adjustments to spacings and layouts.
* Lots of translation updates. * Lots of translation updates.
### 0.50 (2013-08-20) ### 0.50 (2013-08-20)
* New basic app sharing functionality * New basic app sharing functionality
* Handle f-droid.org web repo as well as market:// app uris * Handle f-droid.org web repo as well as market:// app uris
* Search by just typing on main screen and search results screen * Search by just typing on main screen and search results screen
* Flattr and Bitcoin donation methods added * Flattr and Bitcoin donation methods added
* Noticeable speedups when returning from installs and uninstalls * Noticeable speedups when returning from installs and uninstalls
* Add back to home buttons to the ActionBar * Add back to home buttons to the ActionBar
* Don't recommend versions newer than the current or incompatible with the device * Don't recommend versions newer than the current or incompatible with the device
* Use standard Android cache locations rather than .fdroid on the SD card * Use standard Android cache locations rather than .fdroid on the SD card
* Fix for crash at boot time where the SD card was slow to initialise * Fix for crash at boot time where the SD card was slow to initialise
* Lots of bug fixes * Lots of bug fixes
* Lots of translation updates * Lots of translation updates

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,6 @@ import android.content.*;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.annotation.TargetApi;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.AlertDialog.Builder; import android.app.AlertDialog.Builder;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -218,7 +217,6 @@ public class FDroid extends FragmentActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@TargetApi(5)
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {

View File

@ -0,0 +1,347 @@
/*
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import android.content.*;
import android.content.res.Configuration;
import android.support.v4.view.MenuItemCompat;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.NotificationManager;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
public class FDroid extends FragmentActivity {
public static final int REQUEST_APPDETAILS = 0;
public static final int REQUEST_MANAGEREPOS = 1;
public static final int REQUEST_PREFS = 2;
public static final String EXTRA_TAB_UPDATE = "extraTab";
private static final int UPDATE_REPO = Menu.FIRST;
private static final int MANAGE_REPO = Menu.FIRST + 1;
private static final int PREFERENCES = Menu.FIRST + 2;
private static final int ABOUT = Menu.FIRST + 3;
private static final int SEARCH = Menu.FIRST + 4;
private ViewPager viewPager;
private AppListManager manager = null;
private TabManager tabManager = null;
public AppListManager getManager() {
return manager;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
manager = new AppListManager(this);
setContentView(R.layout.fdroid);
createViews();
getTabManager().createTabs();
// Start a search by just typing
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
Intent i = getIntent();
Uri data = i.getData();
String appid = null;
if (data != null) {
if (data.isHierarchical()) {
// http(s)://f-droid.org/repository/browse?fdid=app.id
appid = data.getQueryParameter("fdid");
}
} else if (i.hasExtra(EXTRA_TAB_UPDATE)) {
boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
if (showUpdateTab) {
getTabManager().selectTab(2);
}
}
if (appid != null && appid.length() > 0) {
Intent call = new Intent(this, AppDetails.class);
call.putExtra("appid", appid);
startActivityForResult(call, REQUEST_APPDETAILS);
}
}
@Override
protected void onResume() {
super.onResume();
repopulateViews();
}
/**
* Must be done *after* createViews, because it will involve a
* callback to update the tab label for the "update" tab. This
* will fail unless the tabs have actually been created.
*/
protected void repopulateViews() {
manager.repopulateLists();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getTabManager().onConfigurationChanged(newConfig);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
<<<<<<< HEAD
=======
menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon(
android.R.drawable.ic_menu_rotate);
>>>>>>> master
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
android.R.drawable.ic_menu_agenda);
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(
android.R.drawable.ic_menu_search);
menu.add(Menu.NONE, PREFERENCES, 4, R.string.menu_preferences).setIcon(
android.R.drawable.ic_menu_preferences);
menu.add(Menu.NONE, ABOUT, 5, R.string.menu_about).setIcon(
android.R.drawable.ic_menu_help);
MenuItemCompat.setShowAsAction(search, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case UPDATE_REPO:
updateRepos();
return true;
case MANAGE_REPO:
Intent i = new Intent(this, ManageRepo.class);
startActivityForResult(i, REQUEST_MANAGEREPOS);
return true;
case PREFERENCES:
Intent prefs = new Intent(getBaseContext(), PreferencesActivity.class);
startActivityForResult(prefs, REQUEST_PREFS);
return true;
case SEARCH:
onSearchRequested();
return true;
case ABOUT:
View view = null;
if (Build.VERSION.SDK_INT >= 11) {
LayoutInflater li = LayoutInflater.from(this);
view = li.inflate(R.layout.about, null);
} else {
view = View.inflate(
new ContextThemeWrapper(this, R.style.AboutDialogLight),
R.layout.about, null);
}
// Fill in the version...
try {
PackageInfo pi = getPackageManager()
.getPackageInfo(getApplicationContext()
.getPackageName(), 0);
((TextView) view.findViewById(R.id.version))
.setText(pi.versionName);
} catch (Exception e) {
}
Builder p = null;
if (Build.VERSION.SDK_INT >= 11) {
p = new AlertDialog.Builder(this).setView(view);
} else {
p = new AlertDialog.Builder(
new ContextThemeWrapper(
this, R.style.AboutDialogLight)
).setView(view);
}
final AlertDialog alrt = p.create();
alrt.setIcon(R.drawable.ic_launcher);
alrt.setTitle(getString(R.string.about_title));
alrt.setButton(AlertDialog.BUTTON_NEUTRAL,
getString(R.string.about_website),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
Uri uri = Uri.parse("https://f-droid.org");
startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
});
alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
}
});
alrt.show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_APPDETAILS:
break;
case REQUEST_MANAGEREPOS:
if (data.hasExtra(ManageRepo.REQUEST_UPDATE)) {
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
ask_alrt.setTitle(getString(R.string.repo_update_title));
ask_alrt.setIcon(android.R.drawable.ic_menu_rotate);
ask_alrt.setMessage(getString(R.string.repo_alrt));
ask_alrt.setPositiveButton(getString(R.string.yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
updateRepos();
}
});
ask_alrt.setNegativeButton(getString(R.string.no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
// do nothing
}
});
AlertDialog alert = ask_alrt.create();
alert.show();
}
break;
case REQUEST_PREFS:
// The automatic update settings may have changed, so reschedule (or
// unschedule) the service accordingly. It's cheap, so no need to
// check if the particular setting has actually been changed.
UpdateService.schedule(getBaseContext());
if ((resultCode & PreferencesActivity.RESULT_RELOAD) != 0) {
((FDroidApp) getApplication()).invalidateAllApps();
} else if ((resultCode & PreferencesActivity.RESULT_REFILTER) != 0) {
((FDroidApp) getApplication()).filterApps();
}
if ((resultCode & PreferencesActivity.RESULT_RESTART) != 0) {
((FDroidApp) getApplication()).reloadTheme();
final Intent intent = getIntent();
overridePendingTransition(0, 0);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
finish();
overridePendingTransition(0, 0);
startActivity(intent);
}
break;
}
}
private void createViews() {
viewPager = (ViewPager)findViewById(R.id.main_pager);
AppListFragmentPageAdapter viewPageAdapter = new AppListFragmentPageAdapter(this);
viewPager.setAdapter(viewPageAdapter);
viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
getTabManager().selectTab(position);
}
});
}
/**
* The first time the app is run, we will have an empty app list.
* If this is the case, we will attempt to update with the default repo.
* However, if we have tried this at least once, then don't try to do
* it automatically again, because the repos or internet connection may
* be bad.
*/
public boolean updateEmptyRepos() {
final String TRIED_EMPTY_UPDATE = "triedEmptyUpdate";
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).commit();
updateRepos();
return true;
} else {
Log.d("FDroid", "Empty app list, but it looks like we've had an update previously. Will not force repo update.");
return false;
}
}
// Force a repo update now. A progress dialog is shown and the UpdateService
// is told to do the update, which will result in the database changing. The
// UpdateReceiver class should get told when this is finished.
public void updateRepos() {
UpdateService.updateNow(this).setListener(new ProgressListener() {
@Override
public void onProgress(Event event) {
if (event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES){
repopulateViews();
}
}
});
}
private TabManager getTabManager() {
if (tabManager == null) {
tabManager = TabManager.create(this, viewPager);
}
return tabManager;
}
public void refreshUpdateTabLabel() {
getTabManager().refreshTabLabel(TabManager.INDEX_CAN_UPDATE);
}
public void removeNotification(int id) {
NotificationManager nMgr = (NotificationManager) getBaseContext()
.getSystemService(Context.NOTIFICATION_SERVICE);
nMgr.cancel(id);
}
}

View File

@ -0,0 +1,541 @@
/*
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import android.os.Bundle;
import org.fdroid.fdroid.updater.RepoUpdater;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RepoXMLHandler extends DefaultHandler {
// The repo we're processing.
private DB.Repo repo;
private Map<String, DB.App> apps;
private List<DB.App> appsList;
private DB.App curapp = null;
private DB.Apk curapk = null;
private StringBuilder curchars = new StringBuilder();
// After processing the XML, these will be -1 if the index didn't specify
// them - otherwise it will be the value specified.
private int version = -1;
private int maxage = -1;
// After processing the XML, this will be null if the index specified a
// public key - otherwise a public key. This is used for TOFU where an
// index.xml is read on the first connection, and a signed index.jar is
// expected on all subsequent connections.
private String pubkey;
private String name;
private String description;
private String hashType;
private int progressCounter = 0;
private ProgressListener progressListener;
// The date format used in the repo XML file.
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
private int totalAppCount;
public RepoXMLHandler(DB.Repo repo, List<DB.App> appsList, ProgressListener listener) {
this.repo = repo;
this.apps = new HashMap<String, DB.App>();
for (DB.App app : appsList) this.apps.put(app.id, app);
this.appsList = appsList;
pubkey = null;
name = null;
description = null;
progressListener = listener;
}
public int getMaxAge() { return maxage; }
public int getVersion() { return version; }
public String getDescription() { return description; }
public String getName() { return name; }
public String getPubKey() {
return pubkey;
}
@Override
public void characters(char[] ch, int start, int length) {
curchars.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
super.endElement(uri, localName, qName);
String curel = localName;
String str = curchars.toString();
if (str != null) {
str = str.trim();
}
if (curel.equals("application") && curapp != null) {
// If we already have this application (must be from scanning a
// different repo) then just merge in the apks.
DB.App app = apps.get(curapp.id);
if (app != null) {
app.apks.addAll(curapp.apks);
} else {
appsList.add(curapp);
apps.put(curapp.id, curapp);
}
curapp = null;
} else if (curel.equals("package") && curapk != null && curapp != null) {
curapp.apks.add(curapk);
curapk = null;
} else if (curapk != null && str != null) {
if (curel.equals("version")) {
curapk.version = str;
} else if (curel.equals("versioncode")) {
try {
curapk.vercode = Integer.parseInt(str);
} catch (NumberFormatException ex) {
curapk.vercode = -1;
}
} else if (curel.equals("size")) {
try {
curapk.detail_size = Integer.parseInt(str);
} catch (NumberFormatException ex) {
curapk.detail_size = 0;
}
} else if (curel.equals("hash")) {
if (hashType == null || hashType.equals("md5")) {
if (curapk.detail_hash == null) {
curapk.detail_hash = str;
curapk.detail_hashType = "MD5";
}
} else if (hashType.equals("sha256")) {
curapk.detail_hash = str;
curapk.detail_hashType = "SHA-256";
}
} else if (curel.equals("sig")) {
curapk.sig = str;
} else if (curel.equals("srcname")) {
curapk.srcname = str;
} else if (curel.equals("apkname")) {
curapk.apkName = str;
} else if (curel.equals("sdkver")) {
try {
curapk.minSdkVersion = Integer.parseInt(str);
} catch (NumberFormatException ex) {
curapk.minSdkVersion = 0;
}
} else if (curel.equals("added")) {
try {
curapk.added = str.length() == 0 ? null : mXMLDateFormat
.parse(str);
} catch (ParseException e) {
curapk.added = null;
}
} else if (curel.equals("permissions")) {
curapk.detail_permissions = DB.CommaSeparatedList.make(str);
} else if (curel.equals("features")) {
curapk.features = DB.CommaSeparatedList.make(str);
} else if (curel.equals("nativecode")) {
curapk.nativecode = DB.CommaSeparatedList.make(str);
}
} else if (curapp != null && str != null) {
if (curel.equals("name")) {
curapp.name = str;
} else if (curel.equals("icon")) {
curapp.icon = str;
} else if (curel.equals("description")) {
// This is the old-style description. We'll read it
// if present, to support old repos, but in newer
// repos it will get overwritten straight away!
curapp.detail_description = "<p>" + str + "</p>";
} else if (curel.equals("desc")) {
// New-style description.
curapp.detail_description = str;
} else if (curel.equals("summary")) {
curapp.summary = str;
} else if (curel.equals("license")) {
curapp.license = str;
} else if (curel.equals("source")) {
curapp.detail_sourceURL = str;
} else if (curel.equals("donate")) {
curapp.detail_donateURL = str;
} else if (curel.equals("bitcoin")) {
curapp.detail_bitcoinAddr = str;
} else if (curel.equals("litecoin")) {
curapp.detail_litecoinAddr = str;
} else if (curel.equals("dogecoin")) {
curapp.detail_dogecoinAddr = str;
} else if (curel.equals("flattr")) {
curapp.detail_flattrID = str;
} else if (curel.equals("web")) {
curapp.detail_webURL = str;
} else if (curel.equals("tracker")) {
curapp.detail_trackerURL = str;
} else if (curel.equals("added")) {
try {
curapp.added = str.length() == 0 ? null : mXMLDateFormat
.parse(str);
} catch (ParseException e) {
curapp.added = null;
}
} else if (curel.equals("lastupdated")) {
try {
curapp.lastUpdated = str.length() == 0 ? null
: mXMLDateFormat.parse(str);
} catch (ParseException e) {
curapp.lastUpdated = null;
}
} else if (curel.equals("marketversion")) {
curapp.curVersion = str;
} else if (curel.equals("marketvercode")) {
try {
curapp.curVercode = Integer.parseInt(str);
} catch (NumberFormatException ex) {
curapp.curVercode = -1;
}
} else if (curel.equals("categories")) {
curapp.categories = DB.CommaSeparatedList.make(str);
} else if (curel.equals("antifeatures")) {
curapp.antiFeatures = DB.CommaSeparatedList.make(str);
} else if (curel.equals("requirements")) {
curapp.requirements = DB.CommaSeparatedList.make(str);
}
} else if (curel.equals("description")) {
description = str;
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (localName.equals("repo")) {
String pk = attributes.getValue("", "pubkey");
if (pk != null)
pubkey = pk;
String maxAgeAttr = attributes.getValue("", "maxage");
if (maxAgeAttr != null) {
try {
maxage = Integer.parseInt(maxAgeAttr);
} catch (NumberFormatException nfe) {}
}
String versionAttr = attributes.getValue("", "version");
if (versionAttr != null) {
try {
version = Integer.parseInt(versionAttr);
} catch (NumberFormatException nfe) {}
}
String nm = attributes.getValue("", "name");
if (nm != null)
name = nm;
String dc = attributes.getValue("", "description");
if (dc != null)
description = dc;
} else if (localName.equals("application") && curapp == null) {
curapp = new DB.App();
curapp.detail_Populated = true;
curapp.id = attributes.getValue("", "id");
Bundle progressData = RepoUpdater.createProgressData(repo.address);
progressCounter ++;
progressListener.onProgress(
new ProgressListener.Event(
RepoUpdater.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.id;
hashType = null;
} else if (localName.equals("hash") && curapk != null) {
hashType = attributes.getValue("", "type");
}
curchars.setLength(0);
}
<<<<<<< HEAD
=======
// Get a remote file. Returns the HTTP response code.
// If 'etag' is not null, it's passed to the server as an If-None-Match
// header, in which case expect a 304 response if nothing changed.
// In the event of a 200 response ONLY, 'retag' (which should be passed
// 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,
ProgressListener progressListener,
ProgressListener.Event progressEvent) throws MalformedURLException,
IOException {
long startTime = System.currentTimeMillis();
URL u = new URL(url);
HttpURLConnection connection = (HttpURLConnection) u.openConnection();
if (etag != null)
connection.setRequestProperty("If-None-Match", etag);
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 = connection.getInputStream();
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
Utils.copy(input, output, progressListener, progressEvent);
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
}
String et = connection.getHeaderField("ETag");
if (et != null)
retag.append(et);
}
Log.d("FDroid", "Fetched " + url + " (" + progressEvent.total +
" bytes) in " + (System.currentTimeMillis() - startTime) +
"ms");
return code;
}
// Do an update from the given repo. All applications found, and their
// APKs, are added to 'apps'. (If 'apps' already contains an app, its
// APKs are merged into the existing one).
// Returns null if successful, otherwise an error message to be displayed
// to the user (if there is an interactive user!)
// 'newetag' should be passed empty. On success, it may contain an etag
// 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,
List<DB.App> appsList, StringBuilder newetag, List<Integer> keeprepos,
ProgressListener progressListener) {
try {
int code = 0;
if (repo.pubkey != null) {
// This is a signed repo - we download the jar file,
// check the signature, and extract the index...
Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
logDateFormat.format(new Date(System.currentTimeMillis())));
String address = repo.address + "/index.jar?client_version="
+ ctx.getString(R.string.version_name);
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, progressListener, event );
if (code == 200) {
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
JarFile jar = null;
JarEntry je;
Certificate[] certs;
try {
jar = new JarFile(jarpath, true);
je = (JarEntry) jar.getEntry("index.xml");
File efile = new File(ctx.getFilesDir(),
"/tempindex.xml");
InputStream input = null;
OutputStream output = null;
try {
input = jar.getInputStream(je);
output = new FileOutputStream(efile);
Utils.copy(input, output);
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
}
certs = je.getCertificates();
} catch (SecurityException e) {
Log.e("FDroid", "Invalid hash for index file");
return "Invalid hash for index file";
} finally {
if (jar != null) {
jar.close();
}
}
if (certs == null) {
Log.d("FDroid", "No signature found in index");
return "No signature found in index";
}
Log.d("FDroid", "Index has " + certs.length + " signature"
+ (certs.length > 1 ? "s." : "."));
boolean match = false;
for (Certificate cert : certs) {
String certdata = Hasher.hex(cert.getEncoded());
if (repo.pubkey.equals(certdata)) {
match = true;
break;
}
}
if (!match) {
Log.d("FDroid", "Index signature mismatch");
return "Index signature mismatch";
}
}
} else {
// 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,
progressListener, event);
}
if (code == 200) {
// Process the index...
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
RepoXMLHandler handler = new RepoXMLHandler(repo, appsList, progressListener);
xr.setContentHandler(handler);
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);
if (handler.pubkey != null && repo.pubkey == null) {
// We read an unsigned index, but that indicates that
// a signed version is now available...
Log.d("FDroid",
"Public key found - switching to signed repo for future updates");
repo.pubkey = handler.pubkey;
try {
DB db = DB.getDB();
db.updateRepoByAddress(repo);
} finally {
DB.releaseDB();
}
}
boolean updateRepo = false;
if (handler.version != null) {
int version = Integer.parseInt(handler.version);
if (version != repo.version) {
Log.d("FDroid", "Repo specified a new version: from "
+ repo.version + " to " + version);
repo.version = version;
updateRepo = true;
}
}
if (handler.maxage != null) {
int maxage = Integer.parseInt(handler.maxage);
if (maxage != repo.maxage) {
Log.d("FDroid",
"Repo specified a new maximum age - updated");
repo.maxage = maxage;
updateRepo = true;
}
}
if (updateRepo) {
try {
DB db = DB.getDB();
db.updateRepoByAddress(repo);
} finally {
DB.releaseDB();
}
}
} else if (code == 304) {
// The index is unchanged since we last read it. We just mark
// everything that came from this repo as being updated.
Log.d("FDroid", "Repo index for " + repo.address
+ " is up to date (by etag)");
keeprepos.add(repo.id);
// Make sure we give back the same etag. (The 200 route will
// have supplied a new one.
newetag.append(repo.lastetag);
} else {
return "Failed to read index - HTTP response "
+ Integer.toString(code);
}
} catch (SSLHandshakeException sslex) {
Log.e("FDroid", "SSLHandShakeException updating from "
+ repo.address + ":\n" + Log.getStackTraceString(sslex));
return "A problem occurred while establishing an SSL connection. If this problem persists, AND you have a very old device, you could try using http instead of https for the repo URL.";
} catch (Exception e) {
Log.e("FDroid", "Exception updating from " + repo.address + ":\n"
+ Log.getStackTraceString(e));
return "Failed to update - " + e.getMessage();
} finally {
ctx.deleteFile("tempindex.xml");
ctx.deleteFile("tempindex.jar");
}
return null;
}
>>>>>>> master
public void setTotalAppCount(int totalAppCount) {
this.totalAppCount = totalAppCount;
}
}

View File

@ -118,18 +118,15 @@ public class SearchResults extends ListActivity {
TextView tv = (TextView) findViewById(R.id.description); TextView tv = (TextView) findViewById(R.id.description);
String headertext; String headertext;
try { if (apps.size() == 0) {
if (apps.size() == 0)
headertext = String.format(getString(R.string.searchres_noapps), headertext = String.format(getString(R.string.searchres_noapps),
mQuery); mQuery);
else if (apps.size() == 1) } else if (apps.size() == 1) {
headertext = String.format(getString(R.string.searchres_oneapp), headertext = String.format(getString(R.string.searchres_oneapp),
mQuery); mQuery);
else } else {
headertext = String.format(getString(R.string.searchres_napps), headertext = String.format(getString(R.string.searchres_napps),
apps.size(), mQuery); apps.size(), mQuery);
} catch(Exception ex) {
headertext = "TRANSLATION ERROR!";
} }
tv.setText(headertext); tv.setText(headertext);
Log.d("FDroid", "Search for '" + mQuery + "' returned " + apps.size() Log.d("FDroid", "Search for '" + mQuery + "' returned " + apps.size()

View File

@ -83,7 +83,7 @@ public class SignedRepoUpdater extends RepoUpdater {
} }
protected String getIndexAddress() { protected String getIndexAddress() {
return repo.address + "/index.jar?" + context.getString(R.string.version_name); return repo.address + "/index.jar?client_version=" + context.getString(R.string.version_name);
} }
/** /**