summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh/antennapod/asynctask
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2014-09-17 20:51:45 +0200
committerdaniel oeh <daniel.oeh@gmail.com>2014-09-17 20:51:45 +0200
commit072639b5b22e816df9f78b5cd8a7d4e5379b6aff (patch)
tree341c574bd6eb64497470e7226b3222b0a7c5a824 /app/src/main/java/de/danoeh/antennapod/asynctask
parent76add8ef68dbc9997e901f4c11c397f581e8eabe (diff)
downloadAntennaPod-072639b5b22e816df9f78b5cd8a7d4e5379b6aff.zip
Changed project structure
Switched from custom layout to standard gradle project structure
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/asynctask')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/DownloadObserver.java177
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/FeedRemover.java74
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/FlattrClickWorker.java238
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java47
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/FlattrTokenFetcher.java95
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java114
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java69
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java116
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/PicassoImageResource.java37
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/PicassoProvider.java152
10 files changed, 1119 insertions, 0 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DownloadObserver.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DownloadObserver.java
new file mode 100644
index 000000000..21ae5291e
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DownloadObserver.java
@@ -0,0 +1,177 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.app.Activity;
+import android.content.*;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+
+import org.apache.commons.lang3.Validate;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.service.download.Downloader;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provides access to the DownloadService's list of items that are currently being downloaded.
+ * The DownloadObserver object should be created in the activity's onCreate() method. resume() and pause()
+ * should be called in the activity's onResume() and onPause() methods
+ */
+public class DownloadObserver {
+ private static final String TAG = "DownloadObserver";
+
+ /**
+ * Time period between update notifications.
+ */
+ public static final int WAITING_INTERVAL_MS = 3000;
+
+ private volatile Activity activity;
+ private final Handler handler;
+ private final Callback callback;
+
+ private DownloadService downloadService = null;
+ private AtomicBoolean mIsBound = new AtomicBoolean(false);
+
+ private Thread refresherThread;
+ private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false);
+
+
+ /**
+ * Creates a new download observer.
+ *
+ * @param activity Used for registering receivers
+ * @param handler All callback methods are executed on this handler. The handler MUST run on the GUI thread.
+ * @param callback Callback methods for posting content updates
+ * @throws java.lang.IllegalArgumentException if one of the arguments is null.
+ */
+ public DownloadObserver(Activity activity, Handler handler, Callback callback) {
+ Validate.notNull(activity);
+ Validate.notNull(handler);
+ Validate.notNull(callback);
+
+ this.activity = activity;
+ this.handler = handler;
+ this.callback = callback;
+ }
+
+ public void onResume() {
+ if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed");
+ activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
+ connectToDownloadService();
+ }
+
+ public void onPause() {
+ if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver paused");
+ try {
+ activity.unregisterReceiver(contentChangedReceiver);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ try {
+ activity.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ stopRefresher();
+ }
+
+ private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // reconnect to DownloadService if connection has been closed
+ if (downloadService == null) {
+ connectToDownloadService();
+ }
+ callback.onContentChanged();
+ startRefresher();
+ }
+ };
+
+ public interface Callback {
+ void onContentChanged();
+
+ void onDownloadDataAvailable(List<Downloader> downloaderList);
+ }
+
+ private void connectToDownloadService() {
+ activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0);
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName className) {
+ downloadService = null;
+ mIsBound.set(false);
+ stopRefresher();
+ Log.i(TAG, "Closed connection with DownloadService.");
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ downloadService = ((DownloadService.LocalBinder) service)
+ .getService();
+ mIsBound.set(true);
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Connection to service established");
+ List<Downloader> downloaderList = downloadService.getDownloads();
+ if (downloaderList != null && !downloaderList.isEmpty()) {
+ callback.onDownloadDataAvailable(downloaderList);
+ startRefresher();
+ }
+ }
+ };
+
+ private void stopRefresher() {
+ if (refresherThread != null) {
+ refresherThread.interrupt();
+ }
+ }
+
+ private void startRefresher() {
+ if (refresherThread == null || refresherThread.isInterrupted()) {
+ refresherThread = new Thread(new RefresherThread());
+ refresherThread.start();
+ }
+ }
+
+ private class RefresherThread implements Runnable {
+
+ public void run() {
+ refresherThreadRunning.set(true);
+ while (!Thread.interrupted()) {
+ try {
+ Thread.sleep(WAITING_INTERVAL_MS);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "Refresher thread was interrupted");
+ }
+ if (mIsBound.get()) {
+ postUpdate();
+ }
+ }
+ refresherThreadRunning.set(false);
+ }
+
+ private void postUpdate() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onContentChanged();
+ if (downloadService != null) {
+ List<Downloader> downloaderList = downloadService.getDownloads();
+ if (downloaderList == null || downloaderList.isEmpty()) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public void setActivity(Activity activity) {
+ Validate.notNull(activity);
+ this.activity = activity;
+ }
+
+}
+
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/FeedRemover.java b/app/src/main/java/de/danoeh/antennapod/asynctask/FeedRemover.java
new file mode 100644
index 000000000..0549a4255
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/FeedRemover.java
@@ -0,0 +1,74 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.annotation.SuppressLint;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.os.AsyncTask;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.storage.DBWriter;
+
+import java.util.concurrent.ExecutionException;
+
+/** Removes a feed in the background. */
+public class FeedRemover extends AsyncTask<Void, Void, Void> {
+ Context context;
+ ProgressDialog dialog;
+ Feed feed;
+
+ public FeedRemover(Context context, Feed feed) {
+ super();
+ this.context = context;
+ this.feed = feed;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ DBWriter.deleteFeed(context, feed.getId()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onCancelled() {
+ dialog.dismiss();
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ dialog.dismiss();
+ }
+
+ @Override
+ protected void onPreExecute() {
+ dialog = new ProgressDialog(context);
+ dialog.setMessage(context.getString(R.string.feed_remover_msg));
+ dialog.setOnCancelListener(new OnCancelListener() {
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ cancel(true);
+
+ }
+
+ });
+ dialog.show();
+ }
+
+ @SuppressLint("NewApi")
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrClickWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrClickWorker.java
new file mode 100644
index 000000000..9210ac1d1
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrClickWorker.java
@@ -0,0 +1,238 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.Validate;
+import org.shredzone.flattr4j.exception.FlattrException;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.FlattrAuthActivity;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBWriter;
+import de.danoeh.antennapod.util.NetworkUtils;
+import de.danoeh.antennapod.util.flattr.FlattrThing;
+import de.danoeh.antennapod.util.flattr.FlattrUtils;
+
+/**
+ * Performs a click action in a background thread.
+ * <p/>
+ * When started, the flattr click worker will try to flattr every item that is in the flattr queue. If no network
+ * connection is available it will shut down immediately. The FlattrClickWorker can also be given one additional
+ * FlattrThing which will be flattrd immediately.
+ * <p/>
+ * The FlattrClickWorker will display a toast notification for every item that has been flattrd. If the FlattrClickWorker failed
+ * to flattr something, a notification will be displayed.
+ */
+public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorker.ExitCode> {
+ protected static final String TAG = "FlattrClickWorker";
+
+ private static final int NOTIFICATION_ID = 4;
+
+ private final Context context;
+
+ public static enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS}
+
+ private volatile int countFailed = 0;
+ private volatile int countSuccess = 0;
+
+ private volatile FlattrThing extraFlattrThing;
+
+ /**
+ * Only relevant if just one thing is flattrd
+ */
+ private volatile FlattrException exception;
+
+ /**
+ * Creates a new FlattrClickWorker which will only flattr all things in the queue.
+ * <p/>
+ * The FlattrClickWorker has to be started by calling executeAsync().
+ *
+ * @param context A context for accessing the database and posting notifications. Must not be null.
+ */
+ public FlattrClickWorker(Context context) {
+ Validate.notNull(context);
+ this.context = context.getApplicationContext();
+ }
+
+ /**
+ * Creates a new FlattrClickWorker which will flattr all things in the queue and one additional
+ * FlattrThing.
+ * <p/>
+ * The FlattrClickWorker has to be started by calling executeAsync().
+ *
+ * @param context A context for accessing the database and posting notifications. Must not be null.
+ * @param extraFlattrThing The additional thing to flattr
+ */
+ public FlattrClickWorker(Context context, FlattrThing extraFlattrThing) {
+ this(context);
+ this.extraFlattrThing = extraFlattrThing;
+ }
+
+
+ @Override
+ protected ExitCode doInBackground(Void... params) {
+
+ if (!FlattrUtils.hasToken()) {
+ return ExitCode.NO_TOKEN;
+ }
+
+ if (!NetworkUtils.networkAvailable(context)) {
+ return ExitCode.NO_NETWORK;
+ }
+
+ final List<FlattrThing> flattrQueue = DBReader.getFlattrQueue(context);
+ if (extraFlattrThing != null) {
+ flattrQueue.add(extraFlattrThing);
+ } else if (flattrQueue.size() == 1) {
+ // if only one item is flattrd, the report can specifically mentioned that this item has failed
+ extraFlattrThing = flattrQueue.get(0);
+ }
+
+ if (flattrQueue.isEmpty()) {
+ return ExitCode.NO_THINGS;
+ }
+
+ List<Future> dbFutures = new LinkedList<Future>();
+ for (FlattrThing thing : flattrQueue) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Processing " + thing.getTitle());
+
+ try {
+ thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely
+ FlattrUtils.clickUrl(context, thing.getPaymentLink());
+ thing.getFlattrStatus().setFlattred();
+ publishProgress(R.string.flattr_click_success);
+ countSuccess++;
+
+ } catch (FlattrException e) {
+ e.printStackTrace();
+ countFailed++;
+ if (countFailed == 1) {
+ exception = e;
+ }
+ }
+
+ Future<?> f = DBWriter.setFlattredStatus(context, thing, false);
+ if (f != null) {
+ dbFutures.add(f);
+ }
+ }
+
+ for (Future f : dbFutures) {
+ try {
+ f.get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return ExitCode.EXIT_NORMAL;
+ }
+
+ @Override
+ protected void onPostExecute(ExitCode exitCode) {
+ super.onPostExecute(exitCode);
+ switch (exitCode) {
+ case EXIT_NORMAL:
+ if (countFailed > 0) {
+ postFlattrFailedNotification();
+ }
+ break;
+ case NO_NETWORK:
+ postToastNotification(R.string.flattr_click_enqueued);
+ break;
+ case NO_TOKEN:
+ postNoTokenNotification();
+ break;
+ case NO_THINGS: // nothing to notify here
+ break;
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ super.onProgressUpdate(values);
+ postToastNotification(values[0]);
+ }
+
+ private void postToastNotification(int msg) {
+ Toast.makeText(context, context.getString(msg), Toast.LENGTH_LONG).show();
+ }
+
+ private void postNoTokenNotification() {
+ PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, FlattrAuthActivity.class), 0);
+
+ Notification notification = new NotificationCompat.Builder(context)
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
+ .setContentIntent(contentIntent)
+ .setContentTitle(context.getString(R.string.no_flattr_token_title))
+ .setTicker(context.getString(R.string.no_flattr_token_title))
+ .setSmallIcon(R.drawable.stat_notify_sync_error)
+ .setOngoing(false)
+ .setAutoCancel(true)
+ .build();
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
+ }
+
+ private void postFlattrFailedNotification() {
+ if (countFailed == 0) {
+ return;
+ }
+
+ PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
+ String title;
+ String subtext;
+
+ if (countFailed == 1) {
+ title = context.getString(R.string.flattrd_failed_label);
+ String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : "";
+ subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle())
+ + "\n" + exceptionMsg;
+ } else {
+ title = context.getString(R.string.flattrd_label);
+ subtext = context.getString(R.string.flattr_click_success_count, countSuccess) + "\n"
+ + context.getString(R.string.flattr_click_failure_count, countFailed);
+ }
+
+ Notification notification = new NotificationCompat.Builder(context)
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
+ .setContentIntent(contentIntent)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setSmallIcon(R.drawable.stat_notify_sync_error)
+ .setOngoing(false)
+ .setAutoCancel(true)
+ .build();
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notification);
+ }
+
+
+ /**
+ * Starts the FlattrClickWorker as an AsyncTask.
+ */
+ @TargetApi(11)
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java
new file mode 100644
index 000000000..04d349671
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java
@@ -0,0 +1,47 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.content.Context;
+import android.util.Log;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.storage.DBWriter;
+import de.danoeh.antennapod.util.flattr.FlattrUtils;
+import org.shredzone.flattr4j.exception.FlattrException;
+import org.shredzone.flattr4j.model.Flattr;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Fetch list of flattred things and flattr status in database in a background thread.
+ */
+
+public class FlattrStatusFetcher extends Thread {
+ protected static final String TAG = "FlattrStatusFetcher";
+ protected Context context;
+
+ public FlattrStatusFetcher(Context context) {
+ super();
+ this.context = context;
+ }
+
+ @Override
+ public void run() {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Starting background work: Retrieving Flattr status");
+
+ Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+
+ try {
+ List<Flattr> flattredThings = FlattrUtils.retrieveFlattredThings();
+ DBWriter.setFlattredStatus(context, flattredThings).get();
+ } catch (FlattrException e) {
+ e.printStackTrace();
+ Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+
+ if (BuildConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status");
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrTokenFetcher.java b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrTokenFetcher.java
new file mode 100644
index 000000000..0dcf832f7
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/FlattrTokenFetcher.java
@@ -0,0 +1,95 @@
+package de.danoeh.antennapod.asynctask;
+
+
+import android.annotation.SuppressLint;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.FlattrAuthActivity;
+import de.danoeh.antennapod.util.flattr.FlattrUtils;
+import org.shredzone.flattr4j.exception.FlattrException;
+import org.shredzone.flattr4j.oauth.AccessToken;
+import org.shredzone.flattr4j.oauth.AndroidAuthenticator;
+
+/** Fetches the access token in the background in order to avoid networkOnMainThread exception. */
+
+public class FlattrTokenFetcher extends AsyncTask<Void, Void, AccessToken> {
+ private static final String TAG = "FlattrTokenFetcher";
+ Context context;
+ AndroidAuthenticator auth;
+ AccessToken token;
+ Uri uri;
+ ProgressDialog dialog;
+ FlattrException exception;
+
+ public FlattrTokenFetcher(Context context, AndroidAuthenticator auth, Uri uri) {
+ super();
+ this.context = context;
+ this.auth = auth;
+ this.uri = uri;
+ }
+
+ @Override
+ protected void onPostExecute(AccessToken result) {
+ if (result != null) {
+ FlattrUtils.storeToken(result);
+ }
+ dialog.dismiss();
+ if (exception == null) {
+ FlattrAuthActivity instance = FlattrAuthActivity.getInstance();
+ if (instance != null) {
+ instance.handleAuthenticationSuccess();
+ } else {
+ Log.e(TAG, "FlattrAuthActivity instance was null");
+ }
+ } else {
+ FlattrUtils.showErrorDialog(context, exception.getMessage());
+ }
+ }
+
+
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ dialog = new ProgressDialog(context);
+ dialog.setMessage(context.getString(R.string.processing_label));
+ dialog.setIndeterminate(true);
+ dialog.setCancelable(false);
+ dialog.show();
+ }
+
+
+
+ @Override
+ protected AccessToken doInBackground(Void... params) {
+ try {
+ token = auth.fetchAccessToken(uri);
+ } catch (FlattrException e) {
+ e.printStackTrace();
+ exception = e;
+ return null;
+ }
+ if (token != null) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Successfully got token");
+ return token;
+ } else {
+ Log.w(TAG, "Flattr token was null");
+ return null;
+ }
+ }
+
+ @SuppressLint("NewApi")
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
new file mode 100644
index 000000000..4abb1a67d
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
@@ -0,0 +1,114 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
+import android.util.Log;
+import de.danoeh.antennapod.PodcastApp;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.opml.OpmlWriter;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.LangUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/** Writes an OPML file into the export directory in the background. */
+public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
+ private static final String TAG = "OpmlExportWorker";
+ private static final String DEFAULT_OUTPUT_NAME = "antennapod-feeds.opml";
+ private Context context;
+ private File output;
+
+ private ProgressDialog progDialog;
+ private Exception exception;
+
+ public OpmlExportWorker(Context context, File output) {
+ this.context = context;
+ this.output = output;
+ }
+
+ public OpmlExportWorker(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ OpmlWriter opmlWriter = new OpmlWriter();
+ if (output == null) {
+ output = new File(
+ UserPreferences.getDataFolder(context, PodcastApp.EXPORT_DIR),
+ DEFAULT_OUTPUT_NAME);
+ if (output.exists()) {
+ Log.w(TAG, "Overwriting previously exported file.");
+ output.delete();
+ }
+ }
+ OutputStreamWriter writer = null;
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
+ opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ exception = e;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ progDialog.dismiss();
+ AlertDialog.Builder alert = new AlertDialog.Builder(context)
+ .setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ dialog.dismiss();
+ }
+ });
+ if (exception != null) {
+ alert.setTitle(R.string.export_error_label);
+ alert.setMessage(exception.getMessage());
+ } else {
+ alert.setTitle(R.string.opml_export_success_title);
+ alert.setMessage(context
+ .getString(R.string.opml_export_success_sum)
+ + output.toString());
+ }
+ alert.create().show();
+ }
+
+ @Override
+ protected void onPreExecute() {
+ progDialog = new ProgressDialog(context);
+ progDialog.setMessage(context.getString(R.string.exporting_label));
+ progDialog.setIndeterminate(true);
+ progDialog.show();
+ }
+
+ @SuppressLint("NewApi")
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
new file mode 100644
index 000000000..038b8dcc5
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
@@ -0,0 +1,69 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.annotation.SuppressLint;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.AsyncTask;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.OpmlImportHolder;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.opml.OpmlElement;
+import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.storage.DownloadRequester;
+
+import java.util.Arrays;
+import java.util.Date;
+
+/** Queues items for download in the background. */
+public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
+ private Context context;
+ private ProgressDialog progDialog;
+ private int[] selection;
+
+ public OpmlFeedQueuer(Context context, int[] selection) {
+ super();
+ this.context = context;
+ this.selection = Arrays.copyOf(selection, selection.length);
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ progDialog.dismiss();
+ }
+
+ @Override
+ protected void onPreExecute() {
+ progDialog = new ProgressDialog(context);
+ progDialog.setMessage(context.getString(R.string.processing_label));
+ progDialog.setCancelable(false);
+ progDialog.setIndeterminate(true);
+ progDialog.show();
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ DownloadRequester requester = DownloadRequester.getInstance();
+ for (int idx = 0; idx < selection.length; idx++) {
+ OpmlElement element = OpmlImportHolder.getReadElements().get(
+ selection[idx]);
+ Feed feed = new Feed(element.getXmlUrl(), new Date(),
+ element.getText());
+ try {
+ requester.downloadFeed(context.getApplicationContext(), feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
new file mode 100644
index 000000000..13534fa64
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
@@ -0,0 +1,116 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.AsyncTask;
+import android.util.Log;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.opml.OpmlElement;
+import de.danoeh.antennapod.opml.OpmlReader;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+
+public class OpmlImportWorker extends
+ AsyncTask<Void, Void, ArrayList<OpmlElement>> {
+ private static final String TAG = "OpmlImportWorker";
+
+ private Context context;
+ private Exception exception;
+
+ private ProgressDialog progDialog;
+
+ private Reader mReader;
+
+ public OpmlImportWorker(Context context, Reader reader) {
+ super();
+ this.context = context;
+ this.mReader=reader;
+ }
+
+ @Override
+ protected ArrayList<OpmlElement> doInBackground(Void... params) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Starting background work");
+
+ if (mReader==null) {
+ return null;
+ }
+
+ OpmlReader opmlReader = new OpmlReader();
+ try {
+ ArrayList<OpmlElement> result = opmlReader.readDocument(mReader);
+ mReader.close();
+ return result;
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ exception = e;
+ return null;
+ } catch (IOException e) {
+ e.printStackTrace();
+ exception = e;
+ return null;
+ }
+
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<OpmlElement> result) {
+ if (mReader != null) {
+ try {
+ mReader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ progDialog.dismiss();
+ if (exception != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "An error occured while trying to parse the opml document");
+ AlertDialog.Builder alert = new AlertDialog.Builder(context);
+ alert.setTitle(R.string.error_label);
+ alert.setMessage(context.getString(R.string.opml_reader_error)
+ + exception.getMessage());
+ alert.setNeutralButton(android.R.string.ok, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+
+ });
+ alert.create().show();
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ progDialog = new ProgressDialog(context);
+ progDialog.setMessage(context.getString(R.string.reading_opml_label));
+ progDialog.setIndeterminate(true);
+ progDialog.setCancelable(false);
+ progDialog.show();
+ }
+
+ public boolean wasSuccessful() {
+ return exception != null;
+ }
+
+ @SuppressLint("NewApi")
+ public void executeAsync() {
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ executeOnExecutor(THREAD_POOL_EXECUTOR);
+ } else {
+ execute();
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoImageResource.java b/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoImageResource.java
new file mode 100644
index 000000000..26f9d9278
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoImageResource.java
@@ -0,0 +1,37 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.net.Uri;
+
+/**
+ * Classes that implement this interface provide access to an image resource that can
+ * be loaded by the Picasso library.
+ */
+public interface PicassoImageResource {
+
+ /**
+ * This scheme should be used by PicassoImageResources to
+ * indicate that the image Uri points to a file that is not an image
+ * (e.g. a media file). This workaround is needed so that the Picasso library
+ * loads these Uri with a Downloader instead of trying to load it directly.
+ * <p/>
+ * For example implementations, see FeedMedia or ExternalMedia.
+ */
+ public static final String SCHEME_MEDIA = "media";
+
+
+ /**
+ * Parameter key for an encoded fallback Uri. This Uri MUST point to a local image file
+ */
+ public static final String PARAM_FALLBACK = "fallback";
+
+ /**
+ * Returns a Uri to the image or null if no image is available.
+ * <p/>
+ * The Uri can either be an HTTP-URL, a URL pointing to a local image file or
+ * a non-image file (see SCHEME_MEDIA for more details).
+ * <p/>
+ * The Uri can also have an optional fallback-URL if loading the default URL
+ * failed (see PARAM_FALLBACK).
+ */
+ public Uri getImageUri();
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoProvider.java b/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoProvider.java
new file mode 100644
index 000000000..849725630
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/PicassoProvider.java
@@ -0,0 +1,152 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.content.Context;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.squareup.picasso.Cache;
+import com.squareup.picasso.Downloader;
+import com.squareup.picasso.LruCache;
+import com.squareup.picasso.OkHttpDownloader;
+import com.squareup.picasso.Picasso;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Provides access to Picasso instances.
+ */
+public class PicassoProvider {
+ private static final String TAG = "PicassoProvider";
+
+ private static final boolean DEBUG = false;
+
+ private static ExecutorService executorService;
+ private static Cache memoryCache;
+
+ private static Picasso defaultPicassoInstance;
+ private static Picasso mediaMetadataPicassoInstance;
+
+ private static synchronized ExecutorService getExecutorService() {
+ if (executorService == null) {
+ executorService = Executors.newFixedThreadPool(3);
+ }
+ return executorService;
+ }
+
+ private static synchronized Cache getMemoryCache(Context context) {
+ if (memoryCache == null) {
+ memoryCache = new LruCache(context);
+ }
+ return memoryCache;
+ }
+
+ /**
+ * Returns a Picasso instance that uses an OkHttpDownloader. This instance can only load images
+ * from image files.
+ * <p/>
+ * This instance should be used as long as no images from media files are loaded.
+ */
+ public static synchronized Picasso getDefaultPicassoInstance(Context context) {
+ Validate.notNull(context);
+ if (defaultPicassoInstance == null) {
+ defaultPicassoInstance = new Picasso.Builder(context)
+ .indicatorsEnabled(DEBUG)
+ .loggingEnabled(DEBUG)
+ .downloader(new OkHttpDownloader(context))
+ .executor(getExecutorService())
+ .memoryCache(getMemoryCache(context))
+ .listener(new Picasso.Listener() {
+ @Override
+ public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
+ Log.e(TAG, "Failed to load Uri:" + uri.toString());
+ e.printStackTrace();
+ }
+ })
+ .build();
+ }
+ return defaultPicassoInstance;
+ }
+
+ /**
+ * Returns a Picasso instance that uses a MediaMetadataRetriever if the given Uri is a media file
+ * and a default OkHttpDownloader otherwise.
+ */
+ public static synchronized Picasso getMediaMetadataPicassoInstance(Context context) {
+ Validate.notNull(context);
+ if (mediaMetadataPicassoInstance == null) {
+ mediaMetadataPicassoInstance = new Picasso.Builder(context)
+ .indicatorsEnabled(DEBUG)
+ .loggingEnabled(DEBUG)
+ .downloader(new MediaMetadataDownloader(context))
+ .executor(getExecutorService())
+ .memoryCache(getMemoryCache(context))
+ .listener(new Picasso.Listener() {
+ @Override
+ public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
+ Log.e(TAG, "Failed to load Uri:" + uri.toString());
+ e.printStackTrace();
+ }
+ })
+ .build();
+ }
+ return mediaMetadataPicassoInstance;
+ }
+
+ private static class MediaMetadataDownloader implements Downloader {
+
+ private static final String TAG = "MediaMetadataDownloader";
+
+ private final OkHttpDownloader okHttpDownloader;
+
+ public MediaMetadataDownloader(Context context) {
+ Validate.notNull(context);
+ okHttpDownloader = new OkHttpDownloader(context);
+ }
+
+ @Override
+ public Response load(Uri uri, boolean b) throws IOException {
+ if (StringUtils.equals(uri.getScheme(), PicassoImageResource.SCHEME_MEDIA)) {
+ String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(uri.getLastPathSegment()));
+ if (StringUtils.startsWith(type, "image")) {
+ File imageFile = new File(uri.toString());
+ return new Response(new BufferedInputStream(new FileInputStream(imageFile)), true, imageFile.length());
+ } else {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ mmr.setDataSource(uri.getPath());
+ byte[] data = mmr.getEmbeddedPicture();
+ mmr.release();
+
+ if (data != null) {
+ return new Response(new ByteArrayInputStream(data), true, data.length);
+ } else {
+
+ // check for fallback Uri
+ String fallbackParam = uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK);
+
+ if (fallbackParam != null) {
+ String fallback = Uri.decode(Uri.parse(fallbackParam).getPath());
+ if (fallback != null) {
+ File imageFile = new File(fallback);
+ return new Response(new BufferedInputStream(new FileInputStream(imageFile)), true, imageFile.length());
+ }
+ }
+ return null;
+ }
+ }
+ }
+ return okHttpDownloader.load(uri, b);
+ }
+ }
+}