From 072639b5b22e816df9f78b5cd8a7d4e5379b6aff Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Wed, 17 Sep 2014 20:51:45 +0200 Subject: Changed project structure Switched from custom layout to standard gradle project structure --- .../antennapod/asynctask/DownloadObserver.java | 177 +++++++++++++++ .../danoeh/antennapod/asynctask/FeedRemover.java | 74 +++++++ .../antennapod/asynctask/FlattrClickWorker.java | 238 +++++++++++++++++++++ .../antennapod/asynctask/FlattrStatusFetcher.java | 47 ++++ .../antennapod/asynctask/FlattrTokenFetcher.java | 95 ++++++++ .../antennapod/asynctask/OpmlExportWorker.java | 114 ++++++++++ .../antennapod/asynctask/OpmlFeedQueuer.java | 69 ++++++ .../antennapod/asynctask/OpmlImportWorker.java | 116 ++++++++++ .../antennapod/asynctask/PicassoImageResource.java | 37 ++++ .../antennapod/asynctask/PicassoProvider.java | 152 +++++++++++++ 10 files changed, 1119 insertions(+) create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/DownloadObserver.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/FeedRemover.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/FlattrClickWorker.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/FlattrStatusFetcher.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/FlattrTokenFetcher.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/PicassoImageResource.java create mode 100644 app/src/main/java/de/danoeh/antennapod/asynctask/PicassoProvider.java (limited to 'app/src/main/java/de/danoeh/antennapod/asynctask') 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 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 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 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 { + 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. + *

+ * 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. + *

+ * 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 { + 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. + *

+ * 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. + *

+ * 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 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 dbFutures = new LinkedList(); + 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 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 { + 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 { + 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 { + 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> { + 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 doInBackground(Void... params) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Starting background work"); + + if (mReader==null) { + return null; + } + + OpmlReader opmlReader = new OpmlReader(); + try { + ArrayList 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 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. + *

+ * 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. + *

+ * 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). + *

+ * 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. + *

+ * 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); + } + } +} -- cgit v1.2.3