Commits

Anonymous committed 2f5b84e

add in split off rss

Comments (0)

Files changed (7)

android/placeUvote/src/com/placeuvote/android/rss/FeedData.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+public class FeedData {
+	public static final String CONTENT = "content://";
+	
+	public static final String AUTHORITY = "com.placeuvote.android.rss.FeedData";
+	
+	private static final String TYPE_PRIMARY_KEY = "INTEGER PRIMARY KEY AUTOINCREMENT";
+	
+	private static final String TYPE_TEXT = "TEXT";
+	
+	private static final String TYPE_DATETIME = "DATETIME";
+	
+	public static final String TYPE_INT = "INT";
+
+	public static final String TYPE_BOOLEAN = "INTEGER(1)";
+	
+	public static class FeedColumns implements BaseColumns {
+		public static final Uri CONTENT_URI = Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/feeds").toString());
+		
+		public static final String URL = "url";
+		
+		public static final String NAME = "name";
+		
+		public static final String LASTUPDATE = "lastupdate";
+		
+		public static final String ICON = "icon";
+		
+		public static final String ERROR = "error";
+		
+		public static final String PRIORITY = "priority";
+		
+		public static final String FETCHMODE = "fetchmode";
+		
+		public static final String[] COLUMNS = new String[] {_ID, URL, NAME, LASTUPDATE, ICON, ERROR, PRIORITY, FETCHMODE};
+		
+		public static final String[] TYPES = new String[] {TYPE_PRIMARY_KEY, "TEXT UNIQUE", TYPE_TEXT, TYPE_DATETIME, "BLOB", TYPE_TEXT, TYPE_INT, TYPE_INT};
+		
+		public static final Uri CONTENT_URI(String feedId) {
+			return Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/feeds/").append(feedId).toString());
+		}
+	}
+	
+	public static class EntryColumns implements BaseColumns {
+		public static final String FEED_ID = "feedid";
+		
+		public static final String TITLE = "title";
+		
+		public static final String ABSTRACT = "abstract";
+		
+		public static final String DATE = "date";
+		
+		public static final String READDATE = "readdate";
+		
+		public static final String LINK = "link";
+		
+		public static final String FAVORITE = "favorite";
+		
+		public static final String[] COLUMNS = new String[] {_ID, FEED_ID, TITLE, ABSTRACT, DATE, READDATE, LINK, FAVORITE};
+		
+		public static final String[] TYPES = new String[] {TYPE_PRIMARY_KEY, "INTEGER(7)", TYPE_TEXT, TYPE_TEXT, TYPE_DATETIME, TYPE_DATETIME, TYPE_TEXT, TYPE_BOOLEAN};
+
+		public static Uri CONTENT_URI = Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/entries").toString());
+		
+		public static Uri FAVORITES_CONTENT_URI = Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/favorites").toString());
+
+		public static Uri CONTENT_URI(String feedId) {
+			return Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/feeds/").append(feedId).append("/entries").toString());
+		}
+
+		public static Uri ENTRY_CONTENT_URI(String entryId) {
+			return Uri.parse(new StringBuilder(CONTENT).append(AUTHORITY).append("/entries/").append(entryId).toString());
+		}
+		
+	}
+
+}

android/placeUvote/src/com/placeuvote/android/rss/FeedDataContentProvider.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+import java.io.File;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Environment;
+import android.text.TextUtils;
+import com.placeuvote.android.rss.Strings;
+
+public class FeedDataContentProvider extends ContentProvider {
+	public static boolean USE_SDCARD;
+	
+	private static final String FOLDER = Environment.getExternalStorageDirectory()+"/sparserss/";
+	
+	private static final String DATABASE_NAME = "placeuvote.db";
+	
+	private static final int DATABASE_VERSION = 4;
+	
+	private static final int URI_FEEDS = 1;
+	
+	private static final int URI_FEED = 2;
+	
+	private static final int URI_ENTRIES = 3;
+	
+	private static final int URI_ENTRY= 4;
+	
+	private static final int URI_ALLENTRIES = 5;
+	
+	private static final int URI_ALLENTRIES_ENTRY = 6;
+	
+	private static final int URI_FAVORITES = 7;
+	
+	private static final int URI_FAVORITES_ENTRY = 8;
+	
+	private static final String TABLE_FEEDS = "feeds";
+	
+	private static final String TABLE_ENTRIES = "entries";
+	
+	private static final String ALTER_TABLE = "ALTER TABLE ";
+	
+	private static final String ADD = " ADD ";
+	
+	private static final String EQUALS_ONE = "=1";
+	
+	private static UriMatcher URI_MATCHER;
+	
+	static {
+		URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "feeds", URI_FEEDS);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "feeds/#", URI_FEED);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "feeds/#/entries", URI_ENTRIES);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "feeds/#/entries/#", URI_ENTRY);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "entries", URI_ALLENTRIES);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "entries/#", URI_ALLENTRIES_ENTRY);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "favorites", URI_FAVORITES);
+		URI_MATCHER.addURI(FeedData.AUTHORITY, "favorites/#", URI_FAVORITES_ENTRY);
+	}
+	
+	private static class DatabaseHelper {
+		private SQLiteDatabase database;
+		
+		public DatabaseHelper(Context context, String name, int version) {
+			File file = new File(FOLDER);
+			
+			if ((file.exists() && file.isDirectory() || file.mkdir()) && file.canWrite()) {
+				try {
+					database = SQLiteDatabase.openDatabase(FOLDER+name, null, SQLiteDatabase.OPEN_READWRITE + SQLiteDatabase.CREATE_IF_NECESSARY);
+					
+					if (database.getVersion() == 0) {
+						onCreate(database);
+					} else {
+						onUpgrade(database, database.getVersion(), DATABASE_VERSION);
+					}
+					database.setVersion(DATABASE_VERSION);
+					USE_SDCARD = true;
+				} catch (SQLException sqlException) {
+					database = new SQLiteOpenHelper(context, name, null, version) {
+						@Override
+						public void onCreate(SQLiteDatabase db) {
+							DatabaseHelper.this.onCreate(db);
+						}
+
+						@Override
+						public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+							DatabaseHelper.this.onUpgrade(db, oldVersion, newVersion);
+						}
+					}.getWritableDatabase();
+					USE_SDCARD = false;
+				}
+			} else {
+				database = new SQLiteOpenHelper(context, name, null, version) {
+					@Override
+					public void onCreate(SQLiteDatabase db) {
+						DatabaseHelper.this.onCreate(db);
+					}
+
+					@Override
+					public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+						DatabaseHelper.this.onUpgrade(db, oldVersion, newVersion);
+					}
+				}.getWritableDatabase();
+				USE_SDCARD = false;
+			}
+			context.sendBroadcast(new Intent(Strings.ACTION_UPDATEWIDGET));
+		}
+
+		public void onCreate(SQLiteDatabase database) {
+			database.execSQL(createTable(TABLE_FEEDS, FeedData.FeedColumns.COLUMNS, FeedData.FeedColumns.TYPES));
+			database.execSQL(createTable(TABLE_ENTRIES, FeedData.EntryColumns.COLUMNS, FeedData.EntryColumns.TYPES));
+		}
+		
+		private String createTable(String tableName, String[] columns, String[] types) {
+			if (tableName == null || columns == null || types == null || types.length != columns.length || types.length == 0) {
+				throw new IllegalArgumentException("Invalid parameters for creating table "+tableName);
+			} else {
+				StringBuilder stringBuilder = new StringBuilder("CREATE TABLE ");
+				
+				stringBuilder.append(tableName);
+				stringBuilder.append(" (");
+				for (int n = 0, i = columns.length; n < i; n++) {
+					if (n > 0) {
+						stringBuilder.append(", ");
+					}
+					stringBuilder.append(columns[n]).append(' ').append(types[n]);
+				}
+				return stringBuilder.append(");").toString();
+			}
+		}
+
+		public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
+			if (oldVersion < 2) {
+				database.execSQL(new StringBuilder(ALTER_TABLE).append(TABLE_FEEDS).append(ADD).append(FeedData.FeedColumns.PRIORITY).append(' ').append(FeedData.TYPE_INT).toString());
+			}
+			if (oldVersion < 3) {
+				database.execSQL(new StringBuilder(ALTER_TABLE).append(TABLE_ENTRIES).append(ADD).append(FeedData.EntryColumns.FAVORITE).append(' ').append(FeedData.TYPE_BOOLEAN).toString());
+			}
+			if (oldVersion < 4) {
+				database.execSQL(new StringBuilder(ALTER_TABLE).append(TABLE_FEEDS).append(ADD).append(FeedData.FeedColumns.FETCHMODE).append(' ').append(FeedData.TYPE_INT).toString());
+			}
+		}
+
+		public SQLiteDatabase getWritableDatabase() {
+			return database;
+		}
+	}
+	
+	private SQLiteDatabase database;
+
+	@Override
+	public int delete(Uri uri, String selection, String[] selectionArgs) {
+		int option = URI_MATCHER.match(uri);
+		
+		String table = null;
+		
+		StringBuilder where = new StringBuilder();
+		
+		switch(option) {
+			case URI_FEED : {
+				table = TABLE_FEEDS;
+			
+				final String feedId = uri.getPathSegments().get(1);
+				
+				new Thread() {
+					public void run() {
+						delete(FeedData.EntryColumns.CONTENT_URI(feedId), null, null);
+					}
+				}.start();
+				
+				where.append(FeedData.FeedColumns._ID).append('=').append(feedId);
+				break;
+			}
+			case URI_FEEDS : {
+				table = TABLE_FEEDS;
+				delete(FeedData.FeedColumns.CONTENT_URI("0"), null, null);
+				break;
+			}
+			case URI_ENTRY : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(3));
+				break;
+			}
+			case URI_ENTRIES : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns.FEED_ID).append('=').append(uri.getPathSegments().get(1));
+				break;
+			}
+			case URI_ALLENTRIES : {
+				table = TABLE_ENTRIES;
+				break;
+			}
+			case URI_FAVORITES_ENTRY : 
+			case URI_ALLENTRIES_ENTRY : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(1));
+				break;
+			}
+			case URI_FAVORITES : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns.FAVORITE).append(EQUALS_ONE);
+				break;
+			}
+		}
+		
+		if (!TextUtils.isEmpty(selection)) {
+			where.append(Strings.DB_AND).append(selection);
+		}
+		
+		int count = database.delete(table, where.toString(), selectionArgs);
+		
+		if (count > 0) {
+			getContext().getContentResolver().notifyChange(uri, null);
+		}
+		return count;
+	}
+
+	@Override
+	public String getType(Uri uri) {
+		int option = URI_MATCHER.match(uri);
+		
+		switch(option) {
+			case URI_FEEDS : return "vnd.android.cursor.dir/vnd.feeddata.feed";
+			case URI_FEED : return "vnd.android.cursor.item/vnd.feeddata.feed";
+			case URI_FAVORITES : 
+			case URI_ALLENTRIES :
+			case URI_ENTRIES : return "vnd.android.cursor.dir/vnd.feeddata.entry";
+			case URI_FAVORITES_ENTRY : 
+			case URI_ALLENTRIES_ENTRY : 
+			case URI_ENTRY : return "vnd.android.cursor.item/vnd.feeddata.entry";
+			default : throw new IllegalArgumentException("Unknown URI: "+uri);
+		}
+	}
+
+	@Override
+	public Uri insert(Uri uri, ContentValues values) {
+		long newId = 0;
+		
+		int option = URI_MATCHER.match(uri);
+		
+		switch (option) {
+			case URI_FEEDS : {
+				database.insert(TABLE_FEEDS, null, values);
+				break;
+			}
+			case URI_ENTRIES : {
+				values.put(FeedData.EntryColumns.FEED_ID, uri.getPathSegments().get(1));
+				database.insert(TABLE_ENTRIES, null, values);
+				break;
+			}
+			case URI_ALLENTRIES : {
+				database.insert(TABLE_ENTRIES, null, values);
+				break;
+			}
+			default : throw new IllegalArgumentException("Illegal insert");
+		}
+		if (newId > -1) {
+			getContext().getContentResolver().notifyChange(uri, null);
+			return ContentUris.withAppendedId(uri, newId);
+		} else {
+			throw new SQLException("Could not insert row into "+uri);
+		}
+	}
+
+	@Override
+	public boolean onCreate() {
+		database = new DatabaseHelper(getContext(), DATABASE_NAME, DATABASE_VERSION).getWritableDatabase();
+		
+		return database != null;
+	}
+
+	@Override
+	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+		SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+		
+		int option = URI_MATCHER.match(uri);
+		
+		switch(option) {
+			case URI_FEED : {
+				queryBuilder.setTables(TABLE_FEEDS);
+				queryBuilder.appendWhere(new StringBuilder(FeedData.FeedColumns._ID).append('=').append(uri.getPathSegments().get(1)));
+				break;
+			}
+			case URI_FEEDS : {
+				queryBuilder.setTables(TABLE_FEEDS);
+				break;
+			}
+			case URI_ENTRY : {
+				queryBuilder.setTables(TABLE_ENTRIES);
+				queryBuilder.appendWhere(new StringBuilder(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(3)));
+				break;
+			}
+			case URI_ENTRIES : {
+				queryBuilder.setTables(TABLE_ENTRIES);
+				queryBuilder.appendWhere(new StringBuilder(FeedData.EntryColumns.FEED_ID).append('=').append(uri.getPathSegments().get(1)));
+				break;
+			}
+			case URI_ALLENTRIES : {
+				queryBuilder.setTables("entries join (select name, icon, _id as feed_id from feeds) as F on (entries.feedid = F.feed_id)");
+				break;
+			}
+			case URI_FAVORITES_ENTRY : 
+			case URI_ALLENTRIES_ENTRY : {
+				queryBuilder.setTables(TABLE_ENTRIES);
+				queryBuilder.appendWhere(new StringBuilder(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(1)));
+				break;
+			}
+			case URI_FAVORITES : {
+				queryBuilder.setTables("entries join (select name, icon, _id as feed_id from feeds) as F on (entries.feedid = F.feed_id)");
+				queryBuilder.appendWhere(new StringBuilder(FeedData.EntryColumns.FAVORITE).append(EQUALS_ONE));
+				break;
+			}
+		}
+		Cursor cursor = queryBuilder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
+
+		cursor.setNotificationUri(getContext().getContentResolver(), uri);
+		return cursor;
+	}
+
+	@Override
+	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+		int option = URI_MATCHER.match(uri);
+		
+		String table = null;
+		
+		StringBuilder where = new StringBuilder();
+		
+		switch(option) {
+			case URI_FEED : {
+				table = TABLE_FEEDS;
+				where.append(FeedData.FeedColumns._ID).append('=').append(uri.getPathSegments().get(1));
+				break;
+			}
+			case URI_FEEDS : {
+				table = TABLE_FEEDS;
+				break;
+			}
+			case URI_ENTRY : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(3));
+				break;
+			}
+			case URI_ENTRIES : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns.FEED_ID).append('=').append(uri.getPathSegments().get(1));
+				break;
+			}
+			case URI_ALLENTRIES: {
+				table = TABLE_ENTRIES;
+				break;
+			}
+			case URI_FAVORITES_ENTRY : 
+			case URI_ALLENTRIES_ENTRY : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns._ID).append('=').append(uri.getPathSegments().get(1));
+				break;
+			}
+			case URI_FAVORITES : {
+				table = TABLE_ENTRIES;
+				where.append(FeedData.EntryColumns.FAVORITE).append(EQUALS_ONE);				
+				break;
+			}
+		}
+		
+		if (!TextUtils.isEmpty(selection)) {
+			if (where.length() > 0) {
+				where.append(Strings.DB_AND).append(selection);
+			} else {
+				where.append(selection);
+			}
+		}
+		
+		int count = database.update(table, values, where.toString(), selectionArgs);
+		
+		if (count > 0) {
+			getContext().getContentResolver().notifyChange(uri, null);
+		}
+		return count;
+	}
+
+}

android/placeUvote/src/com/placeuvote/android/rss/FetcherService.java

+package com.placeuvote.android.rss;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Date;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.util.Xml;
+import com.placeuvote.android.R;
+import com.placeuvote.android.PollList;
+import com.placeuvote.android.rss.Strings;
+import com.placeuvote.android.rss.RSSHandler;
+import com.placeuvote.android.rss.FeedData;
+
+public class FetcherService extends Service {
+	private static final int FETCHMODE_DIRECT = 1;
+	
+	private static final int FETCHMODE_REENCODE = 2;
+	
+	private static final String KEY_USERAGENT = "User-agent";
+	
+	private static final String VALUE_USERAGENT = "Mozilla/5.0";
+	
+	private static final String CHARSET = "charset=";
+	
+	private static final String COUNT = "COUNT(*)";
+	
+	private static final String CONTENT_TYPE_TEXT_HTML = "text/html";
+	
+	private static final String LINK_RSS = "<link rel=\"alternate\" ";
+	
+	private static final String LINK_RSS_SLOPPY = "<link rel=alternate "; 
+	
+	private static final String HREF = "href=\"";
+	
+	private static final String HTML_BODY = "<body";
+	
+	private static final String SLASH = "/";
+	
+	private static final String ENCODING = "encoding=\"";
+	
+	boolean running = false;
+	
+	private NotificationManager notificationManager;
+	
+	static {
+		HttpURLConnection.setFollowRedirects(true);
+	}
+	
+	@Override
+	public void onStart(Intent intent, int startId) {
+		handleIntent(intent);
+	}
+
+	@Override
+	public void onLowMemory() {
+		stopSelf();
+	}
+
+	public int onStartCommand(Intent intent, int flags, int startId) {
+		handleIntent(intent);
+		return START_STICKY;
+	}
+	
+	private void handleIntent(final Intent intent) {
+		if (running) {
+			return;
+		}
+		running = true;
+		ConnectivityManager connectivityManager =  (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+		
+		NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+		
+		if (networkInfo != null && networkInfo.getState() == NetworkInfo.State.CONNECTED && intent != null) {
+			new Thread() {
+				public void run() {
+					int newCount = FetcherService.refreshFeedsStatic(FetcherService.this, intent.getStringExtra(Strings.FEEDID));
+					
+					if (newCount > 0) {
+						SharedPreferences preferences = null;
+
+						try {
+							preferences = PreferenceManager.getDefaultSharedPreferences(createPackageContext(Strings.PACKAGE, 0));
+						} catch (NameNotFoundException e) {
+							preferences = PreferenceManager.getDefaultSharedPreferences(FetcherService.this);
+						}
+						if (preferences.getBoolean(Strings.SETTINGS_NOTIFICATIONSENABLED, false)) {
+							Cursor cursor = getContentResolver().query(FeedData.EntryColumns.CONTENT_URI, new String[] {COUNT}, new StringBuilder(FeedData.EntryColumns.READDATE).append(Strings.DB_ISNULL).toString(), null, null);
+							
+							cursor.moveToFirst();
+							newCount = cursor.getInt(0);
+							cursor.close();
+							
+							String text = new StringBuilder().append(newCount).append(' ').append(getString(R.string.new_entries)).toString();
+							
+							Notification notification = new Notification(R.drawable.ic_statusbar_rss, text, System.currentTimeMillis());
+							
+							Intent notificationIntent = new Intent(FetcherService.this, PollList.class);
+							
+							PendingIntent contentIntent = PendingIntent.getActivity(FetcherService.this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+							notification.flags |= Notification.FLAG_AUTO_CANCEL;
+							if (preferences.getBoolean(Strings.SETTINGS_NOTIFICATIONSVIBRATE, false)) {
+								notification.defaults |= Notification.DEFAULT_VIBRATE;
+							}
+							notification.defaults |= Notification.DEFAULT_LIGHTS;
+							
+							String ringtone = preferences.getString(Strings.SETTINGS_NOTIFICATIONSRINGTONE, null);
+							
+							if (ringtone != null && ringtone.length() > 0) {
+								notification.sound = Uri.parse(ringtone);
+							}
+							notification.setLatestEventInfo(FetcherService.this, getString(R.string.rss_feeds), text, contentIntent);
+							notificationManager.notify(0, notification);
+						} else {
+							notificationManager.cancel(0);
+						}
+					}
+					running = false;
+					stopSelf();
+				}
+			}.start();
+		} else {
+			running = false;
+			stopSelf();
+		}
+	}
+	
+	
+	
+	@Override
+	public IBinder onBind(Intent intent) {
+		return null;
+	}
+
+	@Override
+	public void onCreate() {
+		super.onCreate();
+		running = false;
+		notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+	}
+	
+	private static int refreshFeedsStatic(Context context, String feedId) {
+		Cursor cursor = context.getContentResolver().query(feedId == null ? FeedData.FeedColumns.CONTENT_URI : FeedData.FeedColumns.CONTENT_URI(feedId), null, null, null, null); // no managed query here
+		
+		int urlPosition = cursor.getColumnIndex(FeedData.FeedColumns.URL);
+		
+		int idPosition = cursor.getColumnIndex(FeedData.FeedColumns._ID);
+		
+		int lastUpdatePosition = cursor.getColumnIndex(FeedData.FeedColumns.LASTUPDATE);
+		
+		int titlePosition = cursor.getColumnIndex(FeedData.FeedColumns.NAME);
+		
+		int fetchmodePosition = cursor.getColumnIndex(FeedData.FeedColumns.FETCHMODE);
+		
+		int iconPosition = cursor.getColumnIndex(FeedData.FeedColumns.ICON);
+		
+//		HttpURLConnection.setFollowRedirects(false);
+		
+		int result = 0;
+				
+		while(cursor.moveToNext()) {
+			String id = cursor.getString(idPosition);
+			
+			RSSHandler handler = new RSSHandler(context);
+			
+			try {
+				HttpURLConnection connection = setupConnection(cursor.getString(urlPosition));
+				
+				String contentType = connection.getContentType();
+				
+				int fetchMode = cursor.getInt(fetchmodePosition);
+				
+				handler.init(new Date(cursor.getLong(lastUpdatePosition)), id, cursor.getString(titlePosition));
+				
+				if (fetchMode == 0) {
+					if (contentType != null && contentType.startsWith(CONTENT_TYPE_TEXT_HTML)) {
+						BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+						
+						String line = null;
+						
+						int pos = -1;
+						
+						while ((line = reader.readLine()) != null) {
+							connection = null;
+							if (line.indexOf(HTML_BODY) > -1) {
+								break;
+							} else {
+								pos = line.indexOf(LINK_RSS);
+								
+								if (pos == -1) {
+									pos = line.indexOf(LINK_RSS_SLOPPY);
+								}
+								if (pos > -1) {
+									int posStart = line.indexOf(HREF, pos);
+
+									if (posStart > -1) {
+										String url = line.substring(posStart+6, line.indexOf('"', posStart+10)).replace(RSSHandler.AMP_SG, RSSHandler.AMP);
+										
+										ContentValues values = new ContentValues();
+										
+										if (url.startsWith(SLASH)) {
+											url = cursor.getString(urlPosition)+url;
+										} else if (!url.startsWith(Strings.HTTP) && !url.startsWith(Strings.HTTPS)) {
+											url = new StringBuilder(cursor.getString(urlPosition)).append('/').append(url).toString();
+										}
+										values.put(FeedData.FeedColumns.URL, url);
+										context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+										connection = setupConnection(url);
+										contentType = connection.getContentType();
+										break;
+									}
+								}
+							}
+						}
+						if (connection == null) { // this indicates a badly configured feed
+							connection = setupConnection(cursor.getString(urlPosition));
+						}
+					}
+					
+					if (contentType != null) {
+						int index = contentType.indexOf(CHARSET);
+						
+						if (index > -1) {
+							int index2 = contentType.indexOf(';', index);
+							
+							try {
+								Xml.findEncodingByName(index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8));
+								fetchMode = FETCHMODE_DIRECT;
+							} catch (UnsupportedEncodingException usee) {
+								fetchMode = FETCHMODE_REENCODE;
+							}
+						} else {
+							fetchMode = FETCHMODE_REENCODE;
+						}
+						
+					} else {
+						BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+						
+						char[] chars = new char[20];
+						
+						int length = bufferedReader.read(chars);
+						
+						String xmlDescription = new String(chars, 0, length);
+						
+						connection.disconnect();
+						connection = setupConnection(connection.getURL());
+						
+						int start = xmlDescription != null ?  xmlDescription.indexOf(ENCODING) : -1;
+						
+						if (start > -1) {
+							try {
+								Xml.findEncodingByName(xmlDescription.substring(start+10, xmlDescription.indexOf('"', start+11)));
+								fetchMode = FETCHMODE_DIRECT;
+							} catch (UnsupportedEncodingException usee) {
+								fetchMode = FETCHMODE_REENCODE;
+							}
+						} else {
+							fetchMode = FETCHMODE_DIRECT; // absolutely no encoding information found
+						}
+					}
+					
+					ContentValues values = new ContentValues();
+					
+					values.put(FeedData.FeedColumns.FETCHMODE, fetchMode); 
+					context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+				}
+				
+				/* check and optionally find favicon */
+				byte[] iconBytes = cursor.getBlob(iconPosition);
+				
+				if (iconBytes == null) {
+					URL faviconURL = new URL(new StringBuilder(connection.getURL().getProtocol()).append(Strings.PROTOCOL_SEPARATOR).append(connection.getURL().getHost()).append(Strings.FILE_FAVICON).toString());
+
+					try {
+						InputStream input = faviconURL.openStream();
+						
+						ByteArrayOutputStream output = new ByteArrayOutputStream();
+						
+						byte[] buffer = new byte[4096];
+						  
+						int n;
+
+						while ((n = input.read(buffer)) > 0) {
+							output.write(buffer, 0, n);
+						}
+
+						iconBytes = output.toByteArray();
+						
+						output.close();
+						input.close();
+						
+						ContentValues values = new ContentValues();
+						
+						values.put(FeedData.FeedColumns.ICON, iconBytes); 
+						context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+					} catch (Exception e) {
+						ContentValues values = new ContentValues();
+						
+						values.put(FeedData.FeedColumns.ICON, new byte[0]); // no icon found or error
+						context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+					}
+				}
+				
+				switch (fetchMode) {
+					default:
+					case FETCHMODE_DIRECT: {
+						if (contentType != null) {
+							int index = contentType.indexOf(CHARSET);
+							
+							int index2 = contentType.indexOf(';', index);
+							
+							Xml.parse(connection.getInputStream(), Xml.findEncodingByName(index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8)), handler);
+						} else {
+							Xml.parse(new InputStreamReader(connection.getInputStream()), handler);
+						}
+						break;
+					}
+					case FETCHMODE_REENCODE: {
+						
+						ByteArrayOutputStream ouputStream = new ByteArrayOutputStream();
+						
+						InputStream inputStream = connection.getInputStream();
+						
+						byte[] byteBuffer = new byte[4096]; 
+						
+						int n;
+
+						while ( (n = inputStream.read(byteBuffer)) > 0 ) {
+							ouputStream.write(byteBuffer, 0, n);
+						}
+						
+						String xmlText = ouputStream.toString();
+						
+						int start = xmlText != null ?  xmlText.indexOf(ENCODING) : -1;
+						
+						if (start > -1) {
+							Xml.parse(new StringReader(new String(ouputStream.toByteArray(), xmlText.substring(start+10, xmlText.indexOf('"', start+11)))), handler);
+						} else {
+							// use content type
+							if (contentType != null) {
+								
+								int index = contentType.indexOf(CHARSET);
+								
+								if (index > -1) {
+									int index2 = contentType.indexOf(';', index);
+									
+									try {
+										Xml.parse(new StringReader(new String(ouputStream.toByteArray(), index2 > -1 ?contentType.substring(index+8, index2) : contentType.substring(index+8))), handler);
+									} catch (Exception e) {
+
+									}
+								} else {
+									Xml.parse(new StringReader(new String(ouputStream.toByteArray())), handler);
+								}
+							}
+						}
+						break;
+					}
+				}
+				result += handler.getNewCount();
+				
+			} catch (Throwable e) {
+				if (!handler.isDone()) {
+					ContentValues values = new ContentValues();
+					
+					values.put(FeedData.FeedColumns.FETCHMODE, 0); // resets the fetchmode to determine it again later
+					values.put(FeedData.FeedColumns.ERROR, e.getMessage());
+					context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+				}
+			}
+		}
+		cursor.close();
+		
+		if (result > 0) {
+			context.sendBroadcast(new Intent(Strings.ACTION_UPDATEWIDGET));
+		}
+		return result;
+	}
+	
+	private static final HttpURLConnection setupConnection(String url) throws IOException {
+		return setupConnection(new URL(url));
+	}
+	
+	private static final HttpURLConnection setupConnection(URL url) throws IOException {
+		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+		
+		connection.setDoInput(true);
+		connection.setDoOutput(false);
+		connection.setRequestProperty(KEY_USERAGENT, VALUE_USERAGENT); // some feeds need this to work properly
+		connection.connect();
+		return connection;
+	}
+}

android/placeUvote/src/com/placeuvote/android/rss/RSSHandler.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import com.placeuvote.android.rss.Strings;
+import com.placeuvote.android.rss.FeedData;
+
+public class RSSHandler extends DefaultHandler {
+	public static final String AMP_SG = "&amp;";
+	
+	public static final String AMP = "&";
+	
+	private static final String TAG_RSS = "rss";
+	
+	private static final String TAG_RDF = "rdf";
+	
+	private static final String TAG_FEED = "feed";
+	
+	private static final String TAG_ENTRY = "entry";
+	
+	private static final String TAG_ITEM = "item";
+	
+	private static final String TAG_UPDATED = "updated";
+	
+	private static final String TAG_TITLE = "title";
+	
+	private static final String TAG_LINK = "link";
+	
+	private static final String TAG_DESCRIPTION = "description";
+	
+	private static final String TAG_CONTENT = "content";
+	
+	private static final String TAG_SUMMARY = "summary";
+	
+	private static final String TAG_PUBDATE = "pubDate";
+	
+	private static final String TAG_DATE = "date";
+	
+	private static final String ATTRIBUTE_HREF = "href";
+	
+	private static final String MEST = "MEST";
+	
+	private static final String PLUS200 = "+0200";
+	
+	private static final String EST = "EST";
+	
+	private static final String MINUS500 = "-0500";
+	
+	private static long KEEP_TIME = 172800000l; // 2 days
+	
+	
+	private static final DateFormat[] PUBDATE_DATEFORMATS = {
+		new SimpleDateFormat("EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US),
+		new SimpleDateFormat("d' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US),
+		new SimpleDateFormat("EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' 'z", Locale.US),
+		
+	};
+
+	private static final int PUBDATEFORMAT_COUNT = 3;
+	
+	private static final DateFormat[] UPDATE_DATEFORMATS = {
+		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"),
+		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
+		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz", Locale.US),
+		
+	};
+	
+	private static final int DATEFORMAT_COUNT = 3;
+	
+	private static final StringBuilder DB_FAVORITE = new StringBuilder(" AND (").append(FeedData.EntryColumns.FAVORITE).append(Strings.DB_ISNULL).append(" OR ").append(FeedData.EntryColumns.FAVORITE).append('=').append("0)");
+
+	private Context context;
+	
+	private Date lastUpdateDate;
+	
+	private String id;
+
+	private boolean titleTagEntered;
+	
+	private boolean updatedTagEntered;
+	
+	private boolean linkTagEntered;
+	
+	private boolean descriptionTagEntered;
+	
+	private boolean pubDateTagEntered;
+	
+	private boolean dateTagEntered;
+	
+	private StringBuilder title;
+	
+	private StringBuilder dateStringBuilder;
+	
+	private Date entryDate;
+	
+	private StringBuilder entryLink;
+	
+	private StringBuilder description;
+	
+	private Uri feedEntiresUri;
+	
+	private int newCount;
+	
+	private boolean feedRefreshed;
+	
+	private String feedTitle;
+	
+	private boolean done;
+	
+	private Date keepDateBorder;
+	
+	public RSSHandler(Context context) {
+		KEEP_TIME = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(context).getString(Strings.SETTINGS_KEEPTIME, "2"))*86400000l;
+		keepDateBorder = new Date(System.currentTimeMillis()-KEEP_TIME);
+		this.context = context;
+	}
+	
+	public void init(Date lastUpdateDate, String id, String title) {
+		this.lastUpdateDate = lastUpdateDate;
+		this.id = id;
+		feedEntiresUri = FeedData.EntryColumns.CONTENT_URI(id);
+		context.getContentResolver().delete(feedEntiresUri, new StringBuilder(FeedData.EntryColumns.DATE).append('<').append(System.currentTimeMillis()-KEEP_TIME).append(DB_FAVORITE).toString(), null);
+		newCount = 0;
+		feedRefreshed = false;
+		feedTitle = title;
+		done = false;
+	}
+
+	@Override
+	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+		if (TAG_UPDATED.equals(localName)) {
+			updatedTagEntered = true;
+			dateStringBuilder = new StringBuilder();
+		} else if (TAG_ENTRY.equals(localName) || TAG_ITEM.equals(localName)) {
+			if (!feedRefreshed) {
+				ContentValues values = new ContentValues();
+					
+				if (feedTitle == null && title != null && title.length() > 0) {
+					values.put(FeedData.FeedColumns.NAME, title.toString().trim());
+				}
+				values.put(FeedData.FeedColumns.ERROR, (String) null);
+				values.put(FeedData.FeedColumns.LASTUPDATE, /*entryDate != null ? entryDate.getTime() : */System.currentTimeMillis() - 1000);
+				context.getContentResolver().update(FeedData.FeedColumns.CONTENT_URI(id), values, null, null);
+				title = null;
+				feedRefreshed = true;
+			}
+		} else if (TAG_TITLE.equals(localName)) {
+			if (title == null) {
+				titleTagEntered = true;
+				title = new StringBuilder();
+			}
+		} else if (TAG_LINK.equals(localName)) {
+			entryLink = new StringBuilder();
+			
+			boolean foundLink = false;
+			
+			for (int n = 0, i = attributes.getLength(); n < i; n++) {
+				if (ATTRIBUTE_HREF.equals(attributes.getLocalName(n))) {
+					if (attributes.getValue(n) != null) {
+						entryLink.append(attributes.getValue(n));
+						foundLink = true;
+						linkTagEntered = false;
+					} else {
+						linkTagEntered = true;
+					}
+					break;
+				}
+			}
+			if (!foundLink) {
+				linkTagEntered = true;
+			}
+		} else if (TAG_DESCRIPTION.equals(localName) || TAG_SUMMARY.equals(localName) || TAG_CONTENT.equals(localName)) {
+			descriptionTagEntered = true;
+			description = new StringBuilder();
+		} else if (TAG_PUBDATE.equals(localName)) {
+			pubDateTagEntered = true;
+			dateStringBuilder = new StringBuilder();
+		} else if (TAG_DATE.equals(localName)) {
+			dateTagEntered = true;
+			dateStringBuilder = new StringBuilder();
+		}
+	}
+
+	@Override
+	public void characters(char[] ch, int start, int length) throws SAXException {
+		if (titleTagEntered) {
+			title.append(ch, start, length);
+		} else if (updatedTagEntered) {
+			dateStringBuilder.append(ch, start, length);
+		} else if (linkTagEntered) {
+			entryLink.append(ch, start, length);
+		} else if (descriptionTagEntered) {
+			description.append(ch, start, length);
+		} else if (pubDateTagEntered) {
+			dateStringBuilder.append(ch, start, length);
+		} else if (dateTagEntered) {
+			dateStringBuilder.append(ch, start, length);
+		}
+	}
+	
+	@Override
+	public void endElement(String uri, String localName, String qName) throws SAXException {
+		if (TAG_TITLE.equals(localName)) {
+			titleTagEntered = false;
+		} else if (TAG_DESCRIPTION.equals(localName) || TAG_SUMMARY.equals(localName) || TAG_CONTENT.equals(localName)) {
+			descriptionTagEntered = false;
+		} else if (TAG_LINK.equals(localName)) {
+			linkTagEntered = false;
+		} else if (TAG_UPDATED.equals(localName)) {
+			String dateString = dateStringBuilder.toString();
+
+			for (int n = 0; n < DATEFORMAT_COUNT; n++) {
+				try {
+					entryDate = UPDATE_DATEFORMATS[n].parse(dateString);
+					break;
+				} catch (ParseException e) { } // just do nothing
+			}
+
+			updatedTagEntered = false;
+		} else if (TAG_PUBDATE.equals(localName)) {
+			String dateString = dateStringBuilder.toString().replace(MEST, PLUS200).replace(EST, MINUS500).replace(Strings.TWOSPACE, Strings.SPACE);// replace is needed because mest is no supported timezone
+			
+			for (int n = 0; n < PUBDATEFORMAT_COUNT; n++) {
+				try {
+					entryDate = PUBDATE_DATEFORMATS[n].parse(dateString);
+					break;
+				} catch (ParseException e) { } // just do nothing
+			}
+			pubDateTagEntered = false;
+		} else if (TAG_DATE.equals(localName)) {
+			String dateString = dateStringBuilder.toString();
+			
+			for (int n = 0; n < DATEFORMAT_COUNT; n++) {
+				try {
+					entryDate = UPDATE_DATEFORMATS[n].parse(dateString);
+					break;
+				} catch (ParseException e) { } // just do nothing
+			}
+			dateTagEntered = false;
+		} else if (TAG_ENTRY.equals(localName) || TAG_ITEM.equals(localName)) {
+			if (title != null && (entryDate == null || (entryDate.after(lastUpdateDate) && entryDate.after(keepDateBorder)))) {
+				ContentValues values = new ContentValues();
+				
+				if (entryDate != null) {
+					values.put(FeedData.EntryColumns.DATE, entryDate.getTime());
+					values.putNull(FeedData.EntryColumns.READDATE);
+				}
+				values.put(FeedData.EntryColumns.TITLE, title.toString().trim().replace(AMP_SG, AMP));
+				if (description != null) {
+					values.put(FeedData.EntryColumns.ABSTRACT, description.toString().trim()); // maybe better use regex, but this will do it for now
+					description = null;
+				}
+				
+				String entryLinkString = entryLink.toString().trim();
+
+				if (entryLinkString.length() > 0 && context.getContentResolver().update(feedEntiresUri, values, new StringBuilder(FeedData.EntryColumns.LINK).append(Strings.DB_ARG).toString(), new String[] {entryLinkString}) == 0) {
+					values.put(FeedData.EntryColumns.LINK, entryLinkString);
+					if (entryDate == null) {
+						values.put(FeedData.EntryColumns.DATE, System.currentTimeMillis());
+					} else {
+						values.remove(FeedData.EntryColumns.READDATE);
+					}
+					context.getContentResolver().insert(feedEntiresUri, values);
+					newCount++;
+				}
+			}
+			title = null;
+		} else if (TAG_RSS.equals(localName) || TAG_RDF.equals(localName) || TAG_FEED.equals(localName)) {
+			done = true;
+		}
+	}
+	
+	public int getNewCount() {
+		return newCount;
+	}
+	
+	public boolean isDone() {
+		return done;
+	}
+	
+}

android/placeUvote/src/com/placeuvote/android/rss/RefreshBroadcastReceiver.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import com.placeuvote.android.rss.FetcherService;
+
+public class RefreshBroadcastReceiver extends BroadcastReceiver {
+	@Override
+	public void onReceive(Context context, Intent intent) {
+		context.startService(new Intent(context, FetcherService.class).putExtras(intent)); // a thread would mark the process as inactive
+	}
+	
+}

android/placeUvote/src/com/placeuvote/android/rss/RefreshService.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import com.placeuvote.android.rss.Strings;
+
+public class RefreshService extends Service {   
+	private static final String SIXTYMINUTES = "3600000";
+	
+    private OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() {
+		public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+			if (Strings.SETTINGS_REFRESHINTERVAL.equals(key)) {
+				restartTimer();
+			}
+		}
+    };
+
+    private Intent refreshBroadcastIntent;
+    
+	private AlarmManager alarmManager;
+	
+	private PendingIntent timerIntent;
+	
+	
+	
+	private SharedPreferences preferences = null;
+	
+	@Override
+	public IBinder onBind(Intent intent) {
+		onRebind(intent);
+		return null;
+	}
+	
+	@Override
+	public void onRebind(Intent intent) {
+		super.onRebind(intent);
+	}
+
+	@Override
+	public boolean onUnbind(Intent intent) {
+		return true;  // we want to use rebind
+	}
+
+	@Override
+	public void onCreate() {
+		super.onCreate();
+		try {
+			preferences = PreferenceManager.getDefaultSharedPreferences(createPackageContext(Strings.PACKAGE, 0));
+		} catch (NameNotFoundException e) {
+			preferences = PreferenceManager.getDefaultSharedPreferences(this);
+		}
+		
+		refreshBroadcastIntent = new Intent(Strings.ACTION_REFRESHFEEDS);
+		alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
+		preferences.registerOnSharedPreferenceChangeListener(listener);
+		restartTimer();
+	}
+
+	private void restartTimer() {
+		if (timerIntent == null) {
+			timerIntent = PendingIntent.getBroadcast(this, 0, refreshBroadcastIntent, 0);
+		} else {
+			alarmManager.cancel(timerIntent);
+		}
+		
+		int time = 3600000;
+		
+		try {
+			time = Math.max(60000, Integer.parseInt(preferences.getString(Strings.SETTINGS_REFRESHINTERVAL, SIXTYMINUTES)));
+		} catch (Exception exception) {
+
+		}
+		alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 10000, time, timerIntent);
+	}
+
+	@Override
+	public void onDestroy() {
+		if (timerIntent != null) {
+			alarmManager.cancel(timerIntent);
+		}
+		preferences.unregisterOnSharedPreferenceChangeListener(listener);
+		super.onDestroy();
+	}
+}

android/placeUvote/src/com/placeuvote/android/rss/Strings.java

+/**
+ * Sparse rss
+ * 
+ * Copyright (c) 2010 Stefan Handschuh
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+package com.placeuvote.android.rss;
+
+public final class Strings {
+	public static final String PACKAGE = "com.placeuvote.android";
+	
+	public static final String SETTINGS_REFRESHINTERVAL = "refresh.interval";
+	
+	public static final String SETTINGS_NOTIFICATIONSENABLED = "notifications.enabled";
+	
+	public static final String SETTINGS_REFRESHENABLED = "refresh.enabled";
+	
+	public static final String SETTINGS_REFRESHONPENENABLED = "refreshonopen.enabled";
+	
+	public static final String SETTINGS_NOTIFICATIONSRINGTONE = "notifications.ringtone";
+	
+	public static final String SETTINGS_NOTIFICATIONSVIBRATE = "notifications.vibrate";
+	
+	public static final String SETTINGS_PRIORITIZE = "contentpresentation.prioritize";
+	
+	public static final String SETTINGS_SHOWTABS = "tabs.show";
+	
+	public static final String ACTION_REFRESHFEEDS = "de.shandschuh.sparserss.REFRESH";
+	
+	public static final String ACTION_UPDATEWIDGET = "de.shandschuh.sparserss.widget.UPDATE";
+	
+	public static final String ACTION_RESTART = "de.shandschuh.sparserss.RESTART";
+	
+	public static final String FEEDID = "feedid";
+	
+	public static final String DB_ISNULL = " IS NULL";
+	
+	public static final String DB_DESC = " DESC";
+	
+	public static final String DB_ARG = "=?";
+	
+	public static final String DB_AND = " AND ";
+	
+	public static final String EMPTY = "";
+
+	public static final String SETTINGS_KEEPTIME = "keeptime";
+	
+	public static final String HTTP = "http://";
+	
+	public static final String HTTPS = "https://";
+
+	public static final String PROTOCOL_SEPARATOR = "://";
+
+	public static final String FILE_FAVICON = "/favicon.ico";
+	
+	public static final String SPACE = " ";
+	
+	public static final String TWOSPACE = "  ";
+
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.